├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report_en.yml │ ├── bug_report_zh.yml │ ├── feature_request_en.yml │ └── feature_request_zh.yml └── workflows │ ├── main.yml │ └── stale.yml ├── .gitignore ├── .metadata ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_zh-Hans.md ├── analysis_options.yaml ├── assets ├── alisthelper.ico └── alisthelper.png ├── build.yaml ├── devtools_options.yaml ├── lib ├── i18n │ ├── en.i18n.json │ ├── zh-Hans-CN.i18n.json │ └── zh-Hant-TW.i18n.json ├── main.dart ├── model │ ├── alist_helper_state.dart │ ├── alist_state.dart │ ├── rclone_state.dart │ ├── settings_state.dart │ ├── updater_state.dart │ ├── vfsoptions_state.dart │ └── virtual_disk_state.dart ├── provider │ ├── alist_helper_provider.dart │ ├── alist_provider.dart │ ├── app_arguments_provider.dart │ ├── persistence_provider.dart │ ├── rclone_provider.dart │ ├── settings_provider.dart │ ├── updater_provider.dart │ └── window_dimensions_provider.dart ├── theme.dart ├── utils │ ├── init.dart │ ├── native │ │ ├── auto_start_helper.dart │ │ ├── file_helper.dart │ │ ├── tray_helper.dart │ │ ├── tray_manager.dart │ │ └── window_watcher.dart │ └── textutils.dart └── widgets │ ├── add_new_vdisk.dart │ ├── alist_args_tile.dart │ ├── button_card.dart │ ├── choose_alist_package.dart │ ├── choose_rclone_package.dart │ ├── edit_vdisk.dart │ ├── logs_viewer.dart │ ├── pages │ ├── about_page.dart │ ├── alist_helper_page.dart │ ├── debug_page.dart │ ├── first_launch_page.dart │ ├── home.dart │ ├── language_page.dart │ ├── logs_page.dart │ ├── rclone_page.dart │ ├── settings_page.dart │ └── upgrade_page.dart │ ├── proxy_tile.dart │ ├── rclone_account.dart │ ├── responsive_builder.dart │ ├── sponsor_btn.dart │ ├── theme_tile.dart │ ├── toggle_tile.dart │ └── working_directory_tile.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ └── CMakeLists.txt ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ └── Flutter-Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter └── CMakeLists.txt ├── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources │ └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h └── scripts └── innosetup.iss /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: hubcap228 4 | ko_fi: hubcap 5 | custom: ["https://afdian.com/a/hornigold"] 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_en.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Submit discovered bugs 3 | labels: ['bug'] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for taking the time to fill out this bug report! Please ensure the bug report filed is detailed and meaningful. 10 | If you are having problems with usage, consider asking in the [community](https://github.com/Xmarmalade/alisthelper/discussions), or sponsoring to get answers. 11 | Before filling out a bug report, please make sure you have upgraded to the latest stable version, read the [documentation](https://github.com/Xmarmalade/alisthelper/blob/master/README.md) and [Wiki](https://github.com/Xmarmalade/alisthelper/wiki), and provide all the information required by this template, otherwise the issue will be closed. 12 | 13 | - type: textarea 14 | id: what-expected 15 | attributes: 16 | label: What is expected? 17 | placeholder: Tell us what you expect! 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: actual-happened 23 | attributes: 24 | label: What is actually happening? 25 | placeholder: Tell us what you see! 26 | validations: 27 | required: true 28 | 29 | - type: input 30 | id: version 31 | attributes: 32 | label: What version of our software are you running? 33 | validations: 34 | required: true 35 | 36 | - type: input 37 | id: platform-info 38 | attributes: 39 | label: Platform information 40 | description: Please provide your platform information. 41 | placeholder: 'Windows 10 Home (Build 22621)' 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: logs 47 | attributes: 48 | label: Additional info 49 | description: logs, errors, etc. 50 | render: shell 51 | validations: 52 | required: true 53 | 54 | - type: checkboxes 55 | id: docs 56 | attributes: 57 | label: The document does not contain a solution to this problem 58 | options: 59 | - label: I have read the [documentation](https://github.com/Xmarmalade/alisthelper/wiki). 60 | required: true 61 | 62 | - type: checkboxes 63 | id: terms 64 | attributes: 65 | label: This is not a duplicated issue 66 | options: 67 | - label: I have searched [existing issues](https://github.com/Xmarmalade/alisthelper/issues) to ensure this bug has not already been reported. 68 | required: true 69 | 70 | - type: checkboxes 71 | id: relate 72 | attributes: 73 | label: This is not an irrelevant issue. 74 | options: 75 | - label: The configurations of my alist and rclone are correct, the problem lies with alist helper. 76 | required: true 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_zh.yml: -------------------------------------------------------------------------------- 1 | name: 错误报告 2 | description: 提交发现的错误 3 | labels: ['bug'] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您花时间填写此错误报告!请确保提出的错误报告详细且有意义。 10 | 如果您在使用方面存在问题,请考虑在[社区](https://github.com/Xmarmalade/alisthelper/discussions)中提问,或是赞助以获得解答。 11 | 在填写错误报告前,请确保您已升级至最新稳定版本,且阅读了[文档](https://github.com/Xmarmalade/alisthelper/blob/master/README.md)和[Wiki](https://github.com/Xmarmalade/alisthelper/wiki),并提供此模板所需的所有信息,否则问题将被关闭。 12 | 13 | - type: textarea 14 | id: what-expected 15 | attributes: 16 | label: 软件期望的结果是什么? 17 | placeholder: 告诉我们您认为的结果! 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: actual-happened 23 | attributes: 24 | label: 实际发生了什么? 25 | placeholder: 告诉我们你看到了什么! 26 | validations: 27 | required: true 28 | 29 | - type: input 30 | id: version 31 | attributes: 32 | label: 您运行的是我们的哪个版本的软件? 33 | validations: 34 | required: true 35 | 36 | - type: input 37 | id: platform-info 38 | attributes: 39 | label: 平台信息 40 | description: 请提供您的平台信息。 41 | placeholder: 'Windows 10 Home (Build 22621)' 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: logs 47 | attributes: 48 | label: 附加信息 49 | description: 软件的错误信息,或者异常表现。 50 | render: shell 51 | validations: 52 | required: true 53 | 54 | - type: checkboxes 55 | id: docs 56 | attributes: 57 | label: 文档中不包含该问题的解决方案 58 | options: 59 | - label: 我已经阅读了[文档](https://github.com/Xmarmalade/alisthelper/wiki)。 60 | required: true 61 | 62 | - type: checkboxes 63 | id: terms 64 | attributes: 65 | label: 这不是重复的问题 66 | options: 67 | - label: 我搜索了[现有问题](https://github.com/Xmarmalade/alisthelper/issues) 以确保此错误不是重复的。 68 | required: true 69 | 70 | - type: checkboxes 71 | id: relate 72 | attributes: 73 | label: 这不是无关的问题 74 | options: 75 | - label: 经过排查,我的 alist 和 rclone 的配置正常,问题在于 alist helper 。 76 | required: true 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_en.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Submit a new feature request 3 | labels: ['enhancement'] 4 | 5 | body: 6 | 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Please ensure the feature requested is not listed in [documentation](https://github.com/Xmarmalade/alisthelper/blob/master/README.md) or [issue](https://github.com/Xmarmalade/alisthelper/issues), and provide all the information required by this template. 11 | Otherwise the issue will be closed immediately. 12 | 13 | - type: textarea 14 | id: feature 15 | attributes: 16 | label: What feature is it? 17 | placeholder: Please describe the feature you want to see. 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: problem 23 | attributes: 24 | label: What problem does this feature solve? 25 | placeholder: Please describe the problem this feature solves. 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: description 31 | attributes: 32 | label: Additional description 33 | placeholder: Any additional description. 34 | 35 | - type: checkboxes 36 | id: terms 37 | attributes: 38 | label: This is not a duplicate feature request or a feature request unrelated to Alist Helper itself. 39 | options: 40 | - label: I have searched [existing issues](https://github.com/Xmarmalade/alisthelper/issues) to ensure This is not a duplicate feature request or a feature request unrelated to Alist Helper itself. 41 | required: true 42 | 43 | - type: checkboxes 44 | id: sponsor 45 | attributes: 46 | label: I want to support Alist Helper and I have donated / sponsored Alist Helper 47 | options: 48 | - label: I want to support Alist Helper and I have donated / sponsored to Alist Helper 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_zh.yml: -------------------------------------------------------------------------------- 1 | name: 功能需求 2 | description: 提交新的功能需求 3 | labels: ['enhancement'] 4 | 5 | body: 6 | 7 | - type: markdown 8 | attributes: 9 | value: | 10 | 请确保 [文档](https://github.com/Xmarmalade/alisthelper/blob/master/README.md) 和 [issue](https://github.com/Xmarmalade/alisthelper/issue) 中没有相关内容,并按照模版提供信息, 11 | 否则 issue 将被立即关闭。 12 | 13 | - type: textarea 14 | id: feature 15 | attributes: 16 | label: 这是一个什么样的功能? 17 | placeholder: 请描述你想看到的功能。 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: problem 23 | attributes: 24 | label: 这个功能可以解决什么问题? 25 | placeholder: 请描述该功能解决的问题。 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: description 31 | attributes: 32 | label: 额外描述 33 | placeholder: 任何补充说明。 34 | 35 | - type: checkboxes 36 | id: terms 37 | attributes: 38 | label: 这不是重复的功能请求或与 Alist Helper 本身无关的功能请求。 39 | options: 40 | - label: 我已经搜索了[现有问题](https://github.com/Xmarmalade/alisthelper/issues),以确保这不是一个重复的功能请求或与Alist Helper本身无关的功能请求。 41 | required: true 42 | 43 | - type: checkboxes 44 | id: sponsor 45 | attributes: 46 | label: 我想支持 Alist Helper,我已经捐赠/赞助了 Alist Helper 47 | options: 48 | - label: 我想支持 Alist Helper,我已经捐赠/赞助了 Alist Helper 49 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | # Controls when the workflow will run 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | env: 13 | # APP name 14 | APP_NAME: alisthelper 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | build-windows: 19 | # The type of runner that the job will run on 20 | runs-on: windows-latest 21 | 22 | env: 23 | # The name of the installer 24 | WINDOWS_INSTALLER_NAME: AlistHelper_installer_${{ github.ref_name }}_windows-x86_64 25 | WINDOWS_PORTABLE_NAME: AlistHelper_${{ github.ref_name }}_windows-x86_64 26 | 27 | # Steps represent a sequence of tasks that will be executed as part of the job 28 | steps: 29 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 30 | - uses: actions/checkout@v3 31 | - uses: subosito/flutter-action@v2 32 | with: 33 | channel: "stable" 34 | 35 | - name: Build 36 | run: | 37 | flutter config --enable-windows-desktop 38 | flutter pub get 39 | dart run build_runner build 40 | flutter build windows 41 | 42 | - name: Copy innosetup.iss 43 | # copy assets/alisthelper.ico to build/windows/x64/runner/ 44 | # copy build/windows/x64/runner/Release/innosetup.iss to build/windows/x64/runner/ 45 | run: | 46 | cp windows/scripts/innosetup.iss build/windows/x64/runner/ 47 | cp assets/alisthelper.ico build/windows/x64/runner/ 48 | 49 | - name: Build installer executable 50 | working-directory: build/windows/x64/runner/ 51 | run: | 52 | iscc /F${{ env.WINDOWS_INSTALLER_NAME }} innosetup.iss /DAppVersion=${{ github.ref_name }} 53 | 54 | - name: Archive Portable Release 55 | uses: thedoctor0/zip-release@master 56 | with: 57 | type: "zip" 58 | filename: ${{env.WINDOWS_PORTABLE_NAME}}.zip 59 | directory: build/windows/x64/runner/Release 60 | 61 | - name: Release 62 | uses: softprops/action-gh-release@v1 63 | with: 64 | tag_name: ${{github.ref_name}} 65 | draft: true 66 | prerelease: true 67 | token: ${{ secrets.GITHUB_TOKEN }} 68 | files: | 69 | build/windows/x64/runner/Output/${{env.WINDOWS_INSTALLER_NAME}}.exe 70 | build/windows/x64/runner/Release/${{env.WINDOWS_PORTABLE_NAME}}.zip 71 | 72 | - name: Upload Release Asset 73 | uses: actions/upload-artifact@v3 74 | with: 75 | name: artifact-windows 76 | path: build/windows/x64/runner/Release/${{env.WINDOWS_PORTABLE_NAME}}.zip 77 | 78 | build-macos: 79 | # The type of runner that the job will run on 80 | runs-on: macos-latest 81 | 82 | env: 83 | # The name of the app 84 | MACOS_APP_NAME: AlistHelper_app_${{ github.ref_name }}_macos-x86_64 85 | DMG_NAME: AlistHelper_${{ github.ref_name }}_macos.dmg 86 | 87 | # Steps represent a sequence of tasks that will be executed as part of the job 88 | steps: 89 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 90 | - uses: actions/checkout@v3 91 | 92 | - name: Install Flutter 93 | uses: subosito/flutter-action@v2 94 | with: 95 | channel: "stable" 96 | 97 | - name: Build 98 | run: | 99 | flutter config --enable-macos-desktop 100 | flutter pub get 101 | dart run build_runner build 102 | flutter build macos 103 | 104 | - name: Archive App 105 | run: | 106 | cd build/macos/Build/Products/Release/ 107 | tar -czf ${{ env.MACOS_APP_NAME }}.tar.gz alisthelper.app 108 | hdiutil create -volname "alisthelper" -srcfolder alisthelper.app -ov -format UDZO ${{ env.DMG_NAME }} 109 | 110 | - name: Release 111 | uses: softprops/action-gh-release@v1 112 | with: 113 | tag_name: ${{ github.ref_name }} 114 | draft: true 115 | prerelease: true 116 | token: ${{ secrets.GITHUB_TOKEN }} 117 | files: | 118 | build/macos/Build/Products/Release/${{ env.DMG_NAME }} 119 | build/macos/Build/Products/Release/${{ env.MACOS_APP_NAME }}.tar.gz 120 | 121 | - name: Upload Release Asset 122 | uses: actions/upload-artifact@v3 123 | with: 124 | name: artifact-macos 125 | path: build/macos/Build/Products/Release/${{ env.MACOS_APP_NAME }}.tar.gz 126 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * 0' 5 | jobs: 6 | stale: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/stale@v8 10 | with: 11 | stale-issue-message: 'Message to comment on stale issues. If none provided, will not mark issues stale' 12 | stale-pr-message: 'Message to comment on stale PRs. If none provided, will not mark PRs stale' 13 | stale-issue-label: 'no-issue-activity' 14 | days-before-stale: 30 15 | days-before-close: 7 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | .vs/ 12 | .vscode/ 13 | 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # Visual Studio Code related 21 | .classpath 22 | .project 23 | .settings/ 24 | .vscode/* 25 | .VSCodeCounter/ 26 | 27 | # Flutter repo-specific 28 | /bin/cache/ 29 | /bin/internal/bootstrap.bat 30 | /bin/internal/bootstrap.sh 31 | /bin/mingit/ 32 | /dev/benchmarks/mega_gallery/ 33 | /dev/bots/.recipe_deps 34 | /dev/bots/android_tools/ 35 | /dev/devicelab/ABresults*.json 36 | /dev/docs/doc/ 37 | /dev/docs/flutter.docs.zip 38 | /dev/docs/lib/ 39 | /dev/docs/pubspec.yaml 40 | /dev/integration_tests/**/xcuserdata 41 | /dev/integration_tests/**/Pods 42 | /packages/flutter/coverage/ 43 | version 44 | analysis_benchmark.json 45 | 46 | # packages file containing multi-root paths 47 | .packages.generated 48 | 49 | # Flutter/Dart/Pub related 50 | **/doc/api/ 51 | .dart_tool/ 52 | .flutter-plugins 53 | .flutter-plugins-dependencies 54 | **/generated_plugin_registrant.dart 55 | .packages 56 | .pub-preload-cache/ 57 | .pub/ 58 | build/ 59 | flutter_*.png 60 | linked_*.ds 61 | unlinked.ds 62 | unlinked_spec.ds 63 | 64 | # Flutter generated 65 | *.g.dart 66 | *.gen.dart 67 | *.freezed.dart 68 | 69 | # Android related 70 | **/android/**/gradle-wrapper.jar 71 | .gradle/ 72 | **/android/captures/ 73 | **/android/gradlew 74 | **/android/gradlew.bat 75 | **/android/local.properties 76 | **/android/**/GeneratedPluginRegistrant.java 77 | **/android/key.properties 78 | *.jks 79 | 80 | # iOS/XCode related 81 | **/ios/**/*.mode1v3 82 | **/ios/**/*.mode2v3 83 | **/ios/**/*.moved-aside 84 | **/ios/**/*.pbxuser 85 | **/ios/**/*.perspectivev3 86 | **/ios/**/*sync/ 87 | **/ios/**/.sconsign.dblite 88 | **/ios/**/.tags* 89 | **/ios/**/.vagrant/ 90 | **/ios/**/DerivedData/ 91 | **/ios/**/Icon? 92 | **/ios/**/Pods/ 93 | **/ios/**/.symlinks/ 94 | **/ios/**/profile 95 | **/ios/**/xcuserdata 96 | **/ios/.generated/ 97 | **/ios/Flutter/.last_build_id 98 | **/ios/Flutter/App.framework 99 | **/ios/Flutter/Flutter.framework 100 | **/ios/Flutter/Flutter.podspec 101 | **/ios/Flutter/Generated.xcconfig 102 | **/ios/Flutter/ephemeral 103 | **/ios/Flutter/app.flx 104 | **/ios/Flutter/app.zip 105 | **/ios/Flutter/flutter_assets/ 106 | **/ios/Flutter/flutter_export_environment.sh 107 | **/ios/ServiceDefinitions.json 108 | **/ios/Runner/GeneratedPluginRegistrant.* 109 | 110 | # macOS 111 | **/Flutter/ephemeral/ 112 | **/Pods/ 113 | **/macos/Flutter/GeneratedPluginRegistrant.swift 114 | **/macos/Flutter/ephemeral 115 | **/xcuserdata/ 116 | 117 | # Windows 118 | **/windows/flutter/generated_plugin_registrant.cc 119 | **/windows/flutter/generated_plugin_registrant.h 120 | **/windows/flutter/generated_plugins.cmake 121 | 122 | # Linux 123 | **/linux/flutter/generated_plugin_registrant.cc 124 | **/linux/flutter/generated_plugin_registrant.h 125 | **/linux/flutter/generated_plugins.cmake 126 | 127 | # Coverage 128 | coverage/ 129 | 130 | # Symbols 131 | app.*.symbols 132 | 133 | # Exceptions to above rules. 134 | !**/ios/**/default.mode1v3 135 | !**/ios/**/default.mode2v3 136 | !**/ios/**/default.pbxuser 137 | !**/ios/**/default.perspectivev3 138 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 139 | !/dev/ci/**/Gemfile.lock 140 | !.vscode/settings.json -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: d23dba223c75aea55c6c23020050f08cc0c342c4 8 | channel: master 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 17 | base_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 18 | - platform: linux 19 | create_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 20 | base_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 21 | - platform: macos 22 | create_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 23 | base_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 24 | - platform: windows 25 | create_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 26 | base_revision: d23dba223c75aea55c6c23020050f08cc0c342c4 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | If you're interested in contributing code to AlistHelper, you'll need to follow these steps: 4 | 5 | ## Run 6 | 7 | Fork the repository and install [Flutter](https://flutter.dev). 8 | 9 | After you have installed [Flutter](https://flutter.dev), then you can start this app by typing the following commands: 10 | 11 | ```shell 12 | flutter pub get 13 | dart run build_runner build 14 | flutter run 15 | ``` 16 | 17 | ## Translation 18 | 19 | You can help translating this app to other languages! 20 | 21 | 1. Fork this repository 22 | 2. Choose one 23 | - Add missing translations in existing languages: Only update `_missing_translations_.json` in [lib/i18n](https://github.com/Xmarmalade/alisthelper/tree/master/lib/i18n) 24 | - Fix existing translations: Update `strings_.i18n.json` in [lib/i18n](https://github.com/Xmarmalade/alisthelper/tree/master/lib/i18n) 25 | - Add new languages: Create a new file, see also: [locale codes](https://saimana.com/list-of-country-locale-code/). 26 | 3. Optional: Re-run this app 27 | 1. Make sure you have [run](#run) this app once. 28 | 2. Update translations via `dart run build_runner build` 29 | 3. Run app via `flutter run` 30 | 4. Open a pull request 31 | 32 | #### _Take note:_ Fields decorated with `@` are not meant to be translated, they are not used in the app in any way, being merely informative text about the file or to give context to the translator. 33 | 34 | ## Contributing Guidelines 35 | 36 | Before you submit a pull request to AlistHelper, please ensure that you have followed these guidelines: 37 | 38 | - Code should be well-documented and formatted according to the [Dart Style Guide](https://dart.dev/guides/language/effective-dart/style). 39 | - All changes should be covered by tests. 40 | - Commits should be well-written and descriptive, with a clear summary of the changes made and any relevant context. 41 | - Pull requests should target the `master` branch and include a clear summary of the changes made. 42 | 43 | ## Bug Reports and Feature Requests 44 | 45 | If you encounter a bug in AlistHelper or have a feature request, please submit an issue to the [issue tracker](https://github.com/Xmarmalade/alisthelper/issues). Please be sure to provide a clear description of the problem or feature request, along with any relevant context or steps to reproduce the issue. 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alisthelper 2 | 3 |

4 | 5 |

6 | 7 | English | [简体中文](./README_zh-Hans.md) | [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md) 8 | 9 | ![](https://img.shields.io/badge/language-dart-blue.svg?style=for-the-badge&color=00ACC1) 10 | ![Downloads](https://img.shields.io/badge/flutter-00B0FF?style=for-the-badge&logo=flutter) 11 | [![](https://img.shields.io/github/downloads/Xmarmalade/alisthelper/total?style=for-the-badge&color=FF2196)](https://github.com/Xmarmalade/alisthelper/releases) 12 | [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Xmarmalade/alisthelper?include_prereleases&style=for-the-badge)](https://github.com/Xmarmalade/alisthelper/releases/latest) 13 | [![](https://img.shields.io/github/license/Xmarmalade/alisthelper?style=for-the-badge)](./LICENSE) 14 | ![](https://img.shields.io/github/stars/Xmarmalade/alisthelper?style=for-the-badge) 15 | ![](https://img.shields.io/github/issues/Xmarmalade/alisthelper?style=for-the-badge&color=9C27B0) 16 | 17 | Alist Helper is an application developed using Flutter, designed to simplify the use of the desktop version of alist. It can manage alist, allowing you to easily start and stop the alist program. 18 | 19 | *Maintainer needed for the macOS part of the code. No new macOS-related changes or updates will be accepted until volunteers.* 20 | 21 | ### Screenshots 22 | | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/5b77df3a-8b07-40e4-adc5-9f0907f6a3f9) | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/5a85db81-de92-4362-8c01-73e89482dcb7) | 23 | | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | 24 | | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/0f28c3a0-aac5-40ac-87e1-e53ae597a738) | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/e1b23c3c-cc62-4df8-8406-da41f798416e) | 25 | 26 | Alist Helper includes several useful features: 27 | 28 | - Automatic launching of alist 29 | - Minimizing to the system tray 30 | - Automatic startup on boot, with the option for silent startup 31 | - Quick access to alist version and administrator information 32 | - Adjustable alist startup parameters. You can customize the startup parameters to meet your specific needs and preferences. 33 | 34 | Free. No tracking. No ads. 35 | 36 | Currently, this app is available on Windows and macOS. Adaptation plans for more platforms are in progress. 37 | 38 | Please note that this program does not include the binary files for alist. You will need to download them manually. 39 | 40 | | | alist | alisthelper | alist desktop | 41 | | ------------------- | ---------------------------- | ----------- | --------------- | 42 | | Price | 🆓 Free | 🆓 Free | 💰8$/50¥ | 43 | | Startup at boot | 🛠️ Needs manual configuration | ✅ Supported | ✅ Supported | 44 | | Silent startup | ❌ Not supported | ✅ Supported | ✅ Supported | 45 | | Accompanied startup | ❌ Not supported | ✅ Supported | ✅ Supported | 46 | | GUI | ❌ Not supported | ✅ Supported | ✅ Supported | 47 | | System tray | ❌ Not supported | ✅ Supported | ✅ Supported | 48 | | Startup parameters | 🛠️ Needs manual configuration | ✅ Supported | ❌ Not supported | 49 | | Http proxy | 🛠️ Needs manual configuration | ✅ Supported | ❌ Not supported | 50 | 51 | ### Getting Started 52 | [Wiki (Simplified Chinese language)](https://github.com/Xmarmalade/alisthelper/wiki) 53 | 54 | ## Contributing to AlistHelper 55 | 56 | AlistHelper is an open-source project, and we welcome contributions from anyone who is interested in helping improve the app. Whether you're a developer, a translator, or a documentation writer, there are many ways to get involved. 57 | 58 | ### Getting Started 59 | 60 | If you're interested in contributing code to AlistHelper, you'll need to follow these steps: 61 | 62 | ### Run 63 | 64 | Fork the repository and install [Flutter](https://flutter.dev). 65 | 66 | After you have installed [Flutter](https://flutter.dev), then you can start this app by typing the following commands: 67 | 68 | ```shell 69 | flutter pub get 70 | dart run build_runner build 71 | flutter run 72 | ``` 73 | 74 | ### Translation 75 | 76 | You can help translating this app to other languages! 77 | 78 | 1. Fork this repository 79 | 2. Choose one 80 | - Add missing translations in existing languages: Only update `_missing_translations_.json` in [lib/i18n](https://github.com/Xmarmalade/alisthelper/tree/master/lib/i18n) 81 | - Fix existing translations: Update `strings_.i18n.json` in [lib/i18n](https://github.com/Xmarmalade/alisthelper/tree/master/lib/i18n) 82 | - Add new languages: Create a new file, see also: [locale codes](https://saimana.com/list-of-country-locale-code/). 83 | 3. Optional: Re-run this app 84 | 1. Make sure you have [run](#run) this app once. 85 | 2. Update translations via `dart run build_runner build` 86 | 3. Run app via `flutter run` 87 | 4. Open a pull request 88 | 89 | #### _Take note:_ Fields decorated with `@` are not meant to be translated, they are not used in the app in any way, being merely informative text about the file or to give context to the translator. 90 | 91 | ### Contributing Guidelines 92 | 93 | Before you submit a pull request to AlistHelper, please ensure that you have followed these guidelines: 94 | 95 | - Code should be well-documented and formatted according to the [Dart Style Guide](https://dart.dev/guides/language/effective-dart/style). 96 | - All changes should be covered by tests. 97 | - Commits should be well-written and descriptive, with a clear summary of the changes made and any relevant context. 98 | - Pull requests should target the `master` branch and include a clear summary of the changes made. 99 | 100 | ### Bug Reports and Feature Requests 101 | 102 | If you encounter a bug in AlistHelper or have a feature request, please submit an issue to the [issue tracker](https://github.com/Xmarmalade/alisthelper/issues). Please be sure to provide a clear description of the problem or feature request, along with any relevant context or steps to reproduce the issue. 103 | -------------------------------------------------------------------------------- /README_zh-Hans.md: -------------------------------------------------------------------------------- 1 | # alisthelper 2 | 3 |

4 | 5 |

6 | 7 | [English](./README.md) | 简体中文 | [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md) 8 | 9 | ![](https://img.shields.io/badge/language-dart-blue.svg?style=for-the-badge&color=00ACC1) 10 | ![Downloads](https://img.shields.io/badge/flutter-00B0FF?style=for-the-badge&logo=flutter) 11 | [![](https://img.shields.io/github/downloads/Xmarmalade/alisthelper/total?style=for-the-badge&color=FF2196)](https://github.com/Xmarmalade/alisthelper/releases) 12 | [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Xmarmalade/alisthelper?include_prereleases&style=for-the-badge)](https://github.com/Xmarmalade/alisthelper/releases/latest) 13 | [![](https://img.shields.io/github/license/Xmarmalade/alisthelper?style=for-the-badge)](./LICENSE) 14 | ![](https://img.shields.io/github/stars/Xmarmalade/alisthelper?style=for-the-badge) 15 | ![](https://img.shields.io/github/issues/Xmarmalade/alisthelper?style=for-the-badge&color=9C27B0) 16 | 17 | Alist Helper是一款使用Flutter开发的应用程序,旨在简化桌面版alist的使用。它可以管理alist,让您更轻松地开启、关闭alist程序。 18 | 19 | ### 截图 20 | | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/26b3e59a-ab5c-49de-b590-1374f45fbc34) | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/17d661cd-75df-470d-9ee0-afc8b4c6fa6e) | 21 | | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | 22 | | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/5b65fd3c-e0b6-4135-bf3f-7ea10cd7d642) | ![image](https://github.com/Xmarmalade/alisthelper/assets/16839488/f780f2a7-6294-4849-be5b-822f10530796) | 23 | 24 | Alist Helper包括多个实用功能 25 | 26 | - 自动启动alist 27 | - 最小化至系统托盘 28 | - 开机自启和开机静默启动 29 | - 能够快速查看alist的版本和管理员信息 30 | - 可调整的alist启动参数。你可以可以根据自己的特定需求和偏好来自定义启动参数。 31 | 32 | 免费。无跟踪。无广告。 33 | 34 | 目前,此应用可在 Windows 和 macOS 上使用。更多平台的适配计划正在进行中。 35 | 36 | 特别注意,本程序不包含alist的二进制文件,您需要手动下载。 37 | 38 | | | alist | alisthelper | alist desktop | 39 | | -------- | -------------- | ----------- | ------------- | 40 | | 价格 | 🆓 Free | 🆓 Free | 💰8$/50¥ | 41 | | 开机自启 | 🛠️ 需要手动配置 | ✅ 支持 | ✅ 支持 | 42 | | 静默启动 | ❌ 不支持 | ✅ 支持 | ✅ 支持 | 43 | | 伴随启动 | ❌ 不支持 | ✅ 支持 | ✅ 支持 | 44 | | GUI | ❌ 不支持 | ✅ 支持 | ✅ 支持 | 45 | | 系统托盘 | ❌ 不支持 | ✅ 支持 | ✅ 支持 | 46 | | 参数调整 | 🛠️ 需要手动配置 | ✅ 支持 | ❌ 不支持 | 47 | | Http代理 | 🛠️ 需要手动配置 | ✅ 支持 | ❌ 不支持 | 48 | 49 | ### 开始使用 50 | [Wiki](https://github.com/Xmarmalade/alisthelper/wiki) 51 | 52 | ## 贡献 53 | 54 | AlistHelper 是一个开源项目,我们欢迎任何有兴趣帮助改进该应用程序的人进行贡献。无论你是开发人员、翻译者还是文档编写者,都有很多参与方式。 55 | 56 | ### 入门指南 57 | 58 | 如果你有意向为 AlistHelper 贡献代码,你需要遵循以下步骤: 59 | 60 | ### 运行 61 | 62 | Fork存储库并安装[Flutter](https://flutter.dev)。 63 | 64 | 在你安装了[Flutter](https://flutter.dev)之后,你可以通过键入以下命令来启动该应用程序: 65 | 66 | ```shell 67 | flutter pub get 68 | dart run build_runner build 69 | flutter run 70 | ``` 71 | 72 | ### 翻译 73 | 74 | 你可以帮助将该应用程序翻译成其他语言! 75 | 76 | 1. Fork该存储库 77 | 2. 选择一项 78 | - 添加缺失的现有语言翻译:只需更新[lib/i18n](https://github.com/Xmarmalade/alisthelper/tree/master/lib/i18n)中的`_missing_translations_.json` 79 | - 修复现有翻译:更新[lib/i18n](https://github.com/Xmarmalade/alisthelper/tree/master/lib/i18n)中的`strings_.i18n.json` 80 | - 添加新语言:创建一个新文件,关于`locale`参见:[locale codes](https://saimana.com/list-of-country-locale-code/)。 81 | 3. 可选项:重新运行该应用程序 82 | 1. 确保你已经[运行](#run)过该应用程序。 83 | 2. 通过`dart run build_runner build`更新翻译 84 | 3. 通过`flutter run`运行应用程序 85 | 4. 提交拉取请求 86 | 87 | #### _请注意:_ 使用`@`标记装饰的字段不应被翻译,它们在应用程序中不会被使用,仅仅是有关该文件的信息文本或为翻译者提供上下文。 88 | 89 | ### 贡献指南 90 | 91 | 在向AlistHelper提交拉取请求之前,请确保你遵循了以下准则: 92 | 93 | - 代码应该有良好的文档,并根据[Dart风格指南](https://dart.dev/guides/language/effective-dart/style)进行格式化。 94 | - 所有更改都应该有测试覆盖。 95 | - 提交的注释应该写得清晰明了,概述更改内容和任何相关上下文。 96 | - 拉取请求应该针对`master`分支,并包含对更改的清晰概述。 97 | 98 | ### 缺陷报告和功能请求 99 | 100 | 如果你在AlistHelper中遇到了一个缺陷或者有一个功能请求,请在[问题跟踪器](https://github.com/Xmarmalade/alisthelper/issues)中提交一个问题。请确保提供清晰的问题或功能请求描述,以及任何相关的上下文或复现该问题的步骤。 101 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/alisthelper.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/assets/alisthelper.ico -------------------------------------------------------------------------------- /assets/alisthelper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/assets/alisthelper.png -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | # build.yaml 2 | targets: 3 | $default: 4 | builders: 5 | slang_build_runner: 6 | options: 7 | base_locale: en 8 | fallback_strategy: base_locale 9 | generate_for: 10 | - lib/i18n/*.json 11 | json_serializable: 12 | generate_for: 13 | - lib/model/*.dart 14 | freezed|freezed: 15 | generate_for: 16 | - lib/model/*.dart -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /lib/i18n/zh-Hans-CN.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "locale": "简体中文", 3 | "alistOperation": { 4 | "startAlist": "启动", 5 | "endAlist": "关闭", 6 | "openGUI": "打开 Web GUI", 7 | "genRandomPwd": "生成随机密码", 8 | "getVersion": "版本信息" 9 | }, 10 | "rcloneOperation": { 11 | "startRclone": "启动", 12 | "endRclone": "关闭", 13 | "getRcloneInfo": "版本信息", 14 | "viewLogs": "查看日志", 15 | "noVdisks": "未设置远程磁盘。", 16 | "deleteVdisk": "您确定要删除 ${name} 吗?此操作不可逆!", 17 | "createVdisk": { 18 | "title": "创建远程磁盘", 19 | "description": "创建一个远程磁盘,如果您不理解这里的选项,请寻求帮助。", 20 | "name": "名称", 21 | "nameHint": "磁盘的名称", 22 | "path": "路径", 23 | "pathHint": "dav/ 后的路径", 24 | "mountPoint": "挂载点", 25 | "mountPointHint": "系统中磁盘的挂载点", 26 | "extraFlags": "额外参数", 27 | "extraFlagsHint": "rclone 挂载的额外参数", 28 | "enableAutoMount": "启用自动挂载", 29 | "enableAutoMountHint": "rclone 启动后自动挂载此磁盘" 30 | } 31 | }, 32 | "firstLaunch": { 33 | "intro": "欢迎使用 Alist Helper,让我们开始吧!", 34 | "about": "点击此处了解更多关于 Alist Helper 的信息!", 35 | "chooseTheme": "首先,让我们在这里选择您喜欢的主题!", 36 | "chooseDirectory": "第二步,您需要安装 alist 并告诉 Alist Helper 它在哪里。", 37 | "autoInstall": "Alist Helper现已支持自动安装 ${name},点击这里安装。", 38 | "getAlist": "想要使用特定版本的alist或是手动安装?点击这里了解。", 39 | "finish": "完成!您可以在设置中更改这些设置!" 40 | }, 41 | "tabs": { 42 | "home": "首页", 43 | "mount": "挂载", 44 | "settings": "设置" 45 | }, 46 | "home": { 47 | "options": "选项", 48 | "logs": "日志", 49 | "manage": "管理" 50 | }, 51 | "languageSettings": { 52 | "language": "语言", 53 | "system": "跟随系统" 54 | }, 55 | "button": { 56 | "ok": "确认", 57 | "cancel": "取消", 58 | "yes": "是", 59 | "no": "否", 60 | "select": "选择", 61 | "edit": "编辑", 62 | "save": "保存", 63 | "upgrade": "升级", 64 | "add": "添加", 65 | "docs": "文档", 66 | "sponsor": "赞助" 67 | }, 68 | "tray": { 69 | "open": "打开", 70 | "hide": "隐藏", 71 | "quit": "退出", 72 | "openGUI": "打开 Web GUI", 73 | "startAlist": "启动 Alist 服务", 74 | "endAlist": "关闭 Alist 服务", 75 | "tooltip": "Alist Helper", 76 | "workingTooltip": "Alist Helper (Alist 运行中)" 77 | }, 78 | "settings": { 79 | "interfaceSettings": { 80 | "title": "界面", 81 | "themeMode": "主题模式", 82 | "themeColor": "主题颜色", 83 | "language": "选择语言" 84 | }, 85 | "theme": { 86 | "light": "亮色", 87 | "dark": "暗色", 88 | "system": "跟随系统" 89 | }, 90 | "alistHelperSettings": { 91 | "title": "Alist Helper 选项", 92 | "saveWindowPlacement": { 93 | "title": "保存窗口位置", 94 | "description": "这将允许 Alist Helper 保存窗口位置。" 95 | }, 96 | "minimizeToTray": { 97 | "title": "允许最小化到托盘", 98 | "description": "这将允许 Alist Helper 最小化到托盘。" 99 | }, 100 | "autoStart": { 101 | "title": "允许自动启动", 102 | "description": "这将允许 Alist Helper 自动启动。" 103 | }, 104 | "autoStartLaunchMinimized": { 105 | "title": "允许静默自动启动", 106 | "description": "这将允许 Alist Helper 静默自动启动。" 107 | } 108 | }, 109 | "alistSettings": { 110 | "title": "Alist 选项", 111 | "autoStartAlist": { 112 | "title": "允许 alist 自动启动", 113 | "description": "这将在 Alist Helper 启动时自动启动 alist。" 114 | }, 115 | "autoStartLaunchMinimized": { 116 | "title": "允许静默自动启动", 117 | "description": "这将允许 Alist Helper 静默自动启动。" 118 | }, 119 | "workingDirectory": { 120 | "title": "工作目录", 121 | "description": "设置工作目录", 122 | "hint": "这是一个目录,不是文件!", 123 | "chooseFrom": "选择目录", 124 | "notFound": "目录中找不到 ${exec} 程序", 125 | "found": "找到 ${exec} 程序" 126 | }, 127 | "argumentsList": { 128 | "title": "启动参数列表", 129 | "description": "编辑 alist 的启动参数", 130 | "descriptionForRclone": "编辑 rclone 的启动参数。如果你不明白这里的选项,请寻求帮助", 131 | "editArguments": "编辑参数", 132 | "addArgument": "添加参数", 133 | "removeAll": "移除所有参数", 134 | "remove": "移除参数" 135 | }, 136 | "proxy": { 137 | "title": "Http 代理", 138 | "description": "设置 Http 代理", 139 | "hint": "如果留空, 将不会启用 Http 代理", 140 | "success": "Http 代理地址已设置", 141 | "error": "非法的 Http 代理地址" 142 | } 143 | }, 144 | "rcloneSettings": { 145 | "title": "Rclone 选项", 146 | "rcloneDirNotSet": "您可以使用 rclone 将您的网络驱动器挂载到计算机上。但是,尚未设置 Rclone 目录。请转到设置并配置 Rclone 目录以继续。", 147 | "rcloneWebdavNotSet": "您可以使用 rclone 将您的网络驱动器挂载到计算机上。但是,尚未设置用于访问 alist 的 WebDav 帐户。请转到设置并配置 WebDav 帐户以继续。", 148 | "autoStartAlist": { 149 | "title": "允许 rclone 自动启动", 150 | "description": "这将在 Alist Helper 启动时自动启动 rclone。" 151 | }, 152 | "startAfterAlist": { 153 | "title": "要求在 alist 启动后启动", 154 | "description": "这将要求 rclone 在 alist 启动后启动。" 155 | }, 156 | "account": { 157 | "title": "WebDav 帐户", 158 | "description": "设置 WebDav 帐户", 159 | "name": "帐户名", 160 | "pass": "密码" 161 | } 162 | }, 163 | "others": { 164 | "title": "其他", 165 | "checkForUpdates": "检查更新", 166 | "about": "关于" 167 | } 168 | }, 169 | "upgrade": { 170 | "clickToCheck": "单击旁边的按钮以检查更新", 171 | "checkFirst": "请先检查更新", 172 | "canUpgrade": "发现新版本,您想升级吗?", 173 | "noUpgrade": "您正在使用最新版本。", 174 | "upgrade": "升级", 175 | "selectPackage": "选择要下载的更新软件包。", 176 | "networkError": "正在加载中...若长时间未加载请检查网络并重试。", 177 | "alistVersion": { 178 | "title": "Alist 版本", 179 | "currentVersion": "当前 Alist 版本", 180 | "latestVersion": "最新 Alist 版本" 181 | }, 182 | "alistHelperVersion": { 183 | "title": "Alist Helper 版本", 184 | "currentVersion": "当前 Alist Helper 版本", 185 | "latestVersion": "最新 Alist Helper 版本" 186 | }, 187 | "rcloneVersion": { 188 | "title": "Rclone 版本", 189 | "currentVersion": "当前 Rclone 版本", 190 | "latestVersion": "最新 Rclone 版本" 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /lib/i18n/zh-Hant-TW.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "locale": "正體中文", 3 | "alistOperation": { 4 | "startAlist": "啟動", 5 | "endAlist": "關閉", 6 | "openGUI": "開啟 Web GUI", 7 | "genRandomPwd": "生成隨機密碼", 8 | "getVersion": "版本資訊" 9 | }, 10 | "rcloneOperation": { 11 | "startRclone": "啟動", 12 | "endRclone": "關閉", 13 | "getRcloneInfo": "版本資訊", 14 | "viewLogs": "查看日誌", 15 | "noVdisks": "未設置遠程磁盤。", 16 | "deleteVdisk": "您確定要刪除 ${name} 嗎?此操作不可逆!", 17 | "createVdisk": { 18 | "title": "創建遠程磁盤", 19 | "description": "創建遠程磁盤,如果您不理解這裡的選項,請尋求幫助。", 20 | "name": "名稱", 21 | "nameHint": "磁盤的名稱", 22 | "path": "路徑", 23 | "pathHint": "dav/ 之後的路徑", 24 | "mountPoint": "掛載點", 25 | "mountPointHint": "系統上磁盤的掛載點", 26 | "extraFlags": "額外標誌", 27 | "extraFlagsHint": "rclone 掛載的額外標誌", 28 | "enableAutoMount": "啟用自動掛載", 29 | "enableAutoMountHint": "rclone 啟動後自動掛載此磁盤" 30 | } 31 | }, 32 | "firstLaunch": { 33 | "intro": "歡迎使用 Alist Helper,讓我們開始吧!", 34 | "about": "點擊此處瞭解更多關於 Alist Helper 的資訊!", 35 | "chooseTheme": "首先,讓我們在這裡選擇您喜歡的主題!", 36 | "chooseDirectory": "第二步,您需要安装 alist 並告訴 Alist Helper 它在哪裡。", 37 | "autoInstall": "Alist Helper現已支持自動安裝 ${name} ,點擊這裡以開始安裝。", 38 | "getAlist": "想要使用特定版本的alist或是手動安裝?點擊這裡。", 39 | "finish": "完成!您可以在設定中更改這些設定!" 40 | }, 41 | "tabs": { 42 | "home": "首頁", 43 | "mount": "掛載", 44 | "settings": "設定" 45 | }, 46 | "home": { 47 | "options": "選項", 48 | "logs": "日誌", 49 | "manage": "管理" 50 | }, 51 | "languageSettings": { 52 | "language": "語言", 53 | "system": "跟隨系統" 54 | }, 55 | "button": { 56 | "ok": "確認", 57 | "cancel": "取消", 58 | "yes": "是", 59 | "no": "否", 60 | "select": "選擇", 61 | "edit": "編輯", 62 | "save": "儲存", 63 | "upgrade": "升級", 64 | "add": "新增", 65 | "docs": "文档", 66 | "sponsor": "赞助" 67 | }, 68 | "tray": { 69 | "open": "開啟", 70 | "hide": "隱藏", 71 | "quit": "退出", 72 | "openGUI": "開啟 Web GUI", 73 | "startAlist": "啟動 Alist 服務", 74 | "endAlist": "關閉 Alist 服務", 75 | "tooltip": "Alist Helper", 76 | "workingTooltip": "Alist Helper (Alist 運行中)" 77 | }, 78 | "settings": { 79 | "interfaceSettings": { 80 | "title": "介面", 81 | "themeMode": "主題模式", 82 | "themeColor": "主題顏色", 83 | "language": "選擇語言" 84 | }, 85 | "theme": { 86 | "light": "亮色", 87 | "dark": "暗色", 88 | "system": "跟隨系統" 89 | }, 90 | "alistHelperSettings": { 91 | "title": "Alist Helper 選項", 92 | "saveWindowPlacement": { 93 | "title": "儲存視窗位置", 94 | "description": "這將允許 Alist Helper 儲存視窗位置。" 95 | }, 96 | "minimizeToTray": { 97 | "title": "允許最小化到托盤", 98 | "description": "這將允許 Alist Helper 最小化到托盤。" 99 | }, 100 | "autoStart": { 101 | "title": "允許自動啟動", 102 | "description": "這將允許 Alist Helper 自動啟動。" 103 | }, 104 | "autoStartLaunchMinimized": { 105 | "title": "允許靜默自動啟動", 106 | "description": "這將允許 Alist Helper 靜默自動啟動。" 107 | } 108 | }, 109 | "alistSettings": { 110 | "title": "Alist 選項", 111 | "autoStartAlist": { 112 | "title": "允許 alist 自動啟動", 113 | "description": "這將在 Alist Helper 啟動時自動啟動 alist。" 114 | }, 115 | "autoStartLaunchMinimized": { 116 | "title": "允許靜默自動啟動", 117 | "description": "這將允許 Alist Helper 靜默自動啟動。" 118 | }, 119 | "workingDirectory": { 120 | "title": "工作目錄", 121 | "description": "設定工作目錄", 122 | "hint": "這是一個目錄,不是檔案!", 123 | "chooseFrom": "選擇目錄", 124 | "notFound": "目錄中找不到 ${exec} 程式", 125 | "found": "找到 ${exec} 程式" 126 | }, 127 | "argumentsList": { 128 | "title": "啟動參數列表", 129 | "description": "編輯 alist 的啟動參數", 130 | "descriptionForRclone": "編輯 rclone 的啟動參數。如果你不明白這裡的選項,請尋求幫助", 131 | "editArguments": "編輯參數", 132 | "addArgument": "新增參數", 133 | "removeAll": "移除所有參數", 134 | "remove": "移除參數" 135 | }, 136 | "proxy": { 137 | "title": "Http 代理", 138 | "description": "設定 Http 代理", 139 | "hint": "如果留空, 將不會啟用 Http 代理", 140 | "success": "Http 代理位址已設定", 141 | "error": "非法的 Http 代理位址" 142 | } 143 | }, 144 | "rcloneSettings": { 145 | "title": "Rclone 選項", 146 | "rcloneDirNotSet": "您可以使用 rclone 將您的網絡驅動器掛載到計算機上。但是,尚未設置 Rclone 目錄。請轉到設置並配置 Rclone 目錄以繼續。", 147 | "rcloneWebdavNotSet": "您可以使用 rclone 將您的網絡驅動器掛載到計算機上。但是,尚未設置用於訪問 alist 的 WebDav 帳戶。請轉到設置並配置 WebDav 帳戶以繼續。", 148 | "autoStartAlist": { 149 | "title": "允許 rclone 自動啟動", 150 | "description": "這將在 Alist Helper 啟動時自動啟動 rclone。" 151 | }, 152 | "startAfterAlist": { 153 | "title": "要求在 alist 啟動後啟動", 154 | "description": "這將要求 rclone 在 alist 啟動後啟動。" 155 | }, 156 | "account": { 157 | "title": "WebDav 帳戶", 158 | "description": "設定 WebDav 帳戶", 159 | "name": "帳戶名稱", 160 | "pass": "密碼" 161 | } 162 | }, 163 | "others": { 164 | "title": "其他", 165 | "checkForUpdates": "檢查更新", 166 | "about": "關於" 167 | } 168 | }, 169 | "upgrade": { 170 | "clickToCheck": "點擊旁邊的按鈕以檢查更新", 171 | "checkFirst": "請先檢查更新", 172 | "canUpgrade": "發現新版本,您想升級嗎?", 173 | "noUpgrade": "您正在使用最新版本。", 174 | "upgrade": "升級", 175 | "selectPackage": "選擇要下載的更新軟體包。", 176 | "networkError": "正在載入中...若長時間未載入請檢查網路並重試。", 177 | "alistVersion": { 178 | "title": "Alist 版本", 179 | "currentVersion": "目前 Alist 版本", 180 | "latestVersion": "最新 Alist 版本" 181 | }, 182 | "alistHelperVersion": { 183 | "title": "Alist Helper 版本", 184 | "currentVersion": "目前 Alist Helper 版本", 185 | "latestVersion": "最新 Alist Helper 版本" 186 | }, 187 | "rcloneVersion": { 188 | "title": "Rclone 版本", 189 | "currentVersion": "目前 Rclone 版本", 190 | "latestVersion": "最新 Rclone 版本" 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:alisthelper/i18n/strings.g.dart'; 4 | import 'package:alisthelper/provider/alist_provider.dart'; 5 | import 'package:alisthelper/provider/app_arguments_provider.dart'; 6 | import 'package:alisthelper/provider/persistence_provider.dart'; 7 | import 'package:alisthelper/provider/rclone_provider.dart'; 8 | import 'package:alisthelper/provider/settings_provider.dart'; 9 | import 'package:alisthelper/theme.dart'; 10 | import 'package:alisthelper/utils/init.dart'; 11 | import 'package:alisthelper/utils/native/tray_helper.dart'; 12 | import 'package:alisthelper/utils/native/tray_manager.dart'; 13 | import 'package:alisthelper/utils/native/window_watcher.dart'; 14 | import 'package:alisthelper/widgets/pages/home.dart'; 15 | import 'package:flutter/material.dart'; 16 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 17 | import 'package:flutter_localizations/flutter_localizations.dart'; 18 | 19 | Future main(List args) async { 20 | final persistenceService = await preInit(args); 21 | 22 | runApp(ProviderScope(overrides: [ 23 | persistenceProvider.overrideWithValue(persistenceService), 24 | appArgumentsProvider.overrideWith((ref) => args), 25 | ], child: TranslationProvider(child: const MyApp()))); 26 | } 27 | 28 | class MyApp extends ConsumerWidget { 29 | const MyApp({super.key}); 30 | 31 | @override 32 | Widget build(BuildContext context, WidgetRef ref) { 33 | final settings = ref.watch(settingsProvider); 34 | final alistNotifier = ref.watch(alistProvider.notifier); 35 | final rcloneNotifier = ref.watch(rcloneProvider.notifier); 36 | return TrayWatcher( 37 | child: WindowWatcher( 38 | onClose: () async { 39 | try { 40 | if (ref.watch(settingsProvider).minimizeToTray) { 41 | await hideToTray(); 42 | } else { 43 | await alistNotifier.endAlist(); 44 | await rcloneNotifier.endRclone(); 45 | exit(0); 46 | } 47 | } catch (e) { 48 | debugPrint(e.toString()); 49 | } 50 | }, 51 | child: MaterialApp( 52 | title: 'Alist Helper', 53 | locale: TranslationProvider.of(context).flutterLocale, 54 | supportedLocales: AppLocaleUtils.supportedLocales, 55 | localizationsDelegates: GlobalMaterialLocalizations.delegates, 56 | themeMode: settings.themeMode, 57 | theme: AlistHelperTheme(settings.themeColor).lightThemeData, 58 | darkTheme: AlistHelperTheme(settings.themeColor).darkThemeData, 59 | home: const Home(), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/model/alist_helper_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'alist_helper_state.freezed.dart'; 4 | 5 | @freezed 6 | abstract class AlistHelperState with _$AlistHelperState { 7 | const factory AlistHelperState({ 8 | @Default('v0.0.0') String currentVersion, 9 | @Default('v0.0.0') String latestVersion, 10 | @Default([]) List newReleaseAssets, 11 | }) = _AlistHelperState; 12 | } 13 | -------------------------------------------------------------------------------- /lib/model/alist_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'alist_state.freezed.dart'; 4 | 5 | @freezed 6 | abstract class AlistState with _$AlistState { 7 | const factory AlistState({ 8 | @Default(false) bool isRunning, 9 | @Default([]) List output, 10 | @Default('http://localhost:5244') String url, 11 | @Default('v1.0.0') String currentVersion, 12 | @Default('v1.0.0') String latestVersion, 13 | @Default([]) List newReleaseAssets, 14 | @Default('') String workDir, 15 | @Default([]) List alistArgs, 16 | @Default('') String proxy, 17 | @Default(UpgradeStatus.idle) UpgradeStatus upgradeStatus, 18 | }) = _AlistState; 19 | } 20 | 21 | enum UpgradeStatus { 22 | idle, 23 | installing, 24 | complete, 25 | } 26 | -------------------------------------------------------------------------------- /lib/model/rclone_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:alisthelper/model/virtual_disk_state.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | part 'rclone_state.freezed.dart'; 7 | 8 | @freezed 9 | abstract class RcloneState with _$RcloneState { 10 | const factory RcloneState({ 11 | @Default(false) bool isRunning, 12 | @Default([]) List output, 13 | @Default([]) List arg, 14 | @Default('http://localhost:5572') String url, 15 | @Default([]) List vdList, 16 | @Default('') String webdavAccount, 17 | @Default([]) List remoteList, 18 | @Default('') String alistToken, 19 | @Default([]) List alistRoot, 20 | @Default('v1.0.0') String currentVersion, 21 | int? pid, 22 | Process? process, 23 | }) = _RcloneState; 24 | } 25 | -------------------------------------------------------------------------------- /lib/model/settings_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'settings_state.freezed.dart'; 6 | 7 | @freezed 8 | abstract class SettingsState with _$SettingsState { 9 | const factory SettingsState({ 10 | required bool minimizeToTray, 11 | required bool autoStartLaunchMinimized, 12 | required bool autoStart, 13 | required bool autoStartAlist, 14 | required bool saveWindowPlacement, 15 | required String workingDirectory, 16 | required String rcloneDirectory, 17 | required ThemeMode themeMode, 18 | required Color themeColor, 19 | required List alistArgs, 20 | required AppLocale? locale, 21 | required String? proxy, 22 | required List rcloneArgs, 23 | required bool isFirstRun, 24 | required bool autoStartRclone, 25 | required bool startAfterAlist, 26 | required String webdavAccount, 27 | }) = _SettingsState; 28 | } 29 | -------------------------------------------------------------------------------- /lib/model/updater_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'updater_state.freezed.dart'; 4 | 5 | @freezed 6 | abstract class UpdaterState with _$UpdaterState { 7 | const factory UpdaterState({ 8 | @Default('v1.0.0') String rcloneCurrentVersion, 9 | @Default('v1.0.0') String rcloneLatestVersion, 10 | @Default([]) List rcloneAssets, 11 | @Default('') String rcloneDirectory, 12 | @Default(UpgradeStatus.idle) UpgradeStatus upgradeStatus, 13 | }) = _UpdaterState; 14 | } 15 | 16 | enum UpgradeStatus { 17 | idle, 18 | installing, 19 | complete, 20 | } 21 | -------------------------------------------------------------------------------- /lib/model/vfsoptions_state.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'vfsoptions_state.freezed.dart'; 6 | part 'vfsoptions_state.g.dart'; 7 | 8 | @freezed 9 | abstract class VfsOptions with _$VfsOptions { 10 | const factory VfsOptions({ 11 | @Default(3600000000000) int CacheMaxAge, 12 | @Default(10737418240) int CacheMaxSize, 13 | @Default('writes') String CacheMode, 14 | @Default(60000000000) int CachePollInterval, 15 | @Default(false) bool CaseInsensitive, 16 | @Default(67108864) int ChunkSize, 17 | @Default(-1) int ChunkSizeLimit, 18 | @Default(300000000000) int DirCacheTime, 19 | @Default(511) int DirPerms, 20 | @Default(511) int FilePerms, 21 | @Default(4294967295) int GID, 22 | @Default(false) bool NoChecksum, 23 | @Default(false) bool NoModTime, 24 | @Default(false) bool NoSeek, 25 | @Default(60000000000) int PollInterval, 26 | @Default(0) int ReadAhead, 27 | @Default(false) bool ReadOnly, 28 | @Default(20000000) int ReadWait, 29 | @Default(4294967295) int UID, 30 | @Default(0) int Umask, 31 | @Default(5000000000) int WriteBack, 32 | @Default(1000000000) int WriteWait, 33 | @Default(false) bool Refresh, 34 | @Default(false) bool BlockNormDupes, 35 | @Default(false) bool UsedIsSize, 36 | @Default(false) bool FastFingerprint, 37 | @Default(-1) int DiskSpaceTotalSize, 38 | }) = _VfsOptions; 39 | 40 | factory VfsOptions.fromJson(Map json) => 41 | _$VfsOptionsFromJson(json); 42 | } 43 | -------------------------------------------------------------------------------- /lib/model/virtual_disk_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'virtual_disk_state.freezed.dart'; 4 | part 'virtual_disk_state.g.dart'; 5 | 6 | @freezed 7 | abstract class VirtualDiskState with _$VirtualDiskState { 8 | const factory VirtualDiskState({ 9 | @Default(false) bool isMounted, 10 | @Default([]) List extraFlags, 11 | @Default('T') String mountPoint, 12 | @Default('name') String name, 13 | @Default('vendor') String vendor, 14 | @Default('path') String path, 15 | @Default(false) bool autoMount, 16 | }) = _VirtualDiskState; 17 | 18 | factory VirtualDiskState.fromJson(Map json) => 19 | _$VirtualDiskStateFromJson(json); 20 | } 21 | -------------------------------------------------------------------------------- /lib/provider/alist_helper_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:alisthelper/model/alist_helper_state.dart'; 6 | import 'package:alisthelper/provider/persistence_provider.dart'; 7 | import 'package:http/http.dart' as http; 8 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 9 | 10 | final ahProvider = NotifierProvider( 11 | AlistHelperNotifier.new); 12 | 13 | class AlistHelperNotifier extends Notifier { 14 | late PersistenceService _persistenceService; 15 | late String currentAlistHelperVersion; 16 | 17 | @override 18 | AlistHelperState build() { 19 | _persistenceService = ref.watch(persistenceProvider); 20 | currentAlistHelperVersion = _persistenceService.getAlistHelperVersion(); 21 | return const AlistHelperState(); 22 | } 23 | 24 | // Get alist version 25 | Future getAlistHelperCurrentVersion() async { 26 | state = state.copyWith(currentVersion: currentAlistHelperVersion); 27 | } 28 | 29 | Future fetchAlistHelperLatestVersion() async { 30 | final response = await http.get(Uri.parse( 31 | 'https://api.github.com/repos/Xmarmalade/alisthelper/releases/latest')); 32 | final json = jsonDecode(response.body) as Map; 33 | try { 34 | String latest = json['tag_name']; 35 | List assets = json['assets']; 36 | String platformKey = Platform.isWindows 37 | ? 'windows' 38 | : (Platform.isMacOS ? 'macos' : 'linux'); 39 | List assetsForSpecificPlatform = []; 40 | for (Map asset in assets) { 41 | if (asset['name'].contains(platformKey)) { 42 | assetsForSpecificPlatform.add(asset); 43 | } 44 | } 45 | state = state.copyWith( 46 | latestVersion: latest, newReleaseAssets: assetsForSpecificPlatform); 47 | } catch (e) { 48 | throw Exception( 49 | '$e\nFailed to get latest version when fetching: ${json.toString()}'); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/provider/app_arguments_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | 3 | /// Contains the arguments with which the app was started. 4 | final appArgumentsProvider = Provider((ref) => []); -------------------------------------------------------------------------------- /lib/provider/settings_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:alisthelper/utils/native/auto_start_helper.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:alisthelper/model/settings_state.dart'; 5 | import 'package:alisthelper/provider/persistence_provider.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | 8 | final settingsProvider = 9 | NotifierProvider(SettingsNotifier.new); 10 | 11 | class SettingsNotifier extends Notifier { 12 | late PersistenceService _persistenceService; 13 | 14 | @override 15 | SettingsState build() { 16 | _persistenceService = ref.watch(persistenceProvider); 17 | return SettingsState( 18 | locale: _persistenceService.getLocale(), 19 | autoStartAlist: _persistenceService.isAutoStartAlist(), 20 | minimizeToTray: _persistenceService.isMinimizeToTray(), 21 | autoStartLaunchMinimized: 22 | _persistenceService.isAutoStartLaunchMinimized(), 23 | autoStart: _persistenceService.isAutoStart(), 24 | workingDirectory: _persistenceService.getWorkingDirectory(), 25 | rcloneDirectory: _persistenceService.getRcloneDirectory(), 26 | themeMode: _persistenceService.getThemeMode(), 27 | themeColor: _persistenceService.getThemeColor(), 28 | saveWindowPlacement: _persistenceService.getSaveWindowPlacement(), 29 | alistArgs: _persistenceService.getAlistArgs(), 30 | proxy: _persistenceService.getProxy(), 31 | rcloneArgs: _persistenceService.getRcloneArgs(), 32 | isFirstRun: _persistenceService.isFirstRun(), 33 | autoStartRclone: _persistenceService.isAutoStartRclone(), 34 | startAfterAlist: _persistenceService.isStartAfterAlist(), 35 | webdavAccount: _persistenceService.getWebdavAccount(), 36 | ); 37 | } 38 | 39 | Future setWebdavAccount(String value) async { 40 | await _persistenceService.setWebdavAccount(value); 41 | state = state.copyWith(webdavAccount: value); 42 | } 43 | 44 | Future setAutoStartRclone(bool value) async { 45 | await _persistenceService.setAutoStartRclone(value); 46 | state = state.copyWith(autoStartRclone: value); 47 | } 48 | 49 | Future setStartAfterAlist(bool value) async { 50 | await _persistenceService.setStartAfterAlist(value); 51 | state = state.copyWith(startAfterAlist: value); 52 | } 53 | 54 | Future setFirstRun(bool value) async { 55 | await _persistenceService.setFirstRun(value); 56 | state = state.copyWith(isFirstRun: value); 57 | } 58 | 59 | Future setProxy(String? proxy) async { 60 | await _persistenceService.setProxy(proxy); 61 | state = state.copyWith(proxy: proxy); 62 | } 63 | 64 | Future setLocale(AppLocale? locale) async { 65 | await _persistenceService.setLocale(locale); 66 | state = state.copyWith(locale: locale); 67 | } 68 | 69 | Future setAutoStartAlist(bool value) async { 70 | await _persistenceService.setAutoStartAlist(value); 71 | state = state.copyWith(autoStartAlist: value); 72 | } 73 | 74 | Future setThemeColor(Color value) async { 75 | await _persistenceService.setThemeColor(value); 76 | state = state.copyWith(themeColor: value); 77 | } 78 | 79 | Future setThemeMode(ThemeMode value) async { 80 | await _persistenceService.setThemeMode(value); 81 | state = state.copyWith(themeMode: value); 82 | } 83 | 84 | Future setMinimizeToTray(bool value) async { 85 | await _persistenceService.setMinimizeToTray(value); 86 | state = state.copyWith(minimizeToTray: value); 87 | } 88 | 89 | Future setAutoStartLaunchMinimized(bool value) async { 90 | await _persistenceService.setAutoStartLaunchMinimized(value); 91 | state = state.copyWith(autoStartLaunchMinimized: value); 92 | } 93 | 94 | Future setAutoStart(bool value) async { 95 | await _persistenceService.setAutoStart(value); 96 | initAutoStartAndOpenSettings(value); 97 | state = state.copyWith(autoStart: value); 98 | } 99 | 100 | Future setWorkingDirectory(String value) async { 101 | await _persistenceService.setWorkingDirectory(value); 102 | state = state.copyWith(workingDirectory: value); 103 | } 104 | 105 | Future setRcloneDirectory(String value) async { 106 | await _persistenceService.setRcloneDirectory(value); 107 | state = state.copyWith(rcloneDirectory: value); 108 | } 109 | 110 | Future setSaveWindowPlacement(bool value) async { 111 | await _persistenceService.setSaveWindowPlacement(value); 112 | state = state.copyWith(saveWindowPlacement: value); 113 | } 114 | 115 | Future setAlistArgs(List value) async { 116 | await _persistenceService.setAlistArgs(value); 117 | state = state.copyWith(alistArgs: value); 118 | } 119 | 120 | Future setRcloneArgs(List value) async { 121 | await _persistenceService.setRcloneArgs(value); 122 | state = state.copyWith(rcloneArgs: value); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/provider/updater_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:alisthelper/model/updater_state.dart'; 4 | import 'package:alisthelper/provider/rclone_provider.dart'; 5 | import 'package:alisthelper/provider/settings_provider.dart'; 6 | import 'package:alisthelper/utils/native/file_helper.dart'; 7 | import 'package:alisthelper/utils/textutils.dart'; 8 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 9 | import 'package:dio/dio.dart'; 10 | import 'package:http/http.dart' as http; 11 | import 'package:logger/logger.dart'; 12 | 13 | final updaterProvider = 14 | NotifierProvider(UpdaterNotifier.new); 15 | 16 | class UpdaterNotifier extends Notifier { 17 | Dio dio = Dio(); 18 | Logger logger = Logger(); 19 | 20 | @override 21 | UpdaterState build() { 22 | return UpdaterState( 23 | rcloneDirectory: ref.watch(settingsProvider).rcloneDirectory, 24 | ); 25 | } 26 | 27 | Future getRcloneInfo() async { 28 | try { 29 | Process process; 30 | if (Platform.isWindows) { 31 | process = await Process.start( 32 | '${state.rcloneDirectory}\\rclone.exe', ['version'], 33 | workingDirectory: state.rcloneDirectory); 34 | } else { 35 | process = await Process.start( 36 | '${state.rcloneDirectory}/rclone', ['version'], 37 | workingDirectory: state.rcloneDirectory); 38 | } 39 | process.stdout.listen((data) { 40 | String text = TextUtils.stdDecode(data, false); 41 | _parseVersion(text); 42 | }); 43 | process.stderr.listen((data) { 44 | String text = TextUtils.stdDecode(data, false); 45 | logger.e('stderr: $text'); 46 | }); 47 | } on ProcessException catch (e) { 48 | logger.e('ProcessException: $e'); 49 | } 50 | } 51 | 52 | void _parseVersion(String output) { 53 | final versionRegExp = RegExp(r'rclone v([\d.]+)'); 54 | final match = versionRegExp.firstMatch(output); 55 | if (match != null) { 56 | final version = 'v${match.group(1)}'; 57 | logger.e('Current version: $version'); 58 | state = state.copyWith(rcloneCurrentVersion: version); 59 | } 60 | } 61 | 62 | Future getRcloneCurrentVersion() async { 63 | await getRcloneInfo(); 64 | } 65 | 66 | Future fetchLatestVersion() async { 67 | final response = await http.get(Uri.parse( 68 | 'https://api.github.com/repos/rclone/rclone/releases/latest')); 69 | final json = jsonDecode(response.body) as Map; 70 | try { 71 | String latest = json['tag_name']; 72 | List assets = json['assets']; 73 | String platformKey = Platform.isWindows 74 | ? 'windows' 75 | : (Platform.isMacOS ? 'darwin' : 'linux'); 76 | List assetsForSpecificPlatform = []; 77 | for (Map asset in assets) { 78 | if (asset['name'].contains(platformKey)) { 79 | //remove the asset if it's not for the current platform 80 | assetsForSpecificPlatform.add(asset); 81 | } 82 | } 83 | // Preventing uninitialized version string 84 | if (state.rcloneCurrentVersion == "v1.0.0") { 85 | getRcloneCurrentVersion(); 86 | } 87 | state = state.copyWith( 88 | rcloneLatestVersion: latest, 89 | rcloneAssets: assetsForSpecificPlatform, 90 | upgradeStatus: UpgradeStatus.idle); 91 | } catch (e) { 92 | throw Exception( 93 | '$e\nFailed to get latest version when fetching: ${json.toString()}'); 94 | } 95 | } 96 | 97 | Future installRclone(String downloadLink) async { 98 | state = state.copyWith(upgradeStatus: UpgradeStatus.installing); 99 | String targetArchiveFile = '${state.rcloneDirectory}/rclonenew.zip'; 100 | await Dio().download(downloadLink, targetArchiveFile); 101 | FileHelper.extractRclone(targetArchiveFile, state.rcloneDirectory); 102 | await File(targetArchiveFile).delete(); 103 | getRcloneCurrentVersion(); 104 | state = state.copyWith(upgradeStatus: UpgradeStatus.complete); 105 | //startAlist(); 106 | } 107 | 108 | Future upgradeRclone(String downloadLink) async { 109 | state = state.copyWith(upgradeStatus: UpgradeStatus.installing); 110 | String targetArchiveFile = '${state.rcloneDirectory}/rclonenew.zip'; 111 | String backupFolder = '${state.rcloneDirectory}/.old'; 112 | String currentAlist = Platform.isWindows 113 | ? '${state.rcloneDirectory}/rclone.exe' 114 | : '${state.rcloneDirectory}/rclone'; 115 | await Dio().download(downloadLink, targetArchiveFile); 116 | ref.read(rcloneProvider.notifier).endRclone(); 117 | if (!await Directory(backupFolder).exists()) { 118 | await Directory(backupFolder).create(); 119 | } 120 | if (await File('$backupFolder/rclone-${state.rcloneCurrentVersion}.exe') 121 | .exists()) { 122 | await File('$backupFolder/rclone-${state.rcloneCurrentVersion}.exe') 123 | .delete(); 124 | } 125 | await File(currentAlist) 126 | .rename('$backupFolder/rclone-${state.rcloneCurrentVersion}.exe'); 127 | FileHelper.extractRclone( 128 | '${state.rcloneDirectory}/rclonenew.zip', state.rcloneDirectory); 129 | await File(targetArchiveFile).delete(); 130 | getRcloneCurrentVersion(); 131 | state = state.copyWith(upgradeStatus: UpgradeStatus.complete); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/provider/window_dimensions_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:alisthelper/provider/persistence_provider.dart'; 4 | import 'package:screen_retriever/screen_retriever.dart'; 5 | import 'package:window_manager/window_manager.dart'; 6 | 7 | class WindowDimensions { 8 | final Offset position; 9 | final Size size; 10 | 11 | WindowDimensions({ 12 | required this.position, 13 | required this.size, 14 | }); 15 | } 16 | 17 | final windowDimensionProvider = Provider((ref) { 18 | return WindowDimensionsController(ref.watch(persistenceProvider)); 19 | }); 20 | 21 | const Size _minimalSize = Size(400, 500); 22 | const Size _defaultSize = Size(900, 600); 23 | 24 | class WindowDimensionsController { 25 | final PersistenceService _service; 26 | 27 | WindowDimensionsController(this._service); 28 | 29 | /// Sets window position & size according to saved settings. 30 | Future initDimensionsConfiguration() async { 31 | await WindowManager.instance.setMinimumSize(_minimalSize); 32 | 33 | // load saved Window placement and preferences 34 | final useSavedPlacement = _service.getSaveWindowPlacement(); 35 | final persistedDimensions = _service.getWindowLastDimensions(); 36 | 37 | if (useSavedPlacement && 38 | persistedDimensions != null && 39 | await isInScreenBounds( 40 | persistedDimensions.position, persistedDimensions.size)) { 41 | await WindowManager.instance.setSize(persistedDimensions.size); 42 | await WindowManager.instance.setPosition(persistedDimensions.position); 43 | } else { 44 | final primaryDisplay = await ScreenRetriever.instance.getPrimaryDisplay(); 45 | final hasEnoughWidthForDefaultSize = 46 | primaryDisplay.digestedSize.width >= 1200; 47 | await WindowManager.instance 48 | .setSize(hasEnoughWidthForDefaultSize ? _defaultSize : _minimalSize); 49 | await WindowManager.instance.center(); 50 | } 51 | } 52 | 53 | Future isInScreenBounds(Offset windowPosition, 54 | [Size? windowSize]) async { 55 | final displays = await ScreenRetriever.instance.getAllDisplays(); 56 | final sumWidth = displays.fold(0.0, 57 | (previousValue, element) => previousValue + element.digestedSize.width); 58 | final maxHeight = displays.fold( 59 | 0.0, 60 | (previousValue, element) => previousValue > element.digestedSize.height 61 | ? previousValue 62 | : element.digestedSize.height, 63 | ); 64 | return windowPosition.dx + (windowSize?.width ?? 0) < sumWidth && 65 | windowPosition.dy + (windowSize?.height ?? 0) < maxHeight; 66 | } 67 | 68 | Future storeDimensions({ 69 | required Offset windowOffset, 70 | required Size windowSize, 71 | }) async { 72 | if (await isInScreenBounds(windowOffset)) { 73 | await _service.setWindowOffsetX(windowOffset.dx); 74 | await _service.setWindowOffsetY(windowOffset.dy); 75 | await _service.setWindowHeight(windowSize.height); 76 | await _service.setWindowWidth(windowSize.width); 77 | } 78 | } 79 | 80 | Future storePosition({required Offset windowOffset}) async { 81 | if (await isInScreenBounds(windowOffset)) { 82 | await _service.setWindowOffsetX(windowOffset.dx); 83 | await _service.setWindowOffsetY(windowOffset.dy); 84 | } 85 | } 86 | 87 | Future storeSize({required Size windowSize}) async { 88 | await _service.setWindowHeight(windowSize.height); 89 | await _service.setWindowWidth(windowSize.width); 90 | } 91 | } 92 | 93 | extension on Display { 94 | Size get digestedSize => visibleSize ?? size; 95 | } 96 | -------------------------------------------------------------------------------- /lib/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AlistHelperTheme { 4 | Color primaryColor; 5 | 6 | AlistHelperTheme(this.primaryColor); 7 | 8 | get lightThemeData => ThemeData.from( 9 | colorScheme: ColorScheme.fromSeed( 10 | seedColor: primaryColor, 11 | brightness: Brightness.light, 12 | primary: primaryColor, 13 | secondary: primaryColor, 14 | //background: Colors.grey[50], 15 | ), 16 | useMaterial3: true, 17 | textTheme: TextTheme( 18 | displayLarge: TextStyle( 19 | fontSize: 24, 20 | fontWeight: FontWeight.bold, 21 | color: primaryColor, 22 | ), 23 | displayMedium: TextStyle( 24 | fontSize: 20, 25 | fontWeight: FontWeight.bold, 26 | color: primaryColor, 27 | ), 28 | displaySmall: TextStyle( 29 | fontSize: 16, 30 | fontWeight: FontWeight.w500, 31 | color: primaryColor, 32 | ), 33 | headlineLarge: TextStyle( 34 | fontSize: 32, 35 | fontWeight: FontWeight.bold, 36 | color: primaryColor, 37 | ), 38 | )); 39 | 40 | get darkThemeData => ThemeData.from( 41 | colorScheme: ColorScheme.fromSeed( 42 | error: const Color.fromARGB(255, 255, 99, 71), 43 | seedColor: primaryColor, 44 | brightness: Brightness.dark, 45 | primary: primaryColor, 46 | secondary: primaryColor, 47 | surface: const Color.fromARGB(255, 24, 24, 24), 48 | ), 49 | useMaterial3: true, 50 | textTheme: TextTheme( 51 | displayLarge: TextStyle( 52 | fontSize: 24, 53 | fontWeight: FontWeight.bold, 54 | color: primaryColor, 55 | ), 56 | displayMedium: TextStyle( 57 | fontSize: 20, 58 | fontWeight: FontWeight.bold, 59 | color: primaryColor, 60 | ), 61 | displaySmall: TextStyle( 62 | fontSize: 16, 63 | fontWeight: FontWeight.bold, 64 | color: primaryColor, 65 | ), 66 | headlineLarge: TextStyle( 67 | fontSize: 32, 68 | fontWeight: FontWeight.w500, 69 | color: primaryColor, 70 | ), 71 | )); 72 | } 73 | -------------------------------------------------------------------------------- /lib/utils/init.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:alisthelper/i18n/strings.g.dart'; 4 | import 'package:alisthelper/provider/alist_helper_provider.dart'; 5 | import 'package:alisthelper/provider/alist_provider.dart'; 6 | import 'package:alisthelper/provider/persistence_provider.dart'; 7 | import 'package:alisthelper/provider/rclone_provider.dart'; 8 | import 'package:alisthelper/provider/settings_provider.dart'; 9 | import 'package:alisthelper/provider/window_dimensions_provider.dart'; 10 | import 'package:alisthelper/utils/native/tray_helper.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 13 | import 'package:window_manager/window_manager.dart'; 14 | 15 | /// Pre-initializes the app. 16 | /// Reads the command line arguments and initializes the [PersistenceService]. 17 | /// Initializes the tray and the window manager. 18 | Future preInit(List args) async { 19 | WidgetsFlutterBinding.ensureInitialized(); 20 | 21 | final persistenceService = await PersistenceService.initialize(); 22 | 23 | // Register default plural resolver 24 | for (final locale in AppLocale.values) { 25 | if ([AppLocale.en, AppLocale.zhHansCn, AppLocale.zhHantTw] 26 | .contains(locale)) { 27 | continue; 28 | } 29 | 30 | await LocaleSettings.setPluralResolver( 31 | locale: locale, 32 | cardinalResolver: (n, {zero, one, two, few, many, other}) { 33 | if (n == 0) { 34 | return zero ?? other ?? n.toString(); 35 | } 36 | if (n == 1) { 37 | return one ?? other ?? n.toString(); 38 | } 39 | return other ?? n.toString(); 40 | }, 41 | ordinalResolver: (n, {zero, one, two, few, many, other}) { 42 | return other ?? n.toString(); 43 | }, 44 | ); 45 | } 46 | 47 | if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { 48 | // Check if this app is already open and let it "show up". 49 | // If this is the case, then exit the current instance. 50 | // initialize tray AFTER i18n has been initialized 51 | try { 52 | await initTray(); 53 | } catch (e) { 54 | debugPrint('Initializing tray failed: $e'); 55 | } 56 | 57 | // initialize size and position 58 | await WindowManager.instance.ensureInitialized(); 59 | await WindowDimensionsController(persistenceService) 60 | .initDimensionsConfiguration(); 61 | 62 | // 63 | 64 | //If the app is launched with the autostart argument, it will not be minimized by default. 65 | //https://github.com/leanflutter/window_manager#hidden-at-launch 66 | bool isAutoStart = args.contains('autostart'); 67 | bool isAutoStartLaunchMinimized = 68 | persistenceService.isAutoStartLaunchMinimized(); 69 | if (!isAutoStart || !isAutoStartLaunchMinimized) { 70 | await WindowManager.instance.show(); 71 | } 72 | if (isAutoStartLaunchMinimized && Platform.isMacOS) { 73 | await hideToTray(); 74 | } 75 | } 76 | return persistenceService; 77 | } 78 | 79 | /// Post-initializes the app. 80 | /// Starts the Alist if the [Settings.autoStartAlist] is true. 81 | Future postInit(WidgetRef ref) async { 82 | final alistNotifier = ref.watch(alistProvider.notifier); 83 | if (!ref.watch(settingsProvider).isFirstRun) { 84 | alistNotifier.getAlistCurrentVersion(addToOutput: false); 85 | } 86 | final alistHelperNotifier = ref.watch(ahProvider.notifier); 87 | alistHelperNotifier.getAlistHelperCurrentVersion(); 88 | 89 | if (ref.watch(settingsProvider).autoStartAlist && 90 | !ref.watch(alistProvider).isRunning) { 91 | var alistNotifier = ref.watch(alistProvider.notifier); 92 | alistNotifier.startAlist(); 93 | } 94 | 95 | if (ref.watch(settingsProvider).autoStartRclone) { 96 | if (ref.watch(settingsProvider).startAfterAlist) { 97 | //wait 3 second 98 | await Future.delayed(const Duration(seconds: 3)); 99 | if (ref.watch(alistProvider).isRunning) { 100 | var rcloneNotifier = ref.watch(rcloneProvider.notifier); 101 | rcloneNotifier.startRclone(); 102 | } 103 | } else { 104 | var rcloneNotifier = ref.watch(rcloneProvider.notifier); 105 | rcloneNotifier.startRclone(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/utils/native/auto_start_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:launch_at_startup/launch_at_startup.dart'; 4 | import 'package:package_info_plus/package_info_plus.dart'; 5 | 6 | /// Currently, only works for windows 7 | Future initAutoStartAndOpenSettings(bool value) async { 8 | try { 9 | // In case somebody don't use msix 10 | final packageInfo = await PackageInfo.fromPlatform(); 11 | 12 | launchAtStartup.setup( 13 | appName: packageInfo.appName, 14 | appPath: Platform.resolvedExecutable, 15 | args: ['autostart'], 16 | ); 17 | 18 | // We just add this entry so we have the same behaviour like in msix 19 | if (value) { 20 | await launchAtStartup.enable(); 21 | } else { 22 | await launchAtStartup.disable(); 23 | } 24 | } catch (e) { 25 | //print(e); 26 | } 27 | 28 | /* try { 29 | // Ideally, we should configure it programmatically 30 | // The launch_at_startup package does not support this currently 31 | // See: https://learn.microsoft.com/en-us/uwp/api/Windows.ApplicationModel.StartupTask?view=winrt-22621 32 | await launchUrl(Uri.parse('ms-settings:startupapps')); 33 | } catch (e) { 34 | print(e); 35 | } */ 36 | } 37 | -------------------------------------------------------------------------------- /lib/utils/native/file_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:archive/archive_io.dart'; 4 | 5 | class FileHelper { 6 | static void unzipFile(String targetArchiveFile, String outputFolder) { 7 | final inputStream = InputFileStream(targetArchiveFile); 8 | final archive = ZipDecoder().decodeStream(inputStream); 9 | for (var file in archive.files) { 10 | if (file.isFile) { 11 | final outputStream = OutputFileStream('$outputFolder/${file.name}'); 12 | file.writeContent(outputStream); 13 | outputStream.close(); 14 | } 15 | } 16 | inputStream.close(); 17 | } 18 | 19 | static void extractRclone(String targetArchiveFile, String outputFolder) { 20 | final inputStream = InputFileStream(targetArchiveFile); 21 | final archive = ZipDecoder().decodeStream(inputStream); 22 | for (var file in archive.files) { 23 | if (file.isFile && 24 | file.name.endsWith('rclone.exe') && 25 | Platform.isWindows) { 26 | final outputStream = OutputFileStream('$outputFolder/rclone.exe'); 27 | file.writeContent(outputStream); 28 | outputStream.close(); 29 | } else if (file.isFile && 30 | file.name.endsWith('rclone') && 31 | !Platform.isWindows) { 32 | final outputStream = OutputFileStream('$outputFolder/rclone'); 33 | file.writeContent(outputStream); 34 | outputStream.close(); 35 | } 36 | } 37 | inputStream.close(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/utils/native/tray_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:alisthelper/i18n/strings.g.dart'; 3 | import 'package:alisthelper/provider/alist_provider.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:tray_manager/tray_manager.dart' as tm; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | import 'package:window_manager/window_manager.dart'; 8 | 9 | enum TrayEntry { 10 | open, 11 | quit, 12 | hide, 13 | endAlist, 14 | startAlist, 15 | openGUI, 16 | } 17 | 18 | Future initTray() async { 19 | if (!Platform.isWindows && !Platform.isMacOS && !Platform.isLinux) { 20 | return; 21 | } 22 | String iconPath = 23 | Platform.isWindows ? 'assets/alisthelper.ico' : 'assets/alisthelper.png'; 24 | await tm.trayManager.setIcon(iconPath); 25 | 26 | final items = [ 27 | tm.MenuItem(key: TrayEntry.open.name, label: t.tray.open), 28 | tm.MenuItem(key: TrayEntry.hide.name, label: t.tray.hide), 29 | tm.MenuItem(key: TrayEntry.quit.name, label: t.tray.quit), 30 | ]; 31 | await tm.trayManager.setContextMenu(tm.Menu(items: items)); 32 | await tm.trayManager.setToolTip(t.tray.tooltip); 33 | } 34 | 35 | Future changeTray(bool isRunning) async { 36 | final items = [ 37 | tm.MenuItem(key: TrayEntry.open.name, label: t.tray.open), 38 | tm.MenuItem(key: TrayEntry.hide.name, label: t.tray.hide), 39 | tm.MenuItem(key: TrayEntry.quit.name, label: t.tray.quit), 40 | ]; 41 | if (isRunning) { 42 | //add endAlist 43 | items.insert( 44 | 1, tm.MenuItem(key: TrayEntry.endAlist.name, label: t.tray.endAlist)); 45 | items.insert( 46 | 2, tm.MenuItem(key: TrayEntry.openGUI.name, label: t.tray.openGUI)); 47 | tm.trayManager.setContextMenu(tm.Menu(items: items)); 48 | //setToolTip() method is not available on linux,just cancel it! 49 | if (!Platform.isLinux) { 50 | await tm.trayManager.setToolTip(t.tray.tooltip); 51 | } 52 | } else { 53 | //add startAlist 54 | items.insert(1, 55 | tm.MenuItem(key: TrayEntry.startAlist.name, label: t.tray.startAlist)); 56 | tm.trayManager.setContextMenu(tm.Menu(items: items)); 57 | //setToolTip() method is not available on linux,just cancel it! 58 | if (!Platform.isLinux) { 59 | await tm.trayManager.setToolTip(t.tray.tooltip); 60 | } 61 | } 62 | } 63 | 64 | Future hideToTray() async { 65 | await windowManager.hide(); 66 | if (Platform.isMacOS) { 67 | // This will crash on Windows 68 | // https://github.com/localsend/localsend/issues/32 69 | await windowManager.setSkipTaskbar(true); 70 | } 71 | } 72 | 73 | Future showFromTray() async { 74 | await windowManager.show(); 75 | await windowManager.focus(); 76 | if (Platform.isMacOS) { 77 | // This will crash on Windows 78 | // https://github.com/localsend/localsend/issues/32 79 | await windowManager.setSkipTaskbar(false); 80 | } 81 | } 82 | 83 | Future startAlist(WidgetRef ref) async { 84 | final alistNotifier = ref.watch(alistProvider.notifier); 85 | alistNotifier.startAlist(); 86 | } 87 | 88 | Future endAlist(WidgetRef ref) async { 89 | final alistNotifier = ref.watch(alistProvider.notifier); 90 | alistNotifier.endAlist(); 91 | } 92 | 93 | Future openGUI(WidgetRef ref) async { 94 | final alistState = ref.watch(alistProvider); 95 | final Uri url = Uri.parse(alistState.url); 96 | if (!await launchUrl(url)) { 97 | throw Exception('Could not launch the $url'); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/utils/native/tray_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:tray_manager/tray_manager.dart'; 5 | import 'package:alisthelper/provider/alist_provider.dart'; 6 | import 'package:alisthelper/provider/rclone_provider.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | import 'tray_helper.dart'; 10 | 11 | class TrayWatcher extends ConsumerStatefulWidget { 12 | final Widget child; 13 | 14 | const TrayWatcher({required this.child, super.key}); 15 | 16 | @override 17 | ConsumerState createState() => _TrayWatcherState(); 18 | } 19 | 20 | class _TrayWatcherState extends ConsumerState with TrayListener { 21 | @override 22 | Widget build(BuildContext context) { 23 | return widget.child; 24 | } 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | trayManager.addListener(this); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | trayManager.removeListener(this); 35 | super.dispose(); 36 | } 37 | 38 | @override 39 | void onTrayIconMouseDown() async { 40 | if (Platform.isMacOS) { 41 | await trayManager.popUpContextMenu(); 42 | } else { 43 | await showFromTray(); 44 | } 45 | } 46 | 47 | @override 48 | void onTrayIconRightMouseDown() async { 49 | await trayManager.popUpContextMenu(); 50 | } 51 | 52 | @override 53 | void onTrayMenuItemClick(MenuItem menuItem) async { 54 | final entry = 55 | TrayEntry.values.firstWhereOrNull((e) => e.name == menuItem.key); 56 | switch (entry) { 57 | case TrayEntry.open: 58 | await showFromTray(); 59 | break; 60 | case TrayEntry.quit: 61 | await AlistNotifier.endAlistProcess(); 62 | await RcloneNotifier.endRcloneProcess(); 63 | exit(0); 64 | case TrayEntry.startAlist: 65 | await startAlist(ref); 66 | break; 67 | case TrayEntry.endAlist: 68 | await endAlist(ref); 69 | break; 70 | case TrayEntry.hide: 71 | await hideToTray(); 72 | break; 73 | case TrayEntry.openGUI: 74 | await openGUI(ref); 75 | break; 76 | default: 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/utils/native/window_watcher.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:alisthelper/provider/window_dimensions_provider.dart'; 6 | import 'package:window_manager/window_manager.dart'; 7 | 8 | class WindowWatcher extends ConsumerStatefulWidget { 9 | final Widget child; 10 | final VoidCallback onClose; 11 | 12 | const WindowWatcher({ 13 | required this.child, 14 | required this.onClose, 15 | super.key, 16 | }); 17 | 18 | @override 19 | ConsumerState createState() => _WindowWatcherState(); 20 | } 21 | 22 | class _WindowWatcherState extends ConsumerState with WindowListener { 23 | static WindowDimensionsController? _dimensionsController; 24 | static Stopwatch s = Stopwatch(); 25 | 26 | WindowDimensionsController _ensureDimensionsProvider() => ref.watch(windowDimensionProvider); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | _dimensionsController ??= _ensureDimensionsProvider(); 31 | s.start(); 32 | return widget.child; 33 | } 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | windowManager.addListener(this); 39 | // prevent window from closing 40 | if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { 41 | WidgetsBinding.instance.addPostFrameCallback((_) async { 42 | try { 43 | // always handle close actions manually 44 | await windowManager.setPreventClose(true); 45 | } catch (e) { 46 | debugPrint(e.toString()); 47 | } 48 | }); 49 | } 50 | } 51 | 52 | @override 53 | void dispose() { 54 | windowManager.removeListener(this); 55 | super.dispose(); 56 | } 57 | 58 | //Linux alternative for onWindowMoved and onWindowResized 59 | @override 60 | Future onWindowMove() async { 61 | if(Platform.isLinux && s.elapsedMilliseconds >= 600) { 62 | s.reset(); 63 | final windowOffset = await windowManager.getPosition(); 64 | final windowSize = await windowManager.getSize(); 65 | await _dimensionsController?.storeDimensions(windowOffset: windowOffset, windowSize: windowSize); 66 | } 67 | } 68 | 69 | @override 70 | Future onWindowMoved() async { 71 | final windowOffset = await windowManager.getPosition(); 72 | await _dimensionsController?.storePosition(windowOffset: windowOffset); 73 | } 74 | 75 | @override 76 | Future onWindowResized() async { 77 | final windowSize = await windowManager.getSize(); 78 | await _dimensionsController?.storeSize(windowSize: windowSize); 79 | } 80 | 81 | @override 82 | Future onWindowClose() async { 83 | final windowOffset = await windowManager.getPosition(); 84 | final windowSize = await windowManager.getSize(); 85 | await _dimensionsController?.storeDimensions(windowOffset: windowOffset, windowSize: windowSize); 86 | widget.onClose(); 87 | } 88 | 89 | @override 90 | void onWindowFocus() { 91 | // call set state according to window_manager README 92 | setState(() {}); 93 | } 94 | } -------------------------------------------------------------------------------- /lib/utils/textutils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:fast_gbk/fast_gbk.dart'; 4 | 5 | class TextUtils { 6 | static String removeEscapeSequences(String input) { 7 | // Remove all escape sequences from the input string 8 | return input.replaceAll(RegExp(r'\x1B\[[0-?]*[ -/]*[@-~]'), ''); 9 | } 10 | 11 | static List removeExtraBreaks(List input) { 12 | // Remove the last character if it is a newline 13 | if (input.length > 2) { 14 | if (input[input.length - 1] == 10) { 15 | return input.sublist(0, input.length - 1); 16 | } 17 | } 18 | return input; 19 | } 20 | 21 | static String stdDecode(List input, bool isGBK) { 22 | if (isGBK) { 23 | return removeEscapeSequences(gbk.decode(removeExtraBreaks(input))); 24 | } 25 | return removeEscapeSequences(utf8.decode(removeExtraBreaks(input))); 26 | } 27 | 28 | static bool isNewVersion(String currentVersion, String latestVersion) { 29 | List currentVersionNumbers = versionNumbersFromString(currentVersion); 30 | List comparedVersionNumbers = versionNumbersFromString(latestVersion); 31 | 32 | // Compare each component of the version number, starting from the major version 33 | for (int i = 0; i < currentVersionNumbers.length; i++) { 34 | int currentComponent = currentVersionNumbers[i]; 35 | int comparedComponent = 36 | i < comparedVersionNumbers.length ? comparedVersionNumbers[i] : 0; 37 | 38 | if (currentComponent < comparedComponent) { 39 | return true; 40 | } else if (currentComponent > comparedComponent) { 41 | return false; 42 | } 43 | } 44 | 45 | // All version components are equal, so the versions are not new 46 | return false; 47 | } 48 | 49 | static List versionNumbersFromString(String versionString) { 50 | // if version String like v1.2.3-debug, remove -debug 51 | if (versionString.contains('-')) { 52 | versionString = versionString.split('-')[0]; 53 | } 54 | 55 | // Remove the leading "v" character and split the version string into components 56 | 57 | List versionComponents = versionString.substring(1).split('.'); 58 | 59 | // Convert each component to an integer and return the list of numbers 60 | return versionComponents.map((component) => int.parse(component)).toList(); 61 | } 62 | 63 | static List accountParser(String text) { 64 | return utf8.decode(base64.decode(text)).split('\n'); 65 | } 66 | 67 | static String accountEncoder(List account) { 68 | // then base64 encode the account 69 | return base64.encode(utf8.encode(account.join('\n'))); 70 | } 71 | 72 | static List flagsParser(String text) { 73 | List result = []; 74 | if (text.contains('"')) { 75 | text.replaceAll('"', ''); 76 | } 77 | RegExp exp = RegExp(r'--\S+ \S+'); 78 | Iterable matches = exp.allMatches(text); 79 | for (Match match in matches) { 80 | result.add(match.group(0)!); 81 | } 82 | return result; 83 | } 84 | 85 | static String encodeCredentials(List args) { 86 | String user = ''; 87 | String pass = ''; 88 | 89 | for (int i = 0; i < args.length; i++) { 90 | if (args[i] == '--rc-user' && i + 1 < args.length) { 91 | user = args[i + 1]; 92 | } else if (args[i] == '--rc-pass' && i + 1 < args.length) { 93 | pass = args[i + 1]; 94 | } 95 | } 96 | 97 | String credentials = '$user:$pass'; 98 | return base64.encode(utf8.encode(credentials)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/widgets/button_card.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:alisthelper/i18n/strings.g.dart'; 4 | import 'package:alisthelper/provider/alist_provider.dart'; 5 | import 'package:alisthelper/provider/rclone_provider.dart'; 6 | import 'package:alisthelper/widgets/pages/logs_page.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 9 | import 'package:url_launcher/url_launcher.dart'; 10 | 11 | class AlistMultiButtonCard extends ConsumerWidget { 12 | const AlistMultiButtonCard({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context, WidgetRef ref) { 16 | final alistState = ref.watch(alistProvider); 17 | final alistNotifier = ref.watch(alistProvider.notifier); 18 | 19 | Future openGUI() async { 20 | final Uri url = Uri.parse(alistState.url); 21 | if (!await launchUrl(url)) { 22 | throw Exception('Could not launch the $url'); 23 | } 24 | } 25 | 26 | return Card( 27 | margin: const EdgeInsets.fromLTRB(20, 10, 20, 10), 28 | child: Column( 29 | children: [ 30 | Container(height: 10), 31 | Padding( 32 | padding: const EdgeInsets.all(10.0), 33 | child: Wrap( 34 | direction: Axis.horizontal, 35 | runAlignment: WrapAlignment.center, 36 | crossAxisAlignment: WrapCrossAlignment.center, 37 | runSpacing: 10.0, 38 | spacing: 10.0, 39 | children: [ 40 | FilledButton.tonal( 41 | onPressed: alistState.isRunning 42 | ? null 43 | : () => alistNotifier.startAlist(), 44 | child: Text(t.alistOperation.startAlist)), 45 | FilledButton.tonal( 46 | onPressed: alistState.isRunning 47 | ? () => alistNotifier.endAlist() 48 | : null, 49 | child: Text(t.alistOperation.endAlist)), 50 | FilledButton.tonal( 51 | onPressed: alistState.isRunning ? openGUI : null, 52 | child: Text(t.alistOperation.openGUI)), 53 | FilledButton.tonal( 54 | onPressed: () => alistNotifier.genRandomPwd(), 55 | child: Text(t.alistOperation.genRandomPwd)), 56 | FilledButton.tonal( 57 | onPressed: () => 58 | alistNotifier.getAlistCurrentVersion(addToOutput: true), 59 | child: Text(t.alistOperation.getVersion)), 60 | ], 61 | ), 62 | ), 63 | Container(height: 10), 64 | ], 65 | ), 66 | ); 67 | } 68 | } 69 | 70 | class RcloneMultiButtonCard extends ConsumerWidget { 71 | const RcloneMultiButtonCard({super.key}); 72 | 73 | @override 74 | Widget build(BuildContext context, WidgetRef ref) { 75 | final rcloneState = ref.watch(rcloneProvider); 76 | final rcloneNotifier = ref.watch(rcloneProvider.notifier); 77 | 78 | // Assuming rcloneState.output is a list of strings 79 | final unreadCount = rcloneState.output.length; 80 | 81 | return Card( 82 | margin: const EdgeInsets.fromLTRB(20, 10, 20, 10), 83 | child: Column( 84 | children: [ 85 | Container(height: 10), 86 | Padding( 87 | padding: const EdgeInsets.all(10.0), 88 | child: Wrap( 89 | direction: Axis.horizontal, 90 | runAlignment: WrapAlignment.center, 91 | crossAxisAlignment: WrapCrossAlignment.center, 92 | runSpacing: 10.0, 93 | spacing: 10.0, 94 | children: [ 95 | FilledButton.tonal( 96 | onPressed: rcloneState.isRunning 97 | ? null 98 | : () => rcloneNotifier.startRclone(), 99 | child: Text(t.rcloneOperation.startRclone)), 100 | FilledButton.tonal( 101 | onPressed: rcloneState.isRunning 102 | ? () => rcloneNotifier.endRclone() 103 | : null, 104 | child: Text(t.rcloneOperation.endRclone)), 105 | FilledButton.tonal( 106 | onPressed: () => rcloneNotifier.getRcloneInfo(), 107 | child: Text(t.rcloneOperation.getRcloneInfo)), 108 | Badge( 109 | isLabelVisible: unreadCount > 0, 110 | label: Text(unreadCount.toString()), 111 | child: FilledButton.tonal( 112 | onPressed: () { 113 | showDialog( 114 | context: context, 115 | builder: (context) => Dialog.fullscreen( 116 | child: RcloneLogsPage(), 117 | ), 118 | ); 119 | }, 120 | child: Text(t.rcloneOperation.viewLogs), 121 | ), 122 | ), 123 | ], 124 | ), 125 | ), 126 | Container(height: 10), 127 | ], 128 | ), 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/widgets/choose_alist_package.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:alisthelper/model/alist_state.dart'; 3 | import 'package:alisthelper/model/settings_state.dart'; 4 | import 'package:alisthelper/provider/alist_provider.dart'; 5 | import 'package:alisthelper/provider/settings_provider.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | class ChooseAlistPackage extends ConsumerStatefulWidget { 10 | final bool isUpgrade; 11 | const ChooseAlistPackage({super.key, required this.isUpgrade}); 12 | 13 | @override 14 | ConsumerState createState() => 15 | _ChooseAlistPackageState(); 16 | } 17 | 18 | class _ChooseAlistPackageState extends ConsumerState { 19 | @override 20 | Widget build(BuildContext context) { 21 | final alistState = ref.watch(alistProvider); 22 | final AlistNotifier alistNotifier = ref.watch(alistProvider.notifier); 23 | final SettingsState settingsState = ref.watch(settingsProvider); 24 | try { 25 | if (alistState.newReleaseAssets.isEmpty) { 26 | alistNotifier.fetchLatestVersion(); 27 | } 28 | } catch (e) { 29 | if (context.mounted) { 30 | ScaffoldMessenger.of(context) 31 | .showSnackBar(SnackBar(content: Text(e.toString()))); 32 | } 33 | } 34 | return AlertDialog( 35 | title: Text(t.upgrade.selectPackage), 36 | content: Column( 37 | mainAxisSize: MainAxisSize.min, 38 | children: [ 39 | Center( 40 | child: ListTile( 41 | title: widget.isUpgrade 42 | ? Text( 43 | 'Will be upgrade from: ${settingsState.workingDirectory}') 44 | : Text( 45 | 'Will be installed to: ${settingsState.workingDirectory}'), 46 | ), 47 | ), 48 | Center( 49 | child: alistState.upgradeStatus == UpgradeStatus.installing 50 | ? const LinearProgressIndicator() 51 | : alistState.upgradeStatus == UpgradeStatus.complete 52 | ? Container( 53 | decoration: BoxDecoration( 54 | color: Colors.green, 55 | borderRadius: BorderRadius.circular(10), 56 | ), 57 | height: 30, 58 | margin: const EdgeInsets.all(10), 59 | child: const Center( 60 | child: Text( 61 | 'Installation complete', 62 | style: TextStyle(color: Colors.white), 63 | ), 64 | ), 65 | ) 66 | : Container(), 67 | ), 68 | Column( 69 | children: (alistState.newReleaseAssets.isEmpty) 70 | ? [Text(t.upgrade.networkError)] 71 | : alistState.newReleaseAssets.map((asset) { 72 | return ListTile( 73 | title: Text(asset['name']), 74 | leading: const Icon(Icons.grid_view_outlined), 75 | subtitle: Text( 76 | '${(asset['size'] / 1000000).toStringAsFixed(2)} MB'), 77 | trailing: IconButton( 78 | onPressed: () async { 79 | try { 80 | if (widget.isUpgrade) { 81 | alistNotifier.upgradeAlist( 82 | asset['browser_download_url']); 83 | } else { 84 | alistNotifier.installAlist( 85 | asset['browser_download_url']); 86 | } 87 | } catch (e) { 88 | if (context.mounted) { 89 | ScaffoldMessenger.of(context).showSnackBar( 90 | SnackBar(content: Text(e.toString()))); 91 | } 92 | } 93 | }, 94 | icon: const Icon(Icons.file_download_outlined), 95 | ), 96 | ); 97 | }).toList(), 98 | ), 99 | ], 100 | )); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/widgets/choose_rclone_package.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:alisthelper/model/settings_state.dart'; 3 | import 'package:alisthelper/model/updater_state.dart'; 4 | import 'package:alisthelper/provider/settings_provider.dart'; 5 | import 'package:alisthelper/provider/updater_provider.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | class ChooseRclonePackage extends ConsumerStatefulWidget { 10 | final bool isUpgrade; 11 | const ChooseRclonePackage({super.key, required this.isUpgrade}); 12 | 13 | @override 14 | ConsumerState createState() => 15 | _ChooseRclonePackageState(); 16 | } 17 | 18 | class _ChooseRclonePackageState extends ConsumerState { 19 | @override 20 | Widget build(BuildContext context) { 21 | final updaterState = ref.watch(updaterProvider); 22 | final updaterNotifier = ref.watch(updaterProvider.notifier); 23 | 24 | final SettingsState settingsState = ref.watch(settingsProvider); 25 | try { 26 | if (updaterState.rcloneAssets.isEmpty) { 27 | updaterNotifier.fetchLatestVersion(); 28 | } 29 | } catch (e) { 30 | if (context.mounted) { 31 | ScaffoldMessenger.of(context) 32 | .showSnackBar(SnackBar(content: Text(e.toString()))); 33 | } 34 | } 35 | return AlertDialog( 36 | title: Text(t.upgrade.selectPackage), 37 | content: Column( 38 | mainAxisSize: MainAxisSize.min, 39 | children: [ 40 | Center( 41 | child: ListTile( 42 | title: widget.isUpgrade 43 | ? Text( 44 | 'Will be upgrade from: ${settingsState.rcloneDirectory}') 45 | : Text( 46 | 'Will be installed to: ${settingsState.rcloneDirectory}'), 47 | ), 48 | ), 49 | Center( 50 | child: updaterState.upgradeStatus == UpgradeStatus.installing 51 | ? const LinearProgressIndicator() 52 | : updaterState.upgradeStatus == UpgradeStatus.complete 53 | ? Container( 54 | decoration: BoxDecoration( 55 | color: Colors.green, 56 | borderRadius: BorderRadius.circular(10), 57 | ), 58 | height: 30, 59 | margin: const EdgeInsets.all(10), 60 | child: const Center( 61 | child: Text( 62 | 'Installation complete', 63 | style: TextStyle(color: Colors.white), 64 | ), 65 | ), 66 | ) 67 | : Container(), 68 | ), 69 | Column( 70 | children: (updaterState.rcloneAssets.isEmpty) 71 | ? [Text(t.upgrade.networkError)] 72 | : updaterState.rcloneAssets.map((asset) { 73 | return ListTile( 74 | title: Text(asset['name']), 75 | leading: const Icon(Icons.grid_view_outlined), 76 | subtitle: Text( 77 | '${(asset['size'] / 1000000).toStringAsFixed(2)} MB'), 78 | trailing: IconButton( 79 | onPressed: () async { 80 | try { 81 | if (widget.isUpgrade) { 82 | updaterNotifier.upgradeRclone( 83 | asset['browser_download_url']); 84 | } else { 85 | updaterNotifier.installRclone( 86 | asset['browser_download_url']); 87 | } 88 | } catch (e) { 89 | if (context.mounted) { 90 | ScaffoldMessenger.of(context).showSnackBar( 91 | SnackBar(content: Text(e.toString()))); 92 | } 93 | } 94 | }, 95 | icon: const Icon(Icons.file_download_outlined), 96 | ), 97 | ); 98 | }).toList(), 99 | ), 100 | ], 101 | )); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/widgets/edit_vdisk.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/lib/widgets/edit_vdisk.dart -------------------------------------------------------------------------------- /lib/widgets/logs_viewer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | class LogListTile extends StatelessWidget { 6 | final String logText; 7 | 8 | const LogListTile({super.key, required this.logText}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | bool isWarning = logText.contains('WARN') || 13 | logText.contains('ERR') || 14 | logText.contains('FATA') || 15 | logText.contains('Exception') || 16 | logText.contains('CRITICAL'); 17 | return ListTile( 18 | leading: Icon(isWarning ? Icons.warning : Icons.info, 19 | color: isWarning ? Colors.amber : Colors.teal), 20 | title: Text(logText), 21 | trailing: IconButton( 22 | icon: const Icon(Icons.copy), 23 | onPressed: () => Clipboard.setData(ClipboardData(text: logText)))); 24 | } 25 | } 26 | 27 | class LogsViewer extends ConsumerWidget { 28 | final List output; 29 | final ScrollController scrollController = ScrollController(); 30 | 31 | LogsViewer({super.key, required this.output}); 32 | @override 33 | Widget build(BuildContext context, WidgetRef ref) { 34 | void scrollDown() { 35 | if (scrollController.hasClients) { 36 | scrollController.jumpTo(scrollController.position.maxScrollExtent); 37 | } 38 | } 39 | 40 | WidgetsBinding.instance.addPostFrameCallback((_) => scrollDown()); 41 | return ListView.builder( 42 | shrinkWrap: true, 43 | controller: scrollController, 44 | itemCount: output.length, 45 | itemBuilder: (context, index) { 46 | return LogListTile(logText: output[index]); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/widgets/pages/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/widgets/pages/debug_page.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | 5 | final _body = ''' 6 | Alist Helper is an open source app to manage alist. 7 | 8 | Free. No tracking*. No ads. 9 | 10 | Currently, this app is available on Windows and macOS. 11 | 12 | Adaptation plans for more platforms are in progress, if you need faster development speed, please consider donating to me. 13 | 14 | *The operating system may still gather usage data. 15 | ''' 16 | .splitMapJoin( 17 | RegExp(r'^', multiLine: true), 18 | onMatch: (_) => '\n', 19 | onNonMatch: (n) => n.trim(), 20 | ); 21 | 22 | class AboutPage extends StatelessWidget { 23 | const AboutPage({super.key}); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: const Text('About'), 30 | ), 31 | body: ListView( 32 | children: [ 33 | Column( 34 | children: [ 35 | const SizedBox(height: 20), 36 | Image.asset('assets/alisthelper.png', height: 180, width: 180), 37 | const SizedBox(height: 20), 38 | const Text('Alist Helper', 39 | style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), 40 | textAlign: TextAlign.center), 41 | const SizedBox(height: 20) 42 | ], 43 | ), 44 | const SizedBox(height: 20), 45 | Text( 46 | '© ${DateTime.now().year} Xmarmalade', 47 | textAlign: TextAlign.center, 48 | ), 49 | const SizedBox(height: 10), 50 | Center( 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.center, 53 | children: [ 54 | TextButton( 55 | onPressed: () async { 56 | await launchUrl( 57 | Uri.parse('https://github.com/Xmarmalade/alisthelper'), 58 | mode: LaunchMode.externalApplication); 59 | }, 60 | child: const Text('Source Code (Github)'), 61 | ), 62 | TextButton( 63 | onPressed: () async { 64 | await launchUrl(Uri.parse('https://alist.nn.ci/')); 65 | }, 66 | child: const Text('Alist'), 67 | ), 68 | TextButton( 69 | onPressed: () { 70 | //jump to LicensePage() by flutter route 71 | Navigator.push( 72 | context, 73 | MaterialPageRoute( 74 | builder: (context) => LicensePage( 75 | applicationIcon: Image.asset( 76 | 'assets/alisthelper.png', 77 | height: 100, 78 | width: 100), 79 | ))); 80 | }, 81 | child: const Text('License'), 82 | ), 83 | TextButton( 84 | onPressed: () { 85 | //jump to LicensePage() by flutter route 86 | Navigator.push(context, 87 | MaterialPageRoute(builder: (context) => const DebugPage())); 88 | }, 89 | child: const Text('Debug'), 90 | ) 91 | ], 92 | ), 93 | ), 94 | Text( 95 | _body, 96 | textAlign: TextAlign.center, 97 | ), 98 | const SizedBox(height: 20), 99 | ], 100 | ), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/widgets/pages/alist_helper_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:alisthelper/provider/alist_provider.dart'; 3 | import 'package:alisthelper/provider/settings_provider.dart'; 4 | import 'package:alisthelper/widgets/button_card.dart'; 5 | import 'package:alisthelper/widgets/pages/first_launch_page.dart'; 6 | import 'package:alisthelper/widgets/logs_viewer.dart'; 7 | import 'package:alisthelper/widgets/responsive_builder.dart'; 8 | import 'package:alisthelper/widgets/sponsor_btn.dart'; 9 | 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 12 | 13 | class AlistHelperPage extends ConsumerWidget { 14 | final SizingInformation sizingInformation; 15 | 16 | const AlistHelperPage({super.key, required this.sizingInformation}); 17 | 18 | @override 19 | Widget build(BuildContext context, WidgetRef ref) { 20 | final settings = ref.watch(settingsProvider); 21 | if (settings.isFirstRun == true) { 22 | return FirstLaunchPage(sizingInformation: sizingInformation); 23 | } 24 | return Scaffold( 25 | appBar: (sizingInformation.isDesktop 26 | ? null 27 | : AppBar( 28 | title: const Text('Alist Helper', 29 | style: TextStyle(fontWeight: FontWeight.bold)), 30 | )), 31 | body: Center( 32 | child: Container( 33 | constraints: const BoxConstraints(maxWidth: 800), 34 | child: Column( 35 | children: [ 36 | SponsorBtn(), 37 | const AlistMultiButtonCard(), 38 | ListTile( 39 | title: Text(t.home.logs, 40 | style: const TextStyle( 41 | fontWeight: FontWeight.w600, fontSize: 18)), 42 | ), 43 | Expanded( 44 | child: Card( 45 | margin: const EdgeInsets.fromLTRB(20, 10, 20, 20), 46 | child: LogsViewer( 47 | output: ref.watch(alistProvider).output, 48 | )), 49 | ), 50 | ], 51 | ), 52 | ))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/widgets/pages/debug_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:alisthelper/provider/app_arguments_provider.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | 10 | class DebugPage extends ConsumerWidget { 11 | const DebugPage({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context, WidgetRef ref) { 15 | final appArguments = ref.watch(appArgumentsProvider); 16 | 17 | return Scaffold( 18 | appBar: AppBar( 19 | title: const Text('Debug'), 20 | ), 21 | body: ListView( 22 | padding: const EdgeInsets.all(20), 23 | children: [ 24 | DebugEntry(name: 'Debug Mode', value: kDebugMode.toString()), 25 | DebugEntry( 26 | name: 'App Arguments', 27 | value: appArguments.isEmpty ? null : appArguments.join(' ')), 28 | DebugEntry(name: 'Dart SDK Version', value: Platform.version), 29 | DebugEntry(name: 'Platform', value: Platform.operatingSystem), 30 | DebugEntry( 31 | name: 'Platform Version', value: Platform.operatingSystemVersion), 32 | 33 | ], 34 | ), 35 | ); 36 | } 37 | } 38 | 39 | class DebugEntry extends StatelessWidget { 40 | static const headerStyle = TextStyle(fontWeight: FontWeight.bold); 41 | 42 | final String name; 43 | final String? value; 44 | 45 | const DebugEntry({super.key, required this.name, required this.value}); 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Column( 50 | crossAxisAlignment: CrossAxisAlignment.start, 51 | children: [ 52 | const SizedBox(height: 20), 53 | Text(name, style: headerStyle), 54 | CopyableText( 55 | name: name, 56 | value: value, 57 | ), 58 | ], 59 | ); 60 | } 61 | } 62 | 63 | class CopyableText extends StatelessWidget { 64 | final TextSpan? prefix; 65 | final String name; 66 | final String? value; 67 | 68 | const CopyableText( 69 | {super.key, this.prefix, required this.name, required this.value}); 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return InkWell( 74 | onTap: value == null 75 | ? null 76 | : () async { 77 | if (context.mounted) { 78 | Clipboard.setData(ClipboardData(text: value.toString())); 79 | ScaffoldMessenger.of(context).showSnackBar( 80 | SnackBar( 81 | content: Text('Copied $name to clipboard!'), 82 | ), 83 | ); 84 | } 85 | }, 86 | child: Text.rich( 87 | TextSpan( 88 | children: [ 89 | if (prefix != null) prefix!, 90 | TextSpan(text: value ?? '-'), 91 | ], 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/widgets/pages/home.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:alisthelper/utils/init.dart'; 4 | import 'package:alisthelper/widgets/pages/alist_helper_page.dart'; 5 | import 'package:alisthelper/widgets/pages/rclone_page.dart'; 6 | import 'package:alisthelper/widgets/responsive_builder.dart'; 7 | import 'package:alisthelper/widgets/pages/settings_page.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:alisthelper/i18n/strings.g.dart'; 10 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 11 | 12 | class AlistHelperIcon extends StatelessWidget { 13 | const AlistHelperIcon({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Column( 18 | children: [ 19 | const SizedBox(height: 20), 20 | Image.asset('assets/alisthelper.png', height: 80, width: 80), 21 | const SizedBox(height: 20), 22 | const Text('Alist Helper', 23 | style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), 24 | textAlign: TextAlign.center), 25 | const SizedBox(height: 20) 26 | ], 27 | ); 28 | } 29 | } 30 | 31 | class Home extends ConsumerStatefulWidget { 32 | const Home({super.key}); 33 | 34 | @override 35 | ConsumerState createState() => _HomeState(); 36 | } 37 | 38 | enum HomeTab { 39 | home(Icons.home_filled), 40 | rclone(Icons.storage_outlined), 41 | settings(Icons.settings); 42 | 43 | final IconData icon; 44 | 45 | const HomeTab(this.icon); 46 | 47 | String get label { 48 | switch (this) { 49 | case HomeTab.home: 50 | return t.tabs.home; 51 | case HomeTab.rclone: 52 | return t.tabs.mount; 53 | case HomeTab.settings: 54 | return t.tabs.settings; 55 | } 56 | } 57 | } 58 | 59 | class _HomeState extends ConsumerState { 60 | late PageController _pageController; 61 | 62 | HomeTab _currentTab = HomeTab.home; 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | return ResponsiveBuilder( 67 | builder: (sizingInformation) { 68 | return Scaffold( 69 | body: SafeArea( 70 | child: Row( 71 | children: [ 72 | if (!sizingInformation.isMobile) 73 | NavigationRail( 74 | selectedIndex: _currentTab.index, 75 | onDestinationSelected: _goToPage, 76 | extended: sizingInformation.isDesktop, 77 | leading: sizingInformation.isDesktop 78 | ? const AlistHelperIcon() 79 | : null, 80 | destinations: HomeTab.values.map((tab) { 81 | return NavigationRailDestination( 82 | icon: Icon(tab.icon), 83 | label: Text(tab.label), 84 | ); 85 | }).toList()), 86 | Expanded( 87 | child: Stack( 88 | children: [ 89 | PageView( 90 | controller: _pageController, 91 | physics: const NeverScrollableScrollPhysics(), 92 | children: [ 93 | AlistHelperPage(sizingInformation: sizingInformation), 94 | RclonePage(sizingInformation: sizingInformation), 95 | SettingsPage(sizingInformation: sizingInformation), 96 | ], 97 | ), 98 | ], 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | bottomNavigationBar: sizingInformation.isMobile 105 | ? NavigationBar( 106 | selectedIndex: sizingInformation.isMobile 107 | ? min(_currentTab.index, 2) 108 | : _currentTab.index, 109 | onDestinationSelected: _goToPage, 110 | destinations: HomeTab.values.map((tab) { 111 | return NavigationDestination( 112 | icon: Icon(tab.icon), label: tab.label); 113 | }).toList(), 114 | ) 115 | : null, 116 | ); 117 | }, 118 | ); 119 | } 120 | 121 | @override 122 | void initState() { 123 | super.initState(); 124 | _pageController = PageController(initialPage: HomeTab.home.index); 125 | _currentTab = HomeTab.home; 126 | 127 | WidgetsBinding.instance.addPostFrameCallback((_) async { 128 | await postInit(ref); 129 | }); 130 | } 131 | 132 | void _goToPage(int index) { 133 | setState(() { 134 | _currentTab = HomeTab.values[index]; 135 | _pageController.jumpToPage(_currentTab.index); 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/widgets/pages/language_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:alisthelper/i18n/strings.g.dart'; 4 | import 'package:alisthelper/provider/settings_provider.dart'; 5 | 6 | class LanguagePage extends ConsumerWidget { 7 | const LanguagePage({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context, WidgetRef ref) { 11 | final activeLocale = ref.watch(settingsProvider).locale; 12 | 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: Text(t.languageSettings.language, 16 | style: const TextStyle(fontWeight: FontWeight.bold))), 17 | body: Center( 18 | child: Container( 19 | constraints: const BoxConstraints(maxWidth: 800), 20 | child: ListView( 21 | padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), 22 | children: [ 23 | ...[null, ...AppLocale.values].map((locale) { 24 | return ListTile( 25 | onTap: () async { 26 | await ref 27 | .watch(settingsProvider.notifier) 28 | .setLocale(locale); 29 | if (locale == null) { 30 | LocaleSettings.useDeviceLocale(); 31 | } else { 32 | LocaleSettings.setLocale(locale); 33 | } 34 | }, 35 | title: Row( 36 | children: [ 37 | Flexible( 38 | child: Text( 39 | locale?.humanName ?? t.languageSettings.system), 40 | ), 41 | if (locale == activeLocale) ...[ 42 | const SizedBox(width: 10), 43 | const Icon( 44 | Icons.check_circle, 45 | color: Colors.green, 46 | ), 47 | ], 48 | ], 49 | ), 50 | ); 51 | }) 52 | ], 53 | ), 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | 60 | extension AppLocaleExt on AppLocale { 61 | String get humanName { 62 | return LocaleSettings.instance.translationMap[this]!.locale; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/widgets/pages/logs_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/provider/rclone_provider.dart'; 2 | import 'package:alisthelper/widgets/logs_viewer.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:alisthelper/i18n/strings.g.dart'; 6 | 7 | class RcloneLogsPage extends ConsumerWidget { 8 | const RcloneLogsPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context, WidgetRef ref) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | centerTitle: false, 15 | leading: IconButton( 16 | icon: const Icon(Icons.close), 17 | onPressed: () => Navigator.of(context).pop(), 18 | ), 19 | title: Text(t.rcloneOperation.viewLogs), 20 | ), 21 | body: Center( 22 | child: Container( 23 | constraints: const BoxConstraints(maxWidth: 800), 24 | child: SizedBox( 25 | height: double.infinity, 26 | width: double.infinity, 27 | child: Padding( 28 | padding: const EdgeInsets.all(8.0), 29 | child: Card( 30 | child: LogsViewer( 31 | output: ref.watch(rcloneProvider).output, 32 | ), 33 | ), 34 | ), 35 | ), 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/widgets/pages/rclone_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:alisthelper/model/virtual_disk_state.dart'; 3 | import 'package:alisthelper/provider/rclone_provider.dart'; 4 | import 'package:alisthelper/provider/settings_provider.dart'; 5 | import 'package:alisthelper/widgets/add_new_vdisk.dart'; 6 | 7 | import 'package:alisthelper/widgets/button_card.dart'; 8 | import 'package:alisthelper/widgets/responsive_builder.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 11 | import 'package:url_launcher/url_launcher.dart'; 12 | 13 | class RclonePage extends ConsumerWidget { 14 | final SizingInformation sizingInformation; 15 | 16 | const RclonePage({super.key, required this.sizingInformation}); 17 | 18 | @override 19 | Widget build(BuildContext context, WidgetRef ref) { 20 | final settings = ref.watch(settingsProvider); 21 | final rclone = ref.watch(rcloneProvider); 22 | final String message; 23 | 24 | if (settings.rcloneDirectory == '' && settings.webdavAccount == '') { 25 | message = 26 | '${t.settings.rcloneSettings.rcloneDirNotSet}\n${t.settings.rcloneSettings.rcloneWebdavNotSet}'; 27 | } else if (settings.rcloneDirectory == '') { 28 | message = t.settings.rcloneSettings.rcloneDirNotSet; 29 | } else if (settings.webdavAccount == '') { 30 | message = t.settings.rcloneSettings.rcloneWebdavNotSet; 31 | } else { 32 | message = 'Error\nPlease check your settings'; 33 | } 34 | 35 | return Scaffold( 36 | appBar: (sizingInformation.isDesktop 37 | ? null 38 | : AppBar( 39 | title: const Text('Rclone', 40 | style: TextStyle(fontWeight: FontWeight.bold)), 41 | )), 42 | body: (settings.rcloneDirectory == '' || settings.webdavAccount == '') 43 | ? Center( 44 | child: Padding( 45 | padding: const EdgeInsets.all(8.0), 46 | child: Text(message, 47 | textAlign: TextAlign.center, 48 | style: const TextStyle( 49 | fontWeight: FontWeight.w600, fontSize: 18)), 50 | ), 51 | ) 52 | : Center( 53 | child: Container( 54 | constraints: const BoxConstraints(maxWidth: 800), 55 | child: Column( 56 | children: [ 57 | ListTile( 58 | title: Text(t.home.options, 59 | style: const TextStyle( 60 | fontWeight: FontWeight.w600, fontSize: 18)), 61 | trailing: HelpButton(), 62 | ), 63 | const RcloneMultiButtonCard(), 64 | ListTile( 65 | title: Text(t.home.manage, 66 | style: const TextStyle( 67 | fontWeight: FontWeight.w600, fontSize: 18)), 68 | trailing: AddNewRcloneDisk()), 69 | Expanded( 70 | child: (rclone.vdList.isEmpty) 71 | ? NoVdisks() 72 | : Padding( 73 | padding: const EdgeInsets.only(top: 5, bottom: 5), 74 | child: ListView.builder( 75 | itemCount: rclone.vdList.length, 76 | itemBuilder: (context, index) { 77 | return RcloneVirtualDisk( 78 | vd: rclone.vdList[index]); 79 | }, 80 | ), 81 | ), 82 | ) 83 | ], 84 | ), 85 | ))); 86 | } 87 | } 88 | 89 | class NoVdisks extends StatelessWidget { 90 | const NoVdisks({ 91 | super.key, 92 | }); 93 | 94 | @override 95 | Widget build(BuildContext context) { 96 | return Center( 97 | child: Text(t.rcloneOperation.noVdisks, 98 | style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)), 99 | ); 100 | } 101 | } 102 | 103 | class HelpButton extends StatelessWidget { 104 | const HelpButton({ 105 | super.key, 106 | }); 107 | 108 | @override 109 | Widget build(BuildContext context) { 110 | return TextButton.icon( 111 | onPressed: () => launchUrl( 112 | Uri.parse('https://github.com/Xmarmalade/alisthelper/wiki')), 113 | icon: Icon(Icons.info_outline_rounded), 114 | label: Text(t.button.docs)); 115 | } 116 | } 117 | 118 | class RcloneVirtualDisk extends ConsumerWidget { 119 | const RcloneVirtualDisk({ 120 | super.key, 121 | required this.vd, 122 | }); 123 | 124 | final VirtualDiskState vd; 125 | 126 | @override 127 | Widget build(BuildContext context, WidgetRef ref) { 128 | final rcloneNotifier = ref.watch(rcloneProvider.notifier); 129 | 130 | return Card( 131 | margin: const EdgeInsets.fromLTRB(20, 5, 20, 5), 132 | child: ListTile( 133 | titleAlignment: ListTileTitleAlignment.center, 134 | leading: Icon(Icons.settings_system_daydream), 135 | title: Text(vd.name.toUpperCase(), 136 | style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)), 137 | subtitle: Text( 138 | '${vd.mountPoint}: /${vd.path} ${vd.extraFlags}', 139 | ), 140 | trailing: Wrap( 141 | children: [ 142 | EditRcloneDisk(disk: vd), 143 | IconButton( 144 | icon: const Icon(Icons.delete), 145 | onPressed: () { 146 | //rcloneNotifier.deleteSpecific(vd); 147 | // showDialog to confirm deletion 148 | showDialog( 149 | context: context, 150 | builder: (context) => AlertDialog( 151 | title: Text(t.rcloneOperation.deleteVdisk(name: vd.name)), 152 | actions: [ 153 | FilledButton( 154 | onPressed: () { 155 | Navigator.of(context).pop(); 156 | }, 157 | child: Text(t.button.cancel), 158 | ), 159 | TextButton( 160 | onPressed: () { 161 | rcloneNotifier.deleteSpecific(vd); 162 | Navigator.of(context).pop(); 163 | }, 164 | child: Text(t.button.yes), 165 | ), 166 | ], 167 | ), 168 | ); 169 | }, 170 | ), 171 | IconButton( 172 | icon: vd.isMounted 173 | ? const Icon(Icons.stop) 174 | : const Icon(Icons.play_arrow), 175 | onPressed: !ref.watch(rcloneProvider).isRunning 176 | ? null 177 | : () { 178 | rcloneNotifier.toggleMount(vd); 179 | }, 180 | ), 181 | ], 182 | ), 183 | ), 184 | ); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /lib/widgets/proxy_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:alisthelper/provider/alist_provider.dart'; 3 | import 'package:alisthelper/provider/settings_provider.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | 7 | class ProxyTile extends ConsumerWidget { 8 | const ProxyTile({super.key}); 9 | 10 | @override 11 | @override 12 | Widget build(BuildContext context, WidgetRef ref) { 13 | final settings = ref.watch(settingsProvider); 14 | final alistNotifier = ref.read(alistProvider.notifier); 15 | final TextEditingController proxyController = 16 | TextEditingController(text: settings.proxy); 17 | 18 | return ListTile( 19 | contentPadding: const EdgeInsets.fromLTRB(20, 5, 20, 5), 20 | title: Text( 21 | t.settings.alistSettings.proxy.title, 22 | style: const TextStyle(fontWeight: FontWeight.w500), 23 | ), 24 | subtitle: Text((settings.proxy.toString() != '') 25 | ? settings.proxy.toString() 26 | : t.settings.alistSettings.proxy.hint), 27 | trailing: FilledButton.tonal( 28 | onPressed: () async { 29 | final String? proxy = await showDialog( 30 | context: context, // use the new context variable here 31 | builder: (BuildContext context) { 32 | // use the new context variable here 33 | return AlertDialog( 34 | title: Text(t.settings.alistSettings.proxy.title), 35 | content: Column( 36 | mainAxisSize: MainAxisSize.min, 37 | mainAxisAlignment: MainAxisAlignment.start, 38 | children: [ 39 | TextField( 40 | controller: proxyController, 41 | decoration: InputDecoration( 42 | border: OutlineInputBorder(), 43 | labelText: t.settings.alistSettings.proxy.title, 44 | hintText: "http://yourproxy:port", 45 | helperText: t.settings.alistSettings.proxy.hint), 46 | ), 47 | ], 48 | ), 49 | actions: [ 50 | TextButton( 51 | onPressed: () => Navigator.of(context).pop(), 52 | child: Text(t.button.cancel), 53 | ), 54 | FilledButton.tonal( 55 | onPressed: () async { 56 | final proxy = proxyController.text; 57 | Navigator.of(context).pop(proxy); 58 | }, 59 | child: Text(t.button.ok), 60 | ), 61 | ], 62 | ); 63 | }, 64 | ); 65 | if (proxy != null && proxy != settings.proxy) { 66 | //check url is vaild for http://yourproxy:port 67 | try { 68 | if (Uri.parse(proxy).isAbsolute || proxy == '') { 69 | await alistNotifier.setProxy(proxy); 70 | if (context.mounted) { 71 | ScaffoldMessenger.of(context).showSnackBar( 72 | SnackBar( 73 | content: Text(t.settings.alistSettings.proxy.success), 74 | ), 75 | ); 76 | } 77 | } else { 78 | if (context.mounted) { 79 | ScaffoldMessenger.of(context).showSnackBar( 80 | SnackBar( 81 | content: Text(t.settings.alistSettings.proxy.error), 82 | ), 83 | ); 84 | proxyController.text = settings.proxy.toString(); 85 | } 86 | } 87 | } on Exception catch (e) { 88 | if (context.mounted) { 89 | ScaffoldMessenger.of(context).showSnackBar( 90 | SnackBar( 91 | content: Text( 92 | t.settings.alistSettings.proxy.error + e.toString()), 93 | ), 94 | ); 95 | proxyController.text = settings.proxy.toString(); 96 | } 97 | } 98 | } 99 | }, 100 | child: Text(t.button.edit), 101 | ), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/widgets/rclone_account.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/utils/textutils.dart'; 2 | 3 | import 'package:alisthelper/i18n/strings.g.dart'; 4 | import 'package:alisthelper/model/settings_state.dart'; 5 | import 'package:alisthelper/provider/settings_provider.dart'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 9 | 10 | class RcloneMountAccountTile extends ConsumerWidget { 11 | const RcloneMountAccountTile({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context, WidgetRef ref) { 15 | final SettingsState settings = ref.watch(settingsProvider); 16 | final SettingsNotifier settingsNotifier = 17 | ref.read(settingsProvider.notifier); 18 | String user = 'dav'; 19 | String pwd = 'dav'; 20 | if (settings.webdavAccount.isEmpty) { 21 | settingsNotifier.setWebdavAccount(TextUtils.accountEncoder([user, pwd])); 22 | } else { 23 | [user, pwd] = TextUtils.accountParser(settings.webdavAccount); 24 | } 25 | return ListTile( 26 | contentPadding: const EdgeInsets.fromLTRB(20, 5, 20, 5), 27 | title: Text(t.settings.rcloneSettings.account.title, 28 | style: const TextStyle(fontWeight: FontWeight.w500)), 29 | trailing: FilledButton.tonal( 30 | onPressed: () async { 31 | // show a dialog to edit the rclone account 32 | final result = await showDialog>( 33 | context: context, 34 | builder: (context) { 35 | final userController = TextEditingController(text: user); 36 | final pwdController = TextEditingController(text: pwd); 37 | return AlertDialog( 38 | title: Text(t.settings.rcloneSettings.account.description), 39 | actions: [ 40 | FilledButton.tonal( 41 | onPressed: () { 42 | Navigator.of(context) 43 | .pop([userController.text, pwdController.text]); 44 | }, 45 | child: Text(t.button.save), 46 | ), 47 | ], 48 | content: Column( 49 | mainAxisSize: MainAxisSize.min, 50 | children: [ 51 | TextField( 52 | controller: userController, 53 | decoration: InputDecoration( 54 | labelText: 55 | t.settings.rcloneSettings.account.name), 56 | ), 57 | TextField( 58 | controller: pwdController, 59 | decoration: InputDecoration( 60 | labelText: 61 | t.settings.rcloneSettings.account.pass), 62 | ), 63 | ], 64 | ), 65 | ); 66 | }); 67 | if (result != null && result.length == 2) { 68 | final [user, pwd] = result; 69 | settingsNotifier 70 | .setWebdavAccount(TextUtils.accountEncoder([user, pwd])); 71 | } 72 | }, 73 | child: Text(t.button.edit), 74 | )); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/widgets/responsive_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SizingInformation { 4 | final bool isMobile; 5 | final bool isTabletOrDesktop; 6 | final bool isDesktop; 7 | 8 | const SizingInformation(double width) : isMobile = width < 600, isTabletOrDesktop = width >= 700, isDesktop = width >= 800; 9 | } 10 | 11 | class ResponsiveBuilder extends StatelessWidget { 12 | final Widget Function(SizingInformation sizingInformation) builder; 13 | 14 | const ResponsiveBuilder({super.key, required this.builder}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final width = MediaQuery.of(context).size.width; 19 | return builder(SizingInformation(width)); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/widgets/sponsor_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:url_launcher/url_launcher.dart'; 5 | 6 | class SponsorBtn extends StatelessWidget { 7 | const SponsorBtn({ 8 | super.key, 9 | }); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ListTile( 14 | title: Text(t.home.options, 15 | style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)), 16 | trailing: TextButton.icon( 17 | onPressed: () { 18 | final locale = Localizations.localeOf(context).toString(); 19 | final url = locale == 'zh_Hans_CN' 20 | ? 'https://github.com/Xmarmalade/alisthelper/wiki/sponsor_cn' 21 | : 'https://github.com/Xmarmalade/alisthelper/wiki/sponsor'; 22 | launchUrl(Uri.parse(url)); 23 | }, 24 | label: Text(t.button.sponsor), 25 | icon: Icon(Icons.star_border_outlined), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/widgets/theme_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:alisthelper/i18n/strings.g.dart'; 2 | import 'package:alisthelper/model/settings_state.dart'; 3 | import 'package:alisthelper/provider/settings_provider.dart'; 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | 8 | class ChangeThemeModeTile extends ConsumerWidget { 9 | const ChangeThemeModeTile({ 10 | super.key, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context, WidgetRef ref) { 15 | final SettingsState settings = ref.watch(settingsProvider); 16 | final SettingsNotifier settingsNotifier = 17 | ref.read(settingsProvider.notifier); 18 | return ListTile( 19 | leading: Icon(Icons.dark_mode, color: settings.themeColor), 20 | title: Text(t.settings.interfaceSettings.themeMode), 21 | trailing: FilledButton.tonal( 22 | onPressed: () { 23 | showDialog( 24 | context: context, 25 | builder: (context) => AlertDialog( 26 | title: Text(t.settings.interfaceSettings.themeMode), 27 | content: Column( 28 | mainAxisSize: MainAxisSize.min, 29 | children: [ 30 | RadioListTile( 31 | title: Text(t.settings.theme.system), 32 | value: ThemeMode.system, 33 | groupValue: settings.themeMode, 34 | onChanged: (value) { 35 | settingsNotifier.setThemeMode(value!); 36 | Navigator.pop(context); 37 | }, 38 | ), 39 | RadioListTile( 40 | title: Text(t.settings.theme.light), 41 | value: ThemeMode.light, 42 | groupValue: settings.themeMode, 43 | onChanged: (value) { 44 | settingsNotifier.setThemeMode(value!); 45 | Navigator.pop(context); 46 | }, 47 | ), 48 | RadioListTile( 49 | title: Text(t.settings.theme.dark), 50 | value: ThemeMode.dark, 51 | groupValue: settings.themeMode, 52 | onChanged: (value) { 53 | settingsNotifier.setThemeMode(value!); 54 | Navigator.pop(context); 55 | }, 56 | ), 57 | ], 58 | ), 59 | ), 60 | ); 61 | }, 62 | child: Text(settings.themeMode == ThemeMode.system 63 | ? t.settings.theme.system 64 | : settings.themeMode == ThemeMode.light 65 | ? t.settings.theme.light 66 | : t.settings.theme.dark), 67 | ), 68 | ); 69 | } 70 | } 71 | 72 | class ChangeThemeColorTile extends ConsumerWidget { 73 | const ChangeThemeColorTile({super.key}); 74 | 75 | static const themeColors = { 76 | "Crimson": Color.fromARGB(255, 220, 20, 60), 77 | "Orange": Colors.orange, 78 | "Chrome": Color.fromARGB(255, 230, 184, 0), 79 | "Grass": Colors.lightGreen, 80 | "Teal": Colors.teal, 81 | "Cyan": Colors.cyan, 82 | "Sea Foam": Color.fromARGB(255, 112, 193, 207), 83 | "Ice": Color.fromARGB(255, 115, 155, 208), 84 | "Blue": Colors.blue, 85 | "Indigo": Colors.indigo, 86 | "Violet": Colors.deepPurple, 87 | "Orchid": Color.fromARGB(255, 218, 112, 214), 88 | }; 89 | 90 | @override 91 | Widget build(BuildContext context, WidgetRef ref) { 92 | final SettingsState settings = ref.watch(settingsProvider); 93 | final SettingsNotifier settingsNotifier = 94 | ref.read(settingsProvider.notifier); 95 | return ListTile( 96 | leading: Icon(Icons.color_lens_rounded, color: settings.themeColor), 97 | title: Text(t.settings.interfaceSettings.themeColor), 98 | trailing: FilledButton.tonal( 99 | onPressed: () { 100 | showDialog( 101 | context: context, 102 | builder: (context) => AlertDialog( 103 | title: Text(t.settings.interfaceSettings.themeColor), 104 | content: SingleChildScrollView( 105 | child: Column( 106 | mainAxisSize: MainAxisSize.min, 107 | children: themeColors.entries 108 | .map((entry) => RadioListTile( 109 | activeColor: entry.value, 110 | title: Text(entry.key), 111 | value: entry.value, 112 | groupValue: settings.themeColor, 113 | onChanged: (value) { 114 | settingsNotifier.setThemeColor(value!); 115 | Navigator.pop(context); 116 | }, 117 | )) 118 | .toList(), 119 | ), 120 | ), 121 | ), 122 | ); 123 | }, 124 | child: Text(t.button.select), 125 | )); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/widgets/toggle_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class CustomToggleTile extends StatelessWidget { 5 | const CustomToggleTile({ 6 | super.key, 7 | required this.value, 8 | required this.onToggled, 9 | required this.title, 10 | required this.subtitle, 11 | }); 12 | 13 | final bool value; 14 | final Function onToggled; 15 | final String title; 16 | final String subtitle; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return InkWell( 21 | customBorder: RoundedRectangleBorder( 22 | borderRadius: BorderRadius.circular(10), 23 | ), 24 | child: SwitchListTile( 25 | contentPadding: const EdgeInsets.fromLTRB(20, 5, 20, 5), 26 | title: Text(title, 27 | style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), 28 | subtitle: Text(subtitle), 29 | value: value, 30 | onChanged: (value) { 31 | onToggled(value); 32 | }, 33 | ), 34 | ); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "alisthelper") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "dev.xmarmalade.alisthelper.alisthelper") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | 90 | # Generated plugin build rules, which manage building the plugins and adding 91 | # them to the application. 92 | include(flutter/generated_plugins.cmake) 93 | 94 | 95 | # === Installation === 96 | # By default, "installing" just makes a relocatable bundle in the build 97 | # directory. 98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 101 | endif() 102 | 103 | # Start with a clean build bundle directory every time. 104 | install(CODE " 105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 106 | " COMPONENT Runtime) 107 | 108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 110 | 111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 112 | COMPONENT Runtime) 113 | 114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 115 | COMPONENT Runtime) 116 | 117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 118 | COMPONENT Runtime) 119 | 120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 121 | install(FILES "${bundled_library}" 122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 123 | COMPONENT Runtime) 124 | endforeach(bundled_library) 125 | 126 | # Fully re-copy the assets directory on each build to avoid having stale files 127 | # from a previous install. 128 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 129 | install(CODE " 130 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 131 | " COMPONENT Runtime) 132 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 133 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 134 | 135 | # Install the AOT library on non-Debug builds only. 136 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 137 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 138 | COMPONENT Runtime) 139 | endif() 140 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "alisthelper"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "alisthelper"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_realize(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GObject::dispose. 85 | static void my_application_dispose(GObject* object) { 86 | MyApplication* self = MY_APPLICATION(object); 87 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 88 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 89 | } 90 | 91 | static void my_application_class_init(MyApplicationClass* klass) { 92 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 93 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 94 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 95 | } 96 | 97 | static void my_application_init(MyApplication* self) {} 98 | 99 | MyApplication* my_application_new() { 100 | return MY_APPLICATION(g_object_new(my_application_get_type(), 101 | "application-id", APPLICATION_ID, 102 | "flags", G_APPLICATION_NON_UNIQUE, 103 | nullptr)); 104 | } 105 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = alisthelper 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = dev.xmarmalade.alisthelper.alisthelper 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 dev.xmarmalade.alisthelper. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import window_manager 4 | 5 | class MainFlutterWindow: NSWindow { 6 | override func awakeFromNib() { 7 | let flutterViewController = FlutterViewController() 8 | let windowFrame = self.frame 9 | self.contentViewController = flutterViewController 10 | self.setFrame(windowFrame, display: true) 11 | 12 | RegisterGeneratedPlugins(registry: flutterViewController) 13 | 14 | super.awakeFromNib() 15 | } 16 | override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { 17 | super.order(place, relativeTo: otherWin) 18 | hiddenWindowAtLaunch() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.server 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: alisthelper 2 | description: A flutter app designed to provide more convenient management of alist operations. 3 | publish_to: "none" 4 | version: 0.2.0+10 5 | 6 | environment: 7 | sdk: ">=3.4.0 <4.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | collection: ^1.19.0 13 | shared_preferences: ^2.3.1 14 | flutter_riverpod: ^2.6.1 15 | fast_gbk: ^1.0.0 16 | url_launcher: ^6.2.3 17 | tray_manager: ^0.4.0 18 | window_manager: ^0.4.3 19 | screen_retriever: ^0.2.0 20 | launch_at_startup: ^0.4.0 21 | package_info_plus: ^8.0.1 22 | file_selector: ^1.0.2 23 | freezed_annotation: ^3.0.0 24 | path_provider: ^2.1.2 25 | json_annotation: ^4.9.0 26 | logger: ^2.5.0 27 | http: ^1.2.2 28 | dio: ^5.4.0 29 | archive: ^4.0.5 30 | #i18n 31 | slang: ^4.6.0 32 | slang_flutter: ^4.6.0 33 | flutter_localizations: 34 | sdk: flutter 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | flutter_lints: ^5.0.0 40 | flutter_launcher_icons: ^0.14.1 41 | build_runner: ^2.4.11 42 | freezed: ^3.0.4 43 | slang_build_runner: ^4.6.0 44 | json_serializable: ^6.7.1 45 | riverpod_lint: ^2.6.1 46 | 47 | flutter_launcher_icons: 48 | image_path: "assets\\alisthelper.png" 49 | windows: 50 | generate: true 51 | icon_size: 256 # min:48, max:256, default: 48 52 | macos: 53 | generate: true 54 | 55 | flutter: 56 | uses-material-design: true 57 | 58 | assets: 59 | - assets/alisthelper.ico 60 | - assets/alisthelper.png 61 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(alisthelper LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "alisthelper") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(SET CMP0063 NEW) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Fully re-copy the assets directory on each build to avoid having stale files 91 | # from a previous install. 92 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 93 | install(CODE " 94 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 95 | " COMPONENT Runtime) 96 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 97 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 98 | 99 | # Install the AOT library on non-Debug builds only. 100 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 101 | CONFIGURATIONS Profile;Release 102 | COMPONENT Runtime) 103 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "dev.xmarmalade.alisthelper" "\0" 93 | VALUE "FileDescription", "alisthelper" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "alisthelper" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 dev.xmarmalade.alisthelper. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "alisthelper.exe" "\0" 98 | VALUE "ProductName", "alisthelper" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | //this->Show(); 32 | }); 33 | 34 | return true; 35 | } 36 | 37 | void FlutterWindow::OnDestroy() { 38 | if (flutter_controller_) { 39 | flutter_controller_ = nullptr; 40 | } 41 | 42 | Win32Window::OnDestroy(); 43 | } 44 | 45 | LRESULT 46 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 47 | WPARAM const wparam, 48 | LPARAM const lparam) noexcept { 49 | // Give Flutter, including plugins, an opportunity to handle window messages. 50 | if (flutter_controller_) { 51 | std::optional result = 52 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 53 | lparam); 54 | if (result) { 55 | return *result; 56 | } 57 | } 58 | 59 | switch (message) { 60 | case WM_FONTCHANGE: 61 | flutter_controller_->engine()->ReloadSystemFonts(); 62 | break; 63 | } 64 | 65 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 66 | } 67 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) 10 | // Making the app single-instanced https://leanflutter.org/blog/making-the-app-single-instanced 11 | { 12 | HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", L"alisthelper"); 13 | if (hwnd != NULL) 14 | { 15 | ::ShowWindow(hwnd, SW_NORMAL); 16 | ::SetForegroundWindow(hwnd); 17 | return EXIT_FAILURE; 18 | } 19 | 20 | // Attach to console when present (e.g., 'flutter run') or create a 21 | // new console when running with a debugger. 22 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) 23 | { 24 | CreateAndAttachConsole(); 25 | } 26 | 27 | // Initialize COM, so that it is available for use in the library and/or 28 | // plugins. 29 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 30 | 31 | flutter::DartProject project(L"data"); 32 | 33 | std::vector command_line_arguments = 34 | GetCommandLineArguments(); 35 | 36 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 37 | 38 | FlutterWindow window(project); 39 | Win32Window::Point origin(10, 10); 40 | Win32Window::Size size(1280, 720); 41 | if (!window.Create(L"alisthelper", origin, size)) 42 | { 43 | return EXIT_FAILURE; 44 | } 45 | window.SetQuitOnClose(true); 46 | 47 | ::MSG msg; 48 | while (::GetMessage(&msg, nullptr, 0, 0)) 49 | { 50 | ::TranslateMessage(&msg); 51 | ::DispatchMessage(&msg); 52 | } 53 | 54 | ::CoUninitialize(); 55 | return EXIT_SUCCESS; 56 | } 57 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xmarmalade/alisthelper/a1887c53ea60e20c4d7935504c8ad818ebc272b7/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /windows/scripts/innosetup.iss: -------------------------------------------------------------------------------- 1 | [Setup] 2 | AppName=AlistHelper 3 | AppVersion={#AppVersion} 4 | AppPublisher=Xmarmalade 5 | AppCopyright=Copyright (C) 2023 Xmarmalade 6 | WizardStyle=modern 7 | Compression=lzma2 8 | SolidCompression=yes 9 | DefaultDirName={autopf}\AlistHelper\ 10 | DefaultGroupName=AlistHelper 11 | SetupIconFile=alisthelper.ico 12 | UninstallDisplayIcon={app}\alisthelper.exe 13 | UninstallDisplayName=AlistHelper 14 | UsePreviousAppDir=no 15 | PrivilegesRequiredOverridesAllowed=dialog 16 | PrivilegesRequired=lowest 17 | CloseApplications=yes 18 | 19 | 20 | [Messages] 21 | ConfirmUninstall=Are you sure you want to completely remove %1 and all of its components?%nIMPORTANT NOTE: Please quit %1 before clicking Yes! 22 | ApplicationsFound=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications.%nIMPORTANT NOTE: If you allow alisthelper to minimize to tray, you need to exit alisthelper manually! 23 | ApplicationsFound2=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications.%nIMPORTANT NOTE: If you allow alisthelper to minimize to tray, you need to exit alisthelper manually! 24 | 25 | [Files] 26 | Source: "Release\alisthelper.exe"; DestDir: "{app}"; DestName: "alisthelper.exe" 27 | Source: "Release\*"; DestDir: "{app}" 28 | Source: "Release\data\*"; DestDir: "{app}\data\"; Flags: recursesubdirs 29 | 30 | [Icons] 31 | Name: "{userdesktop}\AlistHelper"; Filename: "{app}\AlistHelper.exe" 32 | Name: "{group}\AlistHelper"; Filename: "{app}\AlistHelper.exe" 33 | --------------------------------------------------------------------------------