├── .github └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── .gitignore ├── LICENSE ├── README.md ├── Version_update_log.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── horopic │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── app_icon.png │ │ │ ├── favicon.jpg │ │ │ ├── favicon.png │ │ │ ├── ic_launcher.png │ │ │ ├── launch_image.png │ │ │ └── launch_pic.png │ │ │ ├── mipmap-mdpi │ │ │ ├── app_icon.png │ │ │ ├── favicon.png │ │ │ ├── ic_launcher.png │ │ │ ├── launch_image.png │ │ │ └── launch_pic.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── app_icon.png │ │ │ ├── favicon.png │ │ │ ├── ic_launcher.png │ │ │ ├── launch_image.png │ │ │ └── launch_pic.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── app_icon.png │ │ │ ├── favicon.png │ │ │ ├── ic_launcher.png │ │ │ ├── launch_image.png │ │ │ └── launch_pic.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── app_icon.png │ │ │ ├── favicon.png │ │ │ ├── ic_launcher.png │ │ │ ├── launch_image.png │ │ │ └── launch_pic.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── app_icon.png ├── app_icon_large.png ├── files │ └── UpdateLog.md ├── fonts │ └── iconfont.ttf ├── icons │ ├── 3g2.png │ ├── 3gp.png │ ├── 7z.png │ ├── Imgur.png │ ├── _blank.png │ ├── _page.png │ ├── aac.png │ ├── accdb.png │ ├── adt.png │ ├── ai.png │ ├── aiff.png │ ├── aliyun.png │ ├── aly.png │ ├── amiga.png │ ├── amr.png │ ├── ape.png │ ├── apk.png │ ├── arj.png │ ├── asf.png │ ├── asm.png │ ├── asx.png │ ├── au.png │ ├── avc.png │ ├── avi.png │ ├── avs.png │ ├── bak.png │ ├── bas.png │ ├── bat.png │ ├── bmp.png │ ├── bom.png │ ├── c.png │ ├── cda.png │ ├── cdr.png │ ├── chm.png │ ├── class.png │ ├── cmd.png │ ├── com.png │ ├── cpp.png │ ├── css.png │ ├── csv.png │ ├── dart.png │ ├── dat.png │ ├── ddb.png │ ├── dif.png │ ├── divx.png │ ├── dll.png │ ├── dmg.png │ ├── doc.png │ ├── docm.png │ ├── docx.png │ ├── dot.png │ ├── dotm.png │ ├── dotx.png │ ├── dsl.png │ ├── dv.png │ ├── dvd.png │ ├── dvdaudio.png │ ├── dwg.png │ ├── dxf.png │ ├── emf.png │ ├── env.png │ ├── eot.png │ ├── eps.png │ ├── exe.png │ ├── exif.png │ ├── fakesmms.png │ ├── flc.png │ ├── fli.png │ ├── flv.png │ ├── folder.png │ ├── fon.png │ ├── font.png │ ├── for.png │ ├── fpx.png │ ├── fv.png │ ├── gif.png │ ├── github.png │ ├── gitingore.png │ ├── gitkeep.png │ ├── gz.png │ ├── h.png │ ├── hdri.png │ ├── hlp.png │ ├── hpp.png │ ├── htm.png │ ├── html.png │ ├── ico.png │ ├── ics.png │ ├── int.png │ ├── ipynb.png │ ├── iso.png │ ├── java.png │ ├── jpeg.png │ ├── jpg.png │ ├── js.png │ ├── json.png │ ├── key.png │ ├── ksp.png │ ├── lankong.png │ ├── less.png │ ├── lib.png │ ├── lic.png │ ├── license.png │ ├── log.png │ ├── lskypro.png │ ├── lst.png │ ├── lua.png │ ├── mac.png │ ├── map.png │ ├── markdown.png │ ├── md.png │ ├── mdf.png │ ├── mht.png │ ├── mhtml.png │ ├── mid.png │ ├── midi.png │ ├── mkv.png │ ├── mmf.png │ ├── mod.png │ ├── mov.png │ ├── mp2.png │ ├── mp3.png │ ├── mp4.png │ ├── mpa.png │ ├── mpe.png │ ├── mpeg.png │ ├── mpeg1.png │ ├── mpeg2.png │ ├── mpg.png │ ├── mppro.png │ ├── msg.png │ ├── mts.png │ ├── mux.png │ ├── mv.png │ ├── navi.png │ ├── obj.png │ ├── odf.png │ ├── ods.png │ ├── odt.png │ ├── ogg.png │ ├── one.png │ ├── otf.png │ ├── otp.png │ ├── ots.png │ ├── ott.png │ ├── pas.png │ ├── pcd.png │ ├── pcx.png │ ├── pdf.png │ ├── php.png │ ├── pic.png │ ├── png.png │ ├── ppt.png │ ├── pptx.png │ ├── proe.png │ ├── prt.png │ ├── psd.png │ ├── py.png │ ├── pyc.png │ ├── qiniu.png │ ├── qsv.png │ ├── qt.png │ ├── quicktime.png │ ├── ra.png │ ├── ram.png │ ├── rar.png │ ├── raw.png │ ├── rb.png │ ├── realaudio.png │ ├── rm.png │ ├── rmvb.png │ ├── rp.png │ ├── rtf.png │ ├── s48.png │ ├── sacd.png │ ├── sass.png │ ├── sch.png │ ├── scss.png │ ├── sh.png │ ├── smms.png │ ├── sql.png │ ├── stp.png │ ├── svcd.png │ ├── svg.png │ ├── swf.png │ ├── sys.png │ ├── tcyun.png │ ├── tga.png │ ├── tgz.png │ ├── tiff.png │ ├── tmp.png │ ├── ts.png │ ├── ttc.png │ ├── ttf.png │ ├── txt.png │ ├── ufo.png │ ├── unknown.png │ ├── upyun.png │ ├── vcd.png │ ├── vob.png │ ├── voc.png │ ├── vqf.png │ ├── vue.png │ ├── wav.png │ ├── wdl.png │ ├── webm.png │ ├── webp.png │ ├── wki.png │ ├── wma.png │ ├── wmf.png │ ├── wmv.png │ ├── wmvhd.png │ ├── woff.png │ ├── woff2.png │ ├── wps.png │ ├── wpt.png │ ├── x_t.png │ ├── xls.png │ ├── xlsm.png │ ├── xlsx.png │ ├── xlt.png │ ├── xltm.png │ ├── xltx.png │ ├── xmind.png │ ├── xml.png │ ├── xv.png │ ├── xvid.png │ ├── yaml.png │ ├── yml.png │ ├── z.png │ └── zip.png ├── images │ ├── alist.png │ ├── aws_s3.png │ ├── empty.png │ ├── emptydir.png │ ├── ftp.png │ ├── ftp_back.png │ ├── githubrepo.png │ └── webdav.png ├── launch_pic.png ├── menubar.png ├── roundLogo.png └── validateImage │ └── PicHoroValidate.jpeg ├── ios └── Flutter │ └── flutter_export_environment.sh ├── lib ├── album │ ├── action_button.dart │ ├── album_page.dart │ ├── album_sql.dart │ ├── empty_database.dart │ └── network_pic_preview.dart ├── api │ ├── alist_api.dart │ ├── aliyun_api.dart │ ├── api.dart │ ├── aws_api.dart │ ├── ftp_api.dart │ ├── github_api.dart │ ├── imgur_api.dart │ ├── lskypro_api.dart │ ├── qiniu_api.dart │ ├── smms_api.dart │ ├── tencent_api.dart │ ├── upyun_api.dart │ └── webdav_api.dart ├── configure_page │ ├── common_configure │ │ ├── common_configure.dart │ │ ├── compress_configure.dart │ │ ├── picture_host_import_qr.dart │ │ ├── rename_uploaded_file.dart │ │ ├── select_default_picture_host.dart │ │ └── select_link_format.dart │ ├── configure_page.dart │ ├── logger │ │ └── logs.dart │ └── others │ │ ├── author.dart │ │ ├── select_theme.dart │ │ ├── theme_data.dart │ │ └── update_log.dart ├── main.dart ├── pages │ ├── home_page.dart │ ├── pichoro_app.dart │ └── upload_helper │ │ ├── home_page_uploadlist.dart │ │ ├── upload_request.dart │ │ ├── upload_status.dart │ │ ├── upload_task.dart │ │ └── upload_utils.dart ├── picture_host_configure │ ├── configure_page │ │ ├── alist_configure.dart │ │ ├── aliyun_configure.dart │ │ ├── aws_configure.dart │ │ ├── configure_export.dart │ │ ├── ftp_configure.dart │ │ ├── github_configure.dart │ │ ├── imgur_configure.dart │ │ ├── lskypro_configure.dart │ │ ├── qiniu_configure.dart │ │ ├── smms_configure.dart │ │ ├── tencent_configure.dart │ │ ├── upyun_configure.dart │ │ └── webdav_configure.dart │ ├── configure_store │ │ ├── configure_store_edit_page │ │ │ ├── alist_configure_store_edit.dart │ │ │ ├── aliyun_configure_store_edit.dart │ │ │ ├── aws_configure_store_edit.dart │ │ │ ├── configure_store_edit_export.dart │ │ │ ├── ftp_configure_store_edit.dart │ │ │ ├── github_configure_store_edit.dart │ │ │ ├── imgur_configure_store_edit.dart │ │ │ ├── lskypro_configure_store_edit.dart │ │ │ ├── qiniu_configure_store_edit.dart │ │ │ ├── smms_configure_store_edit.dart │ │ │ ├── tencent_configure_store_edit.dart │ │ │ ├── upyun_configure_store_edit.dart │ │ │ └── webdav_configure_store_edit.dart │ │ ├── configure_store_file.dart │ │ ├── configure_store_page.dart │ │ └── configure_template.dart │ └── default_picture_host_select.dart ├── picture_host_manage │ ├── alist │ │ ├── alist_bucket_information_page.dart │ │ ├── alist_bucket_list_page.dart │ │ ├── alist_file_explorer.dart │ │ └── alist_file_information_page.dart │ ├── aliyun │ │ ├── aliyun_bucket_information_page.dart │ │ ├── aliyun_bucket_list_page.dart │ │ ├── aliyun_file_explorer.dart │ │ ├── aliyun_file_information_page.dart │ │ └── aliyun_new_bucket_configure.dart │ ├── aws │ │ ├── aws_bucket_list_page.dart │ │ ├── aws_file_explorer.dart │ │ ├── aws_file_information_page.dart │ │ └── aws_new_bucket_configure.dart │ ├── common │ │ ├── base_file_explorer_page.dart │ │ ├── base_manage_api.dart │ │ ├── base_up_down_load_manage_page.dart │ │ ├── build_bottom_widget.dart │ │ ├── common_widget.dart │ │ ├── download │ │ │ ├── common_service │ │ │ │ ├── base_download_manager.dart │ │ │ │ ├── base_download_request.dart │ │ │ │ ├── base_download_status.dart │ │ │ │ └── base_download_task.dart │ │ │ └── managers │ │ │ │ ├── alist_download_manager.dart │ │ │ │ ├── aliyun_download_manager.dart │ │ │ │ ├── aws_download_manager.dart │ │ │ │ ├── github_download_manager.dart │ │ │ │ ├── imgur_download_manager.dart │ │ │ │ ├── lskypro_download_manager.dart │ │ │ │ ├── qiniu_download_manager.dart │ │ │ │ ├── sftp_download_manager.dart │ │ │ │ ├── smms_download_manager.dart │ │ │ │ ├── tencent_downloade_manager.dart │ │ │ │ ├── upyun_download_manager.dart │ │ │ │ └── webdav_download_manager.dart │ │ ├── file_explorer │ │ │ ├── local_file_explorer.dart │ │ │ ├── local_image_preview.dart │ │ │ ├── md_preview.dart │ │ │ └── pdf_viewer.dart │ │ ├── file_list_widget.dart │ │ ├── info_page_utils.dart │ │ ├── loading_state.dart │ │ ├── new_folder_widgets.dart │ │ ├── rename_dialog_widgets.dart │ │ └── upload │ │ │ ├── common_service │ │ │ ├── base_upload_manager.dart │ │ │ ├── base_upload_request.dart │ │ │ └── base_upload_task.dart │ │ │ └── managers │ │ │ ├── alist_upload_manager.dart │ │ │ ├── aliyun_upload_manager.dart │ │ │ ├── aws_upload_manager.dart │ │ │ ├── github_upload_manager.dart │ │ │ ├── imgur_upload_manager.dart │ │ │ ├── lskypro_upload_manager.dart │ │ │ ├── qiniu_upload_manager.dart │ │ │ ├── sftp_upload_manager.dart │ │ │ ├── smms_upload_manager.dart │ │ │ ├── tencent_upload_manager.dart │ │ │ ├── upyun_upload_manager.dart │ │ │ └── webdav_upload_manager.dart │ ├── ftp │ │ ├── sftp_file_explorer.dart │ │ ├── sftp_file_information_page.dart │ │ └── sftp_local_image_preview.dart │ ├── github │ │ ├── github_file_explorer.dart │ │ ├── github_file_information_page.dart │ │ ├── github_manage_home_page.dart │ │ ├── github_new_repo_configure.dart │ │ ├── github_repo_information_page.dart │ │ └── github_repos_list_page.dart │ ├── imgur │ │ ├── imgur_file_explorer.dart │ │ ├── imgur_file_information_page.dart │ │ ├── imgur_login.dart │ │ └── imgur_token_manage_page.dart │ ├── lskypro │ │ ├── lskypro_file_explorer.dart │ │ ├── lskypro_file_information_page.dart │ │ └── lskypro_manage_home_page.dart │ ├── manage_api │ │ ├── alist_manage_api.dart │ │ ├── aliyun_manage_api.dart │ │ ├── aws_manage_api.dart │ │ ├── ftp_manage_api.dart │ │ ├── github_manage_api.dart │ │ ├── imgur_manage_api.dart │ │ ├── lskypro_manage_api.dart │ │ ├── qiniu_manage_api.dart │ │ ├── smms_manage_api.dart │ │ ├── tencent_manage_api.dart │ │ ├── upyun_manage_api.dart │ │ └── webdav_manage_api.dart │ ├── picture_host_manage_entry.dart │ ├── qiniu │ │ ├── qiniu_bucket_domain_area_set.dart │ │ ├── qiniu_bucket_list_page.dart │ │ ├── qiniu_file_explorer.dart │ │ ├── qiniu_file_information_page.dart │ │ └── qiniu_new_bucket_configure.dart │ ├── smms │ │ ├── smms_file_explorer.dart │ │ ├── smms_file_information_page.dart │ │ └── smms_manage_home_page.dart │ ├── tencent │ │ ├── tencent_bucket_information_page.dart │ │ ├── tencent_bucket_list_page.dart │ │ ├── tencent_file_explorer.dart │ │ ├── tencent_file_information_page.dart │ │ └── tencent_new_bucket_configure.dart │ ├── upyun │ │ ├── upyun_bucket_information_page.dart │ │ ├── upyun_bucket_list_page.dart │ │ ├── upyun_file_explorer.dart │ │ ├── upyun_file_information_page.dart │ │ ├── upyun_login.dart │ │ ├── upyun_new_bucket_configure.dart │ │ └── upyun_token_manage_page.dart │ └── webdav │ │ ├── webdav_file_explorer.dart │ │ ├── webdav_file_information_page.dart │ │ └── webdav_pic_preview.dart ├── router │ ├── application.dart │ ├── router_handler.dart │ └── routers.dart ├── utils │ ├── analytics_service.dart │ ├── clear_cache.dart │ ├── common_functions.dart │ ├── deleter.dart │ ├── dio_proxy_adapter.dart │ ├── event_bus_utils.dart │ ├── global.dart │ ├── image_compressor.dart │ ├── permission.dart │ ├── picbed │ │ └── upyun.dart │ ├── system_font_provider.dart │ ├── theme_provider.dart │ └── uploader.dart └── widgets │ ├── common_widgets.dart │ ├── configure_widgets.dart │ ├── custom_speed_dial.dart │ ├── load_state_change.dart │ ├── net_loading_dialog.dart │ └── web_view.dart ├── pubspec.lock ├── pubspec.yaml ├── supported_format.md └── version.json /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: " Bug Report" 2 | description: 提交一个问题 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | assignees: 6 | - Kuingsmile 7 | labels: 8 | - bug 9 | body: 10 | - type: markdown 11 | attributes: 12 | value: |+ 13 | ## PicHoro Bug Report 模板 14 | 15 | 请依照该模板来提交,否则将会被关闭。 16 | 17 | - type: input 18 | id: version 19 | attributes: 20 | label: PicHoro的版本 21 | placeholder: 例如 V1.8.3 22 | validations: 23 | required: true 24 | - type: input 25 | id: platform 26 | attributes: 27 | label: 系统信息 28 | placeholder: 例如 ViVo X60 Pro 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: reproduce 33 | attributes: 34 | label: 问题重现 | Bug reproduce 35 | description: 请复述Bug重现流程 36 | validations: 37 | required: true 38 | - type: textarea 39 | id: log 40 | attributes: 41 | label: 相关日志 | Logs 42 | description: 请附上 PicHoro 的相关报错日志(用文本的形式)。报错日志可以在 PicHoro 设置 -> 软件日志 后右上角导出到剪贴板和本地文件。 43 | - type: markdown 44 | attributes: 45 | value: | 46 | 最后,喜欢 PicHoro 的话不妨给它点个 star~ 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: 功能请求 3 | title: "[Feature]: " 4 | labels: ["feature request"] 5 | assignees: 6 | - Kuingsmile 7 | labels: 8 | - enhancement 9 | body: 10 | - type: markdown 11 | attributes: 12 | value: |+ 13 | ## PicHoro Feature 模板 14 | 15 | 请依照该模板来提交,否则将会被关闭。 16 | 17 | - type: input 18 | id: version 19 | attributes: 20 | label: PicHoro的版本 21 | placeholder: 例如 V1.8.3 22 | validations: 23 | required: true 24 | - type: input 25 | id: platform 26 | attributes: 27 | label: 系统信息 28 | placeholder: 例如 ViVo X60 Pro 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: reproduce 33 | attributes: 34 | label: 功能请求 35 | description: 详细描述你所预想的功能或者是现有功能的改进 36 | validations: 37 | required: true 38 | - type: markdown 39 | attributes: 40 | value: | 41 | 最后,喜欢 PicHoro 的话不妨给它点个 star~ 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | # Android related 46 | **/android/**/gradle-wrapper.jar 47 | **/android/.gradle 48 | **/android/captures/ 49 | **/android/gradlew 50 | **/android/gradlew.bat 51 | **/android/local.properties 52 | **/android/**/GeneratedPluginRegistrant.java 53 | **/android/key.properties 54 | *.jks 55 | 56 | # 针对flutter当做module集成进现有项目时,.android目录下的文件 57 | **/.android/**/gradle-wrapper.jar 58 | **/.android/.gradle 59 | **/.android/captures/ 60 | **/.android/gradlew 61 | **/.android/gradlew.bat 62 | **/.android/local.properties 63 | **/.android/**/GeneratedPluginRegistrant.java 64 | **/.android/key.properties 65 | 66 | 67 | # iOS/XCode related 68 | **/ios/**/*.mode1v3 69 | **/ios/**/*.mode2v3 70 | **/ios/**/*.moved-aside 71 | **/ios/**/*.pbxuser 72 | **/ios/**/*.perspectivev3 73 | **/ios/**/*sync/ 74 | **/ios/**/.sconsign.dblite 75 | **/ios/**/.tags* 76 | **/ios/**/.vagrant/ 77 | **/ios/**/DerivedData/ 78 | **/ios/**/Icon? 79 | **/ios/**/Pods/ 80 | **/ios/**/.symlinks/ 81 | **/ios/**/profile 82 | **/ios/**/xcuserdata 83 | **/ios/.generated/ 84 | **/ios/Flutter/App.framework 85 | **/ios/Flutter/Flutter.framework 86 | **/ios/Flutter/Generated.xcconfig 87 | **/ios/Flutter/app.flx 88 | **/ios/Flutter/app.zip 89 | **/ios/Flutter/flutter_assets/ 90 | **/ios/ServiceDefinitions.json 91 | **/ios/Runner/GeneratedPluginRegistrant.* 92 | 93 | # 针对flutter当做module集成进现有项目时,.ios目录下的文件 94 | **/.ios/**/*.mode1v3 95 | **/.ios/**/*.mode2v3 96 | **/.ios/**/*.moved-aside 97 | **/.ios/**/*.pbxuser 98 | **/.ios/**/*.perspectivev3 99 | **/.ios/**/*sync/ 100 | **/.ios/**/.sconsign.dblite 101 | **/.ios/**/.tags* 102 | **/.ios/**/.vagrant/ 103 | **/.ios/**/DerivedData/ 104 | **/.ios/**/Icon? 105 | **/.ios/**/Pods/ 106 | **/.ios/**/.symlinks/ 107 | **/.ios/**/profile 108 | **/.ios/**/xcuserdata 109 | **/.ios/.generated/ 110 | **/.ios/Flutter/App.framework 111 | **/.ios/Flutter/Flutter.framework 112 | **/.ios/Flutter/Generated.xcconfig 113 | **/.ios/Flutter/app.flx 114 | **/.ios/Flutter/app.zip 115 | **/.ios/Flutter/flutter_assets/ 116 | **/.ios/ServiceDefinitions.json 117 | **/.ios/Runner/GeneratedPluginRegistrant.* 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present, Kuingsmile 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /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 | key.jks -------------------------------------------------------------------------------- /android/app/.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 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | def keystorePropertiesFile = rootProject.file("key.properties") 26 | def keystoreProperties = new Properties() 27 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 28 | 29 | android { 30 | //compileSdkVersion flutter.compileSdkVersion 31 | namespace "com.example.horopic" 32 | compileSdkVersion 35 33 | //ndkVersion flutter.ndkVersion 34 | ndkVersion "27.0.12077973" 35 | 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_21 38 | targetCompatibility JavaVersion.VERSION_21 39 | } 40 | 41 | kotlinOptions { 42 | jvmTarget = '21' 43 | } 44 | 45 | sourceSets { 46 | main.java.srcDirs += 'src/main/kotlin' 47 | } 48 | 49 | defaultConfig { 50 | applicationId "com.example.horopic" 51 | // You can update the following values to match your application needs. 52 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 53 | minSdkVersion 24 54 | targetSdkVersion flutter.targetSdkVersion 55 | versionCode flutterVersionCode.toInteger() 56 | versionName flutterVersionName 57 | multiDexEnabled true 58 | } 59 | 60 | signingConfigs { 61 | release { 62 | keyAlias keystoreProperties['keyAlias'] 63 | keyPassword keystoreProperties['keyPassword'] 64 | storeFile file(keystoreProperties['storeFile']) 65 | storePassword keystoreProperties['storePassword'] 66 | } 67 | } 68 | buildTypes { 69 | release { 70 | signingConfig signingConfigs.release 71 | 72 | minifyEnabled false 73 | shrinkResources false 74 | 75 | // proguard文件是混淆 76 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 77 | } 78 | } 79 | } 80 | 81 | flutter { 82 | source '../..' 83 | } 84 | 85 | dependencies { 86 | implementation 'com.android.support:multidex:1.0.3' 87 | } 88 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/horopic/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.horopic 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-hdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-hdpi/favicon.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-hdpi/favicon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-hdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launch_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-hdpi/launch_pic.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-mdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-mdpi/favicon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-mdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launch_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-mdpi/launch_pic.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xhdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xhdpi/favicon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launch_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xhdpi/launch_pic.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxhdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxhdpi/favicon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launch_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxhdpi/launch_pic.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxxhdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxxhdpi/favicon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launch_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/android/app/src/main/res/mipmap-xxxhdpi/launch_pic.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | gradle.projectsEvaluated { 3 | tasks.withType(JavaCompile) { 4 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 5 | } 6 | } 7 | 8 | repositories { 9 | google() 10 | mavenCentral() 11 | jcenter() 12 | //maven { url 'https://maven.aliyun.com/repository/google' } 13 | //maven { url 'https://maven.aliyun.com/repository/jcenter' } 14 | // maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } 15 | } 16 | 17 | subprojects { 18 | afterEvaluate { project -> 19 | if (project.hasProperty('android')) { 20 | project.android { 21 | if (namespace == null) { 22 | namespace project.group 23 | } 24 | } 25 | } 26 | if (project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library")) { 27 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinTask -> 28 | def sourceCompatibility = project.android.compileOptions.sourceCompatibility?.toString() ?: "1.8" 29 | def jvmTarget = kotlinTask.kotlinOptions.jvmTarget ?: "" 30 | if (sourceCompatibility != jvmTarget) { 31 | kotlinTask.kotlinOptions.jvmTarget = sourceCompatibility 32 | println "INFO: Updated jvmTarget for ${project.name} to ${sourceCompatibility}" 33 | } 34 | } 35 | } 36 | } 37 | } 38 | // 修复由于高版本导致namespace检测为空的问题,没遇到可不添加 39 | 40 | } 41 | 42 | rootProject.buildDir = '../build' 43 | subprojects { 44 | project.buildDir = "${rootProject.buildDir}/${project.name}" 45 | } 46 | subprojects { 47 | project.evaluationDependsOn(':app') 48 | } 49 | 50 | tasks.register("clean", Delete) { 51 | delete rootProject.buildDir 52 | } 53 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4608m 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 25 01:27:53 PDT 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /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.8.0" apply false 22 | id "org.jetbrains.kotlin.android" version "2.1.0" apply false 23 | id "com.google.protobuf" version "0.9.1" apply false 24 | } 25 | 26 | include ":app" -------------------------------------------------------------------------------- /assets/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/app_icon.png -------------------------------------------------------------------------------- /assets/app_icon_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/app_icon_large.png -------------------------------------------------------------------------------- /assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /assets/icons/3g2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/3g2.png -------------------------------------------------------------------------------- /assets/icons/3gp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/3gp.png -------------------------------------------------------------------------------- /assets/icons/7z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/7z.png -------------------------------------------------------------------------------- /assets/icons/Imgur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/Imgur.png -------------------------------------------------------------------------------- /assets/icons/_blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/_blank.png -------------------------------------------------------------------------------- /assets/icons/_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/_page.png -------------------------------------------------------------------------------- /assets/icons/aac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/aac.png -------------------------------------------------------------------------------- /assets/icons/accdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/accdb.png -------------------------------------------------------------------------------- /assets/icons/adt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/adt.png -------------------------------------------------------------------------------- /assets/icons/ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ai.png -------------------------------------------------------------------------------- /assets/icons/aiff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/aiff.png -------------------------------------------------------------------------------- /assets/icons/aliyun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/aliyun.png -------------------------------------------------------------------------------- /assets/icons/aly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/aly.png -------------------------------------------------------------------------------- /assets/icons/amiga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/amiga.png -------------------------------------------------------------------------------- /assets/icons/amr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/amr.png -------------------------------------------------------------------------------- /assets/icons/ape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ape.png -------------------------------------------------------------------------------- /assets/icons/apk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/apk.png -------------------------------------------------------------------------------- /assets/icons/arj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/arj.png -------------------------------------------------------------------------------- /assets/icons/asf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/asf.png -------------------------------------------------------------------------------- /assets/icons/asm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/asm.png -------------------------------------------------------------------------------- /assets/icons/asx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/asx.png -------------------------------------------------------------------------------- /assets/icons/au.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/au.png -------------------------------------------------------------------------------- /assets/icons/avc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/avc.png -------------------------------------------------------------------------------- /assets/icons/avi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/avi.png -------------------------------------------------------------------------------- /assets/icons/avs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/avs.png -------------------------------------------------------------------------------- /assets/icons/bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/bak.png -------------------------------------------------------------------------------- /assets/icons/bas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/bas.png -------------------------------------------------------------------------------- /assets/icons/bat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/bat.png -------------------------------------------------------------------------------- /assets/icons/bmp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/bmp.png -------------------------------------------------------------------------------- /assets/icons/bom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/bom.png -------------------------------------------------------------------------------- /assets/icons/c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/c.png -------------------------------------------------------------------------------- /assets/icons/cda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/cda.png -------------------------------------------------------------------------------- /assets/icons/cdr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/cdr.png -------------------------------------------------------------------------------- /assets/icons/chm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/chm.png -------------------------------------------------------------------------------- /assets/icons/class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/class.png -------------------------------------------------------------------------------- /assets/icons/cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/cmd.png -------------------------------------------------------------------------------- /assets/icons/com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/com.png -------------------------------------------------------------------------------- /assets/icons/cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/cpp.png -------------------------------------------------------------------------------- /assets/icons/css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/css.png -------------------------------------------------------------------------------- /assets/icons/csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/csv.png -------------------------------------------------------------------------------- /assets/icons/dart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dart.png -------------------------------------------------------------------------------- /assets/icons/dat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dat.png -------------------------------------------------------------------------------- /assets/icons/ddb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ddb.png -------------------------------------------------------------------------------- /assets/icons/dif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dif.png -------------------------------------------------------------------------------- /assets/icons/divx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/divx.png -------------------------------------------------------------------------------- /assets/icons/dll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dll.png -------------------------------------------------------------------------------- /assets/icons/dmg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dmg.png -------------------------------------------------------------------------------- /assets/icons/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/doc.png -------------------------------------------------------------------------------- /assets/icons/docm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/docm.png -------------------------------------------------------------------------------- /assets/icons/docx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/docx.png -------------------------------------------------------------------------------- /assets/icons/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dot.png -------------------------------------------------------------------------------- /assets/icons/dotm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dotm.png -------------------------------------------------------------------------------- /assets/icons/dotx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dotx.png -------------------------------------------------------------------------------- /assets/icons/dsl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dsl.png -------------------------------------------------------------------------------- /assets/icons/dv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dv.png -------------------------------------------------------------------------------- /assets/icons/dvd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dvd.png -------------------------------------------------------------------------------- /assets/icons/dvdaudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dvdaudio.png -------------------------------------------------------------------------------- /assets/icons/dwg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dwg.png -------------------------------------------------------------------------------- /assets/icons/dxf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/dxf.png -------------------------------------------------------------------------------- /assets/icons/emf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/emf.png -------------------------------------------------------------------------------- /assets/icons/env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/env.png -------------------------------------------------------------------------------- /assets/icons/eot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/eot.png -------------------------------------------------------------------------------- /assets/icons/eps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/eps.png -------------------------------------------------------------------------------- /assets/icons/exe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/exe.png -------------------------------------------------------------------------------- /assets/icons/exif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/exif.png -------------------------------------------------------------------------------- /assets/icons/fakesmms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/fakesmms.png -------------------------------------------------------------------------------- /assets/icons/flc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/flc.png -------------------------------------------------------------------------------- /assets/icons/fli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/fli.png -------------------------------------------------------------------------------- /assets/icons/flv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/flv.png -------------------------------------------------------------------------------- /assets/icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/folder.png -------------------------------------------------------------------------------- /assets/icons/fon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/fon.png -------------------------------------------------------------------------------- /assets/icons/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/font.png -------------------------------------------------------------------------------- /assets/icons/for.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/for.png -------------------------------------------------------------------------------- /assets/icons/fpx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/fpx.png -------------------------------------------------------------------------------- /assets/icons/fv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/fv.png -------------------------------------------------------------------------------- /assets/icons/gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/gif.png -------------------------------------------------------------------------------- /assets/icons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/github.png -------------------------------------------------------------------------------- /assets/icons/gitingore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/gitingore.png -------------------------------------------------------------------------------- /assets/icons/gitkeep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/gitkeep.png -------------------------------------------------------------------------------- /assets/icons/gz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/gz.png -------------------------------------------------------------------------------- /assets/icons/h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/h.png -------------------------------------------------------------------------------- /assets/icons/hdri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/hdri.png -------------------------------------------------------------------------------- /assets/icons/hlp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/hlp.png -------------------------------------------------------------------------------- /assets/icons/hpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/hpp.png -------------------------------------------------------------------------------- /assets/icons/htm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/htm.png -------------------------------------------------------------------------------- /assets/icons/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/html.png -------------------------------------------------------------------------------- /assets/icons/ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ico.png -------------------------------------------------------------------------------- /assets/icons/ics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ics.png -------------------------------------------------------------------------------- /assets/icons/int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/int.png -------------------------------------------------------------------------------- /assets/icons/ipynb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ipynb.png -------------------------------------------------------------------------------- /assets/icons/iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/iso.png -------------------------------------------------------------------------------- /assets/icons/java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/java.png -------------------------------------------------------------------------------- /assets/icons/jpeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/jpeg.png -------------------------------------------------------------------------------- /assets/icons/jpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/jpg.png -------------------------------------------------------------------------------- /assets/icons/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/js.png -------------------------------------------------------------------------------- /assets/icons/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/json.png -------------------------------------------------------------------------------- /assets/icons/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/key.png -------------------------------------------------------------------------------- /assets/icons/ksp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ksp.png -------------------------------------------------------------------------------- /assets/icons/lankong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/lankong.png -------------------------------------------------------------------------------- /assets/icons/less.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/less.png -------------------------------------------------------------------------------- /assets/icons/lib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/lib.png -------------------------------------------------------------------------------- /assets/icons/lic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/lic.png -------------------------------------------------------------------------------- /assets/icons/license.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/license.png -------------------------------------------------------------------------------- /assets/icons/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/log.png -------------------------------------------------------------------------------- /assets/icons/lskypro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/lskypro.png -------------------------------------------------------------------------------- /assets/icons/lst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/lst.png -------------------------------------------------------------------------------- /assets/icons/lua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/lua.png -------------------------------------------------------------------------------- /assets/icons/mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mac.png -------------------------------------------------------------------------------- /assets/icons/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/map.png -------------------------------------------------------------------------------- /assets/icons/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/markdown.png -------------------------------------------------------------------------------- /assets/icons/md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/md.png -------------------------------------------------------------------------------- /assets/icons/mdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mdf.png -------------------------------------------------------------------------------- /assets/icons/mht.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mht.png -------------------------------------------------------------------------------- /assets/icons/mhtml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mhtml.png -------------------------------------------------------------------------------- /assets/icons/mid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mid.png -------------------------------------------------------------------------------- /assets/icons/midi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/midi.png -------------------------------------------------------------------------------- /assets/icons/mkv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mkv.png -------------------------------------------------------------------------------- /assets/icons/mmf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mmf.png -------------------------------------------------------------------------------- /assets/icons/mod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mod.png -------------------------------------------------------------------------------- /assets/icons/mov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mov.png -------------------------------------------------------------------------------- /assets/icons/mp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mp2.png -------------------------------------------------------------------------------- /assets/icons/mp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mp3.png -------------------------------------------------------------------------------- /assets/icons/mp4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mp4.png -------------------------------------------------------------------------------- /assets/icons/mpa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mpa.png -------------------------------------------------------------------------------- /assets/icons/mpe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mpe.png -------------------------------------------------------------------------------- /assets/icons/mpeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mpeg.png -------------------------------------------------------------------------------- /assets/icons/mpeg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mpeg1.png -------------------------------------------------------------------------------- /assets/icons/mpeg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mpeg2.png -------------------------------------------------------------------------------- /assets/icons/mpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mpg.png -------------------------------------------------------------------------------- /assets/icons/mppro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mppro.png -------------------------------------------------------------------------------- /assets/icons/msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/msg.png -------------------------------------------------------------------------------- /assets/icons/mts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mts.png -------------------------------------------------------------------------------- /assets/icons/mux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mux.png -------------------------------------------------------------------------------- /assets/icons/mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/mv.png -------------------------------------------------------------------------------- /assets/icons/navi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/navi.png -------------------------------------------------------------------------------- /assets/icons/obj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/obj.png -------------------------------------------------------------------------------- /assets/icons/odf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/odf.png -------------------------------------------------------------------------------- /assets/icons/ods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ods.png -------------------------------------------------------------------------------- /assets/icons/odt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/odt.png -------------------------------------------------------------------------------- /assets/icons/ogg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ogg.png -------------------------------------------------------------------------------- /assets/icons/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/one.png -------------------------------------------------------------------------------- /assets/icons/otf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/otf.png -------------------------------------------------------------------------------- /assets/icons/otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/otp.png -------------------------------------------------------------------------------- /assets/icons/ots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ots.png -------------------------------------------------------------------------------- /assets/icons/ott.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ott.png -------------------------------------------------------------------------------- /assets/icons/pas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/pas.png -------------------------------------------------------------------------------- /assets/icons/pcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/pcd.png -------------------------------------------------------------------------------- /assets/icons/pcx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/pcx.png -------------------------------------------------------------------------------- /assets/icons/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/pdf.png -------------------------------------------------------------------------------- /assets/icons/php.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/php.png -------------------------------------------------------------------------------- /assets/icons/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/pic.png -------------------------------------------------------------------------------- /assets/icons/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/png.png -------------------------------------------------------------------------------- /assets/icons/ppt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ppt.png -------------------------------------------------------------------------------- /assets/icons/pptx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/pptx.png -------------------------------------------------------------------------------- /assets/icons/proe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/proe.png -------------------------------------------------------------------------------- /assets/icons/prt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/prt.png -------------------------------------------------------------------------------- /assets/icons/psd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/psd.png -------------------------------------------------------------------------------- /assets/icons/py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/py.png -------------------------------------------------------------------------------- /assets/icons/pyc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/pyc.png -------------------------------------------------------------------------------- /assets/icons/qiniu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/qiniu.png -------------------------------------------------------------------------------- /assets/icons/qsv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/qsv.png -------------------------------------------------------------------------------- /assets/icons/qt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/qt.png -------------------------------------------------------------------------------- /assets/icons/quicktime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/quicktime.png -------------------------------------------------------------------------------- /assets/icons/ra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ra.png -------------------------------------------------------------------------------- /assets/icons/ram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ram.png -------------------------------------------------------------------------------- /assets/icons/rar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/rar.png -------------------------------------------------------------------------------- /assets/icons/raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/raw.png -------------------------------------------------------------------------------- /assets/icons/rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/rb.png -------------------------------------------------------------------------------- /assets/icons/realaudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/realaudio.png -------------------------------------------------------------------------------- /assets/icons/rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/rm.png -------------------------------------------------------------------------------- /assets/icons/rmvb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/rmvb.png -------------------------------------------------------------------------------- /assets/icons/rp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/rp.png -------------------------------------------------------------------------------- /assets/icons/rtf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/rtf.png -------------------------------------------------------------------------------- /assets/icons/s48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/s48.png -------------------------------------------------------------------------------- /assets/icons/sacd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/sacd.png -------------------------------------------------------------------------------- /assets/icons/sass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/sass.png -------------------------------------------------------------------------------- /assets/icons/sch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/sch.png -------------------------------------------------------------------------------- /assets/icons/scss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/scss.png -------------------------------------------------------------------------------- /assets/icons/sh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/sh.png -------------------------------------------------------------------------------- /assets/icons/smms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/smms.png -------------------------------------------------------------------------------- /assets/icons/sql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/sql.png -------------------------------------------------------------------------------- /assets/icons/stp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/stp.png -------------------------------------------------------------------------------- /assets/icons/svcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/svcd.png -------------------------------------------------------------------------------- /assets/icons/svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/svg.png -------------------------------------------------------------------------------- /assets/icons/swf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/swf.png -------------------------------------------------------------------------------- /assets/icons/sys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/sys.png -------------------------------------------------------------------------------- /assets/icons/tcyun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/tcyun.png -------------------------------------------------------------------------------- /assets/icons/tga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/tga.png -------------------------------------------------------------------------------- /assets/icons/tgz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/tgz.png -------------------------------------------------------------------------------- /assets/icons/tiff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/tiff.png -------------------------------------------------------------------------------- /assets/icons/tmp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/tmp.png -------------------------------------------------------------------------------- /assets/icons/ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ts.png -------------------------------------------------------------------------------- /assets/icons/ttc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ttc.png -------------------------------------------------------------------------------- /assets/icons/ttf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ttf.png -------------------------------------------------------------------------------- /assets/icons/txt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/txt.png -------------------------------------------------------------------------------- /assets/icons/ufo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/ufo.png -------------------------------------------------------------------------------- /assets/icons/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/unknown.png -------------------------------------------------------------------------------- /assets/icons/upyun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/upyun.png -------------------------------------------------------------------------------- /assets/icons/vcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/vcd.png -------------------------------------------------------------------------------- /assets/icons/vob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/vob.png -------------------------------------------------------------------------------- /assets/icons/voc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/voc.png -------------------------------------------------------------------------------- /assets/icons/vqf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/vqf.png -------------------------------------------------------------------------------- /assets/icons/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/vue.png -------------------------------------------------------------------------------- /assets/icons/wav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wav.png -------------------------------------------------------------------------------- /assets/icons/wdl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wdl.png -------------------------------------------------------------------------------- /assets/icons/webm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/webm.png -------------------------------------------------------------------------------- /assets/icons/webp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/webp.png -------------------------------------------------------------------------------- /assets/icons/wki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wki.png -------------------------------------------------------------------------------- /assets/icons/wma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wma.png -------------------------------------------------------------------------------- /assets/icons/wmf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wmf.png -------------------------------------------------------------------------------- /assets/icons/wmv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wmv.png -------------------------------------------------------------------------------- /assets/icons/wmvhd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wmvhd.png -------------------------------------------------------------------------------- /assets/icons/woff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/woff.png -------------------------------------------------------------------------------- /assets/icons/woff2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/woff2.png -------------------------------------------------------------------------------- /assets/icons/wps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wps.png -------------------------------------------------------------------------------- /assets/icons/wpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/wpt.png -------------------------------------------------------------------------------- /assets/icons/x_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/x_t.png -------------------------------------------------------------------------------- /assets/icons/xls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xls.png -------------------------------------------------------------------------------- /assets/icons/xlsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xlsm.png -------------------------------------------------------------------------------- /assets/icons/xlsx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xlsx.png -------------------------------------------------------------------------------- /assets/icons/xlt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xlt.png -------------------------------------------------------------------------------- /assets/icons/xltm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xltm.png -------------------------------------------------------------------------------- /assets/icons/xltx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xltx.png -------------------------------------------------------------------------------- /assets/icons/xmind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xmind.png -------------------------------------------------------------------------------- /assets/icons/xml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xml.png -------------------------------------------------------------------------------- /assets/icons/xv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xv.png -------------------------------------------------------------------------------- /assets/icons/xvid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/xvid.png -------------------------------------------------------------------------------- /assets/icons/yaml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/yaml.png -------------------------------------------------------------------------------- /assets/icons/yml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/yml.png -------------------------------------------------------------------------------- /assets/icons/z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/z.png -------------------------------------------------------------------------------- /assets/icons/zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/icons/zip.png -------------------------------------------------------------------------------- /assets/images/alist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/alist.png -------------------------------------------------------------------------------- /assets/images/aws_s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/aws_s3.png -------------------------------------------------------------------------------- /assets/images/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/empty.png -------------------------------------------------------------------------------- /assets/images/emptydir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/emptydir.png -------------------------------------------------------------------------------- /assets/images/ftp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/ftp.png -------------------------------------------------------------------------------- /assets/images/ftp_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/ftp_back.png -------------------------------------------------------------------------------- /assets/images/githubrepo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/githubrepo.png -------------------------------------------------------------------------------- /assets/images/webdav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/images/webdav.png -------------------------------------------------------------------------------- /assets/launch_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/launch_pic.png -------------------------------------------------------------------------------- /assets/menubar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/menubar.png -------------------------------------------------------------------------------- /assets/roundLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/roundLogo.png -------------------------------------------------------------------------------- /assets/validateImage/PicHoroValidate.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuingsmile/PicHoro/75e55f38c182aaee40c2206522959cdfefda4105/assets/validateImage/PicHoroValidate.jpeg -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=D:\Program\flutter" 4 | export "FLUTTER_APPLICATION_PATH=D:\githubRepo\myProject\PicHoro" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=lib\main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=2.4.0" 9 | export "FLUTTER_BUILD_NUMBER=1" 10 | export "DART_OBFUSCATION=false" 11 | export "TRACK_WIDGET_CREATION=true" 12 | export "TREE_SHAKE_ICONS=false" 13 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 14 | -------------------------------------------------------------------------------- /lib/album/action_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ActionButton extends StatelessWidget { 4 | final IconData icon; 5 | final String label; 6 | final Color color; 7 | final VoidCallback onPressed; 8 | 9 | const ActionButton({ 10 | super.key, 11 | required this.icon, 12 | required this.label, 13 | required this.color, 14 | required this.onPressed, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Column( 20 | mainAxisSize: MainAxisSize.min, 21 | children: [ 22 | SizedBox( 23 | height: 36, 24 | width: 36, 25 | child: FloatingActionButton( 26 | heroTag: label, 27 | backgroundColor: color, 28 | onPressed: onPressed, 29 | elevation: 3, 30 | child: Icon( 31 | icon, 32 | color: Colors.white, 33 | size: 24, 34 | ), 35 | ), 36 | ), 37 | const SizedBox(height: 4), 38 | Text( 39 | label, 40 | style: TextStyle( 41 | fontSize: 12, 42 | color: Theme.of(context).textTheme.bodySmall?.color, 43 | ), 44 | ), 45 | ], 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/album/network_pic_preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:extended_image/extended_image.dart'; 3 | import 'package:horopic/widgets/common_widgets.dart'; 4 | import 'package:horopic/widgets/load_state_change.dart'; 5 | import 'package:horopic/utils/common_functions.dart'; 6 | 7 | class ImagePreview extends StatefulWidget { 8 | final int index; 9 | final List images; 10 | 11 | const ImagePreview({super.key, required this.index, required this.images}); 12 | 13 | @override 14 | ImagePreviewState createState() => ImagePreviewState(); 15 | } 16 | 17 | class ImagePreviewState extends State { 18 | int _index = 0; 19 | late PageController _pageController; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | _index = widget.index; 25 | _pageController = PageController(initialPage: _index); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | appBar: AppBar( 32 | elevation: 0, 33 | centerTitle: true, 34 | leading: getLeadingIcon(context), 35 | title: titleText('图片预览'), 36 | flexibleSpace: getFlexibleSpace(context), 37 | ), 38 | body: PageView.builder( 39 | controller: _pageController, 40 | onPageChanged: (index) { 41 | setState(() { 42 | _index = index; 43 | }); 44 | }, 45 | physics: const BouncingScrollPhysics(), 46 | itemBuilder: (context, index) { 47 | return ExtendedImage.network( 48 | widget.images[index], 49 | fit: BoxFit.contain, 50 | mode: ExtendedImageMode.gesture, 51 | cache: true, 52 | loadStateChanged: (state) => defaultLoadStateChanged(state, iconSize: 60), 53 | initGestureConfigHandler: (state) { 54 | return GestureConfig( 55 | minScale: 0.9, 56 | animationMinScale: 0.7, 57 | maxScale: 3.0, 58 | animationMaxScale: 3.5, 59 | speed: 1.0, 60 | inertialSpeed: 100.0, 61 | initialScale: 1.0, 62 | inPageView: true); 63 | }, 64 | ); 65 | }, 66 | itemCount: widget.images.length, 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/api/api.dart: -------------------------------------------------------------------------------- 1 | export 'alist_api.dart'; 2 | export 'aliyun_api.dart'; 3 | export 'aws_api.dart'; 4 | export 'ftp_api.dart'; 5 | export 'github_api.dart'; 6 | export 'imgur_api.dart'; 7 | export 'lskypro_api.dart'; 8 | export 'qiniu_api.dart'; 9 | export 'smms_api.dart'; 10 | export 'tencent_api.dart'; 11 | export 'upyun_api.dart'; 12 | export 'webdav_api.dart'; 13 | -------------------------------------------------------------------------------- /lib/api/webdav_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:webdav_client/webdav_client.dart' as webdav; 3 | 4 | import 'package:horopic/utils/common_functions.dart'; 5 | import 'package:horopic/picture_host_manage/manage_api/webdav_manage_api.dart'; 6 | 7 | class WebdavImageUploadUtils { 8 | //上传接口 9 | static uploadApi({required String path, required String name, required Map configMap}) async { 10 | try { 11 | webdav.Client client = await WebdavManageAPI().getWebdavClient(); 12 | String uploadPath = configMap['uploadPath']; 13 | String customUrl = configMap['customUrl'] ?? 'None'; 14 | String webPath = configMap['webPath'] ?? 'None'; 15 | if (uploadPath == 'None') { 16 | uploadPath = '/'; 17 | } else { 18 | if (!uploadPath.startsWith('/')) { 19 | uploadPath = '/$uploadPath'; 20 | } 21 | if (!uploadPath.endsWith('/')) { 22 | uploadPath = '$uploadPath/'; 23 | } 24 | } 25 | String filePath = uploadPath + name; 26 | 27 | await client.writeFromFile(path, filePath); 28 | 29 | String returnUrl = ''; 30 | String displayUrl = ''; 31 | if (customUrl != 'None') { 32 | customUrl = customUrl.replaceAll(RegExp(r'/$'), ''); 33 | if (webPath != 'None') { 34 | webPath = webPath.replaceAll(RegExp(r'^/*'), '').replaceAll(RegExp(r'/*$'), ''); 35 | returnUrl = '$customUrl/$webPath/$name'; 36 | } else { 37 | filePath = filePath.replaceAll(RegExp(r'^/*'), ''); 38 | returnUrl = '$customUrl/$filePath'; 39 | } 40 | displayUrl = returnUrl; 41 | } else { 42 | returnUrl = configMap['host'] + filePath; 43 | displayUrl = returnUrl + generateBasicAuth(configMap['webdavusername'], configMap['password']); 44 | } 45 | 46 | String formatedURL = getFormatedUrl(returnUrl, name); 47 | Map pictureKeyMap = Map.from(configMap); 48 | pictureKeyMap['pictureKey'] = filePath; 49 | String pictureKey = jsonEncode(pictureKeyMap); 50 | 51 | return [ 52 | "success", 53 | formatedURL, 54 | returnUrl, 55 | pictureKey, 56 | displayUrl, 57 | ]; 58 | } catch (e) { 59 | flogErr( 60 | e, 61 | { 62 | 'path': path, 63 | 'name': name, 64 | }, 65 | "WebdavImageUploadUtils", 66 | "uploadApi"); 67 | 68 | return ["failed"]; 69 | } 70 | } 71 | 72 | static deleteApi({required Map deleteMap, required Map configMap}) async { 73 | try { 74 | Map configMapFromPictureKey = jsonDecode(deleteMap['pictureKey']); 75 | 76 | String host = configMapFromPictureKey['host']; 77 | String webdavusername = configMapFromPictureKey['webdavusername']; 78 | String password = configMapFromPictureKey['password']; 79 | 80 | webdav.Client client = webdav.newClient( 81 | host, 82 | user: webdavusername, 83 | password: password, 84 | ); 85 | client.setHeaders({'accept-charset': 'utf-8'}); 86 | client.setConnectTimeout(30000); 87 | client.setSendTimeout(30000); 88 | client.setReceiveTimeout(30000); 89 | await client.remove(configMapFromPictureKey['pictureKey']); 90 | 91 | return ["success"]; 92 | } catch (e) { 93 | flogErr( 94 | e, 95 | { 96 | 'deleteMap': deleteMap, 97 | 'configMap': configMap, 98 | }, 99 | "WebdavImageUploadUtils", 100 | "deleteApi"); 101 | return ["failed"]; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/configure_page/others/author.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:external_path/external_path.dart'; 4 | import 'package:extended_image/extended_image.dart'; 5 | import 'package:horopic/widgets/common_widgets.dart'; 6 | import 'package:horopic/widgets/load_state_change.dart'; 7 | import 'package:horopic/utils/common_functions.dart'; 8 | 9 | class AuthorInformation extends StatelessWidget { 10 | const AuthorInformation({super.key}); 11 | 12 | final String qrCodeUrl = 'https://pichoro.msq.pub/wechat.png'; 13 | 14 | Future _saveQRCodeToGallery(BuildContext context) async { 15 | try { 16 | var path = await ExternalPath.getExternalStoragePublicDirectory(ExternalPath.DIRECTORY_DCIM); 17 | await Dio().download(qrCodeUrl, '$path/wechat_picHoro.png'); 18 | showToast('保存成功'); 19 | } catch (e) { 20 | showToast('保存失败: ${e.toString()}'); 21 | } 22 | } 23 | 24 | void _showSaveConfirmDialog(BuildContext context) { 25 | showCupertinoAlertDialogWithConfirmFunc( 26 | context: context, 27 | title: '保存到相册', 28 | content: '是否保存到相册?', 29 | onConfirm: () { 30 | _saveQRCodeToGallery(context); 31 | }); 32 | } 33 | 34 | GestureConfig _getGestureConfig(ExtendedImageState state) { 35 | return GestureConfig( 36 | minScale: 0.9, 37 | animationMinScale: 0.7, 38 | maxScale: 3.0, 39 | animationMaxScale: 3.5, 40 | speed: 1.0, 41 | inertialSpeed: 100.0, 42 | initialScale: 1.0, 43 | inPageView: true); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Scaffold( 49 | appBar: AppBar( 50 | elevation: 0, 51 | centerTitle: true, 52 | leading: getLeadingIcon(context), 53 | title: titleText('交流群'), 54 | flexibleSpace: getFlexibleSpace(context), 55 | ), 56 | body: Center( 57 | child: ListView( 58 | padding: const EdgeInsets.all(10), 59 | children: [ 60 | const SizedBox(height: 50), 61 | GestureDetector( 62 | onTap: () => _showSaveConfirmDialog(context), 63 | child: Center( 64 | child: ExtendedImage.network( 65 | qrCodeUrl, 66 | fit: BoxFit.contain, 67 | mode: ExtendedImageMode.gesture, 68 | cache: false, 69 | loadStateChanged: (state) => defaultLoadStateChanged(state, iconSize: 60), 70 | initGestureConfigHandler: _getGestureConfig, 71 | ), 72 | )), 73 | const SizedBox(height: 15), 74 | Center( 75 | child: Text( 76 | '点击二维码保存到相册', 77 | style: TextStyle(color: Colors.grey[600]), 78 | ), 79 | ), 80 | ], 81 | )), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/configure_page/others/theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Theme color definitions 4 | const int lightPrimaryValue = 0xFF4596EB; 5 | const int darkPrimaryValue = 0xFF111213; 6 | const int greenPrimaryValue = 0xFF4CAF50; 7 | const int purplePrimaryValue = 0xFF673AB7; 8 | const int orangePrimaryValue = 0xFFFF9800; 9 | const int pinkPrimaryValue = 0xFFF8BBD0; 10 | const int cyanPrimaryValue = 0xFF00BCD4; 11 | const int goldPrimaryValue = 0xFFFFC107; 12 | 13 | // Theme data class to hold theme information 14 | class AppThemeData { 15 | final String name; 16 | final int primaryValue; 17 | final Brightness brightness; 18 | 19 | const AppThemeData({ 20 | required this.name, 21 | required this.primaryValue, 22 | this.brightness = Brightness.light, 23 | }); 24 | } 25 | 26 | // Available themes 27 | final List availableThemes = [ 28 | const AppThemeData(name: 'Light', primaryValue: lightPrimaryValue), 29 | const AppThemeData(name: 'Dark', primaryValue: darkPrimaryValue, brightness: Brightness.dark), 30 | const AppThemeData(name: 'Green', primaryValue: greenPrimaryValue), 31 | const AppThemeData(name: 'Purple', primaryValue: purplePrimaryValue), 32 | const AppThemeData(name: 'Orange', primaryValue: orangePrimaryValue), 33 | const AppThemeData(name: 'Pink', primaryValue: pinkPrimaryValue), 34 | const AppThemeData(name: 'Cyan', primaryValue: cyanPrimaryValue), 35 | const AppThemeData(name: 'Gold', primaryValue: goldPrimaryValue), 36 | ]; 37 | 38 | // Function to generate theme data 39 | ThemeData generateThemeData(AppThemeData themeData) { 40 | return ThemeData( 41 | brightness: themeData.brightness, 42 | useMaterial3: true, 43 | colorScheme: ColorScheme.fromSeed( 44 | seedColor: Color(themeData.primaryValue), 45 | brightness: themeData.brightness, 46 | ), 47 | fontFamily: 'SystemFont', 48 | appBarTheme: AppBarTheme( 49 | backgroundColor: Color(themeData.primaryValue), 50 | ), 51 | ); 52 | } 53 | 54 | // Generated theme data instances 55 | final ThemeData lightThemeData = generateThemeData(availableThemes[0]); 56 | final ThemeData darkThemeData = generateThemeData(availableThemes[1]); 57 | final ThemeData greenThemeData = generateThemeData(availableThemes[2]); 58 | final ThemeData purpleThemeData = generateThemeData(availableThemes[3]); 59 | final ThemeData orangeThemeData = generateThemeData(availableThemes[4]); 60 | final ThemeData pinkThemeData = generateThemeData(availableThemes[5]); 61 | final ThemeData cyanThemeData = generateThemeData(availableThemes[6]); 62 | final ThemeData goldThemeData = generateThemeData(availableThemes[7]); 63 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 3 | import 'package:horopic/utils/analytics_service.dart'; 4 | import 'package:horopic/utils/system_font_provider.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import 'package:horopic/router/application.dart'; 8 | import 'package:horopic/utils/common_functions.dart'; 9 | import 'package:horopic/utils/theme_provider.dart'; 10 | 11 | void main() async { 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | try { 14 | await mainInit(); 15 | AnalyticsService().trackAppOpen(); 16 | } catch (e) { 17 | flogErr( 18 | e, 19 | {}, 20 | 'main', 21 | 'mainInit', 22 | ); 23 | } 24 | 25 | runApp(const MyApp()); 26 | } 27 | 28 | class MyApp extends StatefulWidget { 29 | const MyApp({super.key}); 30 | 31 | @override 32 | MyAppState createState() => MyAppState(); 33 | } 34 | 35 | class MyAppState extends State { 36 | @override 37 | Widget build(BuildContext context) { 38 | return MultiProvider( 39 | providers: [ 40 | ChangeNotifierProvider.value(value: AppInfoProvider()), 41 | ], 42 | child: Consumer(builder: (context, appInfo, child) { 43 | NativeFeatures.loadSystemFont(); 44 | return MaterialApp( 45 | title: 'PicHoro', 46 | debugShowCheckedModeBanner: false, 47 | theme: themeDataMap[appInfo.keyThemeColor]!, 48 | initialRoute: '/', 49 | onGenerateRoute: Application.router.generator, 50 | builder: EasyLoading.init(), 51 | ); 52 | }), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/pichoro_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:horopic/pages/home_page.dart'; 3 | import 'package:horopic/album/album_page.dart'; 4 | import 'package:horopic/configure_page/configure_page.dart'; 5 | import 'package:horopic/picture_host_manage/picture_host_manage_entry.dart'; 6 | 7 | class PicHoroAPP extends StatefulWidget { 8 | final int selectedIndex; 9 | 10 | const PicHoroAPP({super.key, this.selectedIndex = 0}); 11 | 12 | @override 13 | State createState() => _PicHoroAPPState(); 14 | } 15 | 16 | class _PicHoroAPPState extends State { 17 | late int _selectedIndex; 18 | late final PageController _pageController; 19 | 20 | final List _pages = const [ 21 | HomePage(), 22 | UploadedImages(), 23 | PsHostHomePage(), 24 | ConfigurePage(), 25 | ]; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _selectedIndex = widget.selectedIndex; 31 | _pageController = PageController(initialPage: _selectedIndex); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | _pageController.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | void _onItemTapped(int index) { 41 | if (_selectedIndex == index) return; 42 | 43 | setState(() { 44 | _selectedIndex = index; 45 | _pageController.jumpToPage(index); 46 | }); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | final theme = Theme.of(context); 52 | 53 | return Scaffold( 54 | body: PageView( 55 | controller: _pageController, 56 | physics: const NeverScrollableScrollPhysics(), 57 | children: _pages, 58 | ), 59 | bottomNavigationBar: _buildBottomNavigationBar(theme), 60 | ); 61 | } 62 | 63 | Widget _buildBottomNavigationBar(ThemeData theme) { 64 | return Container( 65 | decoration: const BoxDecoration( 66 | boxShadow: [ 67 | BoxShadow( 68 | color: Colors.black12, 69 | blurRadius: 8, 70 | offset: Offset(0, -1), 71 | ), 72 | ], 73 | ), 74 | child: BottomNavigationBar( 75 | currentIndex: _selectedIndex, 76 | showSelectedLabels: true, 77 | showUnselectedLabels: true, 78 | selectedItemColor: theme.colorScheme.primary, 79 | unselectedItemColor: Colors.grey.shade600, 80 | selectedFontSize: 12, 81 | unselectedFontSize: 10, 82 | elevation: 15, 83 | backgroundColor: theme.colorScheme.surface, 84 | onTap: _onItemTapped, 85 | type: BottomNavigationBarType.fixed, 86 | items: const [ 87 | BottomNavigationBarItem( 88 | icon: Icon(Icons.file_upload_outlined), 89 | activeIcon: Icon(Icons.file_upload), 90 | label: '上传', 91 | ), 92 | BottomNavigationBarItem( 93 | icon: Icon(Icons.photo_outlined), 94 | activeIcon: Icon(Icons.photo), 95 | label: '相册', 96 | ), 97 | BottomNavigationBarItem( 98 | icon: Icon(Icons.storage_outlined), 99 | activeIcon: Icon(Icons.storage), 100 | label: '仓库', 101 | ), 102 | BottomNavigationBarItem( 103 | icon: Icon(Icons.settings_outlined), 104 | activeIcon: Icon(Icons.settings), 105 | label: '设置', 106 | ), 107 | ], 108 | ), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/pages/upload_helper/upload_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class UploadRequest { 4 | final String path; 5 | final String name; 6 | var cancelToken = CancelToken(); 7 | 8 | UploadRequest( 9 | this.path, 10 | this.name, 11 | ); 12 | 13 | @override 14 | bool operator ==(Object other) => 15 | identical(this, other) || 16 | other is UploadRequest && runtimeType == other.runtimeType && path == other.path && name == other.name; 17 | 18 | @override 19 | int get hashCode => path.hashCode ^ name.hashCode; 20 | } 21 | -------------------------------------------------------------------------------- /lib/pages/upload_helper/upload_status.dart: -------------------------------------------------------------------------------- 1 | enum UploadStatus { queued, uploading, completed, failed, paused, canceled } 2 | 3 | extension UploadStatusExtension on UploadStatus { 4 | bool get isCompleted { 5 | switch (this) { 6 | case UploadStatus.completed: 7 | case UploadStatus.failed: 8 | case UploadStatus.canceled: 9 | return true; 10 | case UploadStatus.queued: 11 | case UploadStatus.uploading: 12 | case UploadStatus.paused: 13 | return false; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/pages/upload_helper/upload_task.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:horopic/pages/upload_helper/upload_request.dart'; 4 | import 'package:horopic/pages/upload_helper/upload_status.dart'; 5 | 6 | class UploadTask { 7 | final UploadRequest request; 8 | final ValueNotifier status = ValueNotifier(UploadStatus.queued); 9 | final ValueNotifier progress = ValueNotifier(0.0); 10 | String formattedUrl = ''; // Store the formatted URL for clipboard 11 | 12 | UploadTask(this.request); 13 | 14 | Future whenUploadComplete({Duration timeout = const Duration(hours: 2)}) { 15 | var completer = Completer(); 16 | 17 | if (status.value.isCompleted) { 18 | completer.complete(status.value); 19 | } 20 | 21 | void listener() { 22 | if (status.value.isCompleted) { 23 | completer.complete(status.value); 24 | status.removeListener(listener); 25 | } 26 | } 27 | 28 | status.addListener(listener); 29 | return completer.future.timeout(timeout); 30 | } 31 | 32 | String? getFormattedUrl() { 33 | return status.value == UploadStatus.completed ? formattedUrl : null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/picture_host_configure/configure_page/configure_export.dart: -------------------------------------------------------------------------------- 1 | export 'aliyun_configure.dart'; 2 | export 'aws_configure.dart'; 3 | export 'ftp_configure.dart'; 4 | export 'github_configure.dart'; 5 | export 'imgur_configure.dart'; 6 | export 'qiniu_configure.dart'; 7 | export 'smms_configure.dart'; 8 | export 'tencent_configure.dart'; 9 | export 'upyun_configure.dart'; 10 | export 'lskypro_configure.dart'; 11 | export 'alist_configure.dart'; 12 | export 'webdav_configure.dart'; -------------------------------------------------------------------------------- /lib/picture_host_configure/configure_store/configure_store_edit_page/configure_store_edit_export.dart: -------------------------------------------------------------------------------- 1 | export 'alist_configure_store_edit.dart'; 2 | export 'aliyun_configure_store_edit.dart'; 3 | export 'aws_configure_store_edit.dart'; 4 | export 'ftp_configure_store_edit.dart'; 5 | export 'github_configure_store_edit.dart'; 6 | export 'imgur_configure_store_edit.dart'; 7 | export 'lskypro_configure_store_edit.dart'; 8 | export 'qiniu_configure_store_edit.dart'; 9 | export 'smms_configure_store_edit.dart'; 10 | export 'tencent_configure_store_edit.dart'; 11 | export 'upyun_configure_store_edit.dart'; 12 | export 'webdav_configure_store_edit.dart'; -------------------------------------------------------------------------------- /lib/picture_host_configure/configure_store/configure_template.dart: -------------------------------------------------------------------------------- 1 | import 'package:horopic/picture_host_configure/configure_page/configure_export.dart'; 2 | 3 | class ConfigureTemplate { 4 | static String placeholder = 'undetermined'; 5 | static Map> psHostNameToTemplate = { 6 | 'lsky.pro': lskyproConfigureTemplate, 7 | 'aliyun': aliyunConfigureTemplate, 8 | 'qiniu': qiniuConfigureTemplate, 9 | 'tencent': tencentConfigureTemplate, 10 | 'upyun': upyunConfigureTemplate, 11 | 'aws': awsConfigureTemplate, 12 | 'ftp': ftpConfigureTemplate, 13 | 'github': githubConfigureTemplate, 14 | 'sm.ms': smmsConfigureTemplate, 15 | 'imgur': imgurConfigureTemplate, 16 | 'alist': alistConfigureTemplate, 17 | 'webdav': webdavConfigureTemplate, 18 | }; 19 | 20 | static List alistConfigureTemplateKeys = AlistConfigModel.keysList; 21 | static List aliyunConfigureTemplateKeys = AliyunConfigModel.keysList; 22 | static List awsConfigureTemplateKeys = AwsConfigModel.keysList; 23 | static List ftpConfigureTemplateKeys = FTPConfigModel.keysList; 24 | static List githubConfigureTemplateKeys = GithubConfigModel.keysList; 25 | static List imgurConfigureTemplateKeys = ImgurConfigModel.keysList; 26 | static List lskyproConfigureTemplateKeys = HostConfigModel.keysList; 27 | static List qiniuConfigureTemplateKeys = QiniuConfigModel.keysList; 28 | static List smmsConfigureTemplateKeys = SmmsConfigModel.keysList; 29 | static List tencentConfigureTemplateKeys = TencentConfigModel.keysList; 30 | static List upyunConfigureTemplateKeys = UpyunConfigModel.keysList; 31 | static List webdavConfigureTemplateKeys = WebdavConfigModel.keysList; 32 | 33 | static final Map alistConfigureTemplate = {for (var k in alistConfigureTemplateKeys) k: placeholder}; 34 | 35 | static final Map aliyunConfigureTemplate = { 36 | for (var k in aliyunConfigureTemplateKeys) k: placeholder 37 | }; 38 | 39 | static final Map awsConfigureTemplate = {for (var k in awsConfigureTemplateKeys) k: placeholder}; 40 | 41 | static final Map ftpConfigureTemplate = {for (var k in ftpConfigureTemplateKeys) k: placeholder}; 42 | 43 | static final Map githubConfigureTemplate = { 44 | for (var k in githubConfigureTemplateKeys) k: placeholder 45 | }; 46 | 47 | static final Map imgurConfigureTemplate = {for (var k in imgurConfigureTemplateKeys) k: placeholder}; 48 | 49 | static final Map lskyproConfigureTemplate = { 50 | for (var k in lskyproConfigureTemplateKeys) k: placeholder 51 | }; 52 | 53 | static final Map qiniuConfigureTemplate = {for (var k in qiniuConfigureTemplateKeys) k: placeholder}; 54 | 55 | static final Map smmsConfigureTemplate = {for (var k in smmsConfigureTemplateKeys) k: placeholder}; 56 | 57 | static final Map tencentConfigureTemplate = { 58 | for (var k in tencentConfigureTemplateKeys) k: placeholder 59 | }; 60 | 61 | static final Map upyunConfigureTemplate = {for (var k in upyunConfigureTemplateKeys) k: placeholder}; 62 | 63 | static final Map webdavConfigureTemplate = { 64 | for (var k in webdavConfigureTemplateKeys) k: placeholder 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /lib/picture_host_manage/aliyun/aliyun_bucket_information_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:horopic/picture_host_manage/manage_api/aliyun_manage_api.dart'; 3 | import 'package:horopic/picture_host_manage/common/info_page_utils.dart'; 4 | import 'package:horopic/utils/common_functions.dart'; 5 | import 'package:horopic/widgets/common_widgets.dart'; 6 | import 'package:horopic/widgets/custom_speed_dial.dart'; 7 | 8 | class AliyunBucketInformation extends StatefulWidget { 9 | final Map bucketMap; 10 | const AliyunBucketInformation({super.key, required this.bucketMap}); 11 | 12 | @override 13 | AliyunBucketInformationState createState() => AliyunBucketInformationState(); 14 | } 15 | 16 | class AliyunBucketInformationState extends State { 17 | @override 18 | initState() { 19 | super.initState(); 20 | } 21 | 22 | List _buildBasicInfoSection() { 23 | return [ 24 | buildInfoSection( 25 | '基本信息', 26 | [ 27 | buildInfoItem( 28 | context: context, 29 | title: '存储桶名称', 30 | value: widget.bucketMap['name'], 31 | icon: Icons.storage, 32 | copyable: true, 33 | ), 34 | const Divider(height: 1, indent: 56), 35 | buildInfoItem( 36 | context: context, 37 | title: '所属地域', 38 | value: '${widget.bucketMap['location']}(${AliyunManageAPI().areaCodeName[widget.bucketMap['location']]})', 39 | icon: Icons.location_on, 40 | ), 41 | const Divider(height: 1, indent: 56), 42 | buildInfoItem( 43 | context: context, 44 | title: '创建时间', 45 | value: widget.bucketMap['CreationDate'].substring(0, 19), 46 | icon: Icons.calendar_today, 47 | ), 48 | const Divider(height: 1, indent: 56), 49 | buildInfoItem( 50 | context: context, 51 | title: '访问域名', 52 | value: 'https://${widget.bucketMap['name']}.${widget.bucketMap['location']}.aliyuncs.com', 53 | icon: Icons.language, 54 | copyable: true, 55 | ), 56 | ], 57 | ), 58 | ]; 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return Scaffold( 64 | appBar: AppBar( 65 | elevation: 0, 66 | centerTitle: true, 67 | leading: getLeadingIcon(context), 68 | title: titleText('基本信息'), 69 | flexibleSpace: getFlexibleSpace(context), 70 | ), 71 | body: ListView( 72 | physics: const BouncingScrollPhysics(), 73 | padding: const EdgeInsets.symmetric(vertical: 16), 74 | children: [ 75 | ..._buildBasicInfoSection(), 76 | const SizedBox(height: 16), 77 | ], 78 | ), 79 | floatingActionButton: SpeedDial( 80 | icon: Icons.more_vert, 81 | activeIcon: Icons.close, 82 | children: [ 83 | SpeedDialChild( 84 | child: const Icon(Icons.content_copy), 85 | backgroundColor: Theme.of(context).primaryColor, 86 | foregroundColor: Colors.white, 87 | label: '复制存储桶名称', 88 | onTap: () => copyToClipboard(context, widget.bucketMap['name']), 89 | ), 90 | SpeedDialChild( 91 | child: const Icon(Icons.language), 92 | backgroundColor: Colors.blue, 93 | foregroundColor: Colors.white, 94 | label: '复制访问域名', 95 | onTap: () => copyToClipboard( 96 | context, 'https://${widget.bucketMap['name']}.${widget.bucketMap['location']}.aliyuncs.com'), 97 | ), 98 | ], 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/base_manage_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:crypto/crypto.dart'; 5 | import 'package:horopic/utils/common_functions.dart'; 6 | import 'package:horopic/utils/global.dart'; 7 | import 'package:path_provider/path_provider.dart'; 8 | 9 | class BaseManageApi { 10 | /// override this 11 | String configFileName() => 'alist_config.txt'; 12 | 13 | Future localFile() async { 14 | final path = await localPath(); 15 | String defaultUser = Global.getUser(); 16 | return ensureFileExists(File('$path/${defaultUser}_${configFileName()}')); 17 | } 18 | 19 | Future localPath() async { 20 | return (await getApplicationDocumentsDirectory()).path; 21 | } 22 | 23 | Future readCurrentConfig() async { 24 | try { 25 | final file = await localFile(); 26 | return await file.readAsString(); 27 | } catch (e) { 28 | return "Error"; 29 | } 30 | } 31 | 32 | Future getConfigMap() async { 33 | String configStr = await readCurrentConfig(); 34 | if (configStr == '') { 35 | return {}; 36 | } 37 | Map configMap = json.decode(configStr); 38 | return configMap; 39 | } 40 | 41 | bool isString(var variable) => variable is String; 42 | 43 | bool isFile(var variable) => variable is File; 44 | 45 | Future getContentMd5(var variable) async { 46 | if (isString(variable)) { 47 | return base64.encode(md5.convert(utf8.encode(variable)).bytes); 48 | } else if (isFile(variable)) { 49 | List bytes = await variable.readAsBytes(); 50 | return base64.encode(md5.convert(bytes).bytes); 51 | } 52 | return ""; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/common_service/base_download_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class DownloadRequest { 4 | final String url; 5 | final String path; 6 | final String? fileName; 7 | final Map? configMap; 8 | var cancelToken = CancelToken(); 9 | 10 | DownloadRequest( 11 | this.url, 12 | this.path, { 13 | this.fileName = '', 14 | this.configMap = const {}, 15 | }); 16 | 17 | @override 18 | bool operator ==(Object other) => 19 | identical(this, other) || 20 | other is DownloadRequest && 21 | runtimeType == other.runtimeType && 22 | url == other.url && 23 | path == other.path && 24 | fileName == other.fileName && 25 | configMap.toString() == other.configMap.toString(); 26 | 27 | @override 28 | int get hashCode => url.hashCode ^ path.hashCode ^ fileName.hashCode ^ configMap.toString().hashCode; 29 | } 30 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/common_service/base_download_status.dart: -------------------------------------------------------------------------------- 1 | enum DownloadStatus { queued, downloading, completed, failed, paused, canceled, uninitialized } 2 | 3 | extension DownloadStatusExtension on DownloadStatus { 4 | bool get isCompleted => 5 | this == DownloadStatus.completed || this == DownloadStatus.failed || this == DownloadStatus.canceled; 6 | } 7 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/common_service/base_download_task.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_status.dart'; 6 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_request.dart'; 7 | 8 | class DownloadTask { 9 | final DownloadRequest request; 10 | ValueNotifier status = ValueNotifier(DownloadStatus.queued); 11 | ValueNotifier progress = ValueNotifier(0); 12 | 13 | DownloadTask( 14 | this.request, 15 | ); 16 | 17 | Future whenDownloadComplete({Duration timeout = const Duration(hours: 2)}) async { 18 | var completer = Completer(); 19 | 20 | if (status.value.isCompleted) { 21 | completer.complete(status.value); 22 | } 23 | 24 | dynamic listener; 25 | listener = () { 26 | if (status.value.isCompleted) { 27 | try { 28 | completer.complete(status.value); 29 | status.removeListener(listener); 30 | } catch (e) { 31 | status.removeListener(listener); 32 | } 33 | } 34 | }; 35 | 36 | status.addListener(listener); 37 | 38 | return completer.future.timeout(timeout); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/alist_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 5 | 6 | class DownloadManager extends BaseDownloadManager { 7 | static final DownloadManager _dm = DownloadManager._internal(); 8 | 9 | DownloadManager._internal(); 10 | 11 | factory DownloadManager({int? maxConcurrentTasks}) { 12 | if (maxConcurrentTasks != null) { 13 | _dm.maxConcurrentTasks = maxConcurrentTasks; 14 | } 15 | return _dm; 16 | } 17 | 18 | @override 19 | Future> getHeaders(String url, 20 | {bool isPartial = false, int partialFileLength = 0, Map? configMap = const {}}) async { 21 | Map addition = jsonDecode(configMap!['addition']); 22 | return { 23 | if (isPartial) 'Range': 'bytes=$partialFileLength-', 24 | if (configMap['driver'] == 'BaiduNetdisk' && addition['download_api'] == 'official') 25 | "user-agent": 'pan.baidu.com', 26 | }; 27 | } 28 | 29 | @override 30 | Future download(String url, String savePath, cancelToken, {Map? configMap = const {}}) async { 31 | await processDownload(url, savePath, cancelToken, 'alist_DownloadManager', configMap: configMap); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/aliyun_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 5 | import 'package:horopic/picture_host_manage/manage_api/aliyun_manage_api.dart'; 6 | 7 | class DownloadManager extends BaseDownloadManager { 8 | static final DownloadManager _dm = DownloadManager._internal(); 9 | 10 | DownloadManager._internal(); 11 | 12 | factory DownloadManager({int? maxConcurrentTasks}) { 13 | if (maxConcurrentTasks != null) { 14 | _dm.maxConcurrentTasks = maxConcurrentTasks; 15 | } 16 | return _dm; 17 | } 18 | 19 | @override 20 | Future> getHeaders(String url, 21 | {bool isPartial = false, int partialFileLength = 0, Map? configMap = const {}}) async { 22 | String aliyunHost = url.split('/')[2]; 23 | String bucket = aliyunHost.split('.')[0]; 24 | String urlpath = url.substring(aliyunHost.length + 8); 25 | String canonicalizedResource = '/$bucket$urlpath'; 26 | String method = 'GET'; 27 | Map header = { 28 | 'Host': aliyunHost, 29 | 'Date': HttpDate.format(DateTime.now()), 30 | if (isPartial) 'Range': 'bytes=$partialFileLength-', 31 | }; 32 | String authorization = await AliyunManageAPI().aliyunAuthorization(method, canonicalizedResource, header, '', ''); 33 | header['Authorization'] = authorization; 34 | return header; 35 | } 36 | 37 | @override 38 | Future download(String url, String savePath, cancelToken, {Map? configMap = const {}}) async { 39 | await processDownload(url, savePath, cancelToken, 'aliyun_DownloadManager', configMap: configMap); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/github_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 4 | 5 | class DownloadManager extends BaseDownloadManager { 6 | static final DownloadManager _dm = DownloadManager._internal(); 7 | 8 | DownloadManager._internal(); 9 | 10 | factory DownloadManager({int? maxConcurrentTasks}) { 11 | if (maxConcurrentTasks != null) { 12 | _dm.maxConcurrentTasks = maxConcurrentTasks; 13 | } 14 | return _dm; 15 | } 16 | 17 | @override 18 | Future download(String url, String savePath, cancelToken, {Map? configMap = const {}}) async { 19 | await processDownload(url, savePath, cancelToken, 'github_DownloadManager', configMap: configMap); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/imgur_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 4 | 5 | class DownloadManager extends BaseDownloadManager { 6 | static final DownloadManager _dm = DownloadManager._internal(); 7 | 8 | DownloadManager._internal(); 9 | 10 | factory DownloadManager({int? maxConcurrentTasks}) { 11 | if (maxConcurrentTasks != null) { 12 | _dm.maxConcurrentTasks = maxConcurrentTasks; 13 | } 14 | return _dm; 15 | } 16 | 17 | @override 18 | Future download(String url, String savePath, cancelToken, {Map? configMap = const {}}) async { 19 | await processDownload(url, savePath, cancelToken, 'qiniu_DownloadManager', configMap: configMap); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/lskypro_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 4 | 5 | class DownloadManager extends BaseDownloadManager { 6 | static final DownloadManager _dm = DownloadManager._internal(); 7 | 8 | DownloadManager._internal(); 9 | 10 | factory DownloadManager({int? maxConcurrentTasks}) { 11 | if (maxConcurrentTasks != null) { 12 | _dm.maxConcurrentTasks = maxConcurrentTasks; 13 | } 14 | return _dm; 15 | } 16 | 17 | @override 18 | Future download(String url, String savePath, cancelToken, {Map? configMap = const {}}) async { 19 | await processDownload(url, savePath, cancelToken, 'lskypro_DownloadManager', configMap: configMap); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/qiniu_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 4 | 5 | class DownloadManager extends BaseDownloadManager { 6 | static final DownloadManager _dm = DownloadManager._internal(); 7 | 8 | DownloadManager._internal(); 9 | 10 | factory DownloadManager({int? maxConcurrentTasks}) { 11 | if (maxConcurrentTasks != null) { 12 | _dm.maxConcurrentTasks = maxConcurrentTasks; 13 | } 14 | return _dm; 15 | } 16 | 17 | @override 18 | Future download(String url, String savePath, cancelToken, {Map? configMap = const {}}) async { 19 | await processDownload(url, savePath, cancelToken, 'qiniu_DownloadManager', configMap: configMap); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/smms_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 6 | 7 | class DownloadManager extends BaseDownloadManager { 8 | static final DownloadManager _dm = DownloadManager._internal(); 9 | 10 | DownloadManager._internal(); 11 | 12 | factory DownloadManager({int? maxConcurrentTasks}) { 13 | if (maxConcurrentTasks != null) { 14 | _dm.maxConcurrentTasks = maxConcurrentTasks; 15 | } 16 | return _dm; 17 | } 18 | 19 | @override 20 | Future download(String url, String savePath, CancelToken cancelToken, {Map? configMap = const {}}) async { 21 | await processDownload(url, savePath, cancelToken, 'smmsDownloadManager', configMap: configMap); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/tencent_downloade_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | import 'package:horopic/picture_host_manage/manage_api/tencent_manage_api.dart'; 6 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 7 | 8 | class DownloadManager extends BaseDownloadManager { 9 | static final DownloadManager _dm = DownloadManager._internal(); 10 | 11 | DownloadManager._internal(); 12 | 13 | factory DownloadManager({int? maxConcurrentTasks}) { 14 | if (maxConcurrentTasks != null) { 15 | _dm.maxConcurrentTasks = maxConcurrentTasks; 16 | } 17 | return _dm; 18 | } 19 | 20 | @override 21 | Future> getHeaders(String url, 22 | {bool isPartial = false, int partialFileLength = 0, Map? configMap = const {}}) async { 23 | String tencentHost = url.split('/')[2]; 24 | String urlpath = url.substring(tencentHost.length + 8); 25 | Map tencentConfig = await TencentManageAPI().getConfigMap(); 26 | Map headers = { 27 | 'Host': tencentHost, 28 | if (isPartial) 'Range': 'bytes=$partialFileLength-', 29 | }; 30 | String authorization = TencentManageAPI() 31 | .tecentAuthorization('GET', urlpath, headers, tencentConfig['secretId'], tencentConfig['secretKey'], {}); 32 | headers['Authorization'] = authorization; 33 | return headers; 34 | } 35 | 36 | @override 37 | Future download(String url, String savePath, CancelToken cancelToken, {Map? configMap = const {}}) async { 38 | await processDownload(url, savePath, cancelToken, 'tencent_DownloadManager', configMap: configMap); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/upyun_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 4 | 5 | class DownloadManager extends BaseDownloadManager { 6 | static final DownloadManager _dm = DownloadManager._internal(); 7 | 8 | DownloadManager._internal(); 9 | 10 | factory DownloadManager({int? maxConcurrentTasks}) { 11 | if (maxConcurrentTasks != null) { 12 | _dm.maxConcurrentTasks = maxConcurrentTasks; 13 | } 14 | return _dm; 15 | } 16 | 17 | @override 18 | Future download(String url, String savePath, cancelToken, {Map? configMap = const {}}) async { 19 | await processDownload(url, savePath, cancelToken, 'upyun_DownloadManager', configMap: configMap); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/download/managers/webdav_download_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | import 'package:horopic/picture_host_manage/manage_api/webdav_manage_api.dart'; 6 | import 'package:horopic/utils/common_functions.dart'; 7 | import 'package:horopic/picture_host_manage/common/download/common_service/base_download_manager.dart'; 8 | 9 | class DownloadManager extends BaseDownloadManager { 10 | static final DownloadManager _dm = DownloadManager._internal(); 11 | 12 | DownloadManager._internal(); 13 | 14 | factory DownloadManager({int? maxConcurrentTasks}) { 15 | if (maxConcurrentTasks != null) { 16 | _dm.maxConcurrentTasks = maxConcurrentTasks; 17 | } 18 | return _dm; 19 | } 20 | 21 | @override 22 | Future> getHeaders(String url, 23 | {bool isPartial = false, int partialFileLength = 0, Map? configMap = const {}}) async { 24 | Map configMap = await WebdavManageAPI().getConfigMap(); 25 | return { 26 | 'Authorization': generateBasicAuth(configMap['webdavusername'], configMap['password']), 27 | 'User-Agent': 'pan.baidu.com', 28 | if (isPartial) 'Range': 'bytes=$partialFileLength-', 29 | }; 30 | } 31 | 32 | @override 33 | Future download(String url, String savePath, CancelToken cancelToken, {Map? configMap = const {}}) async { 34 | await processDownload(url, savePath, cancelToken, 'webdav_DownloadManager', configMap: configMap); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/file_explorer/local_image_preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:horopic/widgets/common_widgets.dart'; 2 | import 'package:universal_io/io.dart'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:extended_image/extended_image.dart'; 6 | 7 | import 'package:horopic/widgets/load_state_change.dart'; 8 | import 'package:horopic/utils/common_functions.dart'; 9 | 10 | class LocalImagePreview extends StatefulWidget { 11 | final int index; 12 | final List images; 13 | 14 | const LocalImagePreview({super.key, required this.index, required this.images}); 15 | 16 | @override 17 | LocalImagePreviewState createState() => LocalImagePreviewState(); 18 | } 19 | 20 | class LocalImagePreviewState extends State { 21 | int _index = 0; 22 | late PageController _pageController; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _index = widget.index; 28 | _pageController = PageController(initialPage: _index); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | elevation: 0, 36 | centerTitle: true, 37 | leading: getLeadingIcon(context), 38 | title: titleText('图片预览'), 39 | flexibleSpace: getFlexibleSpace(context), 40 | ), 41 | body: PageView.builder( 42 | controller: _pageController, 43 | onPageChanged: (index) { 44 | setState(() { 45 | _index = index; 46 | }); 47 | }, 48 | physics: const BouncingScrollPhysics(), 49 | itemBuilder: (context, index) { 50 | try { 51 | if (File(widget.images[index]).existsSync()) { 52 | return ExtendedImage.file( 53 | File(widget.images[index]), 54 | fit: BoxFit.contain, 55 | mode: ExtendedImageMode.gesture, 56 | clearMemoryCacheIfFailed: true, 57 | loadStateChanged: (state) => defaultLoadStateChanged(state, iconSize: 60), 58 | initGestureConfigHandler: (state) { 59 | return GestureConfig( 60 | minScale: 0.9, 61 | animationMinScale: 0.7, 62 | maxScale: 3.0, 63 | animationMaxScale: 3.5, 64 | speed: 1.0, 65 | inertialSpeed: 100.0, 66 | initialScale: 1.0, 67 | inPageView: true); 68 | }, 69 | ); 70 | } else { 71 | return Center( 72 | child: Column( 73 | mainAxisAlignment: MainAxisAlignment.center, 74 | children: [ 75 | Image.asset( 76 | 'assets/images/empty.png', 77 | width: 100, 78 | height: 100, 79 | ), 80 | const Text('文件不存在', style: TextStyle(fontSize: 20, color: Color.fromARGB(136, 121, 118, 118))) 81 | ], 82 | ), 83 | ); 84 | } 85 | } catch (e) { 86 | flogErr(e, {}, 'LocalImagePreviewState', 'build'); 87 | return Container(); 88 | } 89 | }, 90 | itemCount: widget.images.length, 91 | ), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/file_explorer/md_preview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_markdown/flutter_markdown.dart'; 4 | import 'package:horopic/widgets/common_widgets.dart'; 5 | 6 | import 'package:url_launcher/url_launcher.dart'; 7 | import 'package:extended_image/extended_image.dart'; 8 | import 'package:flutter_text_viewer/flutter_text_viewer.dart'; 9 | 10 | import 'package:horopic/widgets/load_state_change.dart'; 11 | import 'package:horopic/utils/common_functions.dart'; 12 | 13 | class MarkDownPreview extends StatefulWidget { 14 | final String filePath; 15 | final String fileName; 16 | const MarkDownPreview({ 17 | super.key, 18 | required this.filePath, 19 | required this.fileName, 20 | }); 21 | 22 | @override 23 | MarkDownPreviewState createState() => MarkDownPreviewState(); 24 | } 25 | 26 | class MarkDownPreviewState extends State { 27 | late Future _future; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | if (widget.filePath.split(".").last == "md") { 33 | _future = File(widget.filePath).readAsString(); 34 | } 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Scaffold( 40 | appBar: AppBar( 41 | elevation: 0, 42 | centerTitle: true, 43 | leading: getLeadingIcon(context), 44 | title: titleText(widget.fileName), 45 | flexibleSpace: getFlexibleSpace(context), 46 | ), 47 | body: widget.filePath.split(".").last == "md" 48 | ? FutureBuilder( 49 | future: _future, 50 | builder: (context, AsyncSnapshot snapshot) { 51 | if (snapshot.hasData) { 52 | return Markdown( 53 | data: snapshot.data!, 54 | selectable: true, 55 | imageBuilder: (uri, title, alt) { 56 | return ExtendedImage.network( 57 | uri.toString(), 58 | fit: BoxFit.contain, 59 | mode: ExtendedImageMode.gesture, 60 | cache: true, 61 | loadStateChanged: (state) => defaultLoadStateChanged(state, iconSize: 60), 62 | initGestureConfigHandler: (state) { 63 | return GestureConfig( 64 | minScale: 0.9, 65 | animationMinScale: 0.7, 66 | maxScale: 3.0, 67 | animationMaxScale: 3.5, 68 | speed: 1.0, 69 | inertialSpeed: 100.0, 70 | initialScale: 1.0, 71 | inPageView: true); 72 | }, 73 | ); 74 | }, 75 | onTapLink: (text, href, title) async { 76 | Uri url = Uri.parse(href!); 77 | await launchUrl(url); 78 | }, 79 | ); 80 | } else { 81 | return const Center( 82 | child: CircularProgressIndicator(), 83 | ); 84 | } 85 | }, 86 | ) 87 | : TextViewerPage( 88 | textViewer: TextViewer.file( 89 | widget.filePath, 90 | textStyle: const TextStyle( 91 | fontFamily: 'monospace', 92 | fontSize: 12.0, 93 | height: 1.4, 94 | ), 95 | ), 96 | ), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/loading_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum LoadState { loading, empty, error, success } 4 | 5 | abstract class BaseLoadingPageState extends State { 6 | LoadState? state; 7 | 8 | @override 9 | void initState() { 10 | super.initState(); 11 | state = LoadState.loading; 12 | } 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | appBar: appBar, 18 | body: buildStateWidget, 19 | ); 20 | } 21 | 22 | Widget get buildStateWidget { 23 | switch (state) { 24 | case LoadState.empty: 25 | return buildEmpty(); 26 | case LoadState.error: 27 | return buildError(); 28 | case LoadState.loading: 29 | return buildLoading(); 30 | case LoadState.success: 31 | return buildSuccess(); 32 | default: 33 | return buildError(); 34 | } 35 | } 36 | 37 | String get emptyText => '没有数据哦,点击右上角添加吧'; 38 | List get extraEmptyWidgets => []; 39 | 40 | Widget buildEmpty() { 41 | return Center( 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | children: [ 45 | Image.asset( 46 | 'assets/images/empty.png', 47 | width: 120, 48 | height: 120, 49 | ), 50 | const SizedBox(height: 20), 51 | Text(emptyText, 52 | style: TextStyle(fontSize: 18, color: Color.fromARGB(136, 121, 118, 118), fontWeight: FontWeight.w500)), 53 | ...extraEmptyWidgets, 54 | ], 55 | ), 56 | ); 57 | } 58 | 59 | String get errorText => '加载失败'; 60 | String get errorButtonText => '重新加载'; 61 | void onErrorRetry() { 62 | setState(() { 63 | state = LoadState.loading; 64 | }); 65 | // Implement your retry logic here 66 | } 67 | 68 | Widget buildError() { 69 | return Center( 70 | child: Column( 71 | mainAxisAlignment: MainAxisAlignment.center, 72 | children: [ 73 | const Icon(Icons.error_outline, size: 60, color: Colors.red), 74 | const SizedBox(height: 16), 75 | Text(errorText, 76 | style: TextStyle(fontSize: 18, color: Color.fromARGB(136, 121, 118, 118), fontWeight: FontWeight.w500)), 77 | const SizedBox(height: 16), 78 | ElevatedButton.icon( 79 | style: ElevatedButton.styleFrom( 80 | backgroundColor: Colors.blue, 81 | foregroundColor: Colors.white, 82 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), 83 | padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), 84 | ), 85 | onPressed: onErrorRetry, 86 | icon: const Icon(Icons.refresh), 87 | label: Text(errorButtonText), 88 | ) 89 | ], 90 | ), 91 | ); 92 | } 93 | 94 | Widget buildLoading() { 95 | return const Center( 96 | child: Column( 97 | mainAxisAlignment: MainAxisAlignment.center, 98 | children: [ 99 | SizedBox( 100 | width: 40, 101 | height: 40, 102 | child: CircularProgressIndicator( 103 | strokeWidth: 3, 104 | backgroundColor: Colors.transparent, 105 | valueColor: AlwaysStoppedAnimation(Colors.blue), 106 | ), 107 | ), 108 | SizedBox(height: 16), 109 | Text('加载中...', style: TextStyle(fontSize: 16, color: Colors.blue, fontWeight: FontWeight.w500)), 110 | ], 111 | ), 112 | ); 113 | } 114 | 115 | Widget buildSuccess(); 116 | AppBar get appBar; 117 | } 118 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/common_service/base_upload_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class UploadRequest { 4 | final String path; 5 | final String name; 6 | final Map configMap; 7 | var cancelToken = CancelToken(); 8 | 9 | UploadRequest( 10 | this.path, 11 | this.name, 12 | this.configMap, 13 | ); 14 | 15 | @override 16 | bool operator ==(Object other) => 17 | identical(this, other) || 18 | other is UploadRequest && 19 | runtimeType == other.runtimeType && 20 | path == other.path && 21 | name == other.name && 22 | configMap.toString() == other.configMap.toString(); 23 | 24 | @override 25 | int get hashCode => path.hashCode ^ name.hashCode ^ configMap.toString().hashCode; 26 | } 27 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/common_service/base_upload_task.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_request.dart'; 6 | import 'package:horopic/pages/upload_helper/upload_status.dart'; 7 | 8 | class UploadTask { 9 | final UploadRequest request; 10 | ValueNotifier status = ValueNotifier(UploadStatus.queued); 11 | ValueNotifier progress = ValueNotifier(0); 12 | 13 | UploadTask( 14 | this.request, 15 | ); 16 | 17 | Future whenUploadComplete({Duration timeout = const Duration(hours: 2)}) async { 18 | var completer = Completer(); 19 | 20 | if (status.value.isCompleted) { 21 | completer.complete(status.value); 22 | } 23 | 24 | dynamic listener; 25 | listener = () { 26 | if (status.value.isCompleted) { 27 | try { 28 | completer.complete(status.value); 29 | status.removeListener(listener); 30 | } catch (e) { 31 | status.removeListener(listener); 32 | } 33 | } 34 | }; 35 | 36 | status.addListener(listener); 37 | 38 | return completer.future.timeout(timeout); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/alist_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | 6 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 7 | import 'package:horopic/utils/common_functions.dart'; 8 | import 'package:horopic/utils/global.dart'; 9 | 10 | class UploadManager extends BaseUploadManager { 11 | static final UploadManager _instance = UploadManager._internal(); 12 | 13 | UploadManager._internal(); 14 | 15 | factory UploadManager({int? maxConcurrentTasks}) { 16 | if (maxConcurrentTasks != null) { 17 | _instance.maxConcurrentTasks = maxConcurrentTasks; 18 | } 19 | return _instance; 20 | } 21 | 22 | @override 23 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 24 | String uploadPath = configMap['uploadPath']; 25 | //格式化 26 | if (uploadPath == 'None') { 27 | uploadPath = '/'; 28 | } else { 29 | if (!uploadPath.startsWith('/')) { 30 | uploadPath = '/$uploadPath'; 31 | } 32 | if (!uploadPath.endsWith('/')) { 33 | uploadPath = '$uploadPath/'; 34 | } 35 | } 36 | String filePath = uploadPath + fileName; 37 | 38 | FormData formdata = FormData.fromMap({ 39 | "file": await MultipartFile.fromFile(path, filename: fileName), 40 | }); 41 | File uploadFile = File(path); 42 | int contentLength = await uploadFile.length().then((value) { 43 | return value; 44 | }); 45 | BaseOptions baseoptions = setBaseOptions(); 46 | 47 | baseoptions.headers = { 48 | "Authorization": configMap["token"], 49 | "Content-Type": Global.multipartString, 50 | "file-path": Uri.encodeComponent(filePath), 51 | "Content-Length": contentLength, 52 | }; 53 | Dio dio = Dio(baseoptions); 54 | Response response = await dio.put( 55 | '${configMap["host"]}/api/fs/form', 56 | data: formdata, 57 | onSendProgress: createCallback(path, fileName), 58 | cancelToken: cancelToken, 59 | ); 60 | if (response.statusCode != HttpStatus.ok) { 61 | throw Exception('Upload failed: ${response.statusCode} - ${response.data}'); 62 | } 63 | } 64 | 65 | @override 66 | void onUploadError(dynamic error, String path, String fileName) { 67 | flogErr( 68 | error, 69 | { 70 | 'path': path, 71 | 'fileName': fileName, 72 | }, 73 | 'alistUploadManager', 74 | 'upload'); 75 | super.onUploadError(error, path, fileName); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/aws_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:minio/minio.dart'; 7 | import 'package:path/path.dart' as my_path; 8 | 9 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 10 | import 'package:horopic/utils/common_functions.dart'; 11 | 12 | class UploadManager extends BaseUploadManager { 13 | static final UploadManager _instance = UploadManager._internal(); 14 | 15 | UploadManager._internal(); 16 | 17 | factory UploadManager({int? maxConcurrentTasks}) { 18 | if (maxConcurrentTasks != null) { 19 | _instance.maxConcurrentTasks = maxConcurrentTasks; 20 | } 21 | return _instance; 22 | } 23 | 24 | @override 25 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 26 | String accessKeyId = configMap['accessKeyId']; 27 | String secretAccessKey = configMap['secretAccessKey']; 28 | String bucket = configMap['bucket']; 29 | String endpoint = configMap['endpoint']; 30 | int? port; 31 | if (endpoint.contains(':')) { 32 | List endpointList = endpoint.split(':'); 33 | endpoint = endpointList[0]; 34 | port = int.parse(endpointList[1]); 35 | } 36 | String region = configMap['region']; 37 | String uploadPath = configMap['uploadPath']; 38 | bool isEnableSSL = configMap['isEnableSSL'] ?? true; 39 | if (endpoint.contains('amazonaws.com')) { 40 | if (!endpoint.contains(region)) { 41 | endpoint = 's3.$region.amazonaws.com'; 42 | } 43 | } 44 | 45 | //云存储的路径 46 | String urlpath = uploadPath != '' ? '$uploadPath$fileName' : fileName; 47 | Minio minio = Minio( 48 | endPoint: endpoint, 49 | port: port, 50 | accessKey: accessKeyId, 51 | secretKey: secretAccessKey, 52 | useSSL: isEnableSSL, 53 | region: region == 'None' ? null : region, 54 | ); 55 | int fileSize = File(path).lengthSync(); 56 | Stream stream = File(path).openRead().cast(); 57 | String? contentType = getContentType(my_path.extension(path).substring(1)); 58 | await minio.putObject(bucket, urlpath, stream, metadata: {"Content-Type": contentType}, onProgress: (int sent) { 59 | getUpload(fileName)?.progress.value = sent / fileSize; 60 | }); 61 | } 62 | 63 | @override 64 | void onUploadError(dynamic error, String path, String fileName) { 65 | flogErr( 66 | error, 67 | { 68 | 'path': path, 69 | 'fileName': fileName, 70 | }, 71 | 'awsUploadManager', 72 | 'upload'); 73 | super.onUploadError(error, path, fileName); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/github_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:convert'; 4 | 5 | import 'package:dio/dio.dart'; 6 | 7 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 8 | import 'package:horopic/utils/common_functions.dart'; 9 | 10 | class UploadManager extends BaseUploadManager { 11 | static final UploadManager _instance = UploadManager._internal(); 12 | 13 | UploadManager._internal(); 14 | 15 | factory UploadManager({int? maxConcurrentTasks}) { 16 | _instance.maxConcurrentTasks = 1; 17 | 18 | return _instance; 19 | } 20 | 21 | @override 22 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 23 | Response response; 24 | String base64Image = base64Encode(File(path).readAsBytesSync()); 25 | Map queryBody = { 26 | 'message': 'uploaded by PicHoro app', 27 | 'content': base64Image, 28 | 'branch': configMap["default_branch"], //分支 29 | }; 30 | 31 | BaseOptions baseoptions = setBaseOptions(); 32 | baseoptions.headers = { 33 | "Authorization": configMap["token"], 34 | "Accept": "application/vnd.github+json", 35 | }; 36 | String trimedPath = configMap['savePath'].toString().trim(); 37 | 38 | if (trimedPath.startsWith('/')) { 39 | trimedPath = trimedPath.substring(1); 40 | } 41 | if (trimedPath.endsWith('/')) { 42 | trimedPath = trimedPath.substring(0, trimedPath.length - 1); 43 | } 44 | String uploadUrl = ''; 45 | if (trimedPath == '') { 46 | uploadUrl = "https://api.github.com/repos/${configMap["githubusername"]}/${configMap["repo"]}/contents/$fileName"; 47 | } else { 48 | uploadUrl = 49 | "https://api.github.com/repos/${configMap["githubusername"]}/${configMap["repo"]}/contents/$trimedPath/$fileName"; 50 | } 51 | Dio dio = Dio(baseoptions); 52 | response = await dio.put( 53 | uploadUrl, 54 | data: jsonEncode(queryBody), 55 | onSendProgress: createCallback(path, fileName), 56 | ); 57 | if (response.statusCode != HttpStatus.ok && response.statusCode != HttpStatus.created) { 58 | throw Exception('Upload failed: ${response.statusCode} - ${response.data}'); 59 | } 60 | } 61 | 62 | @override 63 | void onUploadError(dynamic error, String path, String fileName) { 64 | flogErr( 65 | error, 66 | { 67 | 'path': path, 68 | 'fileName': fileName, 69 | }, 70 | 'githubUploadManager', 71 | 'upload'); 72 | super.onUploadError(error, path, fileName); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/imgur_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | 6 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 7 | 8 | import 'package:horopic/utils/common_functions.dart'; 9 | import 'package:horopic/utils/dio_proxy_adapter.dart'; 10 | 11 | class UploadManager extends BaseUploadManager { 12 | static final UploadManager _instance = UploadManager._internal(); 13 | 14 | UploadManager._internal(); 15 | 16 | factory UploadManager({int? maxConcurrentTasks}) { 17 | if (maxConcurrentTasks != null) { 18 | _instance.maxConcurrentTasks = maxConcurrentTasks; 19 | } 20 | return _instance; 21 | } 22 | 23 | @override 24 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 25 | String albumHash = configMap['albumhash']; 26 | String proxy = configMap['proxy']; 27 | FormData formdata = FormData.fromMap({ 28 | "image": await MultipartFile.fromFile(path, filename: fileName), 29 | "type": "file", 30 | if (albumHash != 'None') "album": albumHash, 31 | "name": fileName, 32 | "description": "Uploaded by PicHoro", 33 | }); 34 | BaseOptions baseoptions = setBaseOptions(); 35 | baseoptions.headers = { 36 | "Authorization": "Bearer ${configMap['accesstoken']}", 37 | }; 38 | Dio dio = Dio(baseoptions); 39 | String proxyClean = ''; 40 | //判断是否有代理 41 | if (proxy != 'None') { 42 | if (proxy.startsWith('http://') || proxy.startsWith('https://')) { 43 | proxyClean = proxy.split('://')[1]; 44 | } else { 45 | proxyClean = proxy; 46 | } 47 | dio.httpClientAdapter = useProxy(proxyClean); 48 | } 49 | String accountUrl = "https://api.imgur.com/3/image"; 50 | Response response = await dio.post( 51 | accountUrl, 52 | data: formdata, 53 | onSendProgress: createCallback(path, fileName), 54 | cancelToken: cancelToken, 55 | ); 56 | if (!(response.statusCode == HttpStatus.ok && response.data['success'] == true)) { 57 | throw Exception('Upload failed: ${response.statusCode} - ${response.data}'); 58 | } 59 | } 60 | 61 | @override 62 | void onUploadError(dynamic error, String path, String fileName) { 63 | flogErr( 64 | error, 65 | { 66 | 'path': path, 67 | 'fileName': fileName, 68 | }, 69 | 'imgurUploadManager', 70 | 'upload'); 71 | super.onUploadError(error, path, fileName); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/lskypro_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | 6 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 7 | import 'package:horopic/utils/common_functions.dart'; 8 | 9 | class UploadManager extends BaseUploadManager { 10 | static final UploadManager _instance = UploadManager._internal(); 11 | 12 | UploadManager._internal(); 13 | 14 | factory UploadManager({int? maxConcurrentTasks}) { 15 | if (maxConcurrentTasks != null) { 16 | _instance.maxConcurrentTasks = maxConcurrentTasks; 17 | } 18 | return _instance; 19 | } 20 | 21 | @override 22 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 23 | Response response; 24 | FormData formdata = FormData.fromMap({ 25 | "file": await MultipartFile.fromFile(path, filename: fileName), 26 | if (configMap["strategy_id"] != "None") "strategy_id": configMap["strategy_id"], 27 | if (configMap["album_id"] != "None") "album_id": configMap["album_id"].toString(), 28 | }); 29 | String token = configMap["token"]; 30 | 31 | BaseOptions baseoptions = setBaseOptions(); 32 | baseoptions.headers = { 33 | "Authorization": token, 34 | "Accept": "application/json", 35 | "Content-Type": "multipart/form-data", 36 | }; 37 | Dio dio = Dio(baseoptions); 38 | String uploadUrl = configMap["host"] + "/api/v1/upload"; 39 | response = await dio.post( 40 | uploadUrl, 41 | data: formdata, 42 | onSendProgress: createCallback(path, fileName), 43 | cancelToken: cancelToken, 44 | ); 45 | if (!(response.statusCode == HttpStatus.ok && response.data!['status'] == true)) { 46 | throw Exception('Upload failed: ${response.statusCode} - ${response.data}'); 47 | } 48 | } 49 | 50 | @override 51 | void onUploadError(dynamic error, String path, String fileName) { 52 | flogErr( 53 | error, 54 | { 55 | 'path': path, 56 | 'fileName': fileName, 57 | }, 58 | 'lskyproUploadManager', 59 | 'upload'); 60 | super.onUploadError(error, path, fileName); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/qiniu_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | 6 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 7 | import 'package:horopic/api/qiniu_api.dart'; 8 | import 'package:horopic/utils/common_functions.dart'; 9 | 10 | class UploadManager extends BaseUploadManager { 11 | static final UploadManager _instance = UploadManager._internal(); 12 | 13 | UploadManager._internal(); 14 | 15 | factory UploadManager({int? maxConcurrentTasks}) { 16 | if (maxConcurrentTasks != null) { 17 | _instance.maxConcurrentTasks = maxConcurrentTasks; 18 | } 19 | return _instance; 20 | } 21 | 22 | @override 23 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 24 | Response response; 25 | String accessKey = configMap['accessKey']; 26 | String secretKey = configMap['secretKey']; 27 | String bucket = configMap['bucket']; 28 | String area = configMap['area']; 29 | String qiniupath = configMap['path']; 30 | 31 | String urlpath = ''; 32 | //不为None才处理 33 | if (qiniupath != 'None' && qiniupath != '') { 34 | if (qiniupath.startsWith('/')) { 35 | qiniupath = qiniupath.substring(1); 36 | } 37 | if (!qiniupath.endsWith('/')) { 38 | qiniupath = '$qiniupath/'; 39 | } 40 | urlpath = '$qiniupath$fileName'; 41 | } else { 42 | urlpath = fileName; 43 | } 44 | String key = fileName; 45 | 46 | String urlSafeBase64EncodePutPolicy = QiniuImageUploadUtils.geturlSafeBase64EncodePutPolicy(bucket, key, qiniupath); 47 | String uploadToken = QiniuImageUploadUtils.getUploadToken(accessKey, secretKey, urlSafeBase64EncodePutPolicy); 48 | String host = QiniuImageUploadUtils.areaHostMap[area]!; 49 | FormData formData = FormData.fromMap({ 50 | "key": urlpath, 51 | "fileName": fileName, 52 | "token": uploadToken, 53 | "file": await MultipartFile.fromFile(path, filename: fileName), 54 | }); 55 | BaseOptions baseoptions = setBaseOptions(); 56 | baseoptions.headers = { 57 | 'Authorization': 'UpToken $uploadToken', 58 | }; 59 | 60 | Dio dio = Dio(baseoptions); 61 | response = await dio.post( 62 | host, 63 | data: formData, 64 | onSendProgress: createCallback(path, fileName), 65 | cancelToken: cancelToken, 66 | ); 67 | if (response.statusCode != HttpStatus.ok) { 68 | throw Exception('Upload failed: ${response.statusCode} - ${response.data}'); 69 | } 70 | } 71 | 72 | @override 73 | void onUploadError(dynamic error, String path, String fileName) { 74 | flogErr( 75 | error, 76 | { 77 | 'path': path, 78 | 'fileName': fileName, 79 | }, 80 | 'QiniuUploadManager', 81 | 'upload'); 82 | super.onUploadError(error, path, fileName); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/sftp_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:dartssh2/dartssh2.dart'; 6 | 7 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 8 | import 'package:horopic/utils/common_functions.dart'; 9 | 10 | class UploadManager extends BaseUploadManager { 11 | static final UploadManager _instance = UploadManager._internal(); 12 | 13 | UploadManager._internal(); 14 | 15 | factory UploadManager({int? maxConcurrentTasks}) { 16 | if (maxConcurrentTasks != null) { 17 | _instance.maxConcurrentTasks = maxConcurrentTasks; 18 | } 19 | return _instance; 20 | } 21 | 22 | @override 23 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 24 | String ftpHost = configMap["ftpHost"]; 25 | String ftpPort = configMap["ftpPort"]; 26 | String ftpUser = configMap["ftpUser"]; 27 | String ftpPassword = configMap["ftpPassword"]; 28 | String uploadPath = configMap["uploadPath"]; 29 | 30 | final socket = await SSHSocket.connect(ftpHost, int.parse(ftpPort.toString())); 31 | final client = SSHClient( 32 | socket, 33 | username: ftpUser, 34 | onPasswordRequest: () { 35 | return ftpPassword; 36 | }, 37 | ); 38 | final sftp = await client.sftp(); 39 | if (uploadPath == 'None') { 40 | uploadPath = '/'; 41 | } 42 | if (!uploadPath.startsWith('/')) { 43 | uploadPath = '/$uploadPath'; 44 | } 45 | if (!uploadPath.endsWith('/')) { 46 | uploadPath = '$uploadPath/'; 47 | } 48 | String urlPath = uploadPath + fileName; 49 | var file = await sftp.open(urlPath, mode: SftpFileOpenMode.create | SftpFileOpenMode.write); 50 | int fileSize = File(path).lengthSync(); 51 | bool operateDone = false; 52 | file.write(File(path).openRead().cast(), onProgress: (int sent) { 53 | getUpload(fileName)?.progress.value = sent / fileSize; 54 | if (sent == fileSize) { 55 | operateDone = true; 56 | } 57 | }); 58 | while (!operateDone) { 59 | await Future.delayed(const Duration(milliseconds: 100)); 60 | } 61 | client.close(); 62 | } 63 | 64 | @override 65 | void onUploadError(dynamic error, String path, String fileName) { 66 | flogErr( 67 | error, 68 | { 69 | 'path': path, 70 | 'fileName': fileName, 71 | }, 72 | 'sftpUploadManager', 73 | 'upload'); 74 | super.onUploadError(error, path, fileName); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/smms_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:path/path.dart' as my_path; 4 | 5 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 6 | import 'package:horopic/utils/common_functions.dart'; 7 | 8 | class UploadManager extends BaseUploadManager { 9 | static final UploadManager _instance = UploadManager._internal(); 10 | 11 | UploadManager._internal() { 12 | maxConcurrentTasks = 2; 13 | } 14 | 15 | factory UploadManager({int? maxConcurrentTasks}) { 16 | if (maxConcurrentTasks != null) { 17 | _instance.maxConcurrentTasks = maxConcurrentTasks; 18 | } 19 | return _instance; 20 | } 21 | 22 | @override 23 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 24 | FormData formdata = FormData.fromMap({ 25 | "smfile": await MultipartFile.fromFile(path, filename: my_path.basename(path)), 26 | "format": "json", 27 | }); 28 | 29 | String token = configMap['token']; 30 | BaseOptions options = setBaseOptions(); 31 | options.headers = { 32 | "Authorization": token, 33 | "Content-Type": "multipart/form-data", 34 | }; 35 | 36 | Dio dio = Dio(options); 37 | String uploadUrl = "https://smms.app/api/v2/upload"; 38 | 39 | Response response = await dio.post( 40 | uploadUrl, 41 | data: formdata, 42 | onSendProgress: createCallback(path, fileName), 43 | cancelToken: cancelToken, 44 | ); 45 | 46 | if (response.statusCode != HttpStatus.ok || response.data!['success'] != true) { 47 | throw Exception("Upload failed: ${response.statusCode} - ${response.data}"); 48 | } 49 | } 50 | 51 | @override 52 | void onUploadError(dynamic error, String path, String fileName) { 53 | flogErr( 54 | error, 55 | { 56 | 'path': path, 57 | 'fileName': fileName, 58 | }, 59 | 'smmsUploadManager', 60 | 'upload'); 61 | 62 | super.onUploadError(error, path, fileName); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/picture_host_manage/common/upload/managers/webdav_upload_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | import 'package:horopic/utils/common_functions.dart'; 6 | import 'package:horopic/picture_host_manage/manage_api/webdav_manage_api.dart'; 7 | import 'package:horopic/picture_host_manage/common/upload/common_service/base_upload_manager.dart'; 8 | 9 | import 'package:webdav_client/webdav_client.dart' as webdav; 10 | 11 | class UploadManager extends BaseUploadManager { 12 | static final UploadManager _instance = UploadManager._internal(); 13 | 14 | UploadManager._internal() { 15 | maxConcurrentTasks = 2; 16 | } 17 | 18 | factory UploadManager({int? maxConcurrentTasks}) { 19 | if (maxConcurrentTasks != null) { 20 | _instance.maxConcurrentTasks = maxConcurrentTasks; 21 | } 22 | return _instance; 23 | } 24 | 25 | @override 26 | Future performUpload(String path, String fileName, Map configMap, CancelToken cancelToken) async { 27 | String uploadPath = configMap['uploadPath']; 28 | if (uploadPath == 'None') { 29 | uploadPath = '/'; 30 | } 31 | if (!uploadPath.endsWith('/')) { 32 | uploadPath = '$uploadPath/'; 33 | } 34 | webdav.Client client = await WebdavManageAPI().getWebdavClient(); 35 | await client.writeFromFile(path, uploadPath + fileName, onProgress: createCallback(path, fileName)); 36 | } 37 | 38 | @override 39 | void onUploadError(dynamic error, String path, String fileName) { 40 | flogErr( 41 | error, 42 | { 43 | 'path': path, 44 | 'fileName': fileName, 45 | }, 46 | 'webdavUploadManager', 47 | 'upload'); 48 | super.onUploadError(error, path, fileName); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/picture_host_manage/ftp/sftp_local_image_preview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:dartssh2/dartssh2.dart'; 5 | import 'package:horopic/widgets/common_widgets.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | 8 | import 'package:horopic/utils/common_functions.dart'; 9 | 10 | class SFTPLocalImagePreview extends StatefulWidget { 11 | final Map configMap; 12 | final String image; 13 | 14 | const SFTPLocalImagePreview({super.key, required this.configMap, required this.image}); 15 | 16 | @override 17 | SFTPLocalImagePreviewState createState() => SFTPLocalImagePreviewState(); 18 | } 19 | 20 | class SFTPLocalImagePreviewState extends State { 21 | String filePath = ''; 22 | 23 | downloadFile() async { 24 | try { 25 | String ftpHost = widget.configMap['ftpHost']; 26 | String ftpPort = widget.configMap['ftpPort']; 27 | String ftpUser = widget.configMap['ftpUser']; 28 | String ftpPassword = widget.configMap['ftpPassword']; 29 | final socket = await SSHSocket.connect(ftpHost, int.parse(ftpPort)); 30 | final client = SSHClient( 31 | socket, 32 | username: ftpUser, 33 | onPasswordRequest: () { 34 | return ftpPassword; 35 | }, 36 | ); 37 | final sftp = await client.sftp(); 38 | String tempDir = (await getTemporaryDirectory()).path; 39 | String fileName = widget.configMap['name']; 40 | var file = File('$tempDir/$fileName'); 41 | if (file.existsSync()) { 42 | file.deleteSync(); 43 | } 44 | var remoteFile = await sftp.open(widget.image, mode: SftpFileOpenMode.read); 45 | file.writeAsBytesSync(await remoteFile.readBytes()); 46 | return file.path; 47 | } catch (e) { 48 | flogErr(e, {}, 'SFTPLocalImagePreviewState', 'downloadFile'); 49 | } 50 | } 51 | 52 | @override 53 | void initState() { 54 | super.initState(); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Scaffold( 60 | appBar: AppBar( 61 | elevation: 0, 62 | centerTitle: true, 63 | leading: getLeadingIcon(context), 64 | title: titleText('图片预览'), 65 | flexibleSpace: getFlexibleSpace(context), 66 | ), 67 | body: FutureBuilder( 68 | future: downloadFile(), 69 | builder: (context, snapshot) { 70 | if (snapshot.hasData) { 71 | filePath = snapshot.data.toString(); 72 | return Center(child: Image.file(File(filePath), fit: BoxFit.contain, width: double.infinity)); 73 | } else { 74 | return const Center( 75 | child: CircularProgressIndicator(), 76 | ); 77 | } 78 | }, 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/picture_host_manage/webdav/webdav_pic_preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:extended_image/extended_image.dart'; 3 | import 'package:horopic/widgets/common_widgets.dart'; 4 | import 'package:horopic/widgets/load_state_change.dart'; 5 | import 'package:horopic/utils/common_functions.dart'; 6 | 7 | class WebdavImagePreview extends StatefulWidget { 8 | final int index; 9 | final List images; 10 | final List headersList; 11 | 12 | const WebdavImagePreview({super.key, required this.index, required this.images, required this.headersList}); 13 | 14 | @override 15 | WebdavImagePreviewState createState() => WebdavImagePreviewState(); 16 | } 17 | 18 | class WebdavImagePreviewState extends State { 19 | int _index = 0; 20 | late PageController _pageController; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _index = widget.index; 26 | _pageController = PageController(initialPage: _index); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | appBar: AppBar( 33 | elevation: 0, 34 | centerTitle: true, 35 | leading: getLeadingIcon(context), 36 | title: titleText('图片预览'), 37 | flexibleSpace: getFlexibleSpace(context), 38 | ), 39 | body: PageView.builder( 40 | controller: _pageController, 41 | onPageChanged: (index) { 42 | setState(() { 43 | _index = index; 44 | }); 45 | }, 46 | physics: const BouncingScrollPhysics(), 47 | itemBuilder: (context, index) { 48 | return ExtendedImage.network( 49 | widget.images[index], 50 | fit: BoxFit.contain, 51 | mode: ExtendedImageMode.gesture, 52 | cache: true, 53 | headers: Map.from(widget.headersList[index]), 54 | loadStateChanged: (state) => defaultLoadStateChanged(state, iconSize: 60), 55 | initGestureConfigHandler: (state) { 56 | return GestureConfig( 57 | minScale: 0.9, 58 | animationMinScale: 0.7, 59 | maxScale: 3.0, 60 | animationMaxScale: 3.5, 61 | speed: 1.0, 62 | inertialSpeed: 100.0, 63 | initialScale: 1.0, 64 | inPageView: true); 65 | }, 66 | ); 67 | }, 68 | itemCount: widget.images.length, 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/router/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | 3 | class Application { 4 | static late final FluroRouter router; 5 | } 6 | -------------------------------------------------------------------------------- /lib/utils/analytics_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flustars_flutter3/flustars_flutter3.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:http/http.dart' as http; 6 | import 'package:device_info_plus/device_info_plus.dart'; 7 | import 'package:package_info_plus/package_info_plus.dart'; 8 | import 'package:uuid/uuid.dart'; 9 | 10 | import 'package:horopic/utils/common_functions.dart'; 11 | 12 | class AnalyticsService { 13 | static final AnalyticsService _instance = AnalyticsService._internal(); 14 | factory AnalyticsService() => _instance; 15 | AnalyticsService._internal(); 16 | 17 | static const String _apiUrl = 'https://pichoro.horosama.com/pichoro/api/events'; 18 | static const String _deviceIdKey = 'analytics_device_id'; 19 | 20 | Future trackAppOpen() async { 21 | try { 22 | final deviceId = await _getOrCreateDeviceId(); 23 | final deviceInfo = await _collectDeviceInfo(); 24 | final appInfo = await _collectAppInfo(); 25 | 26 | final eventData = { 27 | 'event_type': 'app_open', 28 | 'timestamp': DateTime.now().toIso8601String(), 29 | 'device_id': deviceId, 30 | 'device_info': deviceInfo, 31 | 'app_info': appInfo, 32 | }; 33 | 34 | await _sendEvent(eventData); 35 | } catch (e) { 36 | flogErr( 37 | e, 38 | {'event_type': 'app_open'}, 39 | 'AnalyticsService', 40 | 'trackAppOpen', 41 | ); 42 | } 43 | } 44 | 45 | Future _getOrCreateDeviceId() async { 46 | String? deviceId = SpUtil.getString(_deviceIdKey); 47 | 48 | if (deviceId == '' || deviceId == null) { 49 | deviceId = const Uuid().v4(); 50 | await SpUtil.putString(_deviceIdKey, deviceId); 51 | } 52 | 53 | return deviceId; 54 | } 55 | 56 | Future> _collectDeviceInfo() async { 57 | final deviceInfo = DeviceInfoPlugin(); 58 | if (defaultTargetPlatform == TargetPlatform.android) { 59 | final androidInfo = await deviceInfo.androidInfo; 60 | return { 61 | 'platform': 'android', 62 | 'device': androidInfo.model, 63 | 'manufacturer': androidInfo.manufacturer, 64 | 'android_version': androidInfo.version.release, 65 | 'sdk_version': androidInfo.version.sdkInt.toString(), 66 | }; 67 | } else if (defaultTargetPlatform == TargetPlatform.iOS) { 68 | final iosInfo = await deviceInfo.iosInfo; 69 | return { 70 | 'platform': 'ios', 71 | 'device': iosInfo.model, 72 | 'system_name': iosInfo.systemName, 73 | 'system_version': iosInfo.systemVersion, 74 | }; 75 | } 76 | return {'platform': defaultTargetPlatform.toString()}; 77 | } 78 | 79 | Future> _collectAppInfo() async { 80 | final packageInfo = await PackageInfo.fromPlatform(); 81 | return { 82 | 'app_name': packageInfo.appName, 83 | 'package_name': packageInfo.packageName, 84 | 'version': packageInfo.version, 85 | 'build_number': packageInfo.buildNumber, 86 | }; 87 | } 88 | 89 | Future _sendEvent(Map eventData) async { 90 | try { 91 | final response = await http.post( 92 | Uri.parse(_apiUrl), 93 | headers: {'Content-Type': 'application/json'}, 94 | body: jsonEncode(eventData), 95 | ); 96 | 97 | if (response.statusCode != 200) { 98 | throw Exception('Failed to send analytics event: ${response.statusCode}'); 99 | } 100 | } catch (e) { 101 | flogErr( 102 | e, 103 | {'event_data': eventData}, 104 | 'AnalyticsService', 105 | '_sendEvent', 106 | ); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/utils/clear_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:path_provider/path_provider.dart'; 3 | 4 | class CacheUtil { 5 | static Future total() async { 6 | Directory tempDir = await getTemporaryDirectory(); 7 | int total = await _reduce(tempDir); 8 | return (total / 1024 / 1024).toStringAsFixed(2); 9 | } 10 | 11 | static Future clear() async { 12 | Directory tempDir = await getTemporaryDirectory(); 13 | await _delete(tempDir); 14 | } 15 | 16 | static Future _reduce(final FileSystemEntity file) async { 17 | if (file is File) { 18 | return await file.length(); 19 | } 20 | 21 | if (file is Directory) { 22 | final List children = file.listSync(); 23 | int total = 0; 24 | if (children.isNotEmpty) { 25 | for (final FileSystemEntity child in children) { 26 | total += await _reduce(child); 27 | } 28 | } 29 | return total; 30 | } 31 | return 0; 32 | } 33 | 34 | static Future _delete(FileSystemEntity file) async { 35 | if (file is Directory) { 36 | final List children = file.listSync(); 37 | for (final FileSystemEntity child in children) { 38 | await _delete(child); 39 | } 40 | } else { 41 | await file.delete(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/utils/deleter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | import 'package:horopic/api/api.dart'; 7 | import 'package:horopic/utils/global.dart'; 8 | import 'package:horopic/utils/common_functions.dart'; 9 | 10 | Map deleteFunc = { 11 | 'lskypro': LskyproImageUploadUtils.deleteApi, 12 | 'smms': SmmsImageUploadUtils.deleteApi, 13 | 'github': GithubImageUploadUtils.deleteApi, 14 | 'imgur': ImgurImageUploadUtils.deleteApi, 15 | 'qiniu': QiniuImageUploadUtils.deleteApi, 16 | 'tencent': TencentImageUploadUtils.deleteApi, 17 | 'aliyun': AliyunImageUploadUtils.deleteApi, 18 | 'upyun': UpyunImageUploadUtils.deleteApi, 19 | 'PBhostExtend1': FTPImageUploadUtils.deleteApi, //FTP 20 | 'PBhostExtend2': AwsImageUploadUtils.deleteApi, //AWS 21 | 'PBhostExtend3': AlistImageUploadUtils.deleteApi, //Alist 22 | 'PBhostExtend4': WebdavImageUploadUtils.deleteApi, //Webdav 23 | }; 24 | 25 | //获取图床配置文件 26 | Future get _localFile async { 27 | final directory = await getApplicationDocumentsDirectory(); 28 | String defaultConfig = Global.getShowedPBhost(); 29 | String defaultUser = Global.getUser(); 30 | switch (defaultConfig) { 31 | case 'lskypro': 32 | return File('${directory.path}/${defaultUser}_${getpdconfig('lsky.pro')}.txt'); 33 | case 'smms': 34 | return File('${directory.path}/${defaultUser}_${getpdconfig('sm.ms')}.txt'); 35 | case 'PBhostExtend1': 36 | return File('${directory.path}/${defaultUser}_${getpdconfig('ftp')}.txt'); 37 | case 'PBhostExtend2': 38 | return File('${directory.path}/${defaultUser}_${getpdconfig('aws')}.txt'); 39 | case 'PBhostExtend3': 40 | return File('${directory.path}/${defaultUser}_${getpdconfig('alist')}.txt'); 41 | case 'PBhostExtend4': 42 | return File('${directory.path}/${defaultUser}_${getpdconfig('webdav')}.txt'); 43 | default: 44 | return File('${directory.path}/${defaultUser}_${getpdconfig(defaultConfig)}.txt'); 45 | } 46 | } 47 | 48 | //读取图床配置文件 49 | Future readHostConfig() async { 50 | try { 51 | File file = await _localFile; 52 | return await file.readAsString(); 53 | } catch (e) { 54 | flogErr( 55 | e, 56 | {}, 57 | 'Deleter', 58 | "readPictureHostConfig", 59 | ); 60 | return "Error"; 61 | } 62 | } 63 | 64 | deleterentry(Map deleteConfig) async { 65 | try { 66 | String configData = await readHostConfig(); 67 | if (configData == 'Error') { 68 | return ["Error"]; 69 | } 70 | Map configMap = jsonDecode(configData); 71 | String defaultConfig = Global.getShowedPBhost(); 72 | return await deleteFunc[defaultConfig]!(deleteMap: deleteConfig, configMap: configMap); 73 | } catch (e) { 74 | flogErr( 75 | e, 76 | {}, 77 | 'Deleter', 78 | "deleterentry", 79 | ); 80 | return ["Error"]; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/utils/dio_proxy_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dio/io.dart'; 3 | 4 | /// Creates and returns an [IOHttpClientAdapter] configured with the provided proxy URL. 5 | /// 6 | /// If [proxyUrl] is null or empty, returns a default adapter with no proxy. 7 | /// Otherwise, configures a client with the specified proxy and certificate handling. 8 | IOHttpClientAdapter useProxy(String? proxyUrl) { 9 | if (proxyUrl == null || proxyUrl.isEmpty) { 10 | return IOHttpClientAdapter(); 11 | } 12 | 13 | return IOHttpClientAdapter( 14 | createHttpClient: () { 15 | final client = HttpClient(); 16 | client.findProxy = (_) => 'PROXY $proxyUrl'; 17 | client.badCertificateCallback = (_, __, ___) => true; 18 | return client; 19 | }, 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/event_bus_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | 3 | EventBus eventBus = EventBus(); 4 | 5 | class AlbumRefreshEvent { 6 | bool albumKeepAlive = true; 7 | AlbumRefreshEvent({ 8 | this.albumKeepAlive = true, 9 | }); 10 | } 11 | 12 | class HomePhotoRefreshEvent { 13 | bool homePhotoKeepAlive = true; 14 | HomePhotoRefreshEvent({ 15 | this.homePhotoKeepAlive = true, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/utils/image_compressor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:flutter_avif/flutter_avif.dart'; 5 | import 'package:flutter_image_compress/flutter_image_compress.dart'; 6 | import 'package:horopic/utils/common_functions.dart'; 7 | import 'package:path_provider/path_provider.dart'; 8 | 9 | Future compressAndGetFile(String path, String fileName, String format, 10 | {int minWidth = 1920, int minHeight = 1080, int quality = 80}) async { 11 | try { 12 | Uint8List? result; 13 | 14 | if (format == 'avif') { 15 | result = await encodeAvif(await File(path).readAsBytes()); 16 | } else { 17 | CompressFormat compressFormat = { 18 | 'jpg': CompressFormat.jpeg, 19 | 'png': CompressFormat.png, 20 | }[format] ?? 21 | CompressFormat.webp; 22 | 23 | result = await FlutterImageCompress.compressWithFile(path, 24 | minWidth: minWidth, minHeight: minHeight, quality: quality, rotate: 0, format: compressFormat); 25 | } 26 | 27 | if (result != null) { 28 | var dir = await getTemporaryDirectory(); 29 | String fileNameWithoutExtension = fileName.split('.').first; 30 | String targetPath = "${dir.absolute.path}/$fileNameWithoutExtension.$format"; 31 | return File(targetPath)..writeAsBytesSync(result); 32 | } 33 | 34 | return File(path); 35 | } catch (e) { 36 | flogErr( 37 | e, 38 | { 39 | 'path': path, 40 | 'fileName': fileName, 41 | 'format': format, 42 | 'minWidth': minWidth, 43 | 'minHeight': minHeight, 44 | 'quality': quality 45 | }, 46 | 'ImageCompressor', 47 | 'compressAndGetFile'); 48 | return File(path); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils/permission.dart: -------------------------------------------------------------------------------- 1 | import 'package:permission_handler/permission_handler.dart'; 2 | 3 | class PermissionHelper { 4 | static Future requestPermission(Permission permission) async { 5 | final PermissionStatus status = await permission.status; 6 | if (status.isGranted) { 7 | return true; 8 | } else { 9 | final Map statuses = await [permission].request(); 10 | return statuses[permission] == PermissionStatus.granted; 11 | } 12 | } 13 | 14 | static Future requestStoragePermission() async => requestPermission(Permission.storage); 15 | static Future requestCameraPermission() async => requestPermission(Permission.camera); 16 | static Future requestPhotoPermission() async => requestPermission(Permission.photos); 17 | static Future requestVideoPermission() async => requestPermission(Permission.videos); 18 | static Future requestAudioPermission() async => requestPermission(Permission.audio); 19 | static Future requestInstallPackagePermission() async => requestPermission(Permission.requestInstallPackages); 20 | static Future requestManageExternalStoragePermission() async => 21 | requestPermission(Permission.manageExternalStorage); 22 | static Future requestMediaLibraryAccess() async => requestPermission(Permission.mediaLibrary); 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/picbed/upyun.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:crypto/crypto.dart'; 3 | 4 | String getUpyunUploadPolicy( 5 | {required String bucket, required String saveKey, required String contentMd5, required String date}) { 6 | Map uploadPolicy = { 7 | 'bucket': bucket, 8 | 'save-key': saveKey, 9 | 'expiration': DateTime.now().millisecondsSinceEpoch + 1800000, 10 | 'date': date, 11 | 'content-md5': contentMd5, 12 | }; 13 | return base64.encode(utf8.encode(json.encode(uploadPolicy))); 14 | } 15 | 16 | String getUpyunUploadAuthHeader( 17 | {required String bucket, 18 | required String saveKey, 19 | required String contentMd5, 20 | required String operator, 21 | required String password, 22 | required String base64Policy, 23 | required String date}) { 24 | String stringToSign = 'POST&/$bucket&$date&$base64Policy&$contentMd5'; 25 | String passwordMd5 = md5.convert(utf8.encode(password)).toString(); 26 | String signature = base64.encode(Hmac(sha1, utf8.encode(passwordMd5)).convert(utf8.encode(stringToSign)).bytes); 27 | return 'UPYUN $operator:$signature'; 28 | } 29 | 30 | String getUpyunAntiLeechParam( 31 | {required String saveKey, required String antiLeechToken, required String antiLeechExpiration}) { 32 | if (antiLeechToken == '') { 33 | return ''; 34 | } 35 | 36 | String key = ''; 37 | if (saveKey.startsWith('/')) { 38 | key = saveKey; 39 | } else { 40 | key = '/$saveKey'; 41 | } 42 | 43 | int dateNowInSecond = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); 44 | int expire = antiLeechExpiration == '' ? dateNowInSecond + 3600 : dateNowInSecond + int.parse(antiLeechExpiration); 45 | String sign = md5.convert(utf8.encode('$antiLeechToken&$expire&$key')).toString(); 46 | return '_upt=${sign.substring(12, 20)}$expire'; 47 | } 48 | -------------------------------------------------------------------------------- /lib/utils/system_font_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:android_system_font/android_system_font.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | class NativeFeatures { 7 | static bool _systemFontLoaded = false; 8 | 9 | static Future _readFileBytes(String path) async { 10 | var bytes = await File(path).readAsBytes(); 11 | return ByteData.view(bytes.buffer); 12 | } 13 | 14 | static Future loadSystemFont() async { 15 | if (_systemFontLoaded) return; 16 | var fontLoader = FontLoader('SystemFont'); 17 | var fontFilePath = await AndroidSystemFont().getFilePath(); 18 | fontLoader.addFont(_readFileBytes(fontFilePath!)); 19 | fontLoader.load(); 20 | _systemFontLoaded = true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/utils/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flustars_flutter3/flustars_flutter3.dart'; 3 | import 'package:horopic/configure_page/others/theme_data.dart'; 4 | 5 | Map themeDataMap = { 6 | 'light': lightThemeData, 7 | 'green': greenThemeData, 8 | 'dark': darkThemeData, 9 | 'purple': purpleThemeData, 10 | 'orange': orangeThemeData, 11 | 'pink': pinkThemeData, 12 | 'cyan': cyanThemeData, 13 | 'gold': goldThemeData, 14 | ' ': lightThemeData, 15 | }; 16 | 17 | class AppInfoProvider with ChangeNotifier { 18 | String _themeColor = ''; 19 | String get themeColor => _themeColor; 20 | 21 | String _keyThemeColor = ' '; 22 | String get keyThemeColor => _keyThemeColor; 23 | 24 | AppInfoProvider() { 25 | _initAsync(); 26 | } 27 | 28 | bool isDarkMode() => _themeColor == 'dark'; 29 | 30 | Future _initAsync() async { 31 | await SpUtil.getInstance(); 32 | String colorset = SpUtil.getString('key_theme_color', defValue: 'light')!; 33 | _keyThemeColor = colorset; 34 | setTheme(colorset); 35 | } 36 | 37 | Future setTheme(String themeColor) async { 38 | _themeColor = 39 | themeColor == 'auto' ? (DateTime.now().hour >= 8 && DateTime.now().hour <= 22 ? 'light' : 'dark') : themeColor; 40 | 41 | _keyThemeColor = _themeColor; 42 | 43 | notifyListeners(); 44 | await SpUtil.getInstance(); 45 | SpUtil.putString('key_theme_color', _themeColor); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/utils/uploader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | import 'package:path_provider/path_provider.dart'; 4 | 5 | import 'package:horopic/api/api.dart'; 6 | import 'package:horopic/utils/global.dart'; 7 | import 'package:horopic/utils/common_functions.dart'; 8 | 9 | Map uploadFunc = { 10 | 'lsky.pro': LskyproImageUploadUtils.uploadApi, 11 | 'sm.ms': SmmsImageUploadUtils.uploadApi, 12 | 'github': GithubImageUploadUtils.uploadApi, 13 | 'imgur': ImgurImageUploadUtils.uploadApi, 14 | 'qiniu': QiniuImageUploadUtils.uploadApi, 15 | 'tencent': TencentImageUploadUtils.uploadApi, 16 | 'aliyun': AliyunImageUploadUtils.uploadApi, 17 | 'upyun': UpyunImageUploadUtils.uploadApi, 18 | 'ftp': FTPImageUploadUtils.uploadApi, 19 | 'aws': AwsImageUploadUtils.uploadApi, 20 | 'alist': AlistImageUploadUtils.uploadApi, 21 | 'webdav': WebdavImageUploadUtils.uploadApi, 22 | }; 23 | 24 | ///获取图床配置文件 25 | Future localFile() async { 26 | final directory = await getApplicationDocumentsDirectory(); 27 | String defaultConfig = Global.getPShost(); 28 | String defaultUser = Global.getUser(); 29 | 30 | return ensureFileExists(File('${directory.path}/${defaultUser}_${getpdconfig(defaultConfig)}.txt')); 31 | } 32 | 33 | ///读取图床配置文件 34 | Future readPictureHostConfig() async { 35 | return (await localFile()).readAsString(); 36 | } 37 | 38 | uploaderentry({required String path, required String name}) async { 39 | try { 40 | String configData = await readPictureHostConfig(); 41 | if (configData == '') { 42 | return ["failed"]; 43 | } 44 | String defaultConfig = Global.getPShost(); 45 | return await uploadFunc[defaultConfig]!(path: path, name: name, configMap: jsonDecode(configData)); 46 | } catch (e) { 47 | flogErr(e, {'path': path, 'name': name}, "uploaderentry", "uploaderentry"); 48 | return ["failed"]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/widgets/common_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Widget getFlexibleSpace(BuildContext context) { 4 | return Container( 5 | decoration: BoxDecoration( 6 | gradient: LinearGradient( 7 | colors: [Theme.of(context).primaryColor, Theme.of(context).primaryColor.withValues(alpha: 0.8)], 8 | begin: Alignment.topCenter, 9 | end: Alignment.bottomCenter, 10 | ), 11 | ), 12 | ); 13 | } 14 | 15 | Widget getLeadingIcon(BuildContext context) { 16 | return IconButton( 17 | icon: const Icon( 18 | Icons.arrow_back_ios, 19 | size: 20, 20 | color: Colors.white, 21 | ), 22 | onPressed: () { 23 | Navigator.pop(context); 24 | }, 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /lib/widgets/configure_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:horopic/utils/common_functions.dart'; 3 | import 'package:horopic/widgets/common_widgets.dart'; 4 | 5 | class ConfigureWidgets { 6 | static Widget buildSettingCard({required String title, required List children}) { 7 | return Card( 8 | margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), 9 | elevation: 2, 10 | shape: RoundedRectangleBorder( 11 | borderRadius: BorderRadius.circular(16), 12 | ), 13 | child: Column( 14 | crossAxisAlignment: CrossAxisAlignment.start, 15 | children: [ 16 | Padding( 17 | padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), 18 | child: Text( 19 | title, 20 | style: const TextStyle( 21 | fontSize: 18, 22 | fontWeight: FontWeight.bold, 23 | ), 24 | ), 25 | ), 26 | ...children, 27 | ], 28 | ), 29 | ); 30 | } 31 | 32 | static Widget buildSettingItem({ 33 | required String title, 34 | required IconData icon, 35 | required VoidCallback onTap, 36 | Widget? trailing, 37 | Color? iconColor, 38 | Widget? subtitle, 39 | required BuildContext context, 40 | }) { 41 | return ListTile( 42 | contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 2.0), 43 | leading: Container( 44 | padding: const EdgeInsets.all(8), 45 | decoration: BoxDecoration( 46 | color: iconColor ?? Theme.of(context).primaryColor.withAlpha(51), 47 | borderRadius: BorderRadius.circular(8), 48 | ), 49 | child: Icon(icon, color: iconColor ?? Theme.of(context).primaryColor), 50 | ), 51 | title: Text(title), 52 | subtitle: subtitle, 53 | onTap: onTap, 54 | trailing: trailing, 55 | ); 56 | } 57 | 58 | static Widget buildFormField({ 59 | required TextEditingController controller, 60 | required String labelText, 61 | String? hintText, 62 | IconData? prefixIcon, 63 | bool obscureText = false, 64 | String? Function(String?)? validator, 65 | }) { 66 | return Padding( 67 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), 68 | child: TextFormField( 69 | controller: controller, 70 | obscureText: obscureText, 71 | decoration: InputDecoration( 72 | labelText: labelText, 73 | hintText: hintText, 74 | prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null, 75 | border: OutlineInputBorder( 76 | borderRadius: BorderRadius.circular(12), 77 | ), 78 | contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), 79 | ), 80 | validator: validator, 81 | ), 82 | ); 83 | } 84 | 85 | static AppBar buildConfigAppBar({required String title, required BuildContext context}) { 86 | return AppBar( 87 | elevation: 0, 88 | centerTitle: true, 89 | leading: getLeadingIcon(context), 90 | title: titleText( 91 | title, 92 | fontsize: 18, 93 | ), 94 | flexibleSpace: getFlexibleSpace(context), 95 | ); 96 | } 97 | 98 | static Widget buildDivider() { 99 | return const Divider(height: 1, indent: 56); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/widgets/load_state_change.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:extended_image/extended_image.dart'; 3 | 4 | Widget? defaultLoadStateChanged(ExtendedImageState state, {double iconSize = 16}) { 5 | switch (state.extendedImageLoadState) { 6 | case LoadState.loading: 7 | return Center( 8 | child: Center( 9 | child: SizedBox( 10 | width: iconSize, 11 | height: iconSize, 12 | child: const CircularProgressIndicator( 13 | strokeWidth: 2.0, 14 | ), 15 | ), 16 | ), 17 | ); 18 | case LoadState.failed: 19 | return GestureDetector( 20 | child: Stack( 21 | fit: StackFit.expand, 22 | alignment: AlignmentDirectional.center, 23 | children: [ 24 | Icon( 25 | Icons.error, 26 | size: iconSize, 27 | color: Colors.grey[600], 28 | ) 29 | ], 30 | ), 31 | onTap: () { 32 | state.reLoadImage(); 33 | }, 34 | ); 35 | default: 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/widgets/net_loading_dialog.dart: -------------------------------------------------------------------------------- 1 | // 参考: https://blog.csdn.net/O_time/article/details/86496537 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:horopic/utils/global.dart'; 5 | import 'package:horopic/utils/common_functions.dart'; 6 | 7 | class NetLoadingDialog extends StatefulWidget { 8 | final String loadingText; 9 | final bool outsideDismiss; 10 | final bool loading; 11 | 12 | final Future? requestCallBack; 13 | 14 | const NetLoadingDialog( 15 | {super.key, 16 | this.loadingText = "loading...", 17 | this.outsideDismiss = false, 18 | required this.loading, 19 | required this.requestCallBack}); 20 | 21 | @override 22 | State createState() => _LoadingDialog(); 23 | } 24 | 25 | class _LoadingDialog extends State { 26 | @override 27 | void initState() { 28 | super.initState(); 29 | if (widget.requestCallBack != null) { 30 | widget.requestCallBack?.then((err) { 31 | err; 32 | Global.operateDone = true; 33 | Navigator.pop(context); 34 | }).catchError((err) { 35 | flogErr(err, {}, "NetLoadingDialog", "initState"); 36 | Navigator.pop(context); 37 | }); 38 | } 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | if (!widget.loading) { 44 | return Container(); 45 | } 46 | return GestureDetector( 47 | onTap: null, 48 | child: Material( 49 | type: MaterialType.transparency, 50 | child: Center( 51 | child: SizedBox( 52 | width: 120.0, 53 | height: 120.0, 54 | child: Container( 55 | decoration: ShapeDecoration( 56 | color: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white, 57 | shape: const RoundedRectangleBorder( 58 | borderRadius: BorderRadius.all( 59 | Radius.circular(8.0), 60 | ), 61 | ), 62 | ), 63 | child: Column( 64 | mainAxisAlignment: MainAxisAlignment.center, 65 | crossAxisAlignment: CrossAxisAlignment.center, 66 | children: [ 67 | const CircularProgressIndicator(), 68 | Padding( 69 | padding: const EdgeInsets.only( 70 | top: 20.0, 71 | ), 72 | child: Text( 73 | widget.loadingText, 74 | style: const TextStyle(fontSize: 12.0), 75 | ), 76 | ), 77 | ], 78 | ), 79 | ), 80 | ), 81 | ), 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/widgets/web_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:horopic/widgets/common_widgets.dart'; 3 | import 'package:webview_flutter/webview_flutter.dart'; 4 | 5 | import 'package:horopic/utils/common_functions.dart'; 6 | 7 | class WebViewPage extends StatefulWidget { 8 | final String url; 9 | final String title; 10 | final bool enableJs; 11 | 12 | const WebViewPage({super.key, required this.url, required this.title, this.enableJs = false}); 13 | 14 | @override 15 | WebViewPageState createState() => WebViewPageState(); 16 | } 17 | 18 | class WebViewPageState extends State { 19 | late WebViewController controller; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | controller = WebViewController(); 25 | controller.setJavaScriptMode(widget.enableJs ? JavaScriptMode.unrestricted : JavaScriptMode.disabled); 26 | controller.loadRequest(Uri.parse(widget.url)); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | appBar: AppBar( 33 | centerTitle: true, 34 | elevation: 0, 35 | flexibleSpace: getFlexibleSpace(context), 36 | title: widget.title == 'None' ? titleText('网页浏览') : titleText(widget.title), 37 | ), 38 | body: WebViewWidget( 39 | controller: controller, 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /supported_format.md: -------------------------------------------------------------------------------- 1 | # 支持预览的文件格式列表 2 | 3 | ## 图片 4 | 5 | - .bmp 6 | - .gif 7 | - .heif 8 | - .ico 9 | - .jpeg 10 | - .jpg 11 | - .png 12 | - .svg 13 | - .tif 14 | - .tiff 15 | - .webp 16 | 17 | ## 文本 18 | 19 | - .bat 20 | - .c 21 | - .cmd 22 | - .conf 23 | - .config 24 | - .cpp 25 | - .css 26 | - .csv 27 | - .dart 28 | - .gitattributes 29 | - .gitconfig 30 | - .gitignore 31 | - .gitkeep 32 | - .gitmodules 33 | - .go 34 | - .h 35 | - .hpp 36 | - .htm 37 | - .html 38 | - .ini 39 | - .java 40 | - .js 41 | - .json 42 | - .log 43 | - .php 44 | - .prop 45 | - .properties 46 | - .py 47 | - .rc 48 | - .sh 49 | - .tsv 50 | - .txt 51 | - .xml 52 | - .yaml 53 | - .yml 54 | - .md 55 | 56 | ## PDF 57 | 58 | - .pdf 59 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.0.1" 3 | } --------------------------------------------------------------------------------