├── .github ├── ISSUE_TEMPLATE │ ├── add_build_in_blocked_user.yml │ ├── bug-report.yml │ ├── coding_suggesion.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ └── build_publish.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── README_cn.md ├── README_kr.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── top │ │ │ │ └── jtmonster │ │ │ │ └── jhentai │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── launcher_icon.xml │ │ │ ├── mipmap-hdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── key.properties.sample └── settings.gradle ├── apk.sh ├── assets └── icon │ ├── JHenTai-background.png │ ├── JHenTai-foreground.png │ ├── JHenTai-square.png │ ├── JHenTai.png │ └── JHenTai_512.png ├── built_in_blocked_user.json ├── changelog ├── v4.0.4+77.md ├── v4.0.5+78.md ├── v4.1.0+79.md ├── v4.1.1+80.md ├── v4.1.2+81.md ├── v4.1.3+82.md ├── v4.2.0+83.md ├── v4.2.1+84.md ├── v4.2.2+85.md ├── v4.3.0+86.md ├── v4.3.1+87.md ├── v4.3.2+88.md ├── v4.4.0+89.md ├── v5.0.0+90.md ├── v5.1.0+91.md ├── v5.1.1+92.md ├── v5.1.10+101.md ├── v5.1.2+93.md ├── v5.1.3+94.md ├── v5.1.4+95.md ├── v5.1.5+96.md ├── v5.1.6+97.md ├── v5.1.7+98.md ├── v5.1.8+99.md ├── v5.1.9+100.md ├── v6.0.0+102.md ├── v6.0.1+103.md ├── v6.0.2+104.md ├── v6.0.3+105.md ├── v6.0.4+106.md ├── v6.0.5+107.md ├── v6.0.6+108.md ├── v6.1.0+109.md ├── v6.2.0+110.md ├── v6.3.0+111.md ├── v6.3.1+112.md ├── v6.3.2+113.md ├── v6.4.0+114.md ├── v6.5.0+116.md ├── v6.6.0+117.md ├── v6.6.1+118.md ├── v6.6.2+119.md ├── v6.6.3+120.md ├── v6.6.4+121.md ├── v6.6.5+122.md ├── v6.6.6+123.md ├── v7.0.0+124.md ├── v7.1.0+125.md ├── v7.1.1+126.md ├── v7.1.2+127.md ├── v7.1.3+128.md ├── v7.2.0+129.md ├── v7.2.1+130.md ├── v7.2.2+131.md ├── v7.2.3+132.md ├── v7.3.0+133.md ├── v7.3.1+134.md ├── v7.3.3+136.md ├── v7.3.4+137.md ├── v7.3.5+138.md ├── v7.4.0+139.md ├── v7.4.1+140.md ├── v7.4.10+149.md ├── v7.4.11.md ├── v7.4.12+160.md ├── v7.4.12+161.md ├── v7.4.12+162.md ├── v7.4.12+163.md ├── v7.4.12+164.md ├── v7.4.12+165.md ├── v7.4.12+172.md ├── v7.4.12.md ├── v7.4.13+176.md ├── v7.4.13+188.md ├── v7.4.13+190.md ├── v7.4.13+193.md ├── v7.4.13+194.md ├── v7.4.13+195.md ├── v7.4.13.md ├── v7.4.3+142.md ├── v7.4.4+143.md ├── v7.4.5+144.md ├── v7.4.6+145.md ├── v7.4.7+146.md ├── v7.4.8+147.md ├── v7.4.9+148.md ├── v7.5.0+199.md ├── v7.5.0.md ├── v7.5.1.md ├── v7.5.2.md ├── v7.5.3+208.md ├── v7.5.3+212.md ├── v7.5.3.md ├── v7.5.4+221.md ├── v7.5.4+222.md ├── v7.5.4.md ├── v7.5.5+224.md ├── v7.5.5+240.md ├── v7.5.5+241.md ├── v7.5.5+242.md ├── v7.5.5.md ├── v8.0.0+244.md ├── v8.0.0+245.md ├── v8.0.0+246.md ├── v8.0.0+247.md ├── v8.0.0+248.md ├── v8.0.0+249.md ├── v8.0.0+250.md ├── v8.0.0+251.md ├── v8.0.0+252.md ├── v8.0.0+253.md ├── v8.0.0+254.md ├── v8.0.0.md ├── v8.0.1+256.md ├── v8.0.1+257.md ├── v8.0.1.md ├── v8.0.2+259.md ├── v8.0.2.md ├── v8.0.3.md ├── v8.0.4+262.md ├── v8.0.4+263.md ├── v8.0.4.md ├── v8.0.5+265.md ├── v8.0.5+266.md ├── v8.0.5.md ├── v8.0.6+271.md ├── v8.0.6+273.md ├── v8.0.6+274.md ├── v8.0.6+277.md ├── v8.0.6+279.md ├── v8.0.6+280.md ├── v8.0.6.md ├── v8.0.7+290.md ├── v8.0.7+291.md ├── v8.0.7+292.md ├── v8.0.7+293.md └── v8.0.7.md ├── devtools_options.yaml ├── dmg.sh ├── flutter_launcher_icons.yaml ├── 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-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-50x50@1x.png │ │ ├── Icon-App-50x50@2x.png │ │ ├── Icon-App-57x57@1x.png │ │ ├── Icon-App-57x57@2x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-72x72@1x.png │ │ ├── Icon-App-72x72@2x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info-Debug.plist │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── ipa.sh ├── lib └── src │ ├── config │ ├── README.md │ ├── jh_api_secret_config.dart │ ├── sentry_config.dart │ ├── theme_config.dart │ └── ui_config.dart │ ├── consts │ ├── archive_bot_consts.dart │ ├── eh_consts.dart │ ├── jh_consts.dart │ └── locale_consts.dart │ ├── database │ ├── dao │ │ ├── archive_dao.dart │ │ ├── archive_group_dao.dart │ │ ├── block_rule_dao.dart │ │ ├── dio_cache_dao.dart │ │ ├── gallery_dao.dart │ │ ├── gallery_group_dao.dart │ │ ├── gallery_history_dao.dart │ │ ├── gallery_image_dao.dart │ │ ├── super_resolution_info_dao.dart │ │ ├── tag_count_dao.dart │ │ └── tag_dao.dart │ ├── database.dart │ ├── database.g.dart │ └── table │ │ ├── archive_downloaded.dart │ │ ├── archive_group.dart │ │ ├── block_rule.dart │ │ ├── dio_cache.dart │ │ ├── gallery_downloaded.dart │ │ ├── gallery_group.dart │ │ ├── gallery_history.dart │ │ ├── image.dart │ │ ├── local_config.dart │ │ ├── super_resolution_info.dart │ │ ├── tag.dart │ │ └── tag_count.dart │ ├── enum │ ├── config_enum.dart │ ├── config_type_enum.dart │ └── eh_namespace.dart │ ├── exception │ ├── cancel_exception.dart │ ├── eh_image_exception.dart │ ├── eh_parse_exception.dart │ ├── eh_site_exception.dart │ ├── internal_exception.dart │ ├── retry_exception.dart │ └── upload_exception.dart │ ├── extension │ ├── dio_exception_extension.dart │ ├── directory_extension.dart │ ├── get_logic_extension.dart │ ├── list_extension.dart │ ├── string_extension.dart │ └── widget_extension.dart │ ├── l18n │ ├── en_US.dart │ ├── ko_KR.dart │ ├── locale_text.dart │ ├── pt_BR.dart │ ├── ru_RU.dart │ ├── zh_CN.dart │ └── zh_TW.dart │ ├── main.dart │ ├── mixin │ ├── animation_logic_mixin.dart │ ├── double_tap_to_refresh_logic_mixin.dart │ ├── double_tap_to_refresh_state_mixin.dart │ ├── login_required_logic_mixin.dart │ ├── scroll_status_listener.dart │ ├── scroll_status_listener_state.dart │ ├── scroll_to_top_logic_mixin.dart │ ├── scroll_to_top_page_mixin.dart │ ├── scroll_to_top_state_mixin.dart │ ├── update_global_gallery_status_logic_mixin.dart │ └── window_widget_mixin.dart │ ├── model │ ├── archive_bot_response │ │ ├── archive_bot_response.dart │ │ ├── archive_resolve_vo.dart │ │ ├── balance_vo.dart │ │ └── check_in_vo.dart │ ├── comic_info.dart │ ├── config.dart │ ├── detail_page_info.dart │ ├── eh_raw_tag.dart │ ├── gallery.dart │ ├── gallery_archive.dart │ ├── gallery_comment.dart │ ├── gallery_count.dart │ ├── gallery_detail.dart │ ├── gallery_hh_archive.dart │ ├── gallery_hh_info.dart │ ├── gallery_history_model.dart │ ├── gallery_image.dart │ ├── gallery_image_page_url.dart │ ├── gallery_metadata.dart │ ├── gallery_note.dart │ ├── gallery_page.dart │ ├── gallery_stats.dart │ ├── gallery_tag.dart │ ├── gallery_thumbnail.dart │ ├── gallery_torrent.dart │ ├── gallery_url.dart │ ├── image_page_url.dart │ ├── jh_layout.dart │ ├── jh_response │ │ ├── fetch_image_hashes_vo.dart │ │ └── jh_response.dart │ ├── profile.dart │ ├── read_page_info.dart │ ├── search_config.dart │ ├── search_history.dart │ ├── tab_bar_config.dart │ ├── tab_bar_icon.dart │ └── tag_set.dart │ ├── network │ ├── archive_bot_request.dart │ ├── eh_cache_manager.dart │ ├── eh_cookie_manager.dart │ ├── eh_ip_provider.dart │ ├── eh_request.dart │ ├── eh_timeout_translator.dart │ ├── jh_cookie_manager.dart │ └── jh_request.dart │ ├── pages │ ├── README.md │ ├── base │ │ ├── base_page.dart │ │ ├── base_page_logic.dart │ │ ├── base_page_state.dart │ │ ├── old_base_page_logic.dart │ │ └── old_base_page_state.dart │ ├── blank_page.dart │ ├── details │ │ ├── comment │ │ │ ├── comment_page.dart │ │ │ └── eh_comment.dart │ │ ├── details_page.dart │ │ ├── details_page_logic.dart │ │ ├── details_page_state.dart │ │ └── thumbnails │ │ │ ├── thumbnails_page.dart │ │ │ ├── thumbnails_page_logic.dart │ │ │ └── thumbnails_page_state.dart │ ├── download │ │ ├── download_base_page.dart │ │ ├── grid │ │ │ ├── archive │ │ │ │ ├── archive_grid_download_page.dart │ │ │ │ ├── archive_grid_download_page_logic.dart │ │ │ │ └── archive_grid_download_page_state.dart │ │ │ ├── gallery │ │ │ │ ├── gallery_grid_download_page.dart │ │ │ │ ├── gallery_grid_download_page_logic.dart │ │ │ │ └── gallery_grid_download_page_state.dart │ │ │ ├── local │ │ │ │ ├── local_gallery_grid_page.dart │ │ │ │ ├── local_gallery_grid_page_logic.dart │ │ │ │ └── local_gallery_grid_page_state.dart │ │ │ └── mixin │ │ │ │ ├── grid_download_page_logic_mixin.dart │ │ │ │ ├── grid_download_page_mixin.dart │ │ │ │ ├── grid_download_page_service_mixin.dart │ │ │ │ └── grid_download_page_state_mixin.dart │ │ ├── list │ │ │ ├── archive │ │ │ │ ├── archive_list_download_page.dart │ │ │ │ ├── archive_list_download_page_logic.dart │ │ │ │ └── archive_list_download_page_state.dart │ │ │ ├── gallery │ │ │ │ ├── gallery_list_download_page.dart │ │ │ │ ├── gallery_list_download_page_logic.dart │ │ │ │ └── gallery_list_download_page_state.dart │ │ │ └── local │ │ │ │ ├── local_gallery_list_page.dart │ │ │ │ ├── local_gallery_list_page_logic.dart │ │ │ │ └── local_gallery_list_page_state.dart │ │ └── mixin │ │ │ ├── archive │ │ │ ├── archive_download_page_logic_mixin.dart │ │ │ ├── archive_download_page_mixin.dart │ │ │ └── archive_download_page_state_mixin.dart │ │ │ ├── basic │ │ │ └── multi_select │ │ │ │ ├── multi_select_download_page_logic_mixin.dart │ │ │ │ ├── multi_select_download_page_mixin.dart │ │ │ │ └── multi_select_download_page_state_mixin.dart │ │ │ ├── gallery │ │ │ ├── gallery_download_page_logic_mixin.dart │ │ │ ├── gallery_download_page_mixin.dart │ │ │ └── gallery_download_page_state_mixin.dart │ │ │ └── local │ │ │ └── local_gallery_download_page_logic_mixin.dart │ ├── download_search │ │ ├── download_search_logic.dart │ │ ├── download_search_page.dart │ │ └── download_search_state.dart │ ├── favorite │ │ ├── favorite_page.dart │ │ ├── favorite_page_logic.dart │ │ └── favorite_page_state.dart │ ├── gallery_image │ │ ├── gallery_image_page.dart │ │ ├── gallery_image_page_logic.dart │ │ └── gallery_image_page_state.dart │ ├── gallerys │ │ ├── dashboard │ │ │ ├── dashboard_page.dart │ │ │ ├── dashboard_page_logic.dart │ │ │ ├── dashboard_page_state.dart │ │ │ └── simple │ │ │ │ ├── simple_dashboard_page.dart │ │ │ │ ├── simple_dashboard_page_logic.dart │ │ │ │ └── simple_dashboard_page_state.dart │ │ └── simple │ │ │ ├── gallerys_page.dart │ │ │ ├── gallerys_page_logic.dart │ │ │ └── gallerys_page_state.dart │ ├── history │ │ ├── history_page.dart │ │ ├── history_page_logic.dart │ │ └── history_page_state.dart │ ├── home_page.dart │ ├── layout │ │ ├── desktop │ │ │ ├── desktop_home_page.dart │ │ │ ├── desktop_layout_page.dart │ │ │ ├── desktop_layout_page_logic.dart │ │ │ └── desktop_layout_page_state.dart │ │ ├── mobile_v2 │ │ │ ├── mobile_layout_page_v2.dart │ │ │ ├── mobile_layout_page_v2_logic.dart │ │ │ ├── mobile_layout_page_v2_state.dart │ │ │ └── notification │ │ │ │ ├── tap_menu_button_notification.dart │ │ │ │ └── tap_tab_bat_button_notification.dart │ │ └── tablet_v2 │ │ │ └── tablet_layout_page_v2.dart │ ├── lock_page.dart │ ├── popular │ │ ├── popular_page.dart │ │ ├── popular_page_logic.dart │ │ └── popular_page_state.dart │ ├── ranklist │ │ ├── ranklist_page.dart │ │ ├── ranklist_page_logic.dart │ │ └── ranklist_page_state.dart │ ├── read │ │ ├── layout │ │ │ ├── base │ │ │ │ ├── base_layout.dart │ │ │ │ └── base_layout_logic.dart │ │ │ ├── horizontal_double_column │ │ │ │ ├── horizontal_double_column_layout.dart │ │ │ │ ├── horizontal_double_column_layout_logic.dart │ │ │ │ └── horizontal_double_column_layout_state.dart │ │ │ ├── horizontal_list │ │ │ │ ├── horizontal_list_layout.dart │ │ │ │ ├── horizontal_list_layout_logic.dart │ │ │ │ └── horizontal_list_layout_state.dart │ │ │ ├── horizontal_page │ │ │ │ ├── horizontal_page_layout.dart │ │ │ │ ├── horizontal_page_layout_logic.dart │ │ │ │ └── horizontal_page_layout_state.dart │ │ │ └── vertical_list │ │ │ │ ├── vertical_list_layout.dart │ │ │ │ ├── vertical_list_layout_logic.dart │ │ │ │ └── vertical_list_layout_state.dart │ │ ├── read_page.dart │ │ ├── read_page_logic.dart │ │ └── read_page_state.dart │ ├── search │ │ ├── desktop │ │ │ ├── desktop_search_page.dart │ │ │ ├── desktop_search_page_logic.dart │ │ │ ├── desktop_search_page_state.dart │ │ │ ├── desktop_search_page_tab_logic.dart │ │ │ ├── desktop_search_page_tab_state.dart │ │ │ └── desktop_search_page_tab_view.dart │ │ ├── mixin │ │ │ ├── new_search_argument.dart │ │ │ ├── search_page_logic_mixin.dart │ │ │ ├── search_page_mixin.dart │ │ │ └── search_page_state_mixin.dart │ │ ├── mobile_v2 │ │ │ ├── search_page_mobile_v2.dart │ │ │ ├── search_page_mobile_v2_logic.dart │ │ │ └── search_page_mobile_v2_state.dart │ │ └── quick_search │ │ │ └── quick_search_page.dart │ ├── setting │ │ ├── about │ │ │ └── setting_about_page.dart │ │ ├── account │ │ │ ├── cookie │ │ │ │ └── cookie_page.dart │ │ │ ├── login │ │ │ │ ├── login_page.dart │ │ │ │ ├── login_page_logic.dart │ │ │ │ └── login_page_state.dart │ │ │ └── setting_account_page.dart │ │ ├── advanced │ │ │ ├── loglist │ │ │ │ ├── log │ │ │ │ │ └── log_page.dart │ │ │ │ └── log_list_page.dart │ │ │ ├── setting_advanced_page.dart │ │ │ └── super_resolution │ │ │ │ └── setting_super_resolution_page.dart │ │ ├── cloud │ │ │ ├── config_sync │ │ │ │ └── config_sync_page.dart │ │ │ └── setting_cloud_page.dart │ │ ├── download │ │ │ ├── archive_bot │ │ │ │ └── archive_bot_settings_page.dart │ │ │ ├── extra_gallery_scan_path │ │ │ │ └── extra_gallery_scan_path_page.dart │ │ │ └── setting_download_page.dart │ │ ├── eh │ │ │ ├── profile │ │ │ │ └── setting_eh_profile_page.dart │ │ │ ├── setting_eh_page.dart │ │ │ └── tagsets │ │ │ │ ├── tag_sets_page.dart │ │ │ │ ├── tag_sets_page_logic.dart │ │ │ │ └── tag_sets_page_state.dart │ │ ├── mousewheel │ │ │ └── setting_mouse_wheel_page.dart │ │ ├── network │ │ │ ├── proxy │ │ │ │ └── setting_proxy_page.dart │ │ │ └── setting_network_page.dart │ │ ├── performance │ │ │ └── setting_performace_page.dart │ │ ├── preference │ │ │ ├── block_rule │ │ │ │ ├── add_block_rule │ │ │ │ │ ├── configure_blocking_rule_page.dart │ │ │ │ │ ├── configure_blocking_rule_page_logic.dart │ │ │ │ │ └── configure_blocking_rule_page_state.dart │ │ │ │ ├── blocking_rule_page.dart │ │ │ │ ├── blocking_rule_page_logic.dart │ │ │ │ └── blocking_rule_page_state.dart │ │ │ └── setting_preference_page.dart │ │ ├── read │ │ │ └── setting_read_page.dart │ │ ├── security │ │ │ └── setting_security_page.dart │ │ ├── setting_page.dart │ │ └── style │ │ │ ├── page_list_style │ │ │ └── setting_page_list_style_page.dart │ │ │ ├── setting_style_page.dart │ │ │ └── theme_color │ │ │ ├── preview_page │ │ │ └── detail_preview_page.dart │ │ │ └── setting_theme_color_page.dart │ ├── single_image │ │ └── single_image.dart │ ├── watched │ │ ├── watched_page.dart │ │ ├── watched_page_logic.dart │ │ └── watched_page_state.dart │ └── webview │ │ └── webview_page.dart │ ├── routes │ ├── eh_page.dart │ ├── getx_router_observer.dart │ └── routes.dart │ ├── service │ ├── app_update_service.dart │ ├── archive_download_service.dart │ ├── built_in_blocked_user_service.dart │ ├── cloud_service.dart │ ├── frame_rate_service.dart │ ├── gallery_download_service.dart │ ├── history_service.dart │ ├── isolate_service.dart │ ├── jh_service.dart │ ├── local_block_rule_service.dart │ ├── local_config_service.dart │ ├── local_gallery_service.dart │ ├── log.dart │ ├── path_service.dart │ ├── quick_search_service.dart │ ├── schedule_service.dart │ ├── search_history_service.dart │ ├── storage_service.dart │ ├── super_resolution_service.dart │ ├── tag_search_order_service.dart │ ├── tag_translation_service.dart │ ├── volume_service.dart │ └── windows_service.dart │ ├── setting │ ├── README.md │ ├── advanced_setting.dart │ ├── archive_bot_setting.dart │ ├── download_setting.dart │ ├── eh_setting.dart │ ├── favorite_setting.dart │ ├── mouse_setting.dart │ ├── my_tags_setting.dart │ ├── network_setting.dart │ ├── performance_setting.dart │ ├── preference_setting.dart │ ├── read_setting.dart │ ├── security_setting.dart │ ├── site_setting.dart │ ├── style_setting.dart │ ├── super_resolution_setting.dart │ └── user_setting.dart │ ├── utils │ ├── archive_bot_response_parser.dart │ ├── archive_util.dart │ ├── byte_util.dart │ ├── check_util.dart │ ├── color_util.dart │ ├── convert_util.dart │ ├── cookie_util.dart │ ├── date_util.dart │ ├── eh_executor.dart │ ├── eh_spider_parser.dart │ ├── file_util.dart │ ├── hmac_util.dart │ ├── jh_response_parser.dart │ ├── jh_spider_parser.dart │ ├── locale_util.dart │ ├── permission_util.dart │ ├── process_util.dart │ ├── proxy_util.dart │ ├── recorder_util.dart │ ├── route_util.dart │ ├── screen_size_util.dart │ ├── search_util.dart │ ├── snack_util.dart │ ├── speed_computer.dart │ ├── string_uril.dart │ ├── table.dart │ ├── text_input_formatter.dart │ ├── toast_util.dart │ ├── uuid_util.dart │ └── version_util.dart │ └── widget │ ├── README.md │ ├── app_manager.dart │ ├── auto_mode_interval_dialog.dart │ ├── cached_page_view.dart │ ├── context_menu_region.dart │ ├── eh_add_tag_dialog.dart │ ├── eh_alert_dialog.dart │ ├── eh_app_password_setting_dialog.dart │ ├── eh_archive_bot_setting_dialog.dart │ ├── eh_archive_dialog.dart │ ├── eh_archive_parse_source_select_dialog.dart │ ├── eh_asset.dart │ ├── eh_comment_dialog.dart │ ├── eh_comment_score_details_dialog.dart │ ├── eh_config_type_select_dialog.dart │ ├── eh_dashboard_card.dart │ ├── eh_download_dialog.dart │ ├── eh_download_hh_dialog.dart │ ├── eh_favorite_dialog.dart │ ├── eh_favorite_sort_order_dialog.dart │ ├── eh_gallery_category_tag.dart │ ├── eh_gallery_collection.dart │ ├── eh_gallery_detail_dialog.dart │ ├── eh_gallery_favorite_tag.dart │ ├── eh_gallery_history_dialog.dart │ ├── eh_gallery_list_card_.dart │ ├── eh_gallery_stat_dialog.dart │ ├── eh_gallery_torrents_dialog.dart │ ├── eh_gallery_waterflow_card.dart │ ├── eh_group_name_selector.dart │ ├── eh_image.dart │ ├── eh_keyboard_listener.dart │ ├── eh_log_out_dialog.dart │ ├── eh_mouse_button_listener.dart │ ├── eh_rating_dialog.dart │ ├── eh_read_page_stack.dart │ ├── eh_search_config_dialog.dart │ ├── eh_sliver_header_delegate.dart │ ├── eh_tag.dart │ ├── eh_tag_dialog.dart │ ├── eh_tag_set_dialog.dart │ ├── eh_thumbnail.dart │ ├── eh_warning_image.dart │ ├── eh_wheel_scroll_listener.dart │ ├── eh_wheel_speed_controller.dart │ ├── eh_wheel_speed_controller_for_read_page.dart │ ├── fade_slide_widget.dart │ ├── focus_widget.dart │ ├── grouped_list.dart │ ├── icon_text_button.dart │ ├── jump_page_dialog.dart │ ├── keep_alive.dart │ ├── loading_state_indicator.dart │ ├── re_unlock_dialog.dart │ ├── update_dialog.dart │ └── will_pop_interceptor.dart ├── linux.sh ├── linux ├── .gitignore ├── CMakeLists.txt ├── assets │ ├── AppImageBuilder.yml │ ├── DEBIAN │ │ ├── control │ │ ├── postinst │ │ └── postrm │ └── top.jtmonster.jhentai.desktop ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── 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 ├── pkg.sh ├── pubspec.lock ├── pubspec.yaml ├── screenshot ├── archive.jpg ├── desktop1.png ├── detail.png ├── download.jpg ├── mobile_v2.jpg ├── read.jpg ├── read_continuous_scroll.png ├── read_double_column.png ├── search.jpg ├── setting_en.jpg ├── setting_zh.jpg ├── stat_en.jpg ├── stat_zh.jpg └── tabletV2.png ├── thin-payload.sh ├── windows.sh └── 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 /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Others 4 | url: https://github.com/jiangtian616/JHenTai/discussions/new/choose 5 | about: | 6 | 其余问题请移步至讨论区 / Go to discussion for other questions 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | pubspec.lock 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | /lib/src/firebase_options.dart 50 | /assets/sentry_dsn 51 | -------------------------------------------------------------------------------- /.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: ffccd96b62ee8cec7740dab303538c5fc26ac543 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 17 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 18 | - platform: windows 19 | create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 20 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 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 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "JHenTai", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "JHenTai (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "JHenTai (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /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 13 | **/*.jks 14 | /app/google-services.json 15 | /firebase_app_id_file.json 16 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class androidx.lifecycle.DefaultLifecycleObserver 2 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /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-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /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/launcher_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #e6e623 4 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.jetifier.ignorelist=bcprov-jdk15on -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip 7 | -------------------------------------------------------------------------------- /android/key.properties.sample: -------------------------------------------------------------------------------- 1 | storePassword= 2 | keyPassword= 3 | keyAlias=upload 4 | storeFile=/upload-keystore.jks> -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.3.1" apply false 22 | id "org.jetbrains.kotlin.android" version "2.0.20" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /apk.sh: -------------------------------------------------------------------------------- 1 | version=$(head -n 5 pubspec.yaml | tail -n 1 | cut -d ' ' -f 2) 2 | 3 | flutter build apk -t lib/src/main.dart --split-per-abi \ 4 | && cp build/app/outputs/apk/release/app-arm64-v8a-release.apk ~/Desktop/JHenTai-${version}-arm64-v8a.apk \ 5 | && cp build/app/outputs/apk/release/app-armeabi-v7a-release.apk ~/Desktop/JHenTai-${version}-armeabi-v7a.apk \ 6 | && cp build/app/outputs/apk/release/app-x86_64-release.apk ~/Desktop/JHenTai-${version}-x86_64.apk \ 7 | -------------------------------------------------------------------------------- /assets/icon/JHenTai-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/assets/icon/JHenTai-background.png -------------------------------------------------------------------------------- /assets/icon/JHenTai-foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/assets/icon/JHenTai-foreground.png -------------------------------------------------------------------------------- /assets/icon/JHenTai-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/assets/icon/JHenTai-square.png -------------------------------------------------------------------------------- /assets/icon/JHenTai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/assets/icon/JHenTai.png -------------------------------------------------------------------------------- /assets/icon/JHenTai_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/assets/icon/JHenTai_512.png -------------------------------------------------------------------------------- /changelog/v4.0.5+78.md: -------------------------------------------------------------------------------- 1 | 1. 支持Linux版本的自动构建 2 | 2. 支持仅删除单条历史记录 3 | 3. 现在访问无法访问的画廊会区分无权限或被删除、被版权两种情况 4 | 4. 优化使用多标签搜索时的搜索历史纪录 5 | 5. 增加点赞时的数字动画 6 | 6. 简单修复如果归档下载的图片里有gif的时候无法读取归档的bug 7 | 7. 修复Webview的bug 8 | 8. 修复Windows系统下当你设置下载路径为根路径,如I:\时发生的bug。(不推荐下载路径设置为根路径) 9 | 9. 修复识别图片名的bug 10 | 11 | ------------------------------------------------------------------------------------------ 12 | 13 | 1. Support GitHub actions with Linux version 14 | 2. Support delete single history record 15 | 3. Toast when you visit a gallery which is removed or unavailable due to a copyright 16 | 4. Improve search history when use multiple tags 17 | 5. Add animation when vote for comments 18 | 6. Fix bug with archive which contains .gif 19 | 7. Fix bug with Webview. 20 | 8. Fix bug when you set download path at root path like I:\ on Windows 21 | 9. Fix bug with image reorganization -------------------------------------------------------------------------------- /changelog/v4.1.0+79.md: -------------------------------------------------------------------------------- 1 | 1. 安卓系统支持音量键翻页 2 | 2. 优化Linux下的标题栏 3 | 3. 重构解析缩略图的逻辑 4 | 4. 修复无法加载部分本地画廊的bug 5 | 5. 修复删除本地画廊时的bug 6 | 6. 修复评论中&字符的bug 7 | 7. 修复无法删除归档分组的bug 8 | 8. 修复在线模式下,阅读页面缩略图大小不正确的bug 9 | 10 | ------------------------------------------------------------------------------------------ 11 | 12 | 1. Support turn page by volume keys on Android 13 | 2. Improve window title bar on Linux 14 | 3. Refactor parsing logic about thumbnails 15 | 4. Fix bug with load local gallerys 16 | 5. Fix bug with removing local gallery 17 | 6. Fix bug with character & in comment 18 | 7. Fix bug with delete archive group 19 | 8. Fix bug with incorrect size of thumbnails in read page with online mode -------------------------------------------------------------------------------- /changelog/v4.1.1+80.md: -------------------------------------------------------------------------------- 1 | 1. 优化图片内存使用 2 | 2. 修复在线模式下缩略图的显示问题 3 | 3. 修复下载画廊超过1000页时的错误 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | 1. Improve memory usage 8 | 2. Fix bug with thumbnails in online mode 9 | 3. Fix bug with downloading gallery which has more than 1000 images -------------------------------------------------------------------------------- /changelog/v4.1.2+81.md: -------------------------------------------------------------------------------- 1 | 1. 桌面端支持使用第三方阅读器打开画廊(移动端无更新) 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Support opening gallery with third-party viewer(Desktop Platform Only) -------------------------------------------------------------------------------- /changelog/v4.1.3+82.md: -------------------------------------------------------------------------------- 1 | 1. 安全设置里支持切换至后台再切换回来时,使用指纹验证(需要切换到后台超过3s) 2 | 2. 支持解析Minimal布局 3 | 3. 修复生物认证相关bug 4 | 4. 修复iOS图标bug 5 | 5. 修复使用第三方图片阅览器的bug 6 | 6. 修复部分机型生物认证相关的bug 7 | 8 | ------------------------------------------------------------------------------------------ 9 | 10 | 1. Support lock application on resume(3 second delay) 11 | 2. Support parsing Minimal layout 12 | 3. Fix bugs with auth 13 | 4. Fix bug with app icon on iOS 14 | 5. Fix bug with 3-rd party viewer、 15 | 6. Fix bugs with auth on some devices -------------------------------------------------------------------------------- /changelog/v4.2.0+83.md: -------------------------------------------------------------------------------- 1 | 1. 阅读页支持长按分享或保存单张图片 2 | 2. 支持将画廊封面图移动至右侧 3 | 3. 支持取消检测剪切板中的画廊链接 4 | 4. 修复返回上层搜索页面时黑屏的bug 5 | 5. 优化应用锁 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. Support share or save single image in read page 10 | 2. Support move gallery cover to right side 11 | 3. Support disable detect of gallery link in clipboard 12 | 4. Fix bug with black page when return to previous search page 13 | 5. Optimize app lock -------------------------------------------------------------------------------- /changelog/v4.2.1+84.md: -------------------------------------------------------------------------------- 1 | 1. 优化应用锁 2 | 2. 优化iOS下分享图片功能 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. Optimize app lock 7 | 2. Optimize sharing pictures on iOS -------------------------------------------------------------------------------- /changelog/v4.2.2+85.md: -------------------------------------------------------------------------------- 1 | 1. Cookie登录支持直接设置igneous 2 | 2. 支持使用单独的Profile配置 3 | 3. 修复在线模式下阅读页显示重新下载按钮的bug 4 | 4. 修复切换登录模式时,按钮状态未同步更新的bug 5 | 6 | ------------------------------------------------------------------------------------------ 7 | 8 | 1. Support set `igneous` when log in by cookie 9 | 2. Support use separate profile 10 | 3. Fix the bug that the download button is displayed on the reading page in online mode 11 | 4. Fix the bug when switching login mode between password and cookie -------------------------------------------------------------------------------- /changelog/v4.3.1+87.md: -------------------------------------------------------------------------------- 1 | 1. 优化快速搜索界面 2 | 2. 优化进入在线阅读页面时的屏幕卡顿 3 | 3. 返回之前页面时不再唤起输入键盘 4 | 4. 修复收藏页搜索关键词无效的bug 5 | 6 | ------------------------------------------------------------------------------------------ 7 | 8 | 1. Optimize quick search page UI 9 | 2. Optimize the system lag when entering read page in online mode 10 | 3. No longer pop up the keyboard when returning to the previous page 11 | 4. Fix bug that search keyword is ineffective in favorite page -------------------------------------------------------------------------------- /changelog/v4.3.2+88.md: -------------------------------------------------------------------------------- 1 | 1. 修复iOS下更新应用后下载路径出错的严重bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Fix the bug that the download path is broken after updating the app on iOS -------------------------------------------------------------------------------- /changelog/v4.4.0+89.md: -------------------------------------------------------------------------------- 1 | 1. 优化搜索页UI, 给搜索栏更多空间,如果有其他建议欢迎反馈。 2 | 2. 支持提供多个标签的建议。现在可以通过空格分隔多个标签,并提供最后一个标签的搜索建议。 3 | 3. 现在在搜索结果页点击搜索栏会自动跳转到搜索建议页 4 | 4. 优化快速搜索标签过多时,底部的标签难以点到的问题 5 | 5. 更新改变下载路径时的提示(请不要使用SD卡或系统路径) 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. Optimize the UI of search page, arrange more space to the search bar, any other suggestions are welcome. 10 | 2. Support multiple tag suggestions. You can get it by inserting space between tags. 11 | 3. Optimize the logic with tapping search bar. 12 | 4. Fix the problem in quick search drawer that the bottom tags are hard to click when there are too many tags. 13 | 5. Update hint when changing download path(do not use SD-Card or any system path to avoid related errors) -------------------------------------------------------------------------------- /changelog/v5.0.0+90.md: -------------------------------------------------------------------------------- 1 | 1. 适配里站新的搜索规则,解决翻页问题,暂时移除翻页功能。如果你不使用里站或不知道什么是里站,那么请**不要更新,否则无法正常使用**。此版本目前只适用于里站,表站搜索规则同步更新之后才可使用此版本。 2 | 2. 因为我没有精力同时维护旧版布局,所以此次更新移除了旧版手机布局和旧版平板布局,**如果你使用的是旧版布局,那么快速搜索等配置都将被移除** 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. Adapt to the new search rules of the EX site, fix problems with turning page and temporarily remove the jump page button. 7 | If you don't use EX site(or sad panda site? I don't know its name in English) or don't know what it is, **do not update**. 8 | This version is currently only suitable to EX site, after EH site switches to the new search rules too, you can update then. 9 | 2. Because I don't have enough time to maintain the old layout at the same time, this update removes the old mobile layout and old tablet layout. 10 | **If you use the old layout, the quick search and other configurations will be removed.** -------------------------------------------------------------------------------- /changelog/v5.1.1+92.md: -------------------------------------------------------------------------------- 1 | 1. 如果你不使用里站或不知道什么是里站,那么请不要更新v5.0.0之后的版本,否则无法正常使用。此版本目前只适用于里站,表站搜索规则同步更新之后才可使用此版本 2 | 2. 支持H@H下载 3 | 3. 手机布局和平板布局现在单击底部主页面按钮可快速回顶,双击自动下拉刷新(与桌面布局保持一致) 4 | 4. 支持翻译搜索历史记录 5 | 5. 支持长按搜索历史记录将其追加到搜索框最后 6 | 6. 优化详情页布局 7 | 8 | ------------------------------------------------------------------------------------------ 9 | 10 | 1. If you don't use EX site or don't know what it is, do not update to v5.x.x!!!. This version is currently only suitable to EX site, after EH site switches to the new search rules too, you can update then. 11 | 2. Support H@H download 12 | 3. Now you can double click the bottom main page button to refresh the page 13 | 4. Support append search history to search field by long press the tag 14 | 5. Optimize the layout of detail page -------------------------------------------------------------------------------- /changelog/v5.1.10+101.md: -------------------------------------------------------------------------------- 1 | 1. 现在你可以手动指定额外扫描的本地画廊目录,这意味着你不用再将你本地的本子库放进JHenTai的下载目录了。在下载设置-额外的扫描路径中配置。 2 | 2. 优化读取本地画廊的逻辑,大幅减小所需时间 3 | 3. 优化读取归档画廊的速度 4 | 4. 桌面端现在会保持上一次窗口的尺寸 5 | 5. 默认情况下不再创建error日志和download日志 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. You can now manually specify additional path to scan gallerys, which means you don't have to put your local gallery library into JHenTai's download directory. Configure it in Download Setting. 10 | 2. Optimized the logic of loading local gallery 11 | 3. Optimized the speed of loading archive gallery 12 | 4. Window size will be kept in desktop platform 13 | 5. Won't create error log and download log by default -------------------------------------------------------------------------------- /changelog/v5.1.2+93.md: -------------------------------------------------------------------------------- 1 | 1. 如果你不使用里站或不知道什么是里站,那么请不要更新v5.0.0之后的版本,否则无法正常使用。此版本目前只适用于里站,表站搜索规则同步更新之后才可使用此版本 2 | 2. 重写评论解析,优化评论中文字、图片、链接的展示,支持修改评论和查看得分情况。 3 | 3. 现在详情页也会对关注了的标签进行高亮显示 4 | 4. 优化阅读页页码展示 5 | 5. 平板和电脑端默认阅读方向修改为从左到右 6 | 6. 修复桌面端滚动条过窄的bug 7 | 7. 优化H@H下载框在桌面端的布局 8 | 9 | ------------------------------------------------------------------------------------------ 10 | 11 | 1. If you don't use EX site or don't know what it is, do not update to v5.x.x!!!. This version is currently only suitable to EX site, after EH site switches to the new search rules too, you can update then. 12 | 2. Rewrite the gallery comment parser, optimize the display of text, pictures and links in the comment. You can now update your comment and view the score details. 13 | 3. Now the tag highlight will also work in detail page 14 | 4. Optimize the page number displayed in read page 15 | 5. Default reading direction is changed to left-to-right for tablet and desktop 16 | 6. Fix the bug that the scroll bar is too narrow on desktop 17 | 7. Optimize the layout of H@H download dialog on desktop -------------------------------------------------------------------------------- /changelog/v5.1.3+94.md: -------------------------------------------------------------------------------- 1 | 1. 如果你不使用里站或不知道什么是里站,那么请不要更新v5.0.0之后的版本,否则无法正常使用。此版本目前只适用于里站,表站搜索规则同步更新之后才可使用此版本 2 | 2. 修复上个版本的滚动条bug和评论bug 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. If you don't use EX site or don't know what it is, do not update to v5.x.x!!!. This version is currently only suitable to EX site, after EH site switches to the new search rules too, you can update then. 7 | 2. Fix the scroll bar bug and comment bug in the last version -------------------------------------------------------------------------------- /changelog/v5.1.5+96.md: -------------------------------------------------------------------------------- 1 | 1. **如果你不使用里站或不知道什么是里站,那么请不要更新v5.0.0之后的版本,否则无法正常使用。表站请使用[v4.4.0](https://github.com/jiangtian616/JHenTai/releases/tag/v4.4.0%2B89),此版本目前只适用于里站,表站搜索规则同步更新之后才可使用此版本** 2 | 2. 支持对不同的页面指定不同的画廊列表样式 3 | 3. 瀑布流布局显示收藏标签 4 | 4. 修复iOS文件搜索时崩溃的bug 5 | 5. 修复在详情页无法恢复下载归档的bug 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. **If you don't use EX site or don't know what it is, do not update to v5.x.x!!! If you want to browse EH site, please use [v4.4.0](https://github.com/jiangtian616/JHenTai/releases/tag/v4.4.0%2B89). This version is currently only suitable to EX site, after EH site switches to the new search rules too, you can update then.** 10 | 2. Support to specify different gallery list style for different pages 11 | 3. Show favorite tags in waterfall layout 12 | 4. Fix the bug that the app crashes when using file search 13 | 5. Fix the bug that you can't restore archive download task in detail page -------------------------------------------------------------------------------- /changelog/v5.1.6+97.md: -------------------------------------------------------------------------------- 1 | 1. **如果你不使用里站或不知道什么是里站,那么请不要更新v5.0.0之后的版本,否则无法正常使用。表站请使用[v4.4.0](https://github.com/jiangtian616/JHenTai/releases/tag/v4.4.0%2B89),此版本目前只适用于里站,表站搜索规则同步更新之后才可使用此版本** 2 | 2. 修复了一个bug,该bug曾导致如果在官网选择的画廊布局为Thumbnail或者Minimal+,收藏页将无法加载 3 | 3. 修复了一个bug,该bug曾导致无法收藏页无法加载第二页 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. **If you don't use EX site or don't know what it is, do not update to v5.x.x!!! If you want to browse EH site, please use [v4.4.0](https://github.com/jiangtian616/JHenTai/releases/tag/v4.4.0%2B89). This version is currently only suitable to EX site, after EH site switches to the new search rules too, you can update then.** 7 | 2. Fixed the bug that can't load gallerys if you choosed Thumbnail or Minimal+ layout in the official website 8 | 3. Fixed the bug that can't load next page in favorite page -------------------------------------------------------------------------------- /changelog/v5.1.7+98.md: -------------------------------------------------------------------------------- 1 | 1. **如果你不使用里站或不知道什么是里站,那么请不要更新v5.0.0之后的版本,否则无法正常使用。表站请使用[v4.4.0](https://github.com/jiangtian616/JHenTai/releases/tag/v4.4.0%2B89),此版本目前只适用于里站,表站搜索规则同步更新之后才可使用此版本** 2 | 2. 瀑布流布局图片增加Hero动画 3 | 3. 修复无法读取本地EHViewer画廊的bug 4 | 4. 支持点击EHViewer的画廊封面跳转到对应详情页面 5 | ------------------------------------------------------------------------------------------ 6 | 7 | 1. **If you don't use EX site or don't know what it is, do not update to v5.x.x!!! If you want to browse EH site, please use [v4.4.0](https://github.com/jiangtian616/JHenTai/releases/tag/v4.4.0%2B89). This version is currently only suitable to EX site, after EH site switches to the new search rules too, you can update then.** 8 | 2. Add Hero animation to waterfall layout images 9 | 3. Fix the bug that cannot read local EHViewer gallery 10 | 4. Support click EHViewer gallery's cover to jump to the corresponding detail page -------------------------------------------------------------------------------- /changelog/v5.1.8+99.md: -------------------------------------------------------------------------------- 1 | 1. 搜索筛选条件现在会自动继承上一次搜索的设定(但不包括搜索关键词) 2 | 2. 搜索筛选条件增加语言 3 | 3. 平板布局下,现在在详情页中点击tag搜索时,会打开一个新的搜索页,而不是在原搜索页重新搜索 4 | 4. 手机布局和平板布局下,现在点击快速搜索时,会打开一个新的搜索页,而不是在原搜索页重新搜索 5 | 5. 优化标签dialog 6 | 6. 优化评论解析 7 | 7. 支持一键复制日志 8 | ------------------------------------------------------------------------------------------ 9 | 10 | 1. Search options will now inherit from the previous search(exclude the search keyword) 11 | 2. Add language option to search options 12 | 3. In tablet layout, tap a tag in detail page will open a new search page, rather than searching in the original page 13 | 4. In mobile layout and tablet layout, tap a quick search config will open a new search page, rather than searching in the original page 14 | 5. Optimize tag dialog 15 | 6. Optimize comment parsing 16 | 7. Support copy log -------------------------------------------------------------------------------- /changelog/v5.1.9+100.md: -------------------------------------------------------------------------------- 1 | 1. 细化代理设置 2 | 2. 优化下载路径相关逻辑 3 | 3. 修复手机布局下点击快速搜索的bug 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | 1. Optimize proxy settings 8 | 2. Optimize the logic related to download path 9 | 3. Fix the bug with quick search -------------------------------------------------------------------------------- /changelog/v6.0.0+102.md: -------------------------------------------------------------------------------- 1 | 1. 重写桌面端搜索页,引入类似浏览器的多标签页搜索功能,用于解决以往点击Tag或快速搜索会直接丢弃旧的搜索结果的问题,同时也支持同时进行多个搜索。如果bug或改进建议可在issue里反馈。 2 | 2. 瀑布流布局增加标签信息 3 | 3. 修复详情页关注的标签背景颜色为浅色时,文字不显示的bug 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | 1. Rewrite desktop search page, support multiple tabs search like browser, to solve the problem that the old search 8 | result will be discarded when tapping Tag or quick search. 9 | And also support multiple searches at the same time. If you meet bugs or have any suggestion, welcome to submit 10 | issue. 11 | 2. Add tag info in waterfall layout 12 | 3. Fix the bug with invisible watched tag's text in details page -------------------------------------------------------------------------------- /changelog/v6.0.1+103.md: -------------------------------------------------------------------------------- 1 | 1. 修复有时候归档下载解压图片失败的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Fix the bug that sometimes fail to decompress images in archive -------------------------------------------------------------------------------- /changelog/v6.0.2+104.md: -------------------------------------------------------------------------------- 1 | 1. 优化本地画廊加载逻辑,提高加载速度 2 | 2. 优化桌面端搜索页左右拖动逻辑 3 | 3. 移除桌面布局下focus相关代码 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | 1. Optimize the logic with loading local gallery. 8 | 2. Optimize the logic with dragging in search page on desktop platform. 9 | 3. Remove code related to focus system in desktop layout. -------------------------------------------------------------------------------- /changelog/v6.0.3+105.md: -------------------------------------------------------------------------------- 1 | 1. 本地EHViewer画廊现在在详情页会展示为已下载状态 2 | 2. 修复移动端保存画廊gif图片的bug 3 | 3. 修复阅读本地画廊时未保存阅读进度的bug 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | 1. Now local EHViewer gallery will be displayed with downloaded status in detail page 8 | 2. Fix the bug with saving .gif for mobile platform. 9 | 3. Fix the bug with not saving reading progress for local gallery. -------------------------------------------------------------------------------- /changelog/v6.0.4+106.md: -------------------------------------------------------------------------------- 1 | 1. 修复本地画廊名称显示不正确的bug 2 | 2. 修复本地画廊列表排序错乱的bug 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. Fix the bug with incorrect name of local gallery. 7 | 2. Fix the bug with incorrect order of local gallery list. -------------------------------------------------------------------------------- /changelog/v6.0.5+107.md: -------------------------------------------------------------------------------- 1 | 1. 桌面端也支持在阅读页保存单张图片了,可在下载设置中配置保存路径 2 | 2. 优化本地画廊扫描 3 | 3. 移除桌面端阅读页ctrl键操作 4 | 4. 修复重新下载图片时的显示bug 5 | 6 | ------------------------------------------------------------------------------------------ 7 | 8 | 1. Support saving single image in reading page on desktop platform too. You can configure the save path in download setting. 9 | 2. Optimize local gallery scan. 10 | 3. Remove ctrl key operation in reading page on desktop platform. 11 | 4. Fix bug with re-downloading images. -------------------------------------------------------------------------------- /changelog/v6.0.6+108.md: -------------------------------------------------------------------------------- 1 | 1. 重写图片组件和阅读页图片加载逻辑,优化各页面下的图片表现,为新下载页做准备。 2 | 2. 阅读页手势区域宽度比例由1:4:1改为1:3:1 3 | 3. 修复阅读页在某些场景下翻页抖动的bug 4 | 4. 修复阅读页点击缩略图跳页时,进度条未同步更新的bug 5 | 5. 修复`删除任务和图片`无效的bug 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. Rewrite the image widget and the image loading logic and optimize the performance, in order to prepare for the new download page. 10 | 2. Change Reading page gesture area width ratio from 1:4:1 to 1:3:1 11 | 3. Fix the bug of shaking in read page when turning page 12 | 4. Fix the bug that slider status is not updated after taping the thumbnail in read page 13 | 5. Fix the bug that `Delete task and images` is ineffective -------------------------------------------------------------------------------- /changelog/v6.1.0+109.md: -------------------------------------------------------------------------------- 1 | 1. 测试新下载页(网格模式),旨在解决原先下载页空间利用度低的问题。 2 | 1. 默认在桌面端开启。 3 | 2. 目前功能与原先列表模式保持一致,但各区域的点击逻辑有所改变,自定义排序等更多功能将在未来添加。 4 | 3. 在样式设置中可修改网格列数。 5 | 2. 画廊详情页点击画廊信息区域可显示详细信息 6 | 3. 简中用户默认开启域名前置 7 | 4. 中文用户默认开启标签翻译 8 | 5. 优化阅读页图片加载性能 9 | 6. 限制本地创建的文件夹名称长度(可能导致原有的一些名称较长的画廊无法打开) 10 | 7. 修复无法恢复下载记录的bug 11 | 12 | ------------------------------------------------------------------------------------------ 13 | 14 | 1. New download page 15 | 1. Enabled by default on PC. 16 | 2. More features such as custom sorting will be added in the future. 17 | 3. You can customize column count in Style Settings 18 | 2. Show gallery detail info when you tap gallery info region in detail page 19 | 3. Optimize the performance of local images loading 20 | 4. Limit the length of local folder name (may cause some galleries with long name can't be opened) 21 | 5. Fix a bug that can't restore download tasks -------------------------------------------------------------------------------- /changelog/v6.2.0+110.md: -------------------------------------------------------------------------------- 1 | 1. 下载页网格布局下支持长按拖拽排序画廊或分组 2 | 2. 支持使用鼠标侧键退出阅读页 3 | 3. 支持在标签Dialog中点击标题直接复制 4 | 4. 支持在线阅读时保存原图 5 | 5. 恢复Windows端清除日志的功能 6 | 6. 限制本地创建的归档画廊文件夹名称长度(可能导致原有的一些名称较长的画廊无法打开) 7 | 7. 修复无法正常恢复归档任务的bug 8 | 9 | ------------------------------------------------------------------------------------------ 10 | 11 | 1. Support long press to sort gallery or group manually in download page with grid layout 12 | 2. Support using mouse back button to exit read page 13 | 3. Support copying tag directly by tapping title in tag dialog 14 | 4. Support saving original image when reading online 15 | 5. Support clearing log on Windows again 16 | 6. Limit the length of local folder name (may cause some galleries with long name can't be opened) 17 | 7. Fixed a bug that could not restore archive tasks normally -------------------------------------------------------------------------------- /changelog/v6.3.0+111.md: -------------------------------------------------------------------------------- 1 | 1. 支持自定义阅读页图片间的间隔(上下模式和左右连续模式) 2 | 2. 标签Dialog中现在默认情况下会隐藏R18G的图片 3 | 3. 优化在标签dialog中复制标签的逻辑 4 | 4. 修复E站页面更新导致无法获取apikey的bug,该bug会导致无法点赞或点踩评论等问题 5 | 5. 修复本地画廊页网格模式下无法删除画廊的bug 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. Support customize the space between images in read page 10 | 2. Hide R18G images by default in tag dialog 11 | 3. Optimize the logic of copying tag in tag dialog 12 | 4. Fix the bug that cannot get apikey due to the update of E-Hentai official site, which will cause the problem that 13 | cannot vote for comment and so on 14 | 5. Fix the bug that could not delete local gallery in grid layout -------------------------------------------------------------------------------- /changelog/v6.3.1+112.md: -------------------------------------------------------------------------------- 1 | 1. 修复安卓7版本以下的设备无法在线阅读加载图片失败的bug 2 | 2. 测试支持安卓和苹果部分机型的高刷 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. Fix the bug that cannot load images in online reading mode on devices with Android 7 or below 7 | 2. Support high refresh rate on some Android and iOS devices(experimentally) 8 | -------------------------------------------------------------------------------- /changelog/v6.3.2+113.md: -------------------------------------------------------------------------------- 1 | 1. 优化Cookie登录时解析Cookie的逻辑,放宽了格式限制 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Optimized the logic of cookie login -------------------------------------------------------------------------------- /changelog/v6.4.0+114.md: -------------------------------------------------------------------------------- 1 | 1. 升级至Flutter 3.7 2 | 1.1. 部分组件UI可能会自动更新 3 | 1.2. 桌面端鼠标滚轮速度可能有所变化,若有问题可在滚轮设置中调整 4 | 2. 优化Cookie复制逻辑 5 | 3. 现在登陆后显示昵称而不是用户名 6 | 4. 现在每次启动应用后会自动更新标签翻译数据 7 | 5. 修复阅读页自定义图片间隔在部分布局模式下不生效的bug 8 | 6. 修复阅读页改变阅读方向时图片间隔异常的bug 9 | 10 | ------------------------------------------------------------------------------------------ 11 | 12 | 1. Upgrade to Flutter 3.7 13 | 1.1. Some UI components may be automatically updated 14 | 1.2. The default mousewheel speed may change, you can adjust it in mousewheel setting 15 | 2. Optimized the logic of copy cookie 16 | 3. Display nickname instead of username after login\ 17 | 4. Fix two bugs with space between images in read page -------------------------------------------------------------------------------- /changelog/v6.5.0+116.md: -------------------------------------------------------------------------------- 1 | 1. 支持关闭侧滑返回的手势 2 | 2. 支持关闭侧滑打开左侧菜单的手势 3 | 3. 将部分设置项从`样式设置`中移入`偏好设置` 4 | 4. 修改展示快速回顶按钮的展示逻辑 5 | 5. 修复登陆昵称显示 6 | 6. 修复阅读设置页的bug 7 | 8 | ------------------------------------------------------------------------------------------ 9 | 10 | 1. Support disable the gesture of swipe back 11 | 2. Support disable the gesture of open left menu 12 | 3. Move some setting items from `Style Setting` to `Preference Setting` 13 | 4. Optimize the logic of display the floating button 14 | 5. Fix a bug with nickname display 15 | 6. Fix a bug in read setting page -------------------------------------------------------------------------------- /changelog/v6.6.0+117.md: -------------------------------------------------------------------------------- 1 | 1. 去除移动端手动拖动滚动条的功能,优化桌面和平板布局下的滚动条位置 2 | 2. 现在详情页点击标题也可以直接搜索相似画廊 3 | 3. 收藏或评分画廊成功后toast提示 4 | 4. 优化搜索页按钮UI 5 | 5. 支持应用密码锁 6 | 6. 优化桌面端阅读页焦点 7 | 7. 限制下载页画廊封面图片的最大大小,减轻加载压力 8 | 9 | ------------------------------------------------------------------------------------------ 10 | 11 | 1. Remove the function of manually dragging the scroll bar on mobile, optimize the position of the scroll bar in desktop and tablet layout 12 | 2. Now you can search similar gallery by tapping gallery title too in details page 13 | 3. Toast after favoriting or rating gallery successfully 14 | 4. Optimize button UI in search page 15 | 5. Support app password lock 16 | 6. Optimize focus node in read page for desktop 17 | 7. Limit the maximum size of gallery cover image in download page to reduce loading pressure 18 | -------------------------------------------------------------------------------- /changelog/v6.6.1+118.md: -------------------------------------------------------------------------------- 1 | 1. 恢复暂停全部任务和恢复全部任务的功能 2 | 2. 修复切换主题时的一些bug 3 | 3. 修复桌面端改变窗口大小后,重新启动时会恢复至默认大小的bug 4 | 4. 取消macOS下改变下载路径的功能 5 | 5. 修改下载任务的元数据文件名,使其在移动端可见 6 | 6. 修复下载页在网格模式下,不显示重新解锁状态的bug 7 | 8 | ------------------------------------------------------------------------------------------ 9 | 10 | 1. Support resuming or pausing all tasks 11 | 2. Fix some bugs when switching theme 12 | 3. Fix a bug with custom window size on desktop 13 | 4. Remove the function of changing download path on macOS 14 | 5. Change the name of the metadata file of the download task to make it visible on mobile 15 | 6. Fix the bug that the unlock status is not displayed in grid mode in download page 16 | -------------------------------------------------------------------------------- /changelog/v6.6.2+119.md: -------------------------------------------------------------------------------- 1 | 1. 去除FaceID 2 | ------------------------------------------------------------------------------------------ 3 | 4 | 1. Remove FaceID support 5 | -------------------------------------------------------------------------------- /changelog/v6.6.3+120.md: -------------------------------------------------------------------------------- 1 | 1. 修改配置文件名,使其在移动端可见 2 | 2. 现在阅读时不会自动锁定屏幕 3 | 3. 修复iOS端若同时开启生物认证和后台切换认证保护时,启动应用时会卡循环认证的bug 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. Change the name of the configuration file so that it is visible on mobile 7 | 2. Now the screen will not be locked automatically when reading 8 | 3. Fix a bug with circular auth when launching app 9 | -------------------------------------------------------------------------------- /changelog/v6.6.4+121.md: -------------------------------------------------------------------------------- 1 | 1. 修复iOS端若同时开启生物认证和后台切换认证保护时,启动应用时会卡循环认证的bug 2 | ------------------------------------------------------------------------------------------ 3 | 4 | 1. Fix a bug with circular auth when launching app 5 | -------------------------------------------------------------------------------- /changelog/v6.6.5+122.md: -------------------------------------------------------------------------------- 1 | 1. 使用后台线程解压归档压缩包,优化解压时UI卡顿的问题 2 | 2. 修复解锁页面可以通过左滑手势绕过认证的bug 3 | 3. 优化解锁页软键盘会挡住密码框的问题 4 | 4. 优化里站BT种子下载 5 | 5. 优化里站归档dialog按钮布局 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. Use isolate thread to unzip archive file, optimize UI freeze when unzipping 10 | 2. Fix a bug that you can bypass authentication with swipe back gesture 11 | 3. Fix a bug that soft keyboard blocks the password box in authentication page 12 | 4. Improve BT download in EX site 13 | 5. Optimize archive dialog for EX site 14 | -------------------------------------------------------------------------------- /changelog/v6.6.6+123.md: -------------------------------------------------------------------------------- 1 | 1. macOS安装包格式改为.dmg 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Distribute .dmg file for macOS 6 | -------------------------------------------------------------------------------- /changelog/v7.0.0+124.md: -------------------------------------------------------------------------------- 1 | 1. 优化主题配色,支持自定义主题色 2 | 2. 拦截__utmp cookie 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. Update theme performance, support custom theme color 7 | 2. Intercept __utmp cookie 8 | -------------------------------------------------------------------------------- /changelog/v7.1.0+125.md: -------------------------------------------------------------------------------- 1 | 1. (实验性)桌面端支持对画廊图片进行AI超分辨率放大,详情可在更新后高级设置页面查看。使用方法:https://github.com/jiangtian616/JHenTai/wiki/AI%E5%9B%BE%E7%89%87%E6%94%BE%E5%A4%A7%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95 2 | 2. 优化Cookie登录方式 3 | 3. 重写优化阅读页手势,(实验性)支持单手双击拖动缩放手势(需要在设置中打开开关),修复在缩放状态下翻页的bug 4 | 4. 兜底处理部分Linux系统无法获取DOCUMENTS目录的问题 5 | 5. 兜底处理添加关注标签时达到上限的情况 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. (Experimental) Support AI image super resolution for desktop platform. You can check the usage in https://github.com/jiangtian616/JHenTai/wiki/AI-Image-Super-Resolution-Usage 10 | 2. Optimize cookie login 11 | 3. Rewrite gestures recognition in read page,(Experimental) support double `tap then drag to zoom in/out` (need to turn on the switch in settings), fix a bug with turning page when image is zoomed in 12 | 4. Fix a bug that some Linux system doesn't have DOCUMENTS directory 13 | 5. Toast when you reach the limit of watching tags 14 | -------------------------------------------------------------------------------- /changelog/v7.1.1+126.md: -------------------------------------------------------------------------------- 1 | 1. 安卓端支持通过网页分享打开app 2 | 2. 安卓12+支持在高级设置中手动添加验证链接,以在其他应用中可通过点击链接唤起JHenTai 3 | 3. 优化移动端SnackBar 4 | 4. 阅读页展示本地图片时采用更优的采样方法 5 | 5. 修复在刚登录后或刚启动应用时无法查看cookie的bug 6 | 6. 修复下载页无画廊时排序的bug 7 | 7. 修复手机布局下下载页使用网格布局的bug 8 | 8. 修复手机布局下下载页的显示bug 9 | 10 | ------------------------------------------------------------------------------------------ 11 | 12 | 1. Support opening app via sharing in browser 13 | 2. Support manually add link to verified links in `Advanced Setting` in order to open JHenTai in 3-rd apps 14 | 3. Use better sampling method to display local images in read page 15 | 4. Optimize SnackBar for mobile 16 | 5. Fix a bug that prevents viewing cookies when logging in or launching the app. 17 | 6. Fix a bug with sorting in download page when no gallerys exists 18 | 7. Fix a bug with grid layout in download page for mobile 19 | 8. Fix a bug with gallery display in download page for mobile 20 | -------------------------------------------------------------------------------- /changelog/v7.1.2+127.md: -------------------------------------------------------------------------------- 1 | 1. 修复里站画廊封面无法显示的问题 2 | 2. EH设置页中支持查看当前资产余额 3 | 3. EH设置页中支持重置图片配额 4 | 4. 搜索历史现在只会展示最多50条 5 | 5. Cookie登录优化,若igneous有效则自动切换至里站 6 | 6. 修复归档dialog中gp和credit显示错误的bug 7 | 7. 正确处理在下载过程中画廊被删除的情况 8 | 8. 修复超分辨率图片初始化的bug 9 | 10 | ------------------------------------------------------------------------------------------ 11 | 12 | 1. Fix the bug that fail to load gallery cover 13 | 2. Display your credits and GP in EH Setting page 14 | 3. Support resetting image limit in EH Setting page 15 | 4. Search history now only displays up to 50 records 16 | 5. Optimization of Cookie login: if the igneous cookie is valid, automatically switch to EX site. 17 | 6. Fix the bug with gp & credit display in archive dialog 18 | 7. Fix the bug when a gallery is deleted while being downloaded 19 | 8. Fix the bug with super resolution -------------------------------------------------------------------------------- /changelog/v7.1.3+128.md: -------------------------------------------------------------------------------- 1 | 1. 大幅优化页面渲染性能,缓解UI卡顿 2 | 2. 新增瀑布流(中)布局 3 | 3. 阅读设置页现在不会应用沉浸模式 4 | 4. 修复设置页尾部文字颜色的bug 5 | 5. 修复浏览过的表站画廊被移入里站后显示被删除的bug 6 | 6. 优化iOS和macOS上本地画廊的帮助信息 7 | 7. 修复在访问被版权保护的画廊时,版权作者显示错误的bug 8 | 9 | ------------------------------------------------------------------------------------------ 10 | 11 | 1. Optimize the performance of page rendering greatly 12 | 2. Add a new layout: WaterFallFlow (medium) 13 | 3. The immersive mode is no longer applied in read setting page 14 | 4. Fix the bug with font color in setting page 15 | 5. Fix the bug where previously viewed gallery is shown as deleted after being moved into the EX site 16 | 6. Optimize help information for local gallery on iOS and macOS 17 | 7. Fix the bug with information displayed when accessing a gallery that is protected by copyright -------------------------------------------------------------------------------- /changelog/v7.2.0+129.md: -------------------------------------------------------------------------------- 1 | 1. 下载页支持多选操作 2 | 2. 修复部分操作错误信息未翻译的bug 3 | 3. 修复下载页排序的bug 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | 1. Support batch operation in download page 8 | 2. Fix untranslated error message in some cases 9 | 3. Fix the bug of sorting in download page -------------------------------------------------------------------------------- /changelog/v7.2.1+130.md: -------------------------------------------------------------------------------- 1 | 1. 支持本地标签屏蔽 2 | 2. 优化画廊扫描速度,现在本地画廊路径默认为`{JHentai}/local_gallery` 3 | 3. 修复部分加载失败的场景下无法重试的bug 4 | 4. 修复安全认证的bug 5 | 6 | ------------------------------------------------------------------------------------------ 7 | 8 | 1. Support filtering tags locally 9 | 2. Optimize gallery scanning speed, now the default local gallery path is `{JHentai}/local_gallery` 10 | 3. Fix the bug that some loading failures cannot be retried 11 | 4. Fix bug with security authentication -------------------------------------------------------------------------------- /changelog/v7.2.2+131.md: -------------------------------------------------------------------------------- 1 | 1. 支持自定义Host映射,间接解决直连问题 2 | 2. 双页阅读模式下支持偏移首页 3 | 3. -邮箱 +telegram 4 | ------------------------------------------------------------------------------------------ 5 | 6 | 1. Support custom host mapping 7 | 2. Support displaying first page along in double column reading mode 8 | 3. -Email +Telegram -------------------------------------------------------------------------------- /changelog/v7.2.3+132.md: -------------------------------------------------------------------------------- 1 | 1. iOS和macOS现在也可以自定义本地画廊扫描路径了 2 | 2. 支持修改标签颜色 3 | 3. 阅读页限制缩略图分辨率大小,移动端额外会限制图片分辨率大小 4 | 4. 修复修改标签权重时,无法设置为负数的bug 5 | 5. 修复修改下载设置的bug 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. iOS and macOS can now customize the local gallery scan path 10 | 2. Add support to customize tag color 11 | 3. Add limit to image resolution in read page 12 | 4. Fix the bug that the tag weight cannot be set to a negative number 13 | 5. Fix the bug with download setting -------------------------------------------------------------------------------- /changelog/v7.3.0+133.md: -------------------------------------------------------------------------------- 1 | 1. 双页阅读模式下,自动识别跨页(宽度>长度)并单独显示 2 | 2. Linux系统下应用目录由`~/Documents`改为`~/.local/share/jhentai` 3 | 3. 下载时利用缓存加速 4 | 4. 优化桌面端多线程解析的性能 5 | 5. 现在被本地标签过滤的画廊会直接不显示 6 | 6. 修复手机布局下主页排行榜和热门部分的画廊未应用本地标签屏蔽的问题 7 | 7. 修复一些取消下载后的报错 8 | 9 | ------------------------------------------------------------------------------------------ 10 | 11 | 1. Recognize spread page in double-column read mode and display spread page in a single page 12 | 2. Change application directory from `~/Documents` to `~/.local/share/jhentai` in Linux 13 | 3. Use cache to speed up download 14 | 4. Optimize the performance of multi-threaded parsing in desktop layout 15 | 5. Now galleries filtered by local tags will not be displayed 16 | 6. Fix the bug that galleries are not filtered by local tags at home page in mobile layout 17 | 7. Fix some errors after canceling download -------------------------------------------------------------------------------- /changelog/v7.3.1+134.md: -------------------------------------------------------------------------------- 1 | 1. 优化多线程相关 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Fix bug with thread -------------------------------------------------------------------------------- /changelog/v7.3.3+136.md: -------------------------------------------------------------------------------- 1 | 1. 多线程优化 2 | 2. 现在更新画廊时也会同步迁移原画廊已AI放大过的图片数据 3 | 3. 修复在线阅读在双页模式下,图片大小异常的bug 4 | 4. 修复在双页模式下,AI放大后的图片布局异常的bug 5 | 6 | ------------------------------------------------------------------------------------------ 7 | 8 | 1. Multi-thread optimization 9 | 2. When updating the gallery, the AI-upscaled image data from the original gallery will also be synchronously migrated 10 | 3. Fix the bug with image size in online reading in double column mode 11 | 4. Fix the bug that the wrong layout of AI-upscaled images in double column mode -------------------------------------------------------------------------------- /changelog/v7.3.4+137.md: -------------------------------------------------------------------------------- 1 | 1. 支持隐藏评论、显示所有评论 2 | 2. `本地标签设置`移到`偏好设置`下 3 | 3. 修复应用启动时未自动更新标签翻译数据的bug 4 | 4. 修复账号密码登录失败时解析失败的bug 5 | 5. 修复部分npe 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. Support hide comments and show all comments 10 | 2. Move `Local Tag Setting` to `Preference Setting` 11 | 3. Fix the bug with password login 12 | 4. Fix some npe -------------------------------------------------------------------------------- /changelog/v7.3.5+138.md: -------------------------------------------------------------------------------- 1 | 1. 详情页布局优化 2 | 2. 支持修改收藏页排序条件 3 | 3. 支持在详情页直接删除当前画廊的下载文件 4 | 4. 手机布局下左侧菜单支持上下滚动 5 | 5. 在线阅读模式下支持重新加载图片 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | 1. Optimize the layout of detail page 10 | 2. Support changing the sort condition in favorite page 11 | 3. Support deleting downloaded files of current gallery in detail page 12 | 4. Make left menu scrollable in mobile layout 13 | 5. Support reloading images in online reading -------------------------------------------------------------------------------- /changelog/v7.4.0+139.md: -------------------------------------------------------------------------------- 1 | 1. 阅读页支持锁定屏幕方向 2 | 2. 支持关闭音量键翻页功能 3 | 3. 支持自定义手机布局下左右两侧抽屉菜单的收拾区域宽度 4 | 4. 手机布局下从主页进入排行榜和热门时不再新建路由页面 5 | 5. 详情页右上角按钮布局优化 6 | 6. 支持手动为画廊增加标签 7 | 7. 将原来的双击放大手势和单击后拖拽放大手势分离,可在设置中分别开启 8 | 8. 支持设置启动时的默认菜单 9 | 9. 修复在双列阅读模式下放大后无法左右移动图片的bug 10 | 11 | ------------------------------------------------------------------------------------------ 12 | 13 | 1. Support lock screen orientation in reading page 14 | 2. Support disable volume key page turning 15 | 3. Support customizing drawer gesture edge width 16 | 4. Don't create new route page when entering ranklist and popular page from home dashboard page in mobile layout 17 | 5. Optimize top right button layout in detail page 18 | 6. Support manually adding tags for gallery 19 | 7. Separate double tap zoom gesture and tap drag zoom gesture, they can be enabled separately in settings now 20 | 8. Support setting default tab when app starts 21 | 9. Fix the bug that can't drag image in double column read mode -------------------------------------------------------------------------------- /changelog/v7.4.1+140.md: -------------------------------------------------------------------------------- 1 | 1. 现在给画廊标签投票并刷新页面后,会显示标签的投票状态 2 | 2. 支持默认收藏夹 3 | 3. 优化阅读方向的展示 4 | 4. 优化移动端阅读页UI 5 | 6 | ------------------------------------------------------------------------------------------ 7 | 8 | 1. Display own vote status of gallery tag 9 | 2. Support default favorite selection 10 | 3. Optimize reading direction 11 | 4. Optimize reading page UI on mobile -------------------------------------------------------------------------------- /changelog/v7.4.10+149.md: -------------------------------------------------------------------------------- 1 | - 修复v7.4.8中引入的里站访问bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the bug of accessing the EX site introduced in v7.4.8 -------------------------------------------------------------------------------- /changelog/v7.4.12+160.md: -------------------------------------------------------------------------------- 1 | - 优化标签评分的状态更新逻辑 2 | - 修复评分画廊后UI状态未更新的bug 3 | - 修复详情页刷新时的UI bug 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | - Optimize the status update logic of tag rating 8 | - Fix the bug that the status is not updated after rating 9 | - Fix UI bug when refreshing the details page -------------------------------------------------------------------------------- /changelog/v7.4.12+161.md: -------------------------------------------------------------------------------- 1 | - 优化标签评分的状态更新逻辑 2 | - 修复评分画廊后UI状态未更新的bug 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Optimize the status update logic of tag rating -------------------------------------------------------------------------------- /changelog/v7.4.12+162.md: -------------------------------------------------------------------------------- 1 | - 优化详情页标题 2 | ------------------------------------------------------------------------------------------ 3 | 4 | - Optimize gallery title in details page -------------------------------------------------------------------------------- /changelog/v7.4.12+163.md: -------------------------------------------------------------------------------- 1 | - 支持隐藏快速回顶按钮 2 | ------------------------------------------------------------------------------------------ 3 | 4 | - Support hiding the quick back to top FAB -------------------------------------------------------------------------------- /changelog/v7.4.12+164.md: -------------------------------------------------------------------------------- 1 | - 优化部分网络错误的提示文案 2 | ------------------------------------------------------------------------------------------ 3 | 4 | - Optimized some network error information -------------------------------------------------------------------------------- /changelog/v7.4.12+165.md: -------------------------------------------------------------------------------- 1 | - 修复部分场景下删除本地画廊时的bug 2 | ------------------------------------------------------------------------------------------ 3 | 4 | - Fix the bug of deleting local gallery in some scenarios -------------------------------------------------------------------------------- /changelog/v7.4.12+172.md: -------------------------------------------------------------------------------- 1 | - 修复部分情况下509识别错误的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the bug of 509 recognition error in some cases -------------------------------------------------------------------------------- /changelog/v7.4.12.md: -------------------------------------------------------------------------------- 1 | - 移动端从上至下阅读方向下支持预留顶部刘海区域 2 | - 修复收藏画廊后UI状态未更新的bug 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | - Support adding padding before first image to avoid notch and status bar on mobile with top-to-bottom read direction 7 | - Fix the bug that the status is not updated after favorite -------------------------------------------------------------------------------- /changelog/v7.4.13+176.md: -------------------------------------------------------------------------------- 1 | - 修复部分场景下无法投票评论、新增评论的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the bug that cannot vote or add comments in some scenarios -------------------------------------------------------------------------------- /changelog/v7.4.13+188.md: -------------------------------------------------------------------------------- 1 | - 归档下载切换为4线程下载 2 | - 详情页标签上色适配金星账号的多标签集 3 | - 标签设置适配标签集默认颜色 4 | - 优化删除标签时的UI 5 | - 优化高级设置页面加载逻辑 6 | - 优化相似搜索 7 | - 优化iOS后台模糊颜色 8 | - 修复某些场景下请求重定向的问题 9 | 10 | ------------------------------------------------------------------------------------------ 11 | 12 | - Support 4-thread download for archive download 13 | - Add support for multiple tag set for gold star account 14 | - Add support for default color of tag set in tag settings 15 | - Optimize the UI when deleting tags 16 | - Optimize the loading logic of the advanced settings page 17 | - Optimize similar search 18 | - Optimize the blur color in the background of iOS 19 | - Fix the problem of request redirection in some scenarios -------------------------------------------------------------------------------- /changelog/v7.4.13+190.md: -------------------------------------------------------------------------------- 1 | - 支持自定义归档下载线程数 2 | - 修复归档多线程下载时部分情况下文件操作失败的bug 3 | - 修复归档多线程下载时部分情况下无法删除本地已下载文件的bug 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | - Support customize archive download thread count 8 | - Fix the bug that file operation fails in some cases when downloading archive 9 | - Fix the bug that file deletion fails in some cases when downloading archive -------------------------------------------------------------------------------- /changelog/v7.4.13+193.md: -------------------------------------------------------------------------------- 1 | - 桌面端支持webview登录 2 | - 归档多线程下载支持读取代理配置 3 | - 归档多线程下载支持控制并发度,在下载线程不足时会自动进入等待状态 4 | - 优化归档多线程时出现410、429时的提示 5 | - 修复归档多线程下载时的若干bug 6 | 7 | ------------------------------------------------------------------------------------------ 8 | 9 | - Support webview login on desktop 10 | - Support load proxy config for multi-thread archive downloading 11 | - Support control concurrency for multi-thread archive downloading, it will wait until threads is enough 12 | - Optimized hints when 410、429 error occurs for multi-thread archive downloading 13 | - Fix several bugs for multi-thread archive downloading -------------------------------------------------------------------------------- /changelog/v7.4.13+194.md: -------------------------------------------------------------------------------- 1 | - 修复修改代理设置后未同步至归档下载的bug 2 | - 修复移动端获取代理配置的bug 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | - Fix bug that proxy settings are not synchronized to the archive download after modification 7 | - Fix bug with archive download of proxy settings on mobile -------------------------------------------------------------------------------- /changelog/v7.4.13+195.md: -------------------------------------------------------------------------------- 1 | - 修复socks5代理无法应用用户名密码认证的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix bug that socks5 proxy cannot apply username and password authentication -------------------------------------------------------------------------------- /changelog/v7.4.4+143.md: -------------------------------------------------------------------------------- 1 | 1. 适配10.27日E站更新导致无法在线阅读的问题 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Fix the problem that online reading is not available due to the update of E-Hentai -------------------------------------------------------------------------------- /changelog/v7.4.5+144.md: -------------------------------------------------------------------------------- 1 | 1. 补充适配原图保存与下载逻辑 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | 1. Fix the problem with reading or download original image -------------------------------------------------------------------------------- /changelog/v7.4.9+148.md: -------------------------------------------------------------------------------- 1 | - 修复v7.4.8中引入的下载超时bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the download timeout bug introduced in v7.4.8 -------------------------------------------------------------------------------- /changelog/v7.5.0+199.md: -------------------------------------------------------------------------------- 1 | - 修复部分场景下保存原图失败的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix bug that failed to save the original image in some scenarios -------------------------------------------------------------------------------- /changelog/v7.5.0.md: -------------------------------------------------------------------------------- 1 | - 归档下载支持多线程下载 2 | - 支持自定义归档下载线程数 3 | - 归档多线程下载支持控制并发度,在下载线程不足时会自动进入等待状态 4 | - 详情页标签上色适配金星账号的多标签集 5 | - 标签设置适配标签集默认颜色 6 | - 桌面端支持webview登录 7 | - 优化删除标签时的UI 8 | - 优化高级设置页面加载逻辑 9 | - 优化相似搜索 10 | - 优化iOS后台模糊颜色 11 | - 修复某些场景下请求重定向的问题 12 | - 修复部分场景下无法投票评论、新增评论的bug 13 | - 修复socks5代理无法应用用户名密码认证的bug 14 | 15 | ------------------------------------------------------------------------------------------ 16 | 17 | - Support multi-thread download for archive download 18 | - Support customize archive download thread count 19 | - Support control concurrency for multi-thread archive downloading, it will wait until threads is enough 20 | - Add support for multiple tag set for gold star account 21 | - Add support for default color of tag set in tag settings 22 | - Support webview login on desktop 23 | - Optimize the UI when deleting tags 24 | - Optimize the loading logic of the advanced settings page 25 | - Optimize similar search 26 | - Optimize the blur color in the background of iOS 27 | - Fix the problem of request redirection in some scenarios 28 | - Fix the bug that cannot vote or add comments in some scenarios 29 | - Fix bug that socks5 proxy cannot apply username and password authentication -------------------------------------------------------------------------------- /changelog/v7.5.1.md: -------------------------------------------------------------------------------- 1 | - 更新内置host处理域名前置问题 2 | ------------------------------------------------------------------------------------------ 3 | 4 | - Update built-in host -------------------------------------------------------------------------------- /changelog/v7.5.2.md: -------------------------------------------------------------------------------- 1 | - 详情页显示评论投票状态 2 | - 修复未登录状态下部分场景无法解析阅读页面的bug 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | - Show comment vote status in detail page 7 | - Fix bug that cannot parse read page in some scenarios when not logged in -------------------------------------------------------------------------------- /changelog/v7.5.3+208.md: -------------------------------------------------------------------------------- 1 | - 修复文件搜索时的相关bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix bug with file search -------------------------------------------------------------------------------- /changelog/v7.5.3+212.md: -------------------------------------------------------------------------------- 1 | - 临时修改部分设置默认值(精简主页面开关默认开启、主页面默认只搜索NON-H) 2 | - 支持从单图链接跳转画廊详情页 3 | - 画廊排序改为自然排序 4 | - 部分评论发言过滤 5 | - 修复文件搜索时的相关bug 6 | - 修复高版本安卓无法保存图片的bug 7 | - 修复部分画廊相似搜索时截取的标题不正确的bug 8 | 9 | ------------------------------------------------------------------------------------------ 10 | 11 | - Temporary change default value of some settings 12 | - Support jump to gallery detail page from single image link 13 | - Change gallery sort rule to natural sort 14 | - Filter some words for comments 15 | - Fix bug with file search 16 | - Fix bug that can't save image on high version Android 17 | - Fix bug that search keyword is incorrect for some gallerys when do similar search -------------------------------------------------------------------------------- /changelog/v7.5.3.md: -------------------------------------------------------------------------------- 1 | - 搜索页支持显示搜索结果数目 2 | - 支持为本地阅读设置预载图片数量 3 | - 下载页支持显示分组下画廊数量 4 | - Sad Panda异常支持直接跳转Wiki 5 | - 优化搜索弹窗category选择 6 | - 优化下载记录中日文标题的展示逻辑 7 | - 修复部分场景下下载图片错误的bug 8 | 9 | ------------------------------------------------------------------------------------------ 10 | 11 | - Search page supports displaying the number of search results 12 | - Support setting the number of preloaded images for local reading 13 | - Supports displaying the number of galleries in download group 14 | - Lead to Wiki when Sad Panda occurs 15 | - Optimize the category selection of the search dialog 16 | - Optimize the display logic of Japanese titles in download records 17 | - Fix bug that cannot download images in some scenarios -------------------------------------------------------------------------------- /changelog/v7.5.4+221.md: -------------------------------------------------------------------------------- 1 | - 修复评论解析、收藏解析 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix comment and favorites parsing -------------------------------------------------------------------------------- /changelog/v7.5.4+222.md: -------------------------------------------------------------------------------- 1 | - 修复路由bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix route bug -------------------------------------------------------------------------------- /changelog/v7.5.5+224.md: -------------------------------------------------------------------------------- 1 | - 域名前置访问机制由单点改为轮询,以避免未来可能发生的[类似情况](https://forums.e-hentai.org/index.php?showtopic=244935&hl=round-robin)。 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Change the domain fronting mechanism from single point to round-robin to avoid [similar issues]((https://forums.e-hentai.org/index.php?showtopic=244935&hl=round-robin)) in the future. -------------------------------------------------------------------------------- /changelog/v8.0.0+244.md: -------------------------------------------------------------------------------- 1 | - 优化新版本数据迁移 2 | - 修复在画廊有双标题时,下载后ComicInfo.xml路径错误的问题 #476 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | - Optimize the new version data migration 7 | - Fix the issue that the path of ComicInfo.xml is incorrect after downloading when there are double titles in the gallery #476 -------------------------------------------------------------------------------- /changelog/v8.0.0+245.md: -------------------------------------------------------------------------------- 1 | - 优化新版本数据迁移 2 | - 修复在画廊有双标题时,下载后ComicInfo.xml路径错误的问题 #476 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | - Optimize the new version data migration 7 | - Fix the issue that the path of ComicInfo.xml is incorrect after downloading when there are double titles in the gallery #476 -------------------------------------------------------------------------------- /changelog/v8.0.0+246.md: -------------------------------------------------------------------------------- 1 | - 修复里站保存profile后,重启应用后会自动重置的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the bug that the EX profile saved will be automatically reset after restarting the application -------------------------------------------------------------------------------- /changelog/v8.0.0+247.md: -------------------------------------------------------------------------------- 1 | - 优化日志记录 2 | - 修复历史页在瀑布流布局下的显示bug 3 | 4 | ------------------------------------------------------------------------------------------ 5 | 6 | - Optimize log recording 7 | - Fix the display bug of the history page in the waterfall layout -------------------------------------------------------------------------------- /changelog/v8.0.0+248.md: -------------------------------------------------------------------------------- 1 | - 修复部分老版本升级时数据结构更新失败的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the bug that the data structure update fails when upgrading some old versions -------------------------------------------------------------------------------- /changelog/v8.0.0+249.md: -------------------------------------------------------------------------------- 1 | - 补充繁體中文(台灣)翻译 [kenny03211](https://github.com/kenny03211) 2 | - 优化桌面端详情页按钮区域手势 3 | - 修复安全设置在部分场景下的异常表现 4 | 5 | ------------------------------------------------------------------------------------------ 6 | 7 | - Update traditional chinese translation 8 | - Optimize the gesture area of the button on the desktop details page 9 | - Fix the abnormal behavior of security settings in some scenarios -------------------------------------------------------------------------------- /changelog/v8.0.0+250.md: -------------------------------------------------------------------------------- 1 | - 优化数据迁移逻辑 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Optimize data migration logic -------------------------------------------------------------------------------- /changelog/v8.0.0+251.md: -------------------------------------------------------------------------------- 1 | - 优化数据导出逻辑 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Optimize data export logic -------------------------------------------------------------------------------- /changelog/v8.0.0+252.md: -------------------------------------------------------------------------------- 1 | - 兼容降级应用后再次更新时无法启动的场景 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the issue that the app cannot be started after updating again when downgrading the app -------------------------------------------------------------------------------- /changelog/v8.0.0+253.md: -------------------------------------------------------------------------------- 1 | - 兼容降级应用后再次更新时无法启动的场景 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix the issue that the app cannot be started after updating again when downgrading the app -------------------------------------------------------------------------------- /changelog/v8.0.0+254.md: -------------------------------------------------------------------------------- 1 | - 优化tag补全 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Optimize tag completion -------------------------------------------------------------------------------- /changelog/v8.0.1+256.md: -------------------------------------------------------------------------------- 1 | - 回滚内置hosts #493 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Roll back the built-in hosts #493 -------------------------------------------------------------------------------- /changelog/v8.0.1+257.md: -------------------------------------------------------------------------------- 1 | - 修复本地屏蔽规则中无法以正则形式过滤评论内容的bug 2 | 3 | ------------------------------------------------------------------------------------------ 4 | 5 | - Fix bug that comments content cannot be filtered by regular expressions in local blocking rules -------------------------------------------------------------------------------- /changelog/v8.0.2+259.md: -------------------------------------------------------------------------------- 1 | - 修复图片配额解析的bug 2 | 3 | --- 4 | 5 | - Fix the bug with image quota parsing -------------------------------------------------------------------------------- /changelog/v8.0.2.md: -------------------------------------------------------------------------------- 1 | - Cookie页支持一键刷新igneous cookie 2 | - 重新支持鼠标拖动阅读页中的自动模式时间选择器与缩略图 3 | - 修复在部分场景下,注销后cookie仍会保留的bug 4 | - 修复E站更新后无法显示图片配额的bug;现在仅有捐赠者会显示图片配额;去除重置图片配额的功能 5 | 6 | -------------------- 7 | 8 | - Support one-click refresh of igneous cookie in cookie page 9 | - Re-enable mouse drag to select time in automatic mode and thumbnails in the reading page. 10 | - Fix the bug that cookies are still retained after logout in some scenarios 11 | - Fix the bug that the image quota cannot be displayed after updating of E-Hentai. Now only image quota will only be displayed for donators. Remove the function of resetting the image quota -------------------------------------------------------------------------------- /changelog/v8.0.3.md: -------------------------------------------------------------------------------- 1 | - 移动端主页默认只展示nonH画廊 2 | - 修复重复导入本地屏蔽规则时重复添加的bug 3 | - 修复账密登录后cookie异常的bug 4 | 5 | -------------------- 6 | 7 | - Only show non-H galleries on the mobile homepage by default 8 | - Fix the bug that local block rules are repeatedly added when imported repeatedly 9 | - Fix the bug that cookies are abnormal after logging in with account and password -------------------------------------------------------------------------------- /changelog/v8.0.4+262.md: -------------------------------------------------------------------------------- 1 | - 提前适配10.15日E站画廊详情页缩略图DOM变化 2 | 3 | -------------------- 4 | 5 | - Adapt to the DOM changes of the gallery detail page thumbnails on E-Hentai as of October 15 -------------------------------------------------------------------------------- /changelog/v8.0.4+263.md: -------------------------------------------------------------------------------- 1 | - 提前适配webp格式图片 2 | 3 | -------------------- 4 | 5 | - Adapt to the .webp images in advance -------------------------------------------------------------------------------- /changelog/v8.0.4.md: -------------------------------------------------------------------------------- 1 | - 提前适配10.15日E站画廊详情页缩略图DOM变化 2 | 3 | -------------------- 4 | 5 | - Adapt to the DOM changes of the gallery detail page thumbnails on E-Hentai as of October 15 -------------------------------------------------------------------------------- /changelog/v8.0.5+265.md: -------------------------------------------------------------------------------- 1 | - 修复H@H归档弹窗相关bug 2 | 3 | -------------------- 4 | 5 | - Fix the bug of H@H archive dialog -------------------------------------------------------------------------------- /changelog/v8.0.5+266.md: -------------------------------------------------------------------------------- 1 | - 优化垂直阅读阅读模式在放大模式下的滚动 2 | - h 3 | 4 | -------------------- 5 | 6 | - Optimize the scrolling of the vertical reading mode in the zoom mode 7 | - Optimize the font weight display on mobile -------------------------------------------------------------------------------- /changelog/v8.0.5.md: -------------------------------------------------------------------------------- 1 | - 修复在详情页展示页码时解析错误的bug 2 | 3 | -------------------- 4 | 5 | - Fix the bug of parsing error when displaying page number in detail page -------------------------------------------------------------------------------- /changelog/v8.0.6+271.md: -------------------------------------------------------------------------------- 1 | - 优化双击返回逻辑 2 | 3 | -------------------- 4 | 5 | - Optimize double-click return logic -------------------------------------------------------------------------------- /changelog/v8.0.6+273.md: -------------------------------------------------------------------------------- 1 | - 优化内置ip 2 | - 修复里站无法快速屏蔽评论区用户的bug 3 | 4 | -------------------- 5 | 6 | - Optimize built-in IP 7 | - Fix bug that can't block users from comments in EX site -------------------------------------------------------------------------------- /changelog/v8.0.6+274.md: -------------------------------------------------------------------------------- 1 | - 内置屏蔽用户名单 2 | - 优化内置ip 3 | - 修复里站无法快速屏蔽评论区用户的bug 4 | 5 | -------------------- 6 | 7 | - Built-in block user list 8 | - Optimize built-in IP 9 | - Fix bug that can't block users from comments in EX site -------------------------------------------------------------------------------- /changelog/v8.0.6+277.md: -------------------------------------------------------------------------------- 1 | - 增加Linux-arm64打包 2 | - 优化归档失败的兜底处理 3 | - 修复收藏页切换排序方式时,筛选项失效的bug 4 | 5 | -------------------- 6 | 7 | - Add Linux-arm64 package 8 | - Optimize the fallback processing of failed archiving 9 | - Fix bug that filter options are invalidated in favorite page when sorting order is changed -------------------------------------------------------------------------------- /changelog/v8.0.6+279.md: -------------------------------------------------------------------------------- 1 | - 增加Russian翻译,感谢[bropines](https://github.com/bropines)。 2 | - 优化部分错误文案的展示。 3 | 4 | -------------------- 5 | 6 | - Add Russian translation, thanks to [bropines](https://github.com/bropines). 7 | - Optimize the display of some error messages. -------------------------------------------------------------------------------- /changelog/v8.0.6+280.md: -------------------------------------------------------------------------------- 1 | - 集成[归档bot](https://web.telegram.org/a/#8113449965)。 2 | 3 | -------------------- 4 | 5 | - Integrates [archive bot](https://web.telegram.org/a/#8113449965)。 -------------------------------------------------------------------------------- /changelog/v8.0.6.md: -------------------------------------------------------------------------------- 1 | - 更新繁體中文(台灣)翻译 2 | - 优化搜索标签提示 3 | - 适配E站不再能查看非本人上传画廊的排行数据的变动 4 | - 适配MPV 5 | - 优化从分享链接打开app的跳转逻辑 6 | - 修复移动端退出主路由的问题 7 | - 修复阅读页重载图片后,本地缓存未更新的bug 8 | - 修复阅读页切换为双列模式时页面渲染异常的bug 9 | - 修复无法自动更新已下载画廊标签的bug 10 | - 修复双列模式下本地阅读的bug 11 | 12 | -------------------- 13 | 14 | - Update the Traditional Chinese (Taiwan) translation 15 | - Optimize the search tag prompt 16 | - Adapt to the change that users can no longer view the ranking data of galleries uploaded by others 17 | - Adapt to MPV 18 | - Optimize the jump logic from the shared link to open the app 19 | - Optimize the scrolling experience in read page 20 | - Fix the problem of exiting the main route on the mobile terminal 21 | - Fix the bug that the local cache is not updated after reloading the image on the reading page 22 | - Fix the bug that the page rendering is abnormal when switching to double-column mode on the reading page 23 | - Fix the bug that the tags of downloaded galleries cannot be automatically updated 24 | - Fix the bug of local reading in double-column mode -------------------------------------------------------------------------------- /changelog/v8.0.7+290.md: -------------------------------------------------------------------------------- 1 | - 修复手机和平板模式下,在详情页中长按下载按钮跳转至下载页后无法返回的bug。 2 | 3 | -------------------- 4 | 5 | - Fix the bug that the back button cannot return after long-pressing the download button on the details page in mobile 6 | and tablet mode to jump to the download page. -------------------------------------------------------------------------------- /changelog/v8.0.7+291.md: -------------------------------------------------------------------------------- 1 | - 支持归档bot自动签到 2 | - 修复手机和平板模式下,在详情页中长按下载按钮跳转至下载页后无法返回的bug。 3 | 4 | -------------------- 5 | 6 | - Support archive bot automatic check-in 7 | - Fix the bug that the back button cannot return after long-pressing the download button on the details page in mobile 8 | and tablet mode to jump to the download page. -------------------------------------------------------------------------------- /changelog/v8.0.7+292.md: -------------------------------------------------------------------------------- 1 | - 支持使用MPV加速画廊更新 2 | 3 | -------------------- 4 | 5 | - Support gallery updates accelerated by MPV -------------------------------------------------------------------------------- /changelog/v8.0.7+293.md: -------------------------------------------------------------------------------- 1 | - 优化MPV加速画廊更新 2 | 3 | -------------------- 4 | 5 | - Optimize gallery update accelerated by MPV -------------------------------------------------------------------------------- /changelog/v8.0.7.md: -------------------------------------------------------------------------------- 1 | - 集成[归档bot](https://t.me/EH_ArBot):[教程](https://github.com/jiangtian616/JHenTai/wiki/%E5%BD%92%E6%A1%A3%E6%9C%BA%E5%99%A8%E4%BA%BA%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) 2 | - 增加Russian翻译,感谢[bropines](https://github.com/bropines)。 3 | - 优化部分错误文案的展示。 4 | - 内置屏蔽用户名单 5 | - 移除详情页统计按钮 6 | - 优化双击返回逻辑 7 | - 优化内置ip 8 | - 优化归档失败的兜底处理 9 | - 修复里站无法快速屏蔽评论区用户的bug 10 | - 修复收藏页切换排序方式时,筛选项失效的bug 11 | 12 | -------------------- 13 | 14 | - Integrates [archive bot](https://t.me/EH_ArBot): [Tutorial](https://github.com/jiangtian616/JHenTai/wiki/Archive-Bot-Usage) 15 | - Add Russian translation, thanks to [bropines](https://github.com/bropines). 16 | - Optimize the display of some error messages. 17 | - Built-in block user list 18 | - Remove the statistics button on the details page 19 | - Optimize double-click return logic 20 | - Optimize built-in IP 21 | - Fix bug that can't block users from comments in EX site 22 | - Optimize the fallback processing of failed archiving 23 | - Fix bug that filter options are invalidated in favorite page when sorting order is changed -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /dmg.sh: -------------------------------------------------------------------------------- 1 | version=$(head -n 5 pubspec.yaml | tail -n 1 | cut -d ' ' -f 2) 2 | 3 | flutter build macos --release -t lib/src/main.dart \ 4 | && brew install create-dmg \ 5 | && create-dmg --volname JHenTai-${version} --window-pos 200 120 --window-size 800 450 --icon-size 100 --app-drop-link 600 185 JHenTai-${version}.dmg build/macos/Build/Products/Release/jhentai.app -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | # flutter pub run flutter_launcher_icons 2 | flutter_launcher_icons: 3 | image_path: "assets/icon/JHenTai.png" 4 | 5 | android: "launcher_icon" 6 | image_path_android: "assets/icon/JHenTai.png" 7 | min_sdk_android: 21 # android min sdk min:16, default 21 8 | adaptive_icon_background: "#e6e623" 9 | adaptive_icon_foreground: "assets/icon/JHenTai-foreground.png" 10 | # adaptive_icon_monochrome: "assets/icon/monochrome.png" 11 | 12 | ios: true 13 | image_path_ios: "assets/icon/JHenTai-square.png" 14 | remove_alpha_ios: true 15 | # image_path_ios_dark_transparent: "assets/icon/icon_dark.png" 16 | # image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png" 17 | # desaturate_tinted_to_grayscale_ios: true 18 | 19 | web: 20 | generate: false 21 | 22 | windows: 23 | generate: true 24 | image_path: "assets/icon/JHenTai.png" 25 | icon_size: 256 26 | 27 | macos: 28 | generate: true 29 | image_path: "assets/icon/JHenTai.png" 30 | -------------------------------------------------------------------------------- /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 | firebase_app_id_file.json 30 | 31 | # Exceptions to above rules. 32 | !default.mode1v3 33 | !default.mode2v3 34 | !default.pbxuser 35 | !default.perspectivev3 36 | 37 | -------------------------------------------------------------------------------- /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 | 12.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/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 | @main 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/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.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/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/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/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ipa.sh: -------------------------------------------------------------------------------- 1 | version=$(head -n 5 pubspec.yaml | tail -n 1 | cut -d ' ' -f 2) 2 | 3 | flutter build ios --release -t lib/src/main.dart \ 4 | && mkdir ~/Desktop/Payload \ 5 | && cp -r build/ios/Release-iphoneos/Runner.app/ ~/Desktop/Payload/Runner.app/ \ 6 | && cd ~/Desktop \ 7 | && zip -ro JHenTai_${version}.ipa Payload \ 8 | && rm -rf Payload 9 | -------------------------------------------------------------------------------- /lib/src/config/README.md: -------------------------------------------------------------------------------- 1 | system inner configs, transparent nad unreachable to user. 2 | -------------------------------------------------------------------------------- /lib/src/config/jh_api_secret_config.dart: -------------------------------------------------------------------------------- 1 | class JHApiSecretConfig { 2 | static const secret = ''; 3 | } 4 | -------------------------------------------------------------------------------- /lib/src/config/sentry_config.dart: -------------------------------------------------------------------------------- 1 | class SentryConfig { 2 | static const dsn = ''; 3 | } 4 | -------------------------------------------------------------------------------- /lib/src/config/theme_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemeConfig { 4 | static ThemeData theme(Color color, Brightness brightness) { 5 | ThemeData themeData = ThemeData( 6 | useMaterial3: true, 7 | brightness: brightness, 8 | colorSchemeSeed: color, 9 | 10 | /// default w500 is not supported for chinese characters in some devices 11 | textTheme: const TextTheme(titleMedium: TextStyle(fontWeight: FontWeight.w400)), 12 | appBarTheme: const AppBarTheme(scrolledUnderElevation: 0), 13 | navigationBarTheme: const NavigationBarThemeData( 14 | height: 48, 15 | surfaceTintColor: Colors.transparent, 16 | labelBehavior: NavigationDestinationLabelBehavior.alwaysHide, 17 | ), 18 | popupMenuTheme: const PopupMenuThemeData(surfaceTintColor: Colors.transparent), 19 | ); 20 | 21 | return themeData.copyWith( 22 | appBarTheme: themeData.appBarTheme.copyWith(backgroundColor: themeData.colorScheme.surface), 23 | dialogTheme: DialogTheme(backgroundColor: themeData.colorScheme.surface), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/consts/archive_bot_consts.dart: -------------------------------------------------------------------------------- 1 | class ArchiveBotConsts { 2 | static const String proxyServerAddress = 'https://jhentai.top/archive_bot'; 3 | static const String serverAddress = 'https://eh-arc-api.mhdy.icu'; 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/consts/jh_consts.dart: -------------------------------------------------------------------------------- 1 | class JHConsts { 2 | static const String serverAddress = 'https://jhentai.top'; 3 | 4 | static const String APP_ID_HEADER = "X-App-Id"; 5 | static const String TIMESTAMP_HEADER = "X-Timestamp"; 6 | static const String NONCE_HEADER = "X-Nonce"; 7 | static const String SIGNATURE_HEADER = "X-Signature"; 8 | 9 | static const String APP_ID = "jhentai"; 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/consts/locale_consts.dart: -------------------------------------------------------------------------------- 1 | class LocaleConsts { 2 | static const Map language2Abbreviation = { 3 | 'chinese': 'ZH', 4 | 'japanese': 'JP', 5 | 'english': 'EN', 6 | 'korean': 'KR', 7 | 'spanish': 'ES', 8 | 'portuguese': 'PT', 9 | 'russian': 'RU', 10 | 'french': 'FR', 11 | 'italian': 'IT', 12 | 'german': 'DE', 13 | 'polish': 'PL', 14 | 'hungarian': 'HU', 15 | 'thai': 'TH', 16 | 'dutch': 'NL', 17 | 'vietnamese': 'VI', 18 | }; 19 | 20 | static Map localeCode2Description = { 21 | 'zh_CN': '简体中文', 22 | 'zh_TW': '繁體中文(台灣)', 23 | 'en_US': 'English', 24 | 'pt_BR': 'Português brasileiro', 25 | 'ko_KR': '한국어', 26 | 'ru_RU': 'Русский', 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/database/dao/block_rule_dao.dart: -------------------------------------------------------------------------------- 1 | import '../database.dart'; 2 | 3 | class BlockRuleDao { 4 | static Future> selectBlockRules() { 5 | return (appDb.select(appDb.blockRule)).get(); 6 | } 7 | 8 | static Future> selectBlockRulesByTarget(int target) { 9 | return (appDb.select(appDb.blockRule)..where((r) => r.target.equals(target))).get(); 10 | } 11 | 12 | static Future insertBlockRule(BlockRuleCompanion rule) { 13 | return appDb.into(appDb.blockRule).insert(rule); 14 | } 15 | 16 | static Future upsertBlockRule(BlockRuleCompanion rule) { 17 | return appDb.into(appDb.blockRule).insertOnConflictUpdate(rule); 18 | } 19 | 20 | static Future deleteBlockRuleByGroupId(String groupId) { 21 | return (appDb.delete(appDb.blockRule)..where((r) => r.groupId.equals(groupId))).go(); 22 | } 23 | 24 | static Future existsGroup(String groupId) { 25 | return (appDb.select(appDb.blockRule)..where((r) => r.groupId.equals(groupId))).get().then((value) => value.isNotEmpty); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/database/dao/super_resolution_info_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | import 'package:jhentai/src/database/database.dart'; 3 | 4 | class SuperResolutionInfoDao { 5 | static Future> selectAllSuperResolutionInfo() { 6 | return (appDb.select(appDb.superResolutionInfo)).get(); 7 | } 8 | 9 | static Future insertSuperResolutionInfo(SuperResolutionInfoData entry) { 10 | return appDb.into(appDb.superResolutionInfo).insert(entry); 11 | } 12 | 13 | static Future updateSuperResolutionInfo(SuperResolutionInfoCompanion entry) { 14 | return (appDb.update(appDb.superResolutionInfo)..where((tbl) => tbl.gid.equals(entry.gid.value) & tbl.type.equals(entry.type.value))).write(entry); 15 | } 16 | 17 | static Future deleteSuperResolutionInfo(int gid, int type) { 18 | return (appDb.delete(appDb.superResolutionInfo)..where((tbl) => tbl.gid.equals(gid) & tbl.type.equals(type))).go(); 19 | } 20 | 21 | static Future> selectAllOldSuperResolutionInfo() { 22 | return (appDb.select(appDb.oldSuperResolutionInfo)).get(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/database/dao/tag_count_dao.dart: -------------------------------------------------------------------------------- 1 | import '../database.dart'; 2 | 3 | class TagCountDao { 4 | static const int _batchSize = 200; 5 | 6 | static Future replaceTagCount(List tagCountData) { 7 | return appDb.transaction(() async { 8 | await deleteAllTagCount(); 9 | 10 | for (int i = 0; i < tagCountData.length; i += _batchSize) { 11 | await appDb.batch((batch) { 12 | batch.insertAll(appDb.tagCount, tagCountData.skip(i).take(_batchSize).toList()); 13 | }); 14 | 15 | await Future.delayed(const Duration(milliseconds: 10)); 16 | } 17 | }); 18 | } 19 | 20 | static Future> batchSelectTagCount(List namespaceWithKeys) { 21 | return (appDb.select(appDb.tagCount)..where((tbl) => tbl.namespaceWithKey.isIn(namespaceWithKeys))).get(); 22 | } 23 | 24 | static Future insertTagCount(TagCountData tagCountData) { 25 | return appDb.into(appDb.tagCount).insert(tagCountData); 26 | } 27 | 28 | static Future deleteAllTagCount() { 29 | return appDb.delete(appDb.tagCount).go(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/database/table/archive_group.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | class ArchiveGroup extends Table { 4 | @override 5 | String? get tableName => 'archive_group'; 6 | 7 | TextColumn get groupName => text().named('groupName')(); 8 | 9 | IntColumn get sortOrder => integer().named('sortOrder').withDefault(const Constant(0))(); 10 | 11 | @override 12 | Set>? get primaryKey => {groupName}; 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/database/table/block_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | @TableIndex(name: 'idx_group_id', columns: {#groupId}) 4 | @TableIndex(name: 'idx_target', columns: {#target}) 5 | class BlockRule extends Table { 6 | @override 7 | String? get tableName => 'block_rule'; 8 | 9 | IntColumn get id => integer().autoIncrement()(); 10 | 11 | TextColumn get groupId => text()(); 12 | 13 | IntColumn get target => integer()(); 14 | 15 | IntColumn get attribute => integer()(); 16 | 17 | IntColumn get pattern => integer()(); 18 | 19 | TextColumn get expression => text()(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/database/table/dio_cache.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | @TableIndex(name: 'idx_expire_date', columns: {#expireDate}) 4 | @TableIndex(name: 'idx_url', columns: {#url}) 5 | class DioCache extends Table { 6 | @override 7 | String? get tableName => 'dio_cache'; 8 | 9 | TextColumn get cacheKey => text().named('cacheKey')(); 10 | 11 | TextColumn get url => text().named('url')(); 12 | 13 | DateTimeColumn get expireDate => dateTime().named('expireDate')(); 14 | 15 | BlobColumn get content => blob().named('content')(); 16 | 17 | BlobColumn get headers => blob().named('headers')(); 18 | 19 | @override 20 | Set>? get primaryKey => {cacheKey}; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/database/table/gallery_group.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | class GalleryGroup extends Table { 4 | @override 5 | String? get tableName => 'gallery_group'; 6 | 7 | TextColumn get groupName => text().named('groupName')(); 8 | 9 | IntColumn get sortOrder => integer().named('sortOrder').withDefault(const Constant(0))(); 10 | 11 | @override 12 | Set>? get primaryKey => {groupName}; 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/database/table/gallery_history.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | @TableIndex(name: 'idx_gh2_last_read_time', columns: {#lastReadTime}) 4 | class GalleryHistoryV2 extends Table { 5 | @override 6 | String? get tableName => 'gallery_history_v2'; 7 | 8 | IntColumn get gid => integer()(); 9 | 10 | TextColumn get jsonBody => text().named('jsonBody')(); 11 | 12 | TextColumn get lastReadTime => text().named('lastReadTime')(); 13 | 14 | @override 15 | Set>? get primaryKey => {gid}; 16 | } 17 | 18 | @TableIndex(name: 'idx_last_read_time', columns: {#lastReadTime}) 19 | class GalleryHistory extends Table { 20 | @override 21 | String? get tableName => 'gallery_history'; 22 | 23 | IntColumn get gid => integer()(); 24 | 25 | TextColumn get jsonBody => text().named('jsonBody')(); 26 | 27 | TextColumn get lastReadTime => text().named('lastReadTime')(); 28 | 29 | @override 30 | Set>? get primaryKey => {gid}; 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/database/table/image.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | import 'package:jhentai/src/database/table/gallery_downloaded.dart'; 3 | 4 | class Image extends Table { 5 | @override 6 | String? get tableName => 'image'; 7 | 8 | IntColumn get gid => integer().references(GalleryDownloaded, #gid)(); 9 | 10 | TextColumn get url => text()(); 11 | 12 | IntColumn get serialNo => integer().named('serialNo')(); 13 | 14 | TextColumn get path => text()(); 15 | 16 | TextColumn get imageHash => text().named('imageHash')(); 17 | 18 | IntColumn get downloadStatusIndex => integer().named('downloadStatusIndex')(); 19 | 20 | @override 21 | Set>? get primaryKey => {gid, serialNo}; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/database/table/local_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | @TableIndex(name: 'l_idx_u_time', columns: {#utime}) 4 | class LocalConfig extends Table { 5 | @override 6 | String? get tableName => 'local_config'; 7 | 8 | TextColumn get configKey => text()(); 9 | 10 | TextColumn get subConfigKey => text()(); 11 | 12 | TextColumn get value => text()(); 13 | 14 | TextColumn get utime => text()(); 15 | 16 | @override 17 | Set>? get primaryKey => {configKey, subConfigKey}; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/database/table/super_resolution_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | class SuperResolutionInfo extends Table { 4 | @override 5 | String? get tableName => 'super_resolution_info_v2'; 6 | 7 | IntColumn get gid => integer()(); 8 | 9 | IntColumn get type => integer()(); 10 | 11 | IntColumn get status => integer()(); 12 | 13 | TextColumn get imageStatuses => text()(); 14 | 15 | @override 16 | Set>? get primaryKey => {gid, type}; 17 | } 18 | 19 | class OldSuperResolutionInfo extends Table { 20 | @override 21 | String? get tableName => 'super_resolution_info'; 22 | 23 | IntColumn get gid => integer()(); 24 | 25 | IntColumn get type => integer()(); 26 | 27 | IntColumn get status => integer()(); 28 | 29 | TextColumn get imageStatuses => text().named('imageStatuses')(); 30 | 31 | @override 32 | Set>? get primaryKey => {gid}; 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/database/table/tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | @TableIndex(name: 'idx_key', columns: {#key}) 4 | @TableIndex(name: 'idx_tagName', columns: {#tagName}) 5 | class Tag extends Table { 6 | @override 7 | String? get tableName => 'tag'; 8 | 9 | TextColumn get namespace => text().named('namespace')(); 10 | 11 | @JsonKey('_key') 12 | TextColumn get key => text().named('_key')(); 13 | 14 | TextColumn get translatedNamespace => text().named('translatedNamespace').nullable()(); 15 | 16 | TextColumn get tagName => text().named('tagName').nullable()(); 17 | 18 | TextColumn get fullTagName => text().named('fullTagName').nullable()(); 19 | 20 | TextColumn get intro => text().named('intro').nullable()(); 21 | 22 | TextColumn get links => text().named('links').nullable()(); 23 | 24 | @override 25 | Set>? get primaryKey => {namespace, key}; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/database/table/tag_count.dart: -------------------------------------------------------------------------------- 1 | import 'package:drift/drift.dart'; 2 | 3 | class TagCount extends Table { 4 | @override 5 | String? get tableName => 'tag_count'; 6 | 7 | TextColumn get namespaceWithKey => text().named('namespaceWithKey')(); 8 | 9 | IntColumn get count => integer().named('count')(); 10 | 11 | @override 12 | Set>? get primaryKey => {namespaceWithKey}; 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/enum/config_type_enum.dart: -------------------------------------------------------------------------------- 1 | enum CloudConfigTypeEnum { 2 | readIndexRecord(1, 'readIndexRecord'), 3 | quickSearch(2, 'quickSearch'), 4 | blockRules(3, 'blockRules'), 5 | searchHistory(4, 'searchHistory'), 6 | history(5, 'galleryHistory'), 7 | ; 8 | 9 | final int code; 10 | 11 | final String name; 12 | 13 | const CloudConfigTypeEnum(this.code, this.name); 14 | 15 | static CloudConfigTypeEnum fromCode(int code) { 16 | switch (code) { 17 | case 1: 18 | return CloudConfigTypeEnum.readIndexRecord; 19 | case 2: 20 | return CloudConfigTypeEnum.quickSearch; 21 | case 3: 22 | return CloudConfigTypeEnum.blockRules; 23 | case 4: 24 | return CloudConfigTypeEnum.searchHistory; 25 | case 5: 26 | return CloudConfigTypeEnum.history; 27 | default: 28 | throw Exception('Unknown CloudConfigTypeEnum code: $code'); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/enum/eh_namespace.dart: -------------------------------------------------------------------------------- 1 | enum EHNamespace { 2 | rows('rows', null, '分类'), 3 | language('language', 'l', '语言'), 4 | artist('artist', 'a', '作者'), 5 | character('character', 'c', '角色'), 6 | female('female', 'f', '女性'), 7 | male('male', 'm', '男性'), 8 | parody('parody', 'p', '原作'), 9 | group('group', 'g', '团队'), 10 | mixed('mixed', 'x', '混合'), 11 | cosplayer('cosplayer', 'cos', '角色扮演者'), 12 | reclass('reclass', 'r', '重新分类'), 13 | temp('temp', null, '临时'), 14 | other('other', 'o', '其他'), 15 | ; 16 | 17 | const EHNamespace(this.desc, this.abbr, this.chineseDesc); 18 | 19 | final String desc; 20 | 21 | final String? abbr; 22 | 23 | final String? chineseDesc; 24 | 25 | static EHNamespace? findNameSpaceFromDescOrAbbr(String? desc) { 26 | if (desc == null) { 27 | return null; 28 | } 29 | 30 | for (final EHNamespace ns in values) { 31 | if (ns.desc == desc || ns.abbr == desc) { 32 | return ns; 33 | } 34 | } 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/exception/cancel_exception.dart: -------------------------------------------------------------------------------- 1 | class CancelException implements Exception {} 2 | -------------------------------------------------------------------------------- /lib/src/exception/eh_image_exception.dart: -------------------------------------------------------------------------------- 1 | class EHImageException implements Exception { 2 | EHImageExceptionType type; 3 | String message; 4 | EHImageExceptionAfterOperation operation; 5 | 6 | EHImageException({ 7 | required this.type, 8 | required this.message, 9 | required this.operation, 10 | }); 11 | 12 | @override 13 | String toString() { 14 | return message; 15 | } 16 | } 17 | 18 | enum EHImageExceptionType { 19 | peakHours, 20 | oldGallery, 21 | exceedLimit, 22 | invalidToken, 23 | serverError, 24 | } 25 | 26 | enum EHImageExceptionAfterOperation { 27 | reParse, 28 | pause, 29 | pauseAll, 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/exception/eh_parse_exception.dart: -------------------------------------------------------------------------------- 1 | class EHParseException implements Exception { 2 | EHParseExceptionType type; 3 | String message; 4 | bool shouldPauseAllDownloadTasks; 5 | 6 | EHParseException({required this.type, required this.message, this.shouldPauseAllDownloadTasks = true}); 7 | 8 | @override 9 | String toString() { 10 | return message; 11 | } 12 | } 13 | 14 | enum EHParseExceptionType { exceedLimit, unsupportedImagePageStyle, tagSetExceedLimit, getMetaDataFailed } 15 | -------------------------------------------------------------------------------- /lib/src/exception/eh_site_exception.dart: -------------------------------------------------------------------------------- 1 | class EHSiteException implements Exception { 2 | EHSiteExceptionType type; 3 | String message; 4 | String? referLink; 5 | bool shouldPauseAllDownloadTasks; 6 | 7 | EHSiteException({ 8 | required this.type, 9 | required this.message, 10 | this.referLink, 11 | this.shouldPauseAllDownloadTasks = true, 12 | }); 13 | 14 | @override 15 | String toString() { 16 | return message; 17 | } 18 | } 19 | 20 | enum EHSiteExceptionType { blankBody, banned, exceedLimit, galleryDeleted, internalError, ehServerError } 21 | -------------------------------------------------------------------------------- /lib/src/exception/internal_exception.dart: -------------------------------------------------------------------------------- 1 | class InternalException implements Exception { 2 | final String message; 3 | 4 | InternalException({required this.message}); 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/exception/retry_exception.dart: -------------------------------------------------------------------------------- 1 | class RetryException implements Exception {} 2 | -------------------------------------------------------------------------------- /lib/src/exception/upload_exception.dart: -------------------------------------------------------------------------------- 1 | class NotUploadException implements Exception { 2 | Object innerError; 3 | 4 | NotUploadException(this.innerError); 5 | 6 | @override 7 | String toString() { 8 | return 'NotUploadException{innerError: $innerError}'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/extension/dio_exception_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | extension DioExceptionExtension on DioException { 4 | String? get errorMsg { 5 | return message ?? error?.toString(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/extension/get_logic_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/scheduler.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | extension GetLogicExtension on GetxController { 5 | void updateSafely([List? ids, bool condition = true]) { 6 | update(ids, condition && !isClosed); 7 | } 8 | 9 | void updateSafelyInNextFrame([List? ids, bool condition = true]) { 10 | SchedulerBinding.instance.addPostFrameCallback((_) { 11 | update(ids, condition && !isClosed); 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/extension/string_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | extension StringExtension on String { 4 | /// https://github.com/flutter/flutter/issues/61081 5 | String get breakWord { 6 | return Characters(this).join('\u{200B}'); 7 | } 8 | 9 | String defaultIfEmpty(String defaultString) { 10 | return (isEmpty ? defaultString : this).breakWord; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/l18n/locale_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:jhentai/src/l18n/en_US.dart'; 3 | import 'package:jhentai/src/l18n/zh_CN.dart'; 4 | import 'package:jhentai/src/l18n/zh_TW.dart'; 5 | import 'package:jhentai/src/l18n/pt_BR.dart'; 6 | import 'package:jhentai/src/l18n/ko_KR.dart'; 7 | import 'package:jhentai/src/l18n/ru_RU.dart'; 8 | 9 | class LocaleText extends Translations { 10 | @override 11 | Map> get keys => { 12 | 'en_US': en_US.keys(), 13 | 'zh_CN': zh_CN.keys(), 14 | 'zh_TW': zh_TW.keys(), 15 | 'pt_BR': pt_BR.keys(), 16 | 'ko_KR': ko_KR.keys(), 17 | 'ru_RU': ru_RU.keys(), 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/mixin/double_tap_to_refresh_state_mixin.dart: -------------------------------------------------------------------------------- 1 | mixin DoubleTapToRefreshStateMixin { 2 | /// record tap time to implement 'double tap to refresh' 3 | DateTime? lastTapTime; 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/mixin/login_required_logic_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../setting/user_setting.dart'; 4 | import '../utils/toast_util.dart'; 5 | 6 | mixin LoginRequiredMixin { 7 | bool checkLogin() { 8 | if (!userSetting.hasLoggedIn()) { 9 | showLoginToast(); 10 | return false; 11 | } 12 | 13 | return true; 14 | } 15 | 16 | showLoginToast() { 17 | toast('needLoginToOperate'.tr); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/mixin/scroll_status_listener.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:jhentai/src/mixin/scroll_status_listener_state.dart'; 5 | 6 | mixin ScrollStatusListener { 7 | ScrollStatusListerState get scrollStatusListerState; 8 | 9 | Timer? timer; 10 | 11 | Widget wrapScrollListener(Widget child) { 12 | return NotificationListener( 13 | onNotification: (notification) { 14 | if (notification is ScrollStartNotification) { 15 | timer?.cancel(); 16 | scrollStatusListerState.isScrolling = true; 17 | } 18 | if (notification is ScrollEndNotification) { 19 | timer = Timer(const Duration(milliseconds: 250), () { 20 | scrollStatusListerState.isScrolling = false; 21 | }); 22 | } 23 | return false; 24 | }, 25 | child: child, 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/mixin/scroll_status_listener_state.dart: -------------------------------------------------------------------------------- 1 | mixin ScrollStatusListerState { 2 | bool isScrolling = false; 3 | } 4 | -------------------------------------------------------------------------------- /lib/src/mixin/scroll_to_top_page_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get_state_manager/src/simple/get_state.dart'; 3 | import 'package:jhentai/src/mixin/scroll_to_top_logic_mixin.dart'; 4 | import 'package:jhentai/src/mixin/scroll_to_top_state_mixin.dart'; 5 | 6 | mixin Scroll2TopPageMixin on Widget { 7 | Scroll2TopLogicMixin get scroll2TopLogic; 8 | 9 | Scroll2TopStateMixin get scroll2TopState; 10 | 11 | Widget buildFloatingActionButton() { 12 | return GetBuilder( 13 | id: scroll2TopLogic.scroll2TopButtonId, 14 | global: false, 15 | init: scroll2TopLogic, 16 | builder: (_) { 17 | return AnimatedSwitcher( 18 | duration: const Duration(milliseconds: 200), 19 | child: scroll2TopLogic.shouldDisplayFAB 20 | ? FloatingActionButton( 21 | child: const Icon(Icons.arrow_upward), 22 | heroTag: null, 23 | onPressed: scroll2TopLogic.scroll2Top, 24 | ) 25 | : null, 26 | ); 27 | }, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/mixin/scroll_to_top_state_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | mixin Scroll2TopStateMixin { 4 | bool isScrollingDown = false; 5 | 6 | final ScrollController scrollController = ScrollController(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/model/archive_bot_response/archive_bot_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:jhentai/src/utils/archive_bot_response_parser.dart'; 3 | 4 | class ArchiveBotResponse { 5 | final int code; 6 | final String message; 7 | final Map data; 8 | 9 | const ArchiveBotResponse({required this.code, required this.message, required this.data}); 10 | 11 | factory ArchiveBotResponse.fromJson(Map json) { 12 | return ArchiveBotResponse( 13 | code: json["code"], 14 | message: json["msg"], 15 | data: json["data"], 16 | ); 17 | } 18 | 19 | bool get isSuccess => code == 0; 20 | 21 | String get errorMessage => ArchiveBotResponseCodeEnum.fromCode(code)?.name.tr ?? 'internalError'.tr; 22 | 23 | @override 24 | String toString() { 25 | return 'ArchiveBotResponse{code: $code, message: $message, data: $data}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/model/archive_bot_response/archive_resolve_vo.dart: -------------------------------------------------------------------------------- 1 | class ArchiveResolveVO { 2 | String url; 3 | 4 | ArchiveResolveVO({required this.url}); 5 | 6 | factory ArchiveResolveVO.fromResponse(Map json) { 7 | return ArchiveResolveVO( 8 | url: json["archive_url"], 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/model/archive_bot_response/balance_vo.dart: -------------------------------------------------------------------------------- 1 | class BalanceVO { 2 | int gp; 3 | 4 | BalanceVO({required this.gp}); 5 | 6 | factory BalanceVO.fromResponse(Map json) { 7 | return BalanceVO( 8 | gp: json["current_GP"], 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/model/archive_bot_response/check_in_vo.dart: -------------------------------------------------------------------------------- 1 | class CheckInVO { 2 | int getGP; 3 | int currentGP; 4 | 5 | CheckInVO({required this.getGP, required this.currentGP}); 6 | 7 | factory CheckInVO.fromResponse(Map json) { 8 | return CheckInVO( 9 | getGP: json["get_GP"], 10 | currentGP: json["current_GP"], 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/model/detail_page_info.dart: -------------------------------------------------------------------------------- 1 | import 'gallery_thumbnail.dart'; 2 | 3 | class DetailPageInfo { 4 | final int imageNoFrom; 5 | final int imageNoTo; 6 | final int imageCount; 7 | final int currentPageNo; 8 | final int pageCount; 9 | final List thumbnails; 10 | 11 | const DetailPageInfo({ 12 | required this.imageNoFrom, 13 | required this.imageNoTo, 14 | required this.imageCount, 15 | required this.currentPageNo, 16 | required this.pageCount, 17 | required this.thumbnails, 18 | }); 19 | 20 | /// 20 40 50 100 200 400 21 | int get thumbnailsCountPerPage => currentPageNo != pageCount 22 | ? thumbnails.length 23 | : pageCount != 1 24 | ? (imageNoFrom - 1) ~/ currentPageNo 25 | : [20, 40, 50, 100, 200, 400].firstWhere((number) => number >= imageCount); 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/model/eh_raw_tag.dart: -------------------------------------------------------------------------------- 1 | class EHRawTag { 2 | String namespace; 3 | String key; 4 | 5 | EHRawTag({required this.namespace, required this.key}); 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/model/gallery_archive.dart: -------------------------------------------------------------------------------- 1 | class GalleryArchive { 2 | int? gpCount; 3 | int? creditCount; 4 | 5 | String originalCost; 6 | String originalSize; 7 | String downloadOriginalHint; 8 | 9 | String? resampleCost; 10 | String? resampleSize; 11 | String downloadResampleHint; 12 | 13 | GalleryArchive({ 14 | this.gpCount, 15 | this.creditCount, 16 | required this.originalCost, 17 | required this.originalSize, 18 | required this.downloadOriginalHint, 19 | this.resampleCost, 20 | this.resampleSize, 21 | required this.downloadResampleHint, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/model/gallery_comment.dart: -------------------------------------------------------------------------------- 1 | import 'package:html/dom.dart'; 2 | 3 | class GalleryComment { 4 | int id; 5 | String? username; 6 | int? userId; 7 | String score; 8 | List scoreDetails; 9 | Element content; 10 | String time; 11 | String? lastEditTime; 12 | bool fromMe; 13 | bool votedUp; 14 | bool votedDown; 15 | 16 | GalleryComment({ 17 | required this.id, 18 | this.username, 19 | this.userId, 20 | required this.score, 21 | required this.scoreDetails, 22 | required this.content, 23 | required this.time, 24 | this.lastEditTime, 25 | required this.fromMe, 26 | required this.votedUp, 27 | required this.votedDown, 28 | }); 29 | 30 | @override 31 | String toString() { 32 | return 'GalleryComment{id: $id, username: $username, userId: $userId, score: $score, scoreDetails: $scoreDetails, content: $content, time: $time, lastEditTime: $lastEditTime, fromMe: $fromMe, votedUp: $votedUp, votedDown: $votedDown}'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/model/gallery_count.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class GalleryCount { 4 | final String? count; 5 | final GalleryCountType type; 6 | 7 | const GalleryCount({this.count, required this.type}); 8 | 9 | String toPrintString() { 10 | switch (type) { 11 | case GalleryCountType.accurate: 12 | return 'accurateCountTemplate'.trArgs([count!]); 13 | case GalleryCountType.hundreds: 14 | return 'hundredsOfCountTemplate'.tr; 15 | case GalleryCountType.thousands: 16 | return 'thousandsOfCountTemplate'.tr; 17 | } 18 | } 19 | 20 | @override 21 | String toString() { 22 | return 'GalleryCount{count: $count, type: $type}'; 23 | } 24 | } 25 | 26 | enum GalleryCountType { 27 | /// 1,465,200 28 | /// about 232,805 29 | /// 50,000+ 30 | accurate, 31 | 32 | /// hundreds of 33 | hundreds, 34 | 35 | /// thousands of 36 | thousands, 37 | ; 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/model/gallery_hh_archive.dart: -------------------------------------------------------------------------------- 1 | class GalleryHHArchive { 2 | /// 1280x、Original 3 | final String resolutionDesc; 4 | 5 | /// 1280、org 6 | final String? resolution; 7 | 8 | final String size; 9 | 10 | final String cost; 11 | 12 | const GalleryHHArchive({ 13 | required this.resolutionDesc, 14 | this.resolution, 15 | required this.size, 16 | required this.cost, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/model/gallery_hh_info.dart: -------------------------------------------------------------------------------- 1 | import 'gallery_hh_archive.dart'; 2 | 3 | class GalleryHHInfo { 4 | int? gpCount; 5 | int? creditCount; 6 | List archives; 7 | 8 | GalleryHHInfo({ 9 | this.gpCount, 10 | this.creditCount, 11 | required this.archives, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/model/gallery_metadata.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:jhentai/src/model/gallery_image.dart'; 4 | import 'package:jhentai/src/model/gallery_tag.dart'; 5 | import 'package:jhentai/src/model/gallery_url.dart'; 6 | 7 | class GalleryMetadata { 8 | GalleryUrl galleryUrl; 9 | String title; 10 | String japaneseTitle; 11 | String category; 12 | GalleryImage cover; 13 | int pageCount; 14 | double rating; 15 | String language; 16 | 17 | /// may be null if (Disowned) 18 | String? uploader; 19 | String publishTime; 20 | bool isExpunged; 21 | String size; 22 | int torrentCount; 23 | 24 | LinkedHashMap> tags; 25 | 26 | GalleryMetadata({ 27 | required this.galleryUrl, 28 | required this.title, 29 | required this.japaneseTitle, 30 | required this.category, 31 | required this.cover, 32 | required this.pageCount, 33 | required this.rating, 34 | required this.language, 35 | this.uploader, 36 | required this.publishTime, 37 | required this.isExpunged, 38 | required this.size, 39 | required this.torrentCount, 40 | required this.tags, 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/model/gallery_note.dart: -------------------------------------------------------------------------------- 1 | class GalleryNote { 2 | final String note; 3 | 4 | const GalleryNote({required this.note}); 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/model/gallery_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/model/gallery_count.dart'; 2 | 3 | import 'gallery.dart'; 4 | 5 | enum FavoriteSortOrder { favoritedTime, publishedTime } 6 | 7 | class GalleryPageInfo { 8 | final GalleryCount? totalCount; 9 | 10 | final FavoriteSortOrder? favoriteSortOrder; 11 | 12 | final List gallerys; 13 | 14 | final String? prevGid; 15 | 16 | final String? nextGid; 17 | 18 | GalleryPageInfo({ 19 | required this.gallerys, 20 | this.favoriteSortOrder, 21 | this.totalCount, 22 | this.prevGid, 23 | this.nextGid, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/model/gallery_stats.dart: -------------------------------------------------------------------------------- 1 | class GalleryStats { 2 | int totalVisits; 3 | 4 | int? allTimeRanking; 5 | int? allTimeScore; 6 | int? yearRanking; 7 | int? yearScore; 8 | int? monthRanking; 9 | int? monthScore; 10 | int? dayRanking; 11 | int? dayScore; 12 | 13 | List yearlyStats; 14 | List monthlyStats; 15 | List dailyStats; 16 | 17 | GalleryStats({ 18 | required this.totalVisits, 19 | this.allTimeRanking, 20 | this.allTimeScore, 21 | this.yearRanking, 22 | this.yearScore, 23 | this.monthRanking, 24 | this.monthScore, 25 | this.dayRanking, 26 | this.dayScore, 27 | required this.yearlyStats, 28 | required this.monthlyStats, 29 | required this.dailyStats, 30 | }); 31 | } 32 | 33 | class VisitStat { 34 | /// 1. 2013 35 | /// 2. January 36 | /// 3. 1st 37 | String period; 38 | 39 | /// 1. 16.2M 40 | /// 2. 570K 41 | /// 3. 5731 42 | double visits; 43 | 44 | /// 1. 16.2M 45 | /// 2. 570K 46 | /// 3. 5731 47 | double hits; 48 | 49 | VisitStat({ 50 | required this.period, 51 | required this.visits, 52 | required this.hits, 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/model/gallery_torrent.dart: -------------------------------------------------------------------------------- 1 | class GalleryTorrent { 2 | String title; 3 | String postTime; 4 | String size; 5 | int seeds; 6 | int peers; 7 | int downloads; 8 | String uploader; 9 | String torrentUrl; 10 | String magnetUrl; 11 | bool outdated; 12 | 13 | GalleryTorrent({ 14 | required this.title, 15 | required this.postTime, 16 | required this.size, 17 | required this.seeds, 18 | required this.peers, 19 | required this.downloads, 20 | required this.uploader, 21 | required this.torrentUrl, 22 | required this.magnetUrl, 23 | required this.outdated, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/model/image_page_url.dart: -------------------------------------------------------------------------------- 1 | class ImagePageUrl {} 2 | -------------------------------------------------------------------------------- /lib/src/model/jh_response/fetch_image_hashes_vo.dart: -------------------------------------------------------------------------------- 1 | class FetchImageHashesVO { 2 | List hashes; 3 | 4 | FetchImageHashesVO({required this.hashes}); 5 | 6 | factory FetchImageHashesVO.fromResponse(Map json) { 7 | return FetchImageHashesVO( 8 | hashes: (json["hashes"] as List).cast(), 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/model/jh_response/jh_response.dart: -------------------------------------------------------------------------------- 1 | class JHResponse { 2 | final int code; 3 | final String message; 4 | final T data; 5 | 6 | const JHResponse({required this.code, required this.message, required this.data}); 7 | 8 | factory JHResponse.fromJson(Map json) { 9 | return JHResponse( 10 | code: json["code"], 11 | message: json["message"], 12 | data: json["data"], 13 | ); 14 | } 15 | 16 | bool get isSuccess => code == 0; 17 | 18 | @override 19 | String toString() { 20 | return 'JHResponse{code: $code, message: $message, data: $data}'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/model/profile.dart: -------------------------------------------------------------------------------- 1 | class Profile { 2 | int number; 3 | 4 | String name; 5 | 6 | bool selected; 7 | 8 | Profile({required this.number, required this.name, required this.selected}); 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/model/read_page_info.dart: -------------------------------------------------------------------------------- 1 | import 'gallery_image.dart'; 2 | 3 | enum ReadMode { downloaded, online, archive, local } 4 | 5 | class ReadPageInfo { 6 | ReadMode mode; 7 | 8 | /// null for local gallery 9 | int? gid; 10 | 11 | /// null for local gallery 12 | String? token; 13 | 14 | String galleryTitle; 15 | 16 | String? galleryUrl; 17 | 18 | int initialIndex; 19 | 20 | int currentImageIndex; 21 | 22 | int pageCount; 23 | 24 | /// used for archive 25 | bool isOriginal; 26 | 27 | String readProgressRecordStorageKey; 28 | 29 | /// used for archive&local 30 | List? images; 31 | 32 | /// used for initialize 33 | bool useSuperResolution; 34 | 35 | ReadPageInfo({ 36 | required this.mode, 37 | this.gid, 38 | this.token, 39 | required this.galleryTitle, 40 | this.galleryUrl, 41 | required this.initialIndex, 42 | required this.pageCount, 43 | this.isOriginal = false, 44 | required this.readProgressRecordStorageKey, 45 | this.images, 46 | required this.useSuperResolution, 47 | }) : currentImageIndex = initialIndex; 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/model/search_history.dart: -------------------------------------------------------------------------------- 1 | class SearchHistory { 2 | String rawKeyword; 3 | String? translatedKeyword; 4 | 5 | SearchHistory({ 6 | required this.rawKeyword, 7 | this.translatedKeyword, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/model/tab_bar_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | enum TabBarIconNameEnum { 4 | home, 5 | search, 6 | popular, 7 | ranklist, 8 | watched, 9 | favorite, 10 | history, 11 | download, 12 | setting, 13 | } 14 | 15 | class TabBarIcon { 16 | final TabBarIconNameEnum name; 17 | final String routeName; 18 | final Icon selectedIcon; 19 | final Icon unselectedIcon; 20 | final ValueGetter page; 21 | final ValueGetter? scrollController; 22 | bool shouldRender; 23 | bool enterNewRoute; 24 | 25 | TabBarIcon({ 26 | required this.name, 27 | required this.routeName, 28 | required this.selectedIcon, 29 | required this.unselectedIcon, 30 | required this.page, 31 | this.scrollController, 32 | required this.shouldRender, 33 | this.enterNewRoute = false, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/network/eh_timeout_translator.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:jhentai/src/setting/network_setting.dart'; 4 | 5 | class EHTimeoutTranslator extends Interceptor { 6 | @override 7 | void onError(DioException err, ErrorInterceptorHandler handler) { 8 | if (err.type == DioExceptionType.connectionTimeout) { 9 | return handler.next(err.copyWith(message: '${'connectionTimeoutHint'.tr} (${networkSetting.connectTimeout}ms)')); 10 | } 11 | if (err.type == DioExceptionType.receiveTimeout) { 12 | return handler.next(err.copyWith(message: '${'receiveDataTimeoutHint'.tr} (${networkSetting.receiveTimeout}ms)')); 13 | } 14 | handler.next(err); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/network/jh_cookie_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:jhentai/src/consts/jh_consts.dart'; 5 | import 'package:jhentai/src/network/eh_request.dart'; 6 | 7 | import '../utils/cookie_util.dart'; 8 | 9 | class JHCookieManager extends Interceptor { 10 | 11 | @override 12 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 13 | try { 14 | if (Uri.parse(JHConsts.serverAddress).host == options.uri.host) { 15 | options.headers[HttpHeaders.cookieHeader] = CookieUtil.parse2String(ehRequest.cookies); 16 | } 17 | handler.next(options); 18 | } on Exception catch (e, stackTrace) { 19 | var err = DioException(requestOptions: options, error: e, stackTrace: stackTrace); 20 | handler.reject(err, true); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/pages/README.md: -------------------------------------------------------------------------------- 1 | route pages 2 | -------------------------------------------------------------------------------- /lib/src/pages/base/old_base_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/pages/base/base_page_state.dart'; 2 | 3 | /// load pages by page index, not by nextGid or prevGid, to deal with EHentai's old search rule 4 | abstract class OldBasePageState extends BasePageState { 5 | int pageCount = -1; 6 | int? prevPageIndexToLoad; 7 | int? nextPageIndexToLoad = 0; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/pages/blank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:jhentai/src/config/ui_config.dart'; 3 | 4 | class BlankPage extends StatelessWidget { 5 | const BlankPage({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Material( 10 | color: UIConfig.backGroundColor(context), 11 | child: Center( 12 | child: Text( 13 | 'J', 14 | style: TextStyle(color: UIConfig.jHentaiIconColor(context), fontSize: 120, fontWeight: FontWeight.w600), 15 | ), 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/pages/details/thumbnails/thumbnails_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:jhentai/src/mixin/scroll_to_top_state_mixin.dart'; 3 | 4 | import '../../../model/gallery_thumbnail.dart'; 5 | import '../../../widget/loading_state_indicator.dart'; 6 | 7 | class ThumbnailsPageState with Scroll2TopStateMixin { 8 | late int initialPageIndex; 9 | late int nextPageIndexToLoadThumbnails; 10 | 11 | List thumbnails = []; 12 | 13 | /// if initialPageIndex is not 0, we need to compute the absolute index of the thumbnail 14 | List absoluteIndexOfThumbnails = []; 15 | 16 | LoadingState loadingState = LoadingState.idle; 17 | 18 | ThumbnailsPageState() { 19 | initialPageIndex = Get.arguments; 20 | nextPageIndexToLoadThumbnails = Get.arguments; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/pages/download/grid/archive/archive_grid_download_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:jhentai/src/service/archive_download_service.dart'; 3 | 4 | import '../../../../database/database.dart'; 5 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 6 | import '../../mixin/archive/archive_download_page_state_mixin.dart'; 7 | import '../../mixin/basic/multi_select/multi_select_download_page_state_mixin.dart'; 8 | import '../mixin/grid_download_page_state_mixin.dart'; 9 | 10 | class ArchiveGridDownloadPageState with Scroll2TopStateMixin, MultiSelectDownloadPageStateMixin, ArchiveDownloadPageStateMixin, GridBasePageState { 11 | @override 12 | List get allRootGroups => archiveDownloadService.allGroups; 13 | 14 | @override 15 | List galleryObjectsWithGroup(String groupName) => 16 | archiveDownloadService.archives.where((archive) => archiveDownloadService.archiveDownloadInfos[archive.gid]?.group == groupName).toList(); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/pages/download/grid/gallery/gallery_grid_download_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../../../database/database.dart'; 4 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 5 | import '../../../../service/gallery_download_service.dart'; 6 | import '../../mixin/basic/multi_select/multi_select_download_page_state_mixin.dart'; 7 | import '../../mixin/gallery/gallery_download_page_state_mixin.dart'; 8 | import '../mixin/grid_download_page_state_mixin.dart'; 9 | 10 | class GalleryGridDownloadPageState with Scroll2TopStateMixin, MultiSelectDownloadPageStateMixin, GalleryDownloadPageStateMixin, GridBasePageState { 11 | @override 12 | List get allRootGroups => galleryDownloadService.allGroups; 13 | 14 | @override 15 | List galleryObjectsWithGroup(String groupName) => 16 | galleryDownloadService.gallerys.where((gallery) => galleryDownloadService.galleryDownloadInfos[gallery.gid]?.group == groupName).toList(); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/pages/download/grid/local/local_gallery_grid_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:jhentai/src/service/local_gallery_service.dart'; 3 | 4 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 5 | import '../../mixin/basic/multi_select/multi_select_download_page_state_mixin.dart'; 6 | import '../mixin/grid_download_page_state_mixin.dart'; 7 | 8 | class LocalGalleryGridPageState with Scroll2TopStateMixin, MultiSelectDownloadPageStateMixin, GridBasePageState { 9 | @override 10 | List get allRootGroups => localGalleryService.rootDirectories; 11 | 12 | @override 13 | List galleryObjectsWithGroup(String groupName) { 14 | return localGalleryService.path2GalleryDir[groupName] ?? []; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/pages/download/grid/mixin/grid_download_page_service_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get_state_manager/src/simple/get_controllers.dart'; 2 | 3 | mixin GridBasePageServiceMixin on GetxController { 4 | final String galleryCountChangedId = 'galleryCountChangedId'; 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/pages/download/grid/mixin/grid_download_page_state_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:jhentai/src/service/local_gallery_service.dart'; 3 | 4 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 5 | 6 | mixin GridBasePageState implements Scroll2TopStateMixin { 7 | bool inEditMode = false; 8 | 9 | String currentGroup = LocalGalleryService.rootPath; 10 | 11 | bool get isAtRoot => currentGroup == LocalGalleryService.rootPath; 12 | 13 | List get allRootGroups; 14 | 15 | List get currentGalleryObjects => galleryObjectsWithGroup(currentGroup); 16 | 17 | List galleryObjectsWithGroup(String groupName); 18 | 19 | final ScrollController rootScrollController = ScrollController(); 20 | final ScrollController galleryScrollController = ScrollController(); 21 | 22 | @override 23 | ScrollController get scrollController => isAtRoot ? rootScrollController : galleryScrollController; 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/pages/download/list/archive/archive_list_download_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:jhentai/src/pages/download/mixin/basic/multi_select/multi_select_download_page_state_mixin.dart'; 4 | 5 | import '../../../../database/database.dart'; 6 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 7 | import '../../../../widget/grouped_list.dart'; 8 | import '../../mixin/archive/archive_download_page_state_mixin.dart'; 9 | 10 | class ArchiveListDownloadPageState with Scroll2TopStateMixin, MultiSelectDownloadPageStateMixin, ArchiveDownloadPageStateMixin { 11 | Set displayGroups = {}; 12 | Completer displayGroupsCompleter = Completer(); 13 | 14 | final GroupedListController groupedListController = GroupedListController(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/pages/download/list/gallery/gallery_list_download_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:jhentai/src/mixin/scroll_to_top_state_mixin.dart'; 4 | import 'package:jhentai/src/pages/download/mixin/gallery/gallery_download_page_state_mixin.dart'; 5 | 6 | import '../../../../database/database.dart'; 7 | import '../../../../widget/grouped_list.dart'; 8 | import '../../mixin/basic/multi_select/multi_select_download_page_state_mixin.dart'; 9 | 10 | class GalleryListDownloadPageState with Scroll2TopStateMixin, GalleryDownloadPageStateMixin, MultiSelectDownloadPageStateMixin { 11 | Set displayGroups = {}; 12 | Completer displayGroupsCompleter = Completer(); 13 | 14 | final GroupedListController groupedListController = GroupedListController(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/pages/download/list/local/local_gallery_list_page_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:jhentai/src/mixin/scroll_to_top_logic_mixin.dart'; 3 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 4 | import '../../../../service/local_gallery_service.dart'; 5 | import '../../mixin/local/local_gallery_download_page_logic_mixin.dart'; 6 | import 'local_gallery_list_page_state.dart'; 7 | 8 | class LocalGalleryListPageLogic extends GetxController with Scroll2TopLogicMixin, LocalGalleryDownloadPageLogicMixin { 9 | LocalGalleryListPageState state = LocalGalleryListPageState(); 10 | 11 | @override 12 | Scroll2TopStateMixin get scroll2TopState => state; 13 | 14 | @override 15 | String get currentPath => state.currentPath; 16 | 17 | @override 18 | set currentPath(String value) => state.currentPath = value; 19 | 20 | @override 21 | Future doRemoveItem(LocalGallery gallery) async { 22 | state.removedGalleryTitles.add(gallery.title); 23 | super.doRemoveItem(gallery); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/pages/download/list/local/local_gallery_list_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/mixin/scroll_to_top_state_mixin.dart'; 2 | 3 | import '../../../../service/local_gallery_service.dart'; 4 | 5 | class LocalGalleryListPageState with Scroll2TopStateMixin { 6 | String currentPath = LocalGalleryService.rootPath; 7 | 8 | final Set removedGalleryTitles = {}; 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/pages/download/mixin/archive/archive_download_page_state_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/mixin/scroll_to_top_state_mixin.dart'; 2 | 3 | import '../basic/multi_select/multi_select_download_page_state_mixin.dart'; 4 | 5 | mixin ArchiveDownloadPageStateMixin implements Scroll2TopStateMixin, MultiSelectDownloadPageStateMixin {} 6 | -------------------------------------------------------------------------------- /lib/src/pages/download/mixin/basic/multi_select/multi_select_download_page_state_mixin.dart: -------------------------------------------------------------------------------- 1 | mixin MultiSelectDownloadPageStateMixin { 2 | bool inMultiSelectMode = false; 3 | final Set selectedGids = {}; 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/pages/download/mixin/gallery/gallery_download_page_state_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/mixin/scroll_to_top_state_mixin.dart'; 2 | 3 | import '../../mixin/basic/multi_select/multi_select_download_page_state_mixin.dart'; 4 | 5 | mixin GalleryDownloadPageStateMixin implements Scroll2TopStateMixin, MultiSelectDownloadPageStateMixin {} 6 | -------------------------------------------------------------------------------- /lib/src/pages/favorite/favorite_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/routes/routes.dart'; 2 | 3 | import '../../model/search_config.dart'; 4 | import '../base/base_page_state.dart'; 5 | 6 | class FavoritePageState extends BasePageState { 7 | @override 8 | String get route => Routes.favorite; 9 | 10 | @override 11 | SearchConfig searchConfig = SearchConfig(searchType: SearchType.favorite); 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/pages/gallery_image/gallery_image_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:jhentai/src/pages/gallery_image/gallery_image_page_logic.dart'; 4 | import 'package:jhentai/src/pages/gallery_image/gallery_image_page_state.dart'; 5 | import 'package:jhentai/src/widget/loading_state_indicator.dart'; 6 | 7 | class GalleryImagePage extends StatelessWidget { 8 | final GalleryImagePageLogic logic = Get.put(GalleryImagePageLogic()); 9 | final GalleryImagePageState state = Get.find().state; 10 | 11 | GalleryImagePage({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar(), 17 | body: () { 18 | return GetBuilder(builder: (_) { 19 | return LoadingStateIndicator( 20 | loadingState: state.loadingState, 21 | errorTapCallback: logic.getPageInfoAndRedirect, 22 | ); 23 | }); 24 | }(), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/pages/gallery_image/gallery_image_page_state.dart: -------------------------------------------------------------------------------- 1 | import '../../model/gallery_image_page_url.dart'; 2 | import '../../widget/loading_state_indicator.dart'; 3 | 4 | class GalleryImagePageState { 5 | /// initial param 6 | late GalleryImagePageUrl galleryImagePageUrl; 7 | 8 | LoadingState loadingState = LoadingState.idle; 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/pages/gallerys/dashboard/dashboard_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/pages/base/base_page_state.dart'; 2 | 3 | import '../../../model/gallery.dart'; 4 | import '../../../model/search_config.dart'; 5 | import '../../../routes/routes.dart'; 6 | import '../../../widget/loading_state_indicator.dart'; 7 | 8 | class DashboardPageState extends BasePageState { 9 | @override 10 | SearchConfig searchConfig = SearchConfig.nonHOnly(); 11 | 12 | @override 13 | String get route => Routes.dashboard; 14 | 15 | LoadingState ranklistLoadingState = LoadingState.idle; 16 | LoadingState popularLoadingState = LoadingState.idle; 17 | 18 | List ranklistGallerys = List.empty(growable: true); 19 | List popularGallerys = List.empty(growable: true); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/pages/gallerys/dashboard/simple/simple_dashboard_page_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/pages/base/base_page_logic.dart'; 2 | import 'package:jhentai/src/pages/gallerys/dashboard/simple/simple_dashboard_page_state.dart'; 3 | 4 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 5 | 6 | class SimpleDashboardPageLogic extends BasePageLogic { 7 | @override 8 | bool get useSearchConfig => true; 9 | 10 | @override 11 | String get searchConfigKey => 'DashboardPageLogic'; 12 | 13 | @override 14 | SimpleDashboardPageState state = SimpleDashboardPageState(); 15 | 16 | @override 17 | Scroll2TopStateMixin get scroll2TopState => state; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/pages/gallerys/dashboard/simple/simple_dashboard_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/pages/base/base_page_state.dart'; 2 | 3 | import '../../../../routes/routes.dart'; 4 | 5 | class SimpleDashboardPageState extends BasePageState { 6 | @override 7 | String get route => Routes.dashboard; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/pages/gallerys/simple/gallerys_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:jhentai/src/pages/gallerys/simple/gallerys_page_logic.dart'; 5 | import 'package:jhentai/src/pages/gallerys/simple/gallerys_page_state.dart'; 6 | import '../../base/base_page.dart'; 7 | 8 | /// For desktop layout 9 | class GallerysPage extends BasePage { 10 | const GallerysPage({Key? key}) : super(key: key, showFilterButton: true, showScroll2TopButton: true); 11 | 12 | @override 13 | GallerysPageLogic get logic => Get.put(GallerysPageLogic(), permanent: true); 14 | 15 | @override 16 | GallerysPageState get state => Get.find().state; 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/pages/gallerys/simple/gallerys_page_logic.dart: -------------------------------------------------------------------------------- 1 | import '../../base/base_page_logic.dart'; 2 | import 'gallerys_page_state.dart'; 3 | 4 | class GallerysPageLogic extends BasePageLogic { 5 | @override 6 | bool get useSearchConfig => true; 7 | 8 | @override 9 | final GallerysPageState state = GallerysPageState(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/pages/gallerys/simple/gallerys_page_state.dart: -------------------------------------------------------------------------------- 1 | import '../../../routes/routes.dart'; 2 | import '../../base/base_page_state.dart'; 3 | 4 | class GallerysPageState extends BasePageState { 5 | @override 6 | String get route => Routes.gallerys; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/pages/history/history_page_state.dart: -------------------------------------------------------------------------------- 1 | import '../../routes/routes.dart'; 2 | import '../base/old_base_page_state.dart'; 3 | 4 | class HistoryPageState extends OldBasePageState { 5 | @override 6 | String get route => Routes.history; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/pages/layout/desktop/desktop_home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import 'desktop_layout_page_logic.dart'; 6 | import 'desktop_layout_page_state.dart'; 7 | 8 | class DesktopHomePage extends StatelessWidget { 9 | final DesktopLayoutPageLogic logic = Get.find(); 10 | final DesktopLayoutPageState state = Get.find().state; 11 | 12 | DesktopHomePage({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GetBuilder( 17 | id: logic.leftColumnId, 18 | builder: (_) => Stack( 19 | children: state.icons 20 | .where((icon) => icon.shouldRender) 21 | .mapIndexed((index, icon) => Offstage( 22 | offstage: state.selectedTabOrder != index, 23 | child: icon.page.call(), 24 | )) 25 | .toList(), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/pages/layout/mobile_v2/notification/tap_menu_button_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class TapMenuButtonNotification extends Notification {} 4 | -------------------------------------------------------------------------------- /lib/src/pages/layout/mobile_v2/notification/tap_tab_bat_button_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class TapTabBarButtonNotification extends Notification { 4 | final String routeName; 5 | 6 | const TapTabBarButtonNotification(this.routeName); 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/pages/popular/popular_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:jhentai/src/pages/popular/popular_page_logic.dart'; 5 | import 'package:jhentai/src/pages/popular/popular_page_state.dart'; 6 | 7 | import '../base/base_page.dart'; 8 | 9 | class PopularPage extends BasePage { 10 | const PopularPage({ 11 | Key? key, 12 | bool showMenuButton = false, 13 | bool showTitle = false, 14 | String? name, 15 | }) : super( 16 | key: key, 17 | showMenuButton: showMenuButton, 18 | showTitle: showTitle, 19 | showJumpButton: false, 20 | showScroll2TopButton: true, 21 | name: name, 22 | ); 23 | 24 | @override 25 | PopularPageLogic get logic => Get.put(PopularPageLogic(), permanent: true); 26 | 27 | @override 28 | PopularPageState get state => Get.find().state; 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/pages/popular/popular_page_logic.dart: -------------------------------------------------------------------------------- 1 | import '../base/base_page_logic.dart'; 2 | import 'popular_page_state.dart'; 3 | 4 | class PopularPageLogic extends BasePageLogic { 5 | @override 6 | final PopularPageState state = PopularPageState(); 7 | 8 | @override 9 | bool get useSearchConfig => false; 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/pages/popular/popular_page_state.dart: -------------------------------------------------------------------------------- 1 | import '../../model/search_config.dart'; 2 | import '../../routes/routes.dart'; 3 | import '../base/base_page_state.dart'; 4 | 5 | class PopularPageState extends BasePageState { 6 | @override 7 | String get route => Routes.popular; 8 | 9 | @override 10 | SearchConfig searchConfig = SearchConfig(searchType: SearchType.popular); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/pages/ranklist/ranklist_page_state.dart: -------------------------------------------------------------------------------- 1 | import '../../routes/routes.dart'; 2 | import '../base/old_base_page_state.dart'; 3 | 4 | enum RanklistType { 5 | allTime, 6 | year, 7 | month, 8 | day, 9 | } 10 | 11 | class RanklistPageState extends OldBasePageState { 12 | @override 13 | String get route => Routes.ranklist; 14 | 15 | RanklistType ranklistType = RanklistType.day; 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/pages/read/layout/horizontal_double_column/horizontal_double_column_layout_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | 5 | class HorizontalDoubleColumnLayoutState { 6 | late PageController pageController; 7 | 8 | late int pageCount; 9 | 10 | late List isSpreadPage; 11 | Completer isSpreadPageCompleter = Completer(); 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/pages/read/layout/horizontal_list/horizontal_list_layout_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:photo_view/photo_view.dart'; 2 | import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; 3 | 4 | 5 | class HorizontalListLayoutState { 6 | final PhotoViewController photoViewController = PhotoViewController(); 7 | 8 | final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create(); 9 | final ItemScrollController itemScrollController = ItemScrollController(); 10 | final ScrollOffsetController scrollOffsetController = ScrollOffsetController(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/pages/read/layout/horizontal_page/horizontal_page_layout_state.dart: -------------------------------------------------------------------------------- 1 | class HorizontalPageLayoutState {} 2 | -------------------------------------------------------------------------------- /lib/src/pages/read/layout/vertical_list/vertical_list_layout_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:photo_view/photo_view.dart'; 2 | import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; 3 | 4 | class VerticalListLayoutState { 5 | final PhotoViewController photoViewController = PhotoViewController(); 6 | 7 | final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create(); 8 | final ItemScrollController itemScrollController = ItemScrollController(); 9 | final ScrollOffsetController scrollOffsetController = ScrollOffsetController(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/pages/search/desktop/desktop_search_page_tab_state.dart: -------------------------------------------------------------------------------- 1 | import '../../../routes/routes.dart'; 2 | import '../../base/base_page_state.dart'; 3 | import '../mixin/search_page_state_mixin.dart'; 4 | 5 | class DesktopSearchPageTabState extends BasePageState with SearchPageStateMixin { 6 | @override 7 | String get route => Routes.desktopSearch; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/pages/search/mixin/new_search_argument.dart: -------------------------------------------------------------------------------- 1 | import '../../../model/search_config.dart'; 2 | import '../../../setting/preference_setting.dart'; 3 | 4 | class NewSearchArgument { 5 | final String? keyword; 6 | final SearchBehaviour? keywordSearchBehaviour; 7 | 8 | final SearchConfig? rewriteSearchConfig; 9 | 10 | const NewSearchArgument({ 11 | this.keyword, 12 | this.keywordSearchBehaviour, 13 | this.rewriteSearchConfig, 14 | }) : assert((keyword != null && keywordSearchBehaviour != null) || rewriteSearchConfig != null); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/pages/search/mobile_v2/search_page_mobile_v2_state.dart: -------------------------------------------------------------------------------- 1 | import '../../../routes/routes.dart'; 2 | import '../../base/base_page_state.dart'; 3 | import '../mixin/search_page_state_mixin.dart'; 4 | 5 | class SearchPageMobileV2State extends BasePageState with SearchPageStateMixin { 6 | @override 7 | String get route => Routes.mobileV2Search; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/pages/setting/account/login/login_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:jhentai/src/widget/loading_state_indicator.dart'; 3 | 4 | enum LoginType { password, cookie, web } 5 | 6 | class LoginPageState { 7 | LoginType loginType = LoginType.password; 8 | 9 | FocusNode passwordFocusNode = FocusNode(); 10 | FocusNode ipbPassHashFocusNode = FocusNode(); 11 | FocusNode igneousFocusNode = FocusNode(); 12 | 13 | bool obscureText = true; 14 | 15 | String? userName; 16 | String? password; 17 | String? ipbMemberId; 18 | String? ipbPassHash; 19 | String? igneous; 20 | 21 | LoadingState loginState = LoadingState.idle; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/pages/setting/eh/tagsets/tag_sets_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:jhentai/src/mixin/scroll_to_top_state_mixin.dart'; 4 | import 'package:jhentai/src/model/tag_set.dart'; 5 | import 'package:jhentai/src/widget/loading_state_indicator.dart'; 6 | 7 | class TagSetsState with Scroll2TopStateMixin { 8 | int currentTagSetNo = 1; 9 | 10 | List<({int number, String name})> tagSets = []; 11 | List tags = []; 12 | 13 | late bool currentTagSetEnable; 14 | Color? currentTagSetBackgroundColor; 15 | late String apikey; 16 | 17 | LoadingState loadingState = LoadingState.idle; 18 | LoadingState updateTagSetState = LoadingState.idle; 19 | LoadingState updateTagState = LoadingState.idle; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_state.dart: -------------------------------------------------------------------------------- 1 | import '../../../../../mixin/scroll_to_top_state_mixin.dart'; 2 | import '../../../../../service/local_block_rule_service.dart'; 3 | 4 | class ConfigureBlockingRulePageState with Scroll2TopStateMixin { 5 | late String groupId; 6 | 7 | List rules = []; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/pages/setting/preference/block_rule/blocking_rule_page_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:jhentai/src/service/local_block_rule_service.dart'; 4 | 5 | import '../../../../mixin/scroll_to_top_state_mixin.dart'; 6 | import '../../../../widget/grouped_list.dart'; 7 | 8 | class BlockingRulePageState with Scroll2TopStateMixin { 9 | Map> groupedRules = {}; 10 | 11 | bool showGroup = false; 12 | Completer showGroupCompleter = Completer(); 13 | 14 | final GroupedListController> groupedListController = GroupedListController>(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/pages/single_image/single_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_image/extended_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:jhentai/src/config/ui_config.dart'; 5 | import 'package:jhentai/src/widget/eh_image.dart'; 6 | 7 | class SingleImagePage extends StatelessWidget { 8 | const SingleImagePage({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ExtendedImageSlidePage( 13 | resetPageDuration: const Duration(milliseconds: 200), 14 | slidePageBackgroundHandler: (Offset offset, Size pageSize) => UIConfig.backGroundColor(context), 15 | child: EHImage( 16 | galleryImage: Get.arguments, 17 | enableSlideOutPage: true, 18 | heroTag: Get.arguments, 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/pages/watched/watched_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:jhentai/src/pages/watched/watched_page_state.dart'; 5 | 6 | import '../base/base_page.dart'; 7 | import 'watched_page_logic.dart'; 8 | 9 | class WatchedPage extends BasePage { 10 | const WatchedPage({ 11 | Key? key, 12 | bool showMenuButton = false, 13 | bool showTitle = false, 14 | String? name, 15 | }) : super( 16 | key: key, 17 | showMenuButton: showMenuButton, 18 | showJumpButton: true, 19 | showFilterButton: true, 20 | showTitle: showTitle, 21 | showScroll2TopButton: true, 22 | name: name, 23 | ); 24 | 25 | @override 26 | WatchedPageLogic get logic => Get.put(WatchedPageLogic(), permanent: true); 27 | 28 | @override 29 | WatchedPageState get state => Get.find().state; 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/pages/watched/watched_page_logic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:jhentai/src/pages/watched/watched_page_state.dart'; 4 | 5 | import '../../enum/config_enum.dart'; 6 | import '../../model/search_config.dart'; 7 | import '../../service/local_config_service.dart'; 8 | import '../base/base_page_logic.dart'; 9 | 10 | class WatchedPageLogic extends BasePageLogic { 11 | @override 12 | bool get useSearchConfig => true; 13 | 14 | @override 15 | bool get autoLoadNeedLogin => true; 16 | 17 | @override 18 | final WatchedPageState state = WatchedPageState(); 19 | 20 | @override 21 | Future saveSearchConfig(SearchConfig searchConfig) async { 22 | await localConfigService.write( 23 | configKey: ConfigEnum.searchConfig, 24 | subConfigKey: searchConfigKey, 25 | value: jsonEncode(searchConfig.copyWith(keyword: '', tags: [])), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/pages/watched/watched_page_state.dart: -------------------------------------------------------------------------------- 1 | import '../../model/search_config.dart'; 2 | import '../../routes/routes.dart'; 3 | import '../base/base_page_state.dart'; 4 | 5 | class WatchedPageState extends BasePageState { 6 | @override 7 | String get route => Routes.watched; 8 | 9 | @override 10 | SearchConfig searchConfig = SearchConfig(searchType: SearchType.watched); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/routes/eh_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | enum Side { 4 | left, 5 | right, 6 | fullScreen, 7 | } 8 | 9 | class EHPage extends GetPage { 10 | /// in tablet layout, the side this page on. 11 | final Side side; 12 | 13 | /// used when pushing a new route to right screen 14 | final bool offAllBefore; 15 | 16 | EHPage({ 17 | required super.name, 18 | required super.page, 19 | this.side = Side.right, 20 | this.offAllBefore = true, 21 | super.transition, 22 | super.popGesture, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/routes/getx_router_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get_navigation/src/router_report.dart'; 3 | 4 | /// Make sure GetXController will be recycled when using native Navigator api by letting 5 | /// GetX be aware of native Navigator api operation 6 | class GetXRouterObserver extends NavigatorObserver { 7 | @override 8 | void didPush(Route route, Route? previousRoute) { 9 | RouterReportManager.reportCurrentRoute(route); 10 | } 11 | 12 | @override 13 | void didPop(Route route, Route? previousRoute) async { 14 | RouterReportManager.reportRouteWillDispose(route); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/service/frame_rate_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_displaymode/flutter_displaymode.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import 'jh_service.dart'; 5 | 6 | FrameRateService frameRateService = FrameRateService(); 7 | 8 | class FrameRateService with JHLifeCircleBeanErrorCatch implements JHLifeCircleBean { 9 | @override 10 | Future doInitBean() async { 11 | if (GetPlatform.isAndroid) { 12 | await FlutterDisplayMode.setHighRefreshRate(); 13 | } 14 | } 15 | 16 | @override 17 | Future doAfterBeanReady() async {} 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/service/isolate_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:integral_isolates/integral_isolates.dart'; 4 | 5 | import 'jh_service.dart'; 6 | 7 | IsolateService isolateService = IsolateService(); 8 | 9 | class IsolateService with JHLifeCircleBeanErrorCatch implements JHLifeCircleBean { 10 | late final StatefulIsolate _isolate; 11 | 12 | @override 13 | Future doInitBean() async { 14 | _isolate = StatefulIsolate(); 15 | await _isolate.init(); 16 | } 17 | 18 | @override 19 | Future doAfterBeanReady() async {} 20 | 21 | Future jsonEncodeAsync(Object object) async { 22 | return run(jsonEncode, object); 23 | } 24 | 25 | Future jsonDecodeAsync(String string) async { 26 | return run(jsonDecode, string); 27 | } 28 | 29 | Future run(IsolateCallback callback, Q message, {String? debugLabel}) { 30 | return _isolate.isolate(callback, message, debugLabel: debugLabel); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/setting/README.md: -------------------------------------------------------------------------------- 1 | the user configs 2 | 3 | some are stored in local, loaded every time launching app 4 | 5 | can be changed by user 6 | 7 | these settings may be used by other Logic class, but because there's no logic in these classes, so I use [obx] and [Rxn] rather 8 | than 9 | [GetController] and [GetBuilder] 10 | -------------------------------------------------------------------------------- /lib/src/utils/archive_bot_response_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:dio/dio.dart'; 3 | 4 | import '../model/archive_bot_response/archive_bot_response.dart'; 5 | 6 | class ArchiveBotResponseParser { 7 | static ArchiveBotResponse commonParse(Headers headers, dynamic data) { 8 | return ArchiveBotResponse.fromJson(data); 9 | } 10 | } 11 | 12 | enum ArchiveBotResponseCodeEnum { 13 | invalidParam(1), 14 | invalidApiKey(2), 15 | banned(3), 16 | fetchGalleryInfoFailed(4), 17 | insufficientGP(5), 18 | parseFailed(6), 19 | checkedIn(7), 20 | serverError(99), 21 | ; 22 | 23 | final int code; 24 | 25 | const ArchiveBotResponseCodeEnum(this.code); 26 | 27 | static ArchiveBotResponseCodeEnum? fromCode(int code) { 28 | return ArchiveBotResponseCodeEnum.values.firstWhereOrNull((e) => e.code == code); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/utils/archive_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:archive/archive_io.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | 4 | Future extractZipArchive(String archivePath, String extractPath) { 5 | return compute( 6 | (List path) async { 7 | InputFileStream? inputStream; 8 | try { 9 | inputStream = InputFileStream(path[0]); 10 | await extractArchiveToDisk(ZipDecoder().decodeBuffer(inputStream), path[1]); 11 | } on Exception catch (_) { 12 | return false; 13 | } finally { 14 | inputStream?.close(); 15 | } 16 | return true; 17 | }, 18 | [archivePath, extractPath], 19 | ); 20 | } 21 | 22 | Future> extractGZipArchive(String archivePath) { 23 | return compute( 24 | (String path) async { 25 | InputFileStream inputStream = InputFileStream(path); 26 | try { 27 | return GZipDecoder().decodeBuffer(inputStream); 28 | } on Exception catch (_) { 29 | return []; 30 | } finally { 31 | inputStream.close(); 32 | } 33 | }, 34 | archivePath, 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/utils/byte_util.dart: -------------------------------------------------------------------------------- 1 | String byte2String(double bytes) { 2 | if (bytes < 1024) { 3 | return '${bytes}B'; 4 | } 5 | 6 | bytes /= 1024; 7 | if (bytes < 1024) { 8 | return '${bytes.toStringAsFixed(2)}KB'; 9 | } 10 | 11 | bytes /= 1024; 12 | if (bytes < 1024) { 13 | return '${bytes.toStringAsFixed(2)}MB'; 14 | } 15 | 16 | bytes /= 1024; 17 | return '${bytes.toStringAsFixed(2)}GB'; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/utils/color_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | /// #7EFFDD => Color 4 | Color? aRGBString2Color(String? string) { 5 | if (string == null || string.isEmpty) { 6 | return null; 7 | } 8 | return Color(int.parse('FF${string.replaceAll('#', '')}', radix: 16)); 9 | } 10 | 11 | /// Color => #7EFFDD 12 | String? color2aRGBString(Color? color) { 13 | if (color == null) { 14 | return null; 15 | } 16 | return '#' + color.value.toRadixString(16).substring(2); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/utils/date_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | 3 | class DateUtil { 4 | static String transformUtc2LocalTimeString(String utcTimeString) { 5 | final DateTime utcTime = DateFormat('yyyy-MM-dd HH:mm', 'en_US').parseUtc(utcTimeString).toLocal(); 6 | final String localTime = DateFormat('yyyy-MM-dd HH:mm').format(utcTime); 7 | return localTime; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/utils/hmac_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:crypto/crypto.dart'; 3 | 4 | class HmacUtil { 5 | static String hmacSha256(String data, String secretKey) { 6 | final key = utf8.encode(secretKey); 7 | final bytes = utf8.encode(data); 8 | 9 | final hmacSha256 = Hmac(sha256, key); 10 | final digest = hmacSha256.convert(bytes); 11 | 12 | return base64.encode(digest.bytes); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/utils/jh_response_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:jhentai/src/model/jh_response/jh_response.dart'; 3 | 4 | 5 | class JHResponseParser { 6 | static JHResponse commonParse(Headers headers, dynamic data) { 7 | return JHResponse.fromJson(data); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/utils/jh_spider_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import '../model/config.dart'; 4 | import '../model/jh_response/jh_response.dart'; 5 | 6 | class JHResponseParser { 7 | static bool api2Success(Headers headers, dynamic data) { 8 | JHResponse response = JHResponse.fromJson(data); 9 | return response.code == 0; 10 | } 11 | 12 | static List listConfigApi2Configs(Headers headers, dynamic data) { 13 | JHResponse response = JHResponse.fromJson(data); 14 | List list = response.data['configs']; 15 | return list.map((e) => CloudConfig.fromJson(e)).toList(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/utils/recorder_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:jhentai/src/service/log.dart'; 2 | 3 | Future recordTimeCost(String name, Function function) async { 4 | DateTime startTime = DateTime.now(); 5 | await function.call(); 6 | DateTime endTime = DateTime.now(); 7 | log.trace('Time cost of $name: ${endTime.difference(startTime).inMilliseconds}ms'); 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/utils/screen_size_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:jhentai/src/config/ui_config.dart'; 3 | import 'package:jhentai/src/setting/style_setting.dart'; 4 | 5 | double get fullScreenWidth => Get.width; 6 | 7 | double get screenWidth => styleSetting.isInMobileLayout 8 | ? Get.width 9 | : styleSetting.isInTabletLayout 10 | ? Get.width / 2 11 | : (Get.width - UIConfig.desktopLeftTabBarWidth) / 2; 12 | 13 | double get screenHeight => Get.height; 14 | -------------------------------------------------------------------------------- /lib/src/utils/string_uril.dart: -------------------------------------------------------------------------------- 1 | bool isEmptyOrNull(String? string) { 2 | return string == null || string.isEmpty; 3 | } 4 | -------------------------------------------------------------------------------- /lib/src/utils/uuid_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:uuid/v1.dart'; 2 | 3 | const UuidV1 uuid = UuidV1(); 4 | 5 | String newUUID() { 6 | return uuid.generate(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/utils/version_util.dart: -------------------------------------------------------------------------------- 1 | /// v7.7.7 2 | int compareVersion(String a, String b) { 3 | List numberA = a.replaceFirst('v', '').split('.'); 4 | List numberB = b.replaceFirst('v', '').split('.'); 5 | 6 | if (numberA.length != numberB.length) { 7 | return 0; 8 | } 9 | 10 | for (int i = 0; i < numberA.length; i++) { 11 | int a = int.parse(numberA[i]); 12 | int b = int.parse(numberB[i]); 13 | if (a > b) { 14 | return 1; 15 | } else if (a < b) { 16 | return -1; 17 | } 18 | } 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/widget/README.md: -------------------------------------------------------------------------------- 1 | global common widgets 2 | -------------------------------------------------------------------------------- /lib/src/widget/eh_alert_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:jhentai/src/utils/route_util.dart'; 4 | 5 | class EHDialog extends StatelessWidget { 6 | final String title; 7 | final String? content; 8 | final bool showCancelButton; 9 | 10 | const EHDialog({ 11 | Key? key, 12 | required this.title, 13 | this.content, 14 | this.showCancelButton = true, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return AlertDialog( 20 | title: Text(title), 21 | content: content == null ? null : Text(content!), 22 | actions: [ 23 | if (showCancelButton) TextButton(onPressed: backRoute, child: Text('cancel'.tr)), 24 | TextButton(child: Text('OK'.tr), onPressed: () => backRoute(result: true)), 25 | ], 26 | actionsPadding: const EdgeInsets.only(left: 24, right: 24, bottom: 12), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/widget/eh_comment_score_details_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EHCommentScoreDetailsDialog extends StatelessWidget { 4 | final List scoreDetails; 5 | 6 | const EHCommentScoreDetailsDialog({Key? key, required this.scoreDetails}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return SimpleDialog( 11 | children: scoreDetails 12 | .map( 13 | (detail) => Center( 14 | child: Text(detail), 15 | ), 16 | ) 17 | .toList(), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/widget/eh_gallery_favorite_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:jhentai/src/config/ui_config.dart'; 4 | 5 | class EHGalleryFavoriteTag extends StatelessWidget { 6 | final String name; 7 | final Color color; 8 | 9 | const EHGalleryFavoriteTag({Key? key, required this.name, required this.color}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(10)), 15 | padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), 16 | child: Row( 17 | children: [ 18 | const Icon(Icons.favorite, size: 8, color: UIConfig.galleryCardFavoriteTagTextColor), 19 | Text(name, style: const TextStyle(fontSize: 10, height: 1, color: UIConfig.galleryCardFavoriteTagTextColor)).marginOnly(left: 2), 20 | ], 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/widget/eh_log_out_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:jhentai/src/config/ui_config.dart'; 4 | 5 | import '../network/eh_request.dart'; 6 | import '../utils/route_util.dart'; 7 | 8 | class LogoutDialog extends StatelessWidget { 9 | const LogoutDialog({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return CupertinoAlertDialog( 14 | title: Text('logout'.tr + ' ?'), 15 | actions: [ 16 | CupertinoDialogAction(child: Text('cancel'.tr), onPressed: backRoute), 17 | CupertinoDialogAction( 18 | child: Text('OK'.tr, style: TextStyle(color: UIConfig.alertColor(context))), 19 | onPressed: () async { 20 | await ehRequest.requestLogout(); 21 | backRoute(); 22 | }, 23 | ), 24 | ], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/widget/eh_wheel_scroll_listener.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class EHWheelListener extends StatelessWidget { 5 | final Widget child; 6 | final ValueChanged? onPointerScroll; 7 | 8 | const EHWheelListener({Key? key, required this.child, this.onPointerScroll}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Listener( 13 | onPointerSignal: (PointerSignalEvent event) { 14 | if (event is PointerScrollEvent) { 15 | onPointerScroll?.call(event); 16 | } 17 | }, 18 | child: child, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/widget/icon_text_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class IconTextButton extends StatelessWidget { 4 | final double? height; 5 | final double? width; 6 | final Icon icon; 7 | final Widget text; 8 | final VoidCallback? onPressed; 9 | final VoidCallback? onLongPress; 10 | 11 | const IconTextButton({ 12 | Key? key, 13 | this.height, 14 | this.width, 15 | required this.icon, 16 | required this.text, 17 | this.onPressed, 18 | this.onLongPress, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return SizedBox( 24 | height: height, 25 | width: width, 26 | child: GestureDetector( 27 | onLongPress: onLongPress, 28 | child: IconButton( 29 | splashColor: Colors.transparent, 30 | highlightColor: Colors.transparent, 31 | hoverColor: Colors.transparent, 32 | onPressed: onPressed, 33 | icon: Column( 34 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 35 | children: [icon, text], 36 | ), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/widget/keep_alive.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// KeepAliveWrapper can keep the item(s) of scrollview alive, **Not dispose**. 4 | class KeepAliveWrapper extends StatefulWidget { 5 | const KeepAliveWrapper({ 6 | Key? key, 7 | this.keepAlive = true, 8 | required this.child, 9 | }) : super(key: key); 10 | final bool keepAlive; 11 | final Widget child; 12 | 13 | @override 14 | _KeepAliveWrapperState createState() => _KeepAliveWrapperState(); 15 | } 16 | 17 | class _KeepAliveWrapperState extends State 18 | with AutomaticKeepAliveClientMixin { 19 | @override 20 | Widget build(BuildContext context) { 21 | super.build(context); 22 | return widget.child; 23 | } 24 | 25 | @override 26 | void didUpdateWidget(covariant KeepAliveWrapper oldWidget) { 27 | if (oldWidget.keepAlive != widget.keepAlive) { 28 | updateKeepAlive(); 29 | } 30 | super.didUpdateWidget(oldWidget); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | //print("KeepAliveWrapper dispose"); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | bool get wantKeepAlive => widget.keepAlive; 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/widget/re_unlock_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../config/ui_config.dart'; 5 | import '../utils/route_util.dart'; 6 | 7 | class ReUnlockDialog extends StatelessWidget { 8 | const ReUnlockDialog({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return CupertinoAlertDialog( 13 | title: Text('reUnlock'.tr + ' ?'), 14 | content: Text('reUnlockHint'.tr), 15 | actions: [ 16 | CupertinoDialogAction( 17 | child: Text('cancel'.tr), 18 | onPressed: backRoute, 19 | ), 20 | CupertinoDialogAction( 21 | child: Text('OK'.tr, style: TextStyle(color: UIConfig.alertColor(context))), 22 | onPressed: () => backRoute(result: true), 23 | ), 24 | ], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /linux.sh: -------------------------------------------------------------------------------- 1 | version=$(head -n 5 pubspec.yaml | tail -n 1 | cut -d ' ' -f 2) 2 | 3 | flutter build linux --release -t lib/src/main.dart \ 4 | && mkdir ~/Desktop/JHenTai_${version} \ 5 | && cp -r build/linux/x64/release/bundle/* ~/Desktop/JHenTai_${version}/ \ 6 | && cd ~/Desktop \ 7 | && zip -ro JHenTai_${version}.zip JHenTai_${version} \ 8 | && rm -rf mkdir ~/Desktop/JHenTai_${version} 9 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/assets/AppImageBuilder.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | AppDir: 3 | path: ../../build/linux/AppDir 4 | app_info: 5 | id: top.jtmonster.jhentai 6 | name: JHenTai 7 | icon: top.jtmonster.jhentai 8 | version: latest 9 | exec: jhentai 10 | exec_args: $@ 11 | files: 12 | include: 13 | - /lib64/ld-linux-x86-64.so.2 14 | exclude: 15 | - usr/share/man 16 | - usr/share/doc/*/README.* 17 | - usr/share/doc/*/changelog.* 18 | - usr/share/doc/*/NEWS.* 19 | - usr/share/doc/*/TODO.* 20 | test: 21 | fedora-30: 22 | image: appimagecrafters/tests-env:fedora-30 23 | command: ./AppRun 24 | debian-stable: 25 | image: appimagecrafters/tests-env:debian-stable 26 | command: ./AppRun 27 | archlinux-latest: 28 | image: appimagecrafters/tests-env:archlinux-latest 29 | command: ./AppRun 30 | centos-7: 31 | image: appimagecrafters/tests-env:centos-7 32 | command: ./AppRun 33 | ubuntu-xenial: 34 | image: appimagecrafters/tests-env:ubuntu-xenial 35 | command: ./AppRun 36 | AppImage: 37 | arch: x86_64 38 | update-information: guess -------------------------------------------------------------------------------- /linux/assets/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Maintainer: madoka773 2 | Package: jhentai 3 | Version: 4 | Section: x11 5 | Priority: optional 6 | Architecture: amd64 7 | Essential: no 8 | Installed-Size: 34648 9 | Description: A cross-platform app made for e-hentai & exhentai by Flutter. 10 | Homepage: https://github.com/jiangtian616/JHenTai 11 | Depends: webkit2gtk-4.1 12 | -------------------------------------------------------------------------------- /linux/assets/DEBIAN/postinst: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | ln -sf /opt/jhentai/jhentai /usr/bin/jhentai 3 | chmod +x /usr/bin/jhentai 4 | update-mime-database /usr/share/mime || true 5 | update-desktop-database /usr/share/applications || true 6 | exit 0 -------------------------------------------------------------------------------- /linux/assets/DEBIAN/postrm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | rm /usr/bin/jhentai 3 | update-mime-database /usr/share/mime || true 4 | update-desktop-database /usr/share/applications || true 5 | exit 0 -------------------------------------------------------------------------------- /linux/assets/top.jtmonster.jhentai.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=JHenTai 4 | Comment=A cross-platform app made for e-hentai & exhentai by Flutter. 5 | Exec=jhentai 6 | Icon=top.jtmonster.jhentai 7 | Categories=Network;Graphics 8 | Terminal=false -------------------------------------------------------------------------------- /linux/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 fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_webview_window 7 | screen_retriever 8 | smart_auth 9 | sqlite3_flutter_libs 10 | url_launcher_linux 11 | window_manager 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}/linux 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}/linux plugins/${ffi_plugin}) 28 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 29 | endforeach(ffi_plugin) 30 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /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/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/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/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 = jhentai 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = top.jtmonster.jhentai 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 top.jtmonster. 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.assets.movies.read-write 8 | 9 | com.apple.security.assets.music.read-write 10 | 11 | com.apple.security.assets.pictures.read-write 12 | 13 | com.apple.security.cs.allow-jit 14 | 15 | com.apple.security.files.downloads.read-write 16 | 17 | com.apple.security.files.user-selected.read-write 18 | 19 | com.apple.security.network.client 20 | 21 | com.apple.security.network.server 22 | 23 | com.apple.security.print 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import window_manager 4 | 5 | class MainFlutterWindow: NSWindow { 6 | override func awakeFromNib() { 7 | let flutterViewController = FlutterViewController.init() 8 | let windowFrame = self.frame 9 | self.contentViewController = flutterViewController 10 | self.setFrame(windowFrame, display: true) 11 | 12 | RegisterGeneratedPlugins(registry: flutterViewController) 13 | 14 | super.awakeFromNib() 15 | } 16 | 17 | override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { 18 | super.order(place, relativeTo: otherWin) 19 | hiddenWindowAtLaunch() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.assets.movies.read-write 8 | 9 | com.apple.security.assets.music.read-write 10 | 11 | com.apple.security.assets.pictures.read-write 12 | 13 | com.apple.security.files.downloads.read-write 14 | 15 | com.apple.security.files.user-selected.read-write 16 | 17 | com.apple.security.network.client 18 | 19 | com.apple.security.network.server 20 | 21 | com.apple.security.print 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pkg.sh: -------------------------------------------------------------------------------- 1 | version=$(head -n 5 pubspec.yaml | tail -n 1 | cut -d ' ' -f 2) 2 | 3 | flutter build macos --release -t lib/src/main.dart \ 4 | && hdiutil create -size 150m -fs HFS+ -volname JHenTai JHenTai.dmg \ 5 | && hdiutil attach JHenTai.dmg \ 6 | && cp -R build/macos/Build/Products/Release/jhentai.app /Volumes/JHenTai \ 7 | && pkgbuild --install-location /Applications/JHenTai.app --identifier top.jtmonster.jhentai --version ${version} --root /Volumes/JHenTai/jhentai.app build/macos/JHenTai-${version}.pkg \ 8 | && hdiutil detach /Volumes/JHenTai 9 | -------------------------------------------------------------------------------- /screenshot/archive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/archive.jpg -------------------------------------------------------------------------------- /screenshot/desktop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/desktop1.png -------------------------------------------------------------------------------- /screenshot/detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/detail.png -------------------------------------------------------------------------------- /screenshot/download.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/download.jpg -------------------------------------------------------------------------------- /screenshot/mobile_v2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/mobile_v2.jpg -------------------------------------------------------------------------------- /screenshot/read.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/read.jpg -------------------------------------------------------------------------------- /screenshot/read_continuous_scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/read_continuous_scroll.png -------------------------------------------------------------------------------- /screenshot/read_double_column.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/read_double_column.png -------------------------------------------------------------------------------- /screenshot/search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/search.jpg -------------------------------------------------------------------------------- /screenshot/setting_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/setting_en.jpg -------------------------------------------------------------------------------- /screenshot/setting_zh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/setting_zh.jpg -------------------------------------------------------------------------------- /screenshot/stat_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/stat_en.jpg -------------------------------------------------------------------------------- /screenshot/stat_zh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/stat_zh.jpg -------------------------------------------------------------------------------- /screenshot/tabletV2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/screenshot/tabletV2.png -------------------------------------------------------------------------------- /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 | echo "no argument" 26 | else 27 | foreachThin $1 28 | fi -------------------------------------------------------------------------------- /windows.sh: -------------------------------------------------------------------------------- 1 | version=$(head -n 5 pubspec.yaml | tail -n 1 | cut -d ' ' -f 2) 2 | 3 | flutter build windows -t lib/src/main.dart \ 4 | && cp -r build/windows/runner/Release/ ~/Desktop/JHenTai_${version}_windows/ \ 5 | && cd ~/Desktop \ 6 | && zip -ro JHenTai_${version}_windows.zip JHenTai_${version}_windows \ 7 | && rm -rf JHenTai_${version}_windows 8 | -------------------------------------------------------------------------------- /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.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 | battery_plus 7 | desktop_webview_window 8 | local_auth_windows 9 | permission_handler_windows 10 | screen_brightness_windows 11 | screen_retriever 12 | share_plus 13 | smart_auth 14 | sqlite3_flutter_libs 15 | url_launcher_windows 16 | window_manager 17 | ) 18 | 19 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 20 | ) 21 | 22 | set(PLUGIN_BUNDLED_LIBRARIES) 23 | 24 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 26 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 28 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 29 | endforeach(plugin) 30 | 31 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 32 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 33 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 34 | endforeach(ffi_plugin) 35 | -------------------------------------------------------------------------------- /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/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/jiangtian616/JHenTai/c66673831fcb07369aa214cffe702b55f995eb88/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.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 | --------------------------------------------------------------------------------