├── .github
└── workflows
│ ├── abi.yml
│ └── build.yml
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── localsend_rs
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── i18n
│ ├── strings.i18n.json
│ └── strings_zh.i18n.json
└── icon
│ ├── logo.ico
│ ├── logo_128.png
│ ├── logo_256.png
│ └── logo_512.png
├── build.yaml
├── cliff.toml
├── flutter_launcher_icons.yaml
├── flutter_rust_bridge.yaml
├── integration_test
└── simple_test.dart
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
└── RunnerTests
│ └── RunnerTests.swift
├── lib
├── common
│ ├── constants.dart
│ ├── device_info_utils.dart
│ ├── string_extensions.dart
│ └── utils.dart
├── core
│ ├── listeners
│ │ ├── core_listener.dart
│ │ └── mission_listener.dart
│ ├── providers
│ │ ├── core_provider.dart
│ │ ├── core_provider.g.dart
│ │ ├── locale_provider.dart
│ │ ├── locale_provider.freezed.dart
│ │ ├── locale_provider.g.dart
│ │ ├── mission_provider.dart
│ │ ├── mission_provider.g.dart
│ │ ├── theme_provider.dart
│ │ └── theme_provider.g.dart
│ ├── rust
│ │ ├── actor
│ │ │ ├── core.dart
│ │ │ ├── mission.dart
│ │ │ ├── mission.freezed.dart
│ │ │ └── model.dart
│ │ ├── api
│ │ │ └── model.dart
│ │ ├── bridge.dart
│ │ ├── frb_generated.dart
│ │ ├── frb_generated.io.dart
│ │ └── logger.dart
│ └── store
│ │ └── config_store.dart
├── i18n
│ └── strings.g.dart
├── main.dart
└── view
│ ├── pages
│ ├── frame_page.dart
│ ├── home_page.dart
│ ├── mission_page.dart
│ └── setting_page.dart
│ └── widget
│ ├── common_widget.dart
│ ├── device_widget.dart
│ ├── discover_widget.dart
│ ├── mission_widget.dart
│ ├── network_widget.dart
│ └── setting_widgets.dart
├── linux
├── .gitignore
├── CMakeLists.txt
├── flutter
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
├── main.cc
├── my_application.cc
└── my_application.h
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── 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
├── rust
├── .gitignore
├── Cargo.toml
├── build.cmd
└── src
│ ├── actor
│ ├── core.rs
│ ├── device.rs
│ ├── discovery.rs
│ ├── http.rs
│ ├── mission.rs
│ ├── mission
│ │ ├── notify.rs
│ │ ├── pending.rs
│ │ └── transfer.rs
│ ├── mod.rs
│ └── model.rs
│ ├── api
│ ├── mod.rs
│ ├── model.rs
│ └── v2.rs
│ ├── bridge.rs
│ ├── frb_generated.io.rs
│ ├── frb_generated.rs
│ ├── frb_generated.web.rs
│ ├── lib.rs
│ ├── logger.rs
│ └── util.rs
├── rust_builder
├── .gitignore
├── README.md
├── android
│ ├── .gitignore
│ ├── build.gradle
│ ├── settings.gradle
│ └── src
│ │ └── main
│ │ └── AndroidManifest.xml
├── cargokit
│ ├── .gitignore
│ ├── LICENSE
│ ├── README
│ ├── build_pod.sh
│ ├── build_tool
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── bin
│ │ │ └── build_tool.dart
│ │ ├── lib
│ │ │ ├── build_tool.dart
│ │ │ └── src
│ │ │ │ ├── android_environment.dart
│ │ │ │ ├── artifacts_provider.dart
│ │ │ │ ├── build_cmake.dart
│ │ │ │ ├── build_gradle.dart
│ │ │ │ ├── build_pod.dart
│ │ │ │ ├── build_tool.dart
│ │ │ │ ├── builder.dart
│ │ │ │ ├── cargo.dart
│ │ │ │ ├── crate_hash.dart
│ │ │ │ ├── environment.dart
│ │ │ │ ├── logging.dart
│ │ │ │ ├── options.dart
│ │ │ │ ├── precompile_binaries.dart
│ │ │ │ ├── rustup.dart
│ │ │ │ ├── target.dart
│ │ │ │ ├── util.dart
│ │ │ │ └── verify_binaries.dart
│ │ ├── pubspec.lock
│ │ └── pubspec.yaml
│ ├── cmake
│ │ ├── cargokit.cmake
│ │ └── resolve_symlinks.ps1
│ ├── gradle
│ │ └── plugin.gradle
│ ├── run_build_tool.cmd
│ └── run_build_tool.sh
├── ios
│ ├── Classes
│ │ └── dummy_file.c
│ └── rust_builder.podspec
├── linux
│ └── CMakeLists.txt
├── macos
│ ├── Classes
│ │ └── dummy_file.c
│ └── rust_builder.podspec
├── pubspec.yaml
└── windows
│ ├── .gitignore
│ └── CMakeLists.txt
├── screenshots
└── desktop.png
├── setup.dart
├── test_driver
└── integration_test.dart
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.github/workflows/abi.yml:
--------------------------------------------------------------------------------
1 | name: Build Abi for Android
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | workflow_call:
7 |
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | defaults:
14 | run:
15 | working-directory: ./rust
16 |
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v2
20 |
21 | # Set up Android NDK and Cargo toolchain for cross-compiling to Android
22 | - name: Set up environment
23 | uses: actions-rs/toolchain@v1
24 | with:
25 | toolchain: stable
26 |
27 | # Download Android NDK for cross-compiling
28 | - name: Download Android NDK
29 | uses: nttld/setup-ndk@v1.5.0
30 | with:
31 | # Exact version to use
32 | ndk-version: r26d
33 | local-cache: true
34 |
35 | - name: Setup cargo ndk
36 | run: |
37 | cargo install cargo-ndk
38 | rustup target add aarch64-linux-android armv7-linux-androideabi
39 | rustup target list
40 |
41 | # Install Cargo target for Android
42 | - name: Build
43 | run: |
44 | mkdir output
45 | cargo ndk -o ./output -t arm64-v8a -t armeabi-v7a --manifest-path ./Cargo.toml build --release
46 | ls ./output
47 |
48 | # Upload the built artifacts as an artifact
49 | - name: Upload Artifacts
50 | uses: actions/upload-artifact@v4.3.3
51 | with:
52 | name: android-abi-arm
53 | path: rust/output
54 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | build-apk:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v4
16 |
17 | - name: Set up Flutter SDK
18 | uses: subosito/flutter-action@v2.16.0
19 | with:
20 | channel: stable
21 | cache: false
22 |
23 | - name: Set up rust
24 | uses: actions-rs/toolchain@v1
25 | with:
26 | toolchain: stable
27 |
28 | - name: Set up java 17
29 | uses: actions/setup-java@v4
30 | with:
31 | distribution: "zulu" # See 'Supported distributions' for available options
32 | java-version: "17"
33 |
34 | - name: Build Prepare
35 | run: |
36 | flutter pub get
37 | chmod +x ./rust_builder
38 | flutter doctor -v
39 |
40 | - name: Build APK
41 | run: |
42 | flutter build apk
43 | ls -alh ./build/app/outputs/flutter-apk/*.apk
44 | cp -r ./build/app/outputs/flutter-apk /tmp/flutter-apk
45 |
46 | # - name: Build Linux
47 | # run: |
48 | # flutter build linux --release
49 | # ls -alh ./build/linux/x64/release/bundle/*
50 | # cp -r ./build/linux/x64/release/bundle /tmp/linux-bundle
51 |
52 | - name: Upload Android
53 | uses: actions/upload-artifact@v4.3.3
54 | with:
55 | name: android-release
56 | path: /tmp/flutter-apk/*.apk
57 |
58 | build-windows:
59 | runs-on: windows-latest
60 |
61 | steps:
62 | - name: Checkout code
63 | uses: actions/checkout@v4
64 |
65 | - name: Set up Flutter SDK
66 | uses: subosito/flutter-action@v2.16.0
67 | with:
68 | channel: stable
69 | cache: true
70 |
71 | - name: Set up rust
72 | uses: actions-rs/toolchain@v1
73 | with:
74 | toolchain: stable
75 |
76 | - name: Build Prepare
77 | run: |
78 | flutter pub get
79 | flutter config --enable-native-assets
80 |
81 | - name: Build Windows
82 | run: |
83 | flutter build windows --release
84 |
85 | - name: Build iss script
86 | run: |
87 | dart setup.dart
88 | ls build\windows
89 |
90 | - name: Upload Windows
91 | uses: actions/upload-artifact@v4.3.3
92 | with:
93 | name: windows-release
94 | path: build\windows\localsend_rs-setup.exe
95 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | .vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Symbolication related
35 | app.*.symbols
36 |
37 | # Obfuscation related
38 | app.*.map.json
39 |
40 | # Android Studio will place build artifacts here
41 | /android/app/debug
42 | /android/app/profile
43 | /android/app/release
44 |
45 | /android/app/src/main/jniLibs
--------------------------------------------------------------------------------
/.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 and should not be manually edited.
5 |
6 | version:
7 | revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
17 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
18 | - platform: android
19 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
20 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
21 | - platform: ios
22 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
23 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
24 | - platform: linux
25 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
26 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
27 | - platform: macos
28 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
29 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
30 | - platform: web
31 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
32 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
33 | - platform: windows
34 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
35 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## [unreleased]
6 |
7 | ### Bug Fixes
8 |
9 | - 🐞 fix: const widget not respond to locale change
10 |
11 | - 🐞 fix: locale loading issue
12 |
13 |
14 | ### Features
15 |
16 | - ✨feature: add adaptive navigation
17 |
18 | - ✨feature: add app icon
19 |
20 | - ✨feature: add app icon in app view
21 |
22 |
23 | ### Others
24 |
25 | - 🔵 other: delete deprecated action workflow
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # localsend_rs
2 |
3 | 
4 |
5 | [](https://github.com/tom8zds/localsend_rs/actions/workflows/build.yml) 
6 |
7 | WIP: this repository is still WIP.
8 |
9 | A localsend protocol V2 implementation in flutter and rust for better performance.
10 |
11 | ## Screen shots
12 |
13 | 
14 |
15 | ## Performance
16 |
17 | Performance compare between localsend original and localsend_rs
18 |
19 | Test condition :
20 |
21 | - router: TpLink AX3000M
22 | - sender: Xiaomi 13 ( localsend )
23 | - receiver: Windows PC ( localsend_rs / localsend )
24 |
25 | | sender | receiver | network speed | disk speed |
26 | | --------- | ------------ | ------------- | ---------- |
27 | | localsend | localsend | 144Mbps | 26MB/s |
28 | | localsend | localsend_rs | 511Mbps | 102M/s |
29 |
30 | ## Roadmap
31 |
32 | - [ ] Protocol V2
33 | - [x] Udp announce
34 | - [x] Register
35 | - [x] Prepare upload
36 | - [x] Upload
37 | - [ ] Cancel
38 | - [ ] Send
39 | - [ ] User interface
40 | - [ ] discover page
41 | - [x] device list
42 | - [ ] device favorite
43 | - [x] receive page
44 | - [x] task progress
45 | - [ ] pic preview
46 | - [ ] mission progress
47 | - [ ] send page
48 | - [ ] setting page
49 | - [x] theme setting
50 | - [x] locale setting
51 | - [x] server setting
52 | - [x] start / stop
53 | - [ ] server config
54 | - [x] save directory
55 | - [ ] save pic to album
56 | - [ ] save to history
57 | - [x] Platform
58 | - [x] Windows
59 | - [x] Android
60 | - [ ] linux
61 |
62 | ## Getting Started
63 |
64 | This project is a starting point for a Flutter application.
65 |
66 | A few resources to get you started if this is your first Flutter project:
67 |
68 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
69 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
70 |
71 | For help getting started with Flutter development, view the
72 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
73 | samples, guidance on mobile development, and a full API reference.
74 |
--------------------------------------------------------------------------------
/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 https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | namespace "github.tom8zds.localsend_rs"
27 | compileSdkVersion flutter.compileSdkVersion
28 | ndkVersion flutter.ndkVersion
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_17
32 | targetCompatibility JavaVersion.VERSION_17
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '17'
37 | }
38 |
39 | sourceSets {
40 | main.java.srcDirs += 'src/main/kotlin'
41 | }
42 |
43 | defaultConfig {
44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
45 | applicationId "github.tom8zds.localsend_rs"
46 | // You can update the following values to match your application needs.
47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
48 | minSdkVersion flutter.minSdkVersion
49 | targetSdkVersion 35
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | }
53 |
54 | buildTypes {
55 | release {
56 | // TODO: Add your own signing config for the release build.
57 | // Signing with the debug keys for now, so `flutter run --release` works.
58 | signingConfig signingConfigs.debug
59 | }
60 | }
61 | }
62 |
63 | flutter {
64 | source '../..'
65 | }
66 |
67 | dependencies {}
68 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
29 |
33 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
47 |
50 |
51 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/localsend_rs/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package github.tom8zds.localsend_rs
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '2.0.0'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | mavenCentral()
17 | }
18 | }
19 |
20 | rootProject.buildDir = '../build'
21 | subprojects {
22 | project.buildDir = "${rootProject.buildDir}/${project.name}"
23 | }
24 | subprojects {
25 | project.evaluationDependsOn(':app')
26 | }
27 |
28 | tasks.register("clean", Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
21 | }
22 | }
23 |
24 | plugins {
25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
26 | id "com.android.application" version "7.3.0" apply false
27 | }
28 |
29 | include ":app"
30 |
--------------------------------------------------------------------------------
/assets/i18n/strings.i18n.json:
--------------------------------------------------------------------------------
1 | {
2 | "appTitle": {
3 | "parta": "LocalSend",
4 | "partb": "_RS"
5 | },
6 | "home": {
7 | "title": "Home Page"
8 | },
9 | "mission": {
10 | "accept": "Accept",
11 | "cancel": "Cancel",
12 | "complete": "Complete",
13 | "finished": "Finished",
14 | "tranfer": "Transfering",
15 | "pending": "Pending",
16 | "failed": "Failed",
17 | "skip": "Skip",
18 | "advance": "Advance"
19 | },
20 | "common": {
21 | "file": "File",
22 | "size": "Size"
23 | },
24 | "setting": {
25 | "title": "Settings",
26 | "common": "Common",
27 | "brightness": {
28 | "title": "Brightness",
29 | "subTitle": "Current mode: $mode",
30 | "themeMode": {
31 | "system": "Follow system",
32 | "light": "Light mode",
33 | "dark": "Dark mode"
34 | }
35 | },
36 | "language": {
37 | "title": "Language",
38 | "subTitle": "Current language: $language"
39 | },
40 | "receive": {
41 | "title": "Receive",
42 | "quickSave": "Quick Save",
43 | "quickSaveHint": "Start tranfer without accept",
44 | "saveFolder": "Save Folder",
45 | "selectSaveFolder": "Select"
46 | },
47 | "core": {
48 | "title": "core setting",
49 | "server": {
50 | "title": "server"
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/assets/i18n/strings_zh.i18n.json:
--------------------------------------------------------------------------------
1 | {
2 | "appTitle": {
3 | "parta": "快传",
4 | "partb": "锈"
5 | },
6 | "home": {
7 | "title": "主页"
8 | },
9 | "mission": {
10 | "accept": "接收",
11 | "cancel": "取消",
12 | "complete": "完成",
13 | "finished": "已完成",
14 | "tranfer": "传输中",
15 | "pending": "等待中",
16 | "failed": "失败",
17 | "skip": "跳过",
18 | "advance": "高级"
19 | },
20 | "common": {
21 | "file": "文件",
22 | "size": "大小"
23 | },
24 | "setting": {
25 | "title": "设置",
26 | "common": "通用",
27 | "brightness": {
28 | "title": "明暗",
29 | "subTitle": "当前模式: $mode",
30 | "themeMode": {
31 | "system": "跟随系统",
32 | "light": "浅色模式",
33 | "dark": "深色模式"
34 | }
35 | },
36 | "language": {
37 | "title": "语言",
38 | "subTitle": "当前语言: $language"
39 | },
40 | "receive": {
41 | "title": "接收设置",
42 | "quickSave": "快速保存",
43 | "quickSaveHint": "不需要等待确认直接接受",
44 | "saveFolder": "保存目录",
45 | "selectSaveFolder": "选择"
46 | },
47 | "core": {
48 | "title": "核心设置",
49 | "server": {
50 | "title": "服务器"
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/assets/icon/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo.ico
--------------------------------------------------------------------------------
/assets/icon/logo_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo_128.png
--------------------------------------------------------------------------------
/assets/icon/logo_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo_256.png
--------------------------------------------------------------------------------
/assets/icon/logo_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo_512.png
--------------------------------------------------------------------------------
/build.yaml:
--------------------------------------------------------------------------------
1 | targets:
2 | $default:
3 | builders:
4 | slang_build_runner:
5 | options:
6 | input_directory: assets/i18n
7 | output_directory: lib/i18n # defaulting to lib/gen if input is outside of lib/
8 | sources:
9 | exclude:
10 | # Example that excludes intellij's swap files
11 | - lib/rust/**
--------------------------------------------------------------------------------
/cliff.toml:
--------------------------------------------------------------------------------
1 | # git-cliff ~ default configuration file
2 | # https://git-cliff.org/docs/configuration
3 | #
4 | # Lines starting with "#" are comments.
5 | # Configuration options are organized into tables and keys.
6 | # See documentation for more information on available options.
7 |
8 | [changelog]
9 | # changelog header
10 | header = """
11 | # Changelog\n
12 | All notable changes to this project will be documented in this file.\n
13 | """
14 | # template for the changelog body
15 | # https://keats.github.io/tera/docs/#introduction
16 | body = """
17 | {% if version %}\
18 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
19 | {% else %}\
20 | ## [unreleased]
21 | {% endif %}\
22 | {% for group, commits in commits | group_by(attribute="group") %}
23 | ### {{ group | upper_first }}
24 | {% for commit in commits %}
25 | - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
26 | {% endfor %}
27 | {% endfor %}\n
28 | """
29 | # remove the leading and trailing whitespace from the template
30 | trim = true
31 | # changelog footer
32 | footer = """
33 |
34 | """
35 | # postprocessors
36 | postprocessors = [
37 | # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
38 | ]
39 | [git]
40 | # parse the commits based on https://www.conventionalcommits.org
41 | conventional_commits = false
42 | # filter out the commits that are not conventional
43 | filter_unconventional = false
44 | # process each line of a commit as an individual commit
45 | split_commits = false
46 | # regex for preprocessing the commit messages
47 | commit_preprocessors = [
48 | # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, # replace issue numbers
49 | ]
50 | # regex for parsing and grouping commits
51 | commit_parsers = [
52 | { message = ".*feature:", group = "Features" },
53 | { message = ".*fix:", group = "Bug Fixes" },
54 | { message = ".*docs:", group = "Documentation" },
55 | { message = ".*perf:", group = "Performance" },
56 | { message = ".*other:", group = "Others" },
57 | ]
58 | # protect breaking changes from being skipped due to matching a skipping commit_parser
59 | protect_breaking_commits = false
60 | # filter out the commits that are not matched by commit parsers
61 | filter_commits = false
62 | # regex for matching git tags
63 | tag_pattern = "v[0-9].*"
64 |
65 | # regex for skipping tags
66 | skip_tags = "v0.1.0-beta.1"
67 | # regex for ignoring tags
68 | ignore_tags = ""
69 | # sort the tags topologically
70 | topo_order = false
71 | # sort the commits inside sections by oldest/newest order
72 | sort_commits = "oldest"
73 | # limit the number of commits included in the changelog.
74 | # limit_commits = 42
75 |
--------------------------------------------------------------------------------
/flutter_launcher_icons.yaml:
--------------------------------------------------------------------------------
1 | flutter_launcher_icons:
2 | android: "launcher_icon"
3 | ios: false
4 | image_path: "assets/icon/logo_512.png"
5 | min_sdk_android: 21 # android min sdk min:16, default 21
6 | web:
7 | generate: true
8 | image_path: "assets/icon/logo_128.png"
9 | windows:
10 | generate: true
11 | image_path: "assets/icon/logo_128.png"
12 | icon_size: 48 # min:48, max:256, default: 48
--------------------------------------------------------------------------------
/flutter_rust_bridge.yaml:
--------------------------------------------------------------------------------
1 | rust_input: crate::bridge
2 | rust_root: rust/
3 | dart_output: lib/core/rust
4 | web: false
--------------------------------------------------------------------------------
/integration_test/simple_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:integration_test/integration_test.dart';
3 | import 'package:localsend_rs/core/rust/frb_generated.dart';
4 | import 'package:localsend_rs/main.dart';
5 |
6 | void main() {
7 | IntegrationTestWidgetsFlutterBinding.ensureInitialized();
8 | setUpAll(() async => await RustLib.init());
9 | testWidgets('Can call rust function', (WidgetTester tester) async {
10 | await tester.pumpWidget(const MyApp());
11 | expect(find.textContaining('Result: `Hello, Tom!`'), findsOneWidget);
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.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 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Localsend Rs
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | localsend_rs
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
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 |
--------------------------------------------------------------------------------
/lib/common/constants.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Language {
4 | final String name;
5 | final String localeName;
6 |
7 | const Language(this.name, this.localeName);
8 | }
9 |
10 | const Map supportLanguages = {
11 | "zh": Language("中文", "zh"),
12 | "en": Language("English", "en"),
13 | };
14 |
15 | enum LocaleMode {
16 | system,
17 | custom,
18 | }
19 |
20 | enum DeviceType {
21 | mobile,
22 | desktop,
23 | web,
24 | headless,
25 | server,
26 | }
27 |
28 | extension DeviceTypeExt on DeviceType {
29 | IconData get icon {
30 | return switch (this) {
31 | DeviceType.mobile => Icons.smartphone,
32 | DeviceType.desktop => Icons.computer,
33 | DeviceType.web => Icons.language,
34 | DeviceType.headless => Icons.terminal,
35 | DeviceType.server => Icons.dns,
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/common/string_extensions.dart:
--------------------------------------------------------------------------------
1 | // ignore: depend_on_referenced_packages
2 | import 'package:collection/collection.dart';
3 | import 'package:slang/builder/model/enums.dart';
4 |
5 | extension StringExtensions on String {
6 | /// capitalizes a given string
7 | /// 'hello' => 'Hello'
8 | /// 'Hello' => 'Hello'
9 | /// '' => ''
10 | String capitalize() {
11 | if (isEmpty) return '';
12 | return '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
13 | }
14 |
15 | /// transforms the string to the specified case
16 | /// if case is null, then no transformation will be applied
17 | String toCase(CaseStyle? style) {
18 | switch (style) {
19 | case CaseStyle.camel:
20 | return getWords()
21 | .mapIndexed((index, word) =>
22 | index == 0 ? word.toLowerCase() : word.capitalize())
23 | .join('');
24 | case CaseStyle.pascal:
25 | return getWords().map((word) => word.capitalize()).join('');
26 | case CaseStyle.snake:
27 | return getWords().map((word) => word.toLowerCase()).join('_');
28 | case null:
29 | return this;
30 | default:
31 | return this;
32 | }
33 | }
34 |
35 | /// de-DE will be interpreted as [de,DE]
36 | /// normally, it would be [de,D,E] which we do not want
37 | String toCaseOfLocale(CaseStyle style) {
38 | return toLowerCase().toCase(style);
39 | }
40 |
41 | /// get word list from string input
42 | /// assume that words are separated by special characters or by camel case
43 | List getWords() {
44 | final input = this;
45 | final StringBuffer buffer = StringBuffer();
46 | final List words = [];
47 | final bool isAllCaps = input.toUpperCase() == input;
48 |
49 | for (int i = 0; i < input.length; i++) {
50 | final String currChar = input[i];
51 | final String? nextChar = i + 1 == input.length ? null : input[i + 1];
52 |
53 | if (_symbolSet.contains(currChar)) {
54 | continue;
55 | }
56 |
57 | buffer.write(currChar);
58 |
59 | final bool isEndOfWord = nextChar == null ||
60 | (!isAllCaps && _upperAlphaRegex.hasMatch(nextChar)) ||
61 | _symbolSet.contains(nextChar);
62 |
63 | if (isEndOfWord) {
64 | words.add(buffer.toString());
65 | buffer.clear();
66 | }
67 | }
68 |
69 | return words;
70 | }
71 | }
72 |
73 | final RegExp _upperAlphaRegex = RegExp(r'[A-Z]');
74 | final Set _symbolSet = {' ', '.', '_', '-', '/', '\\'};
75 |
--------------------------------------------------------------------------------
/lib/core/listeners/core_listener.dart:
--------------------------------------------------------------------------------
1 | import 'package:localsend_rs/core/rust/actor/model.dart';
2 | import 'package:localsend_rs/core/rust/bridge.dart';
3 |
4 | class CoreListener {
5 | static CoreListener? _instance;
6 | CoreListener._();
7 |
8 | static CoreListener instance() {
9 | _instance ??= CoreListener._();
10 | return _instance!;
11 | }
12 |
13 | Stream stateStream = listenServerState();
14 | Stream> deviceStream = listenDevice();
15 | }
16 |
--------------------------------------------------------------------------------
/lib/core/listeners/mission_listener.dart:
--------------------------------------------------------------------------------
1 | import 'package:localsend_rs/core/rust/bridge.dart';
2 |
3 | import '../rust/actor/mission.dart';
4 |
5 | class MissionListener {
6 | static MissionListener? _instance;
7 | MissionListener._();
8 |
9 | static MissionListener instance() {
10 | _instance ??= MissionListener._();
11 | return _instance!;
12 | }
13 |
14 | Stream stream = listenMission();
15 | }
16 |
--------------------------------------------------------------------------------
/lib/core/providers/core_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:localsend_rs/core/listeners/core_listener.dart';
2 | import 'package:riverpod_annotation/riverpod_annotation.dart';
3 |
4 | import '../rust/actor/model.dart';
5 |
6 | part 'core_provider.g.dart';
7 |
8 | class RustCoreState {
9 | final bool serverState;
10 | final List devices;
11 |
12 | RustCoreState({required this.serverState, required this.devices});
13 |
14 | RustCoreState copyWith({bool? serverState, List? devices}) =>
15 | RustCoreState(
16 | serverState: serverState ?? this.serverState,
17 | devices: devices ?? this.devices,
18 | );
19 | }
20 |
21 | @riverpod
22 | class CoreState extends _$CoreState {
23 | @override
24 | RustCoreState build() {
25 | final subServerState = CoreListener.instance().stateStream.listen(
26 | (event) {
27 | state = state.copyWith(serverState: event);
28 | },
29 | );
30 | final subDevice = CoreListener.instance().deviceStream.listen(
31 | (event) {
32 | state = state.copyWith(devices: event);
33 | },
34 | );
35 | ref.onDispose(() {
36 | subServerState.cancel();
37 | subDevice.cancel();
38 | });
39 | return RustCoreState(serverState: false, devices: []);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/core/providers/core_provider.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'core_provider.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$coreStateHash() => r'2a21926ad86879088cb49e04efbc32590a05746f';
10 |
11 | /// See also [CoreState].
12 | @ProviderFor(CoreState)
13 | final coreStateProvider =
14 | AutoDisposeNotifierProvider.internal(
15 | CoreState.new,
16 | name: r'coreStateProvider',
17 | debugGetCreateSourceHash:
18 | const bool.fromEnvironment('dart.vm.product') ? null : _$coreStateHash,
19 | dependencies: null,
20 | allTransitiveDependencies: null,
21 | );
22 |
23 | typedef _$CoreState = AutoDisposeNotifier;
24 | // ignore_for_file: type=lint
25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
26 |
--------------------------------------------------------------------------------
/lib/core/providers/locale_provider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 | import 'package:riverpod_annotation/riverpod_annotation.dart';
6 | import 'package:window_manager/window_manager.dart';
7 |
8 | import '../../common/constants.dart';
9 | import '../../common/utils.dart';
10 | import '../../i18n/strings.g.dart';
11 | import '../store/config_store.dart';
12 |
13 | part 'locale_provider.g.dart';
14 | part 'locale_provider.freezed.dart';
15 |
16 | @freezed
17 | class LocaleConfig with _$LocaleConfig {
18 | factory LocaleConfig({
19 | required LocaleMode mode,
20 | required Locale customLocale,
21 | }) = _LocaleConfig;
22 | }
23 |
24 | extension LocaleConfigExt on LocaleConfig {
25 | Locale getLocale() {
26 | switch (mode) {
27 | case LocaleMode.system:
28 | return stringToLocale(Platform.localeName);
29 | case LocaleMode.custom:
30 | return customLocale;
31 | }
32 | }
33 | }
34 |
35 | @riverpod
36 | class LocaleState extends _$LocaleState {
37 | @override
38 | LocaleConfig build() {
39 | String localeValue = ConfigStore().locale();
40 | return LocaleConfig(
41 | mode: LocaleMode.system, customLocale: stringToLocale(localeValue));
42 | }
43 |
44 | void onModeChange() {
45 | if (state.mode == LocaleMode.system) {
46 | LocaleSettings.useDeviceLocale();
47 | } else {
48 | LocaleSettings.setLocaleRaw(state.customLocale.languageCode);
49 | }
50 | if (Platform.isWindows) {
51 | windowManager.setTitle(t.appTitle.parta + t.appTitle.partb);
52 | }
53 | }
54 |
55 | void setLocale(Locale locale) {
56 | ConfigStore().updateLocale(state.toString());
57 | state = state.copyWith(mode: LocaleMode.custom, customLocale: locale);
58 | onModeChange();
59 | }
60 |
61 | LocaleMode getMode() {
62 | return state.mode;
63 | }
64 |
65 | void changeMode(LocaleMode mode) {
66 | state = state.copyWith(mode: mode);
67 | onModeChange();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/core/providers/locale_provider.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'locale_provider.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$localeStateHash() => r'e639ad5930410f9b4d38fd1d65777d2836014665';
10 |
11 | /// See also [LocaleState].
12 | @ProviderFor(LocaleState)
13 | final localeStateProvider =
14 | AutoDisposeNotifierProvider.internal(
15 | LocaleState.new,
16 | name: r'localeStateProvider',
17 | debugGetCreateSourceHash:
18 | const bool.fromEnvironment('dart.vm.product') ? null : _$localeStateHash,
19 | dependencies: null,
20 | allTransitiveDependencies: null,
21 | );
22 |
23 | typedef _$LocaleState = AutoDisposeNotifier;
24 | // ignore_for_file: type=lint
25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
26 |
--------------------------------------------------------------------------------
/lib/core/providers/mission_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:riverpod_annotation/riverpod_annotation.dart';
2 |
3 | import '../listeners/mission_listener.dart';
4 | import '../rust/actor/mission.dart';
5 |
6 | part 'mission_provider.g.dart';
7 |
8 | @riverpod
9 | class CoreMission extends _$CoreMission {
10 | @override
11 | MissionInfo? build() {
12 | final subMission = MissionListener.instance().stream.listen(
13 | (event) {
14 | state = event;
15 | },
16 | );
17 | ref.onDispose(() {
18 | subMission.cancel();
19 | });
20 | return null;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/core/providers/mission_provider.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'mission_provider.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$coreMissionHash() => r'11026c14485ff442423627f646ecc515cb310c43';
10 |
11 | /// See also [CoreMission].
12 | @ProviderFor(CoreMission)
13 | final coreMissionProvider =
14 | AutoDisposeNotifierProvider.internal(
15 | CoreMission.new,
16 | name: r'coreMissionProvider',
17 | debugGetCreateSourceHash:
18 | const bool.fromEnvironment('dart.vm.product') ? null : _$coreMissionHash,
19 | dependencies: null,
20 | allTransitiveDependencies: null,
21 | );
22 |
23 | typedef _$CoreMission = AutoDisposeNotifier;
24 | // ignore_for_file: type=lint
25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
26 |
--------------------------------------------------------------------------------
/lib/core/providers/theme_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:riverpod_annotation/riverpod_annotation.dart';
3 |
4 | import '../store/config_store.dart';
5 |
6 | part 'theme_provider.g.dart';
7 |
8 | @riverpod
9 | class ThemeState extends _$ThemeState {
10 | @override
11 | ThemeMode build() {
12 | return ConfigStore().themeMode();
13 | }
14 |
15 | ThemeMode getTheme() {
16 | return state;
17 | }
18 |
19 | void setTheme(ThemeMode mode) {
20 | state = mode;
21 | ConfigStore().updateThemeMode(state);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/core/providers/theme_provider.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'theme_provider.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$themeStateHash() => r'6f0d061bc0306df79968b8a6a40a50220428ec9d';
10 |
11 | /// See also [ThemeState].
12 | @ProviderFor(ThemeState)
13 | final themeStateProvider =
14 | AutoDisposeNotifierProvider.internal(
15 | ThemeState.new,
16 | name: r'themeStateProvider',
17 | debugGetCreateSourceHash:
18 | const bool.fromEnvironment('dart.vm.product') ? null : _$themeStateHash,
19 | dependencies: null,
20 | allTransitiveDependencies: null,
21 | );
22 |
23 | typedef _$ThemeState = AutoDisposeNotifier;
24 | // ignore_for_file: type=lint
25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
26 |
--------------------------------------------------------------------------------
/lib/core/rust/actor/core.dart:
--------------------------------------------------------------------------------
1 | // This file is automatically generated, so please do not edit it.
2 | // @generated by `flutter_rust_bridge`@ 2.4.0.
3 |
4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
5 |
6 | import '../frb_generated.dart';
7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
8 |
9 | class CoreConfig {
10 | final int port;
11 | final String interfaceAddr;
12 | final String multicastAddr;
13 | final int multicastPort;
14 | final String storePath;
15 |
16 | const CoreConfig({
17 | required this.port,
18 | required this.interfaceAddr,
19 | required this.multicastAddr,
20 | required this.multicastPort,
21 | required this.storePath,
22 | });
23 |
24 | @override
25 | int get hashCode =>
26 | port.hashCode ^
27 | interfaceAddr.hashCode ^
28 | multicastAddr.hashCode ^
29 | multicastPort.hashCode ^
30 | storePath.hashCode;
31 |
32 | @override
33 | bool operator ==(Object other) =>
34 | identical(this, other) ||
35 | other is CoreConfig &&
36 | runtimeType == other.runtimeType &&
37 | port == other.port &&
38 | interfaceAddr == other.interfaceAddr &&
39 | multicastAddr == other.multicastAddr &&
40 | multicastPort == other.multicastPort &&
41 | storePath == other.storePath;
42 | }
43 |
--------------------------------------------------------------------------------
/lib/core/rust/actor/mission.dart:
--------------------------------------------------------------------------------
1 | // This file is automatically generated, so please do not edit it.
2 | // @generated by `flutter_rust_bridge`@ 2.4.0.
3 |
4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
5 |
6 | import '../api/model.dart';
7 | import '../frb_generated.dart';
8 | import 'model.dart';
9 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
10 | import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
11 | part 'mission.freezed.dart';
12 |
13 | @freezed
14 | sealed class FileState with _$FileState {
15 | const FileState._();
16 |
17 | const factory FileState.pending() = FileState_Pending;
18 | const factory FileState.transfer() = FileState_Transfer;
19 | const factory FileState.finish() = FileState_Finish;
20 | const factory FileState.skip() = FileState_Skip;
21 | const factory FileState.fail({
22 | required String msg,
23 | }) = FileState_Fail;
24 | }
25 |
26 | class MissionFileInfo {
27 | final FileInfo info;
28 | final FileState state;
29 |
30 | const MissionFileInfo({
31 | required this.info,
32 | required this.state,
33 | });
34 |
35 | @override
36 | int get hashCode => info.hashCode ^ state.hashCode;
37 |
38 | @override
39 | bool operator ==(Object other) =>
40 | identical(this, other) ||
41 | other is MissionFileInfo &&
42 | runtimeType == other.runtimeType &&
43 | info == other.info &&
44 | state == other.state;
45 | }
46 |
47 | class MissionInfo {
48 | final String id;
49 | final NodeDevice sender;
50 | final List files;
51 | final MissionState state;
52 |
53 | const MissionInfo({
54 | required this.id,
55 | required this.sender,
56 | required this.files,
57 | required this.state,
58 | });
59 |
60 | @override
61 | int get hashCode =>
62 | id.hashCode ^ sender.hashCode ^ files.hashCode ^ state.hashCode;
63 |
64 | @override
65 | bool operator ==(Object other) =>
66 | identical(this, other) ||
67 | other is MissionInfo &&
68 | runtimeType == other.runtimeType &&
69 | id == other.id &&
70 | sender == other.sender &&
71 | files == other.files &&
72 | state == other.state;
73 | }
74 |
--------------------------------------------------------------------------------
/lib/core/rust/actor/model.dart:
--------------------------------------------------------------------------------
1 | // This file is automatically generated, so please do not edit it.
2 | // @generated by `flutter_rust_bridge`@ 2.4.0.
3 |
4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
5 |
6 | import '../frb_generated.dart';
7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
8 |
9 | enum MissionState {
10 | idle,
11 | pending,
12 | transfering,
13 | finished,
14 | failed,
15 | canceled,
16 | busy,
17 | ;
18 | }
19 |
20 | class NodeDevice {
21 | final String alias;
22 | final String version;
23 | final String deviceModel;
24 | final String deviceType;
25 | final String fingerprint;
26 | final String address;
27 | final int port;
28 | final String protocol;
29 | final bool download;
30 | final bool announcement;
31 | final bool announce;
32 |
33 | const NodeDevice({
34 | required this.alias,
35 | required this.version,
36 | required this.deviceModel,
37 | required this.deviceType,
38 | required this.fingerprint,
39 | required this.address,
40 | required this.port,
41 | required this.protocol,
42 | required this.download,
43 | required this.announcement,
44 | required this.announce,
45 | });
46 |
47 | @override
48 | int get hashCode =>
49 | alias.hashCode ^
50 | version.hashCode ^
51 | deviceModel.hashCode ^
52 | deviceType.hashCode ^
53 | fingerprint.hashCode ^
54 | address.hashCode ^
55 | port.hashCode ^
56 | protocol.hashCode ^
57 | download.hashCode ^
58 | announcement.hashCode ^
59 | announce.hashCode;
60 |
61 | @override
62 | bool operator ==(Object other) =>
63 | identical(this, other) ||
64 | other is NodeDevice &&
65 | runtimeType == other.runtimeType &&
66 | alias == other.alias &&
67 | version == other.version &&
68 | deviceModel == other.deviceModel &&
69 | deviceType == other.deviceType &&
70 | fingerprint == other.fingerprint &&
71 | address == other.address &&
72 | port == other.port &&
73 | protocol == other.protocol &&
74 | download == other.download &&
75 | announcement == other.announcement &&
76 | announce == other.announce;
77 | }
78 |
--------------------------------------------------------------------------------
/lib/core/rust/api/model.dart:
--------------------------------------------------------------------------------
1 | // This file is automatically generated, so please do not edit it.
2 | // @generated by `flutter_rust_bridge`@ 2.4.0.
3 |
4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
5 |
6 | import '../frb_generated.dart';
7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
8 |
9 | class FileInfo {
10 | final String id;
11 | final String fileName;
12 | final PlatformInt64 size;
13 | final String fileType;
14 | final String? sha256;
15 | final Uint8List? preview;
16 |
17 | const FileInfo({
18 | required this.id,
19 | required this.fileName,
20 | required this.size,
21 | required this.fileType,
22 | this.sha256,
23 | this.preview,
24 | });
25 |
26 | @override
27 | int get hashCode =>
28 | id.hashCode ^
29 | fileName.hashCode ^
30 | size.hashCode ^
31 | fileType.hashCode ^
32 | sha256.hashCode ^
33 | preview.hashCode;
34 |
35 | @override
36 | bool operator ==(Object other) =>
37 | identical(this, other) ||
38 | other is FileInfo &&
39 | runtimeType == other.runtimeType &&
40 | id == other.id &&
41 | fileName == other.fileName &&
42 | size == other.size &&
43 | fileType == other.fileType &&
44 | sha256 == other.sha256 &&
45 | preview == other.preview;
46 | }
47 |
--------------------------------------------------------------------------------
/lib/core/rust/bridge.dart:
--------------------------------------------------------------------------------
1 | // This file is automatically generated, so please do not edit it.
2 | // @generated by `flutter_rust_bridge`@ 2.4.0.
3 |
4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
5 |
6 | import 'actor/core.dart';
7 | import 'actor/mission.dart';
8 | import 'actor/model.dart';
9 | import 'api/model.dart';
10 | import 'frb_generated.dart';
11 | import 'logger.dart';
12 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
13 |
14 | // These functions are ignored because they are not marked as `pub`: `_get_core`
15 | // These types are ignored because they are not used by any `pub` functions: `CORE`
16 | // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `deref`, `initialize`
17 |
18 | Future setup({required NodeDevice device, required CoreConfig config}) =>
19 | RustLib.instance.api.crateBridgeSetup(device: device, config: config);
20 |
21 | Stream listenServerState() =>
22 | RustLib.instance.api.crateBridgeListenServerState();
23 |
24 | Future startServer() => RustLib.instance.api.crateBridgeStartServer();
25 |
26 | Future shutdownServer() =>
27 | RustLib.instance.api.crateBridgeShutdownServer();
28 |
29 | Future restartServer() => RustLib.instance.api.crateBridgeRestartServer();
30 |
31 | Future changePath({required String path}) =>
32 | RustLib.instance.api.crateBridgeChangePath(path: path);
33 |
34 | Future changeConfig({required CoreConfig config}) =>
35 | RustLib.instance.api.crateBridgeChangeConfig(config: config);
36 |
37 | Stream> listenDevice() =>
38 | RustLib.instance.api.crateBridgeListenDevice();
39 |
40 | Stream listenMission() =>
41 | RustLib.instance.api.crateBridgeListenMission();
42 |
43 | Stream listenTaskProgress() =>
44 | RustLib.instance.api.crateBridgeListenTaskProgress();
45 |
46 | Future clearMission() => RustLib.instance.api.crateBridgeClearMission();
47 |
48 | Future cancelPending({required String id}) =>
49 | RustLib.instance.api.crateBridgeCancelPending(id: id);
50 |
51 | Future acceptPending({required String id}) =>
52 | RustLib.instance.api.crateBridgeAcceptPending(id: id);
53 |
54 | Stream createLogStream() =>
55 | RustLib.instance.api.crateBridgeCreateLogStream();
56 |
57 | Future announce() => RustLib.instance.api.crateBridgeAnnounce();
58 |
--------------------------------------------------------------------------------
/lib/core/rust/logger.dart:
--------------------------------------------------------------------------------
1 | // This file is automatically generated, so please do not edit it.
2 | // @generated by `flutter_rust_bridge`@ 2.4.0.
3 |
4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
5 |
6 | import 'frb_generated.dart';
7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
8 |
9 | class LogEntry {
10 | final PlatformInt64 timeMillis;
11 | final int level;
12 | final String tag;
13 | final String msg;
14 |
15 | const LogEntry({
16 | required this.timeMillis,
17 | required this.level,
18 | required this.tag,
19 | required this.msg,
20 | });
21 |
22 | @override
23 | int get hashCode =>
24 | timeMillis.hashCode ^ level.hashCode ^ tag.hashCode ^ msg.hashCode;
25 |
26 | @override
27 | bool operator ==(Object other) =>
28 | identical(this, other) ||
29 | other is LogEntry &&
30 | runtimeType == other.runtimeType &&
31 | timeMillis == other.timeMillis &&
32 | level == other.level &&
33 | tag == other.tag &&
34 | msg == other.msg;
35 | }
36 |
--------------------------------------------------------------------------------
/lib/core/store/config_store.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:shared_preferences/shared_preferences.dart';
5 | import 'package:uuid/uuid.dart';
6 |
7 | import '../../common/constants.dart';
8 |
9 | class ConfigStore {
10 | static ConfigStore? _instance;
11 | static SharedPreferences? _prefs;
12 |
13 | factory ConfigStore() {
14 | if (_instance == null) {
15 | throw Exception('AppSharedPrefs is not initialized. '
16 | 'Please call AppSharedPrefs.ensureInitialized before.');
17 | }
18 | return _instance!;
19 | }
20 |
21 | const ConfigStore._();
22 |
23 | static ensureInitialized() async {
24 | _prefs ??= await SharedPreferences.getInstance();
25 | _instance ??= const ConfigStore._();
26 | }
27 |
28 | static const _deviceIdKey = 'deviceId';
29 |
30 | String deviceId() {
31 | final deviceId = _prefs!.getString(_deviceIdKey);
32 | if (deviceId == null) {
33 | final newId = const Uuid().v4();
34 | _prefs!.setString(_deviceIdKey, newId);
35 | return newId;
36 | } else {
37 | return deviceId;
38 | }
39 | }
40 |
41 | static const _themeModeKey = 'themeMode';
42 |
43 | ThemeMode themeMode() {
44 | final themeValue = _prefs!.getInt(_themeModeKey);
45 | if (themeValue == null) return ThemeMode.system;
46 |
47 | return ThemeMode.values[themeValue];
48 | }
49 |
50 | Future updateThemeMode(ThemeMode theme) async {
51 | await _prefs!.setInt(_themeModeKey, theme.index);
52 | }
53 |
54 | static const _localeKey = 'locale';
55 |
56 | String locale() {
57 | final localeValue = _prefs!.getString(_localeKey) ?? Platform.localeName;
58 | return localeValue;
59 | }
60 |
61 | Future updateLocale(String locale) async {
62 | await _prefs!.setString(_localeKey, locale);
63 | }
64 |
65 | static const _localeModeKey = 'localeMode';
66 |
67 | LocaleMode localeMode() {
68 | final index = _prefs!.getInt(_localeModeKey) ?? 0;
69 | final localeMode = LocaleMode.values[index];
70 | return localeMode;
71 | }
72 |
73 | Future updateLocaleMode(LocaleMode mode) async {
74 | await _prefs!.setInt(_localeKey, mode.index);
75 | }
76 |
77 | static const _storePathKey = 'storePath';
78 |
79 | bool storePathSet() {
80 | return _prefs!.containsKey(_storePathKey);
81 | }
82 |
83 | String storePath() {
84 | final value = _prefs!.getString(_storePathKey) ?? "./";
85 | return value;
86 | }
87 |
88 | Future setStorePath(String path) async {
89 | await _prefs!.setString(_storePathKey, path);
90 | }
91 |
92 | static const _quickSaveKey = 'quickSave';
93 |
94 | bool quickSave() {
95 | final value = _prefs!.getBool(_quickSaveKey) ?? false;
96 | return value;
97 | }
98 |
99 | Future setQuickSave(bool value) async {
100 | await _prefs!.setBool(_quickSaveKey, value);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 |
4 | import 'package:chinese_font_library/chinese_font_library.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_localizations/flutter_localizations.dart';
7 | import 'package:flutter_riverpod/flutter_riverpod.dart';
8 | import 'package:window_manager/window_manager.dart';
9 |
10 | import 'common/device_info_utils.dart';
11 | import 'common/utils.dart';
12 | import 'core/providers/locale_provider.dart';
13 | import 'core/providers/theme_provider.dart';
14 | import 'core/rust/bridge.dart';
15 | import 'core/rust/frb_generated.dart';
16 | import 'core/store/config_store.dart';
17 | import 'i18n/strings.g.dart';
18 | import 'view/pages/frame_page.dart';
19 |
20 | Future main() async {
21 | WidgetsFlutterBinding.ensureInitialized();
22 | await RustLib.init();
23 | initLogger();
24 |
25 | await ConfigStore.ensureInitialized();
26 |
27 | final device = await getDevice();
28 | final config = await getConfig(device.port);
29 | await setup(device: device, config: config);
30 |
31 | if (Platform.isWindows) {
32 | // Must add this line.
33 | await windowManager.ensureInitialized();
34 |
35 | WindowOptions windowOptions = const WindowOptions(
36 | size: Size(1080, 960),
37 | center: true,
38 | );
39 | windowManager.waitUntilReadyToShow(windowOptions, () async {
40 | await windowManager.show();
41 | await windowManager.focus();
42 | });
43 | }
44 |
45 | initLocale();
46 |
47 | runApp(ProviderScope(child: TranslationProvider(child: const MyApp())));
48 | }
49 |
50 | class MyApp extends ConsumerWidget {
51 | const MyApp({super.key});
52 |
53 | ThemeData _buildTheme(Brightness brightness, Locale locale) {
54 | var baseTheme = ThemeData(
55 | useMaterial3: true,
56 | colorSchemeSeed: const Color(0xfff74c00),
57 | brightness: brightness);
58 |
59 | if (locale.languageCode == "zh") {
60 | return baseTheme.useSystemChineseFont(brightness);
61 | }
62 | return baseTheme;
63 | }
64 |
65 | @override
66 | Widget build(BuildContext context, WidgetRef ref) {
67 | final themeMode = ref.watch(themeStateProvider);
68 | final localeConfig = ref.watch(localeStateProvider);
69 | final locale = localeConfig.getLocale();
70 | return MaterialApp(
71 | title: t.appTitle.parta + t.appTitle.partb,
72 | locale: locale,
73 | // use provider
74 | supportedLocales: AppLocaleUtils.supportedLocales,
75 | localizationsDelegates: GlobalMaterialLocalizations.delegates,
76 | theme: _buildTheme(
77 | Brightness.light,
78 | locale,
79 | ),
80 | darkTheme: _buildTheme(
81 | Brightness.dark,
82 | locale,
83 | ),
84 | themeMode: themeMode,
85 | home: const FramePage(),
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/lib/view/pages/setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../i18n/strings.g.dart';
4 | import '../widget/common_widget.dart';
5 | import '../widget/setting_widgets.dart';
6 |
7 | class SettingPage extends StatelessWidget {
8 | const SettingPage({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Scaffold(
13 | body: CustomScrollView(
14 | slivers: [
15 | SliverToBoxAdapter(
16 | child: StaticAppbar(title: context.t.setting.title),
17 | ),
18 | SliverPadding(
19 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
20 | sliver: SliverList(
21 | delegate: SliverChildListDelegate(
22 | [
23 | SettingTileGroup(
24 | title: t.setting.common,
25 | children: const [
26 | ThemeTile(), // color tile
27 | // language tile
28 | LocaleTile(),
29 | ],
30 | ),
31 | const SizedBox(
32 | height: 16,
33 | ),
34 | SettingTileGroup(
35 | title: t.setting.receive.title,
36 | children: const [
37 | QuickSaveWidget(),
38 | StorePathWIdget()
39 | ],
40 | ),
41 | const SizedBox(
42 | height: 16,
43 | ),
44 | SettingTileGroup(
45 | title: t.setting.core.title,
46 | children: const [
47 | // core status
48 | ServerTile(), // core log
49 | ],
50 | ),
51 | const SizedBox(
52 | height: 16,
53 | ),
54 | SizedBox(
55 | height: 200,
56 | child: Image.asset("assets/icon/logo_512.png"),
57 | ),
58 | const Column(
59 | children: [
60 | AppTitle(),
61 | SizedBox(
62 | height: 8,
63 | ),
64 | Text("Version: 1.0.0"),
65 | Text("by tom8zds @ github")
66 | ],
67 | ),
68 | const SizedBox(
69 | height: 32,
70 | ),
71 | ],
72 | ),
73 | ),
74 | )
75 | ],
76 | ),
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lib/view/widget/common_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../i18n/strings.g.dart';
4 |
5 | class AppTitle extends StatelessWidget {
6 | const AppTitle({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Text.rich(
11 | TextSpan(
12 | text: t.appTitle.parta,
13 | children: [
14 | TextSpan(
15 | text: t.appTitle.partb,
16 | style: Theme.of(context).textTheme.displaySmall!.copyWith(
17 | color: const Color(0xfff74c00),
18 | fontWeight: FontWeight.bold,
19 | ),
20 | ),
21 | ],
22 | ),
23 | style: Theme.of(context).textTheme.displaySmall,
24 | );
25 | }
26 | }
27 |
28 | class Tag extends StatelessWidget {
29 | final String title;
30 |
31 | const Tag({super.key, required this.title});
32 | @override
33 | Widget build(BuildContext context) {
34 | return Padding(
35 | padding: const EdgeInsets.only(right: 8.0),
36 | child: Container(
37 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
38 | decoration: BoxDecoration(
39 | borderRadius: BorderRadius.circular(6),
40 | color: Theme.of(context).colorScheme.primaryContainer,
41 | ),
42 | child: Text(
43 | title,
44 | style: TextStyle(
45 | color: Theme.of(context).colorScheme.primary,
46 | ),
47 | ),
48 | ),
49 | );
50 | }
51 | }
52 |
53 | class StaticAppbar extends StatelessWidget {
54 | final String title;
55 |
56 | const StaticAppbar({super.key, required this.title});
57 | @override
58 | Widget build(BuildContext context) {
59 | return SizedBox(
60 | height: kToolbarHeight,
61 | child: Center(
62 | child: Text(
63 | title,
64 | style: Theme.of(context).textTheme.titleLarge,
65 | ),
66 | ),
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/view/widget/discover_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_riverpod/flutter_riverpod.dart';
3 | import 'package:localsend_rs/view/widget/device_widget.dart';
4 |
5 | import '../../core/providers/core_provider.dart';
6 |
7 | class DiscoverWidget extends ConsumerWidget {
8 | const DiscoverWidget({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context, WidgetRef ref) {
12 | final core = ref.watch(coreStateProvider);
13 | return Container(
14 | decoration: BoxDecoration(
15 | borderRadius: BorderRadius.circular(12),
16 | color: Theme.of(context).colorScheme.surface,
17 | ),
18 | child: Builder(
19 | builder: (context) {
20 | final data = core.devices;
21 | if (data.isEmpty) {
22 | return const Center(
23 | child: Text("empty"),
24 | );
25 | }
26 |
27 | return ListView.builder(
28 | itemBuilder: (context, index) {
29 | final item = data.elementAt(index);
30 | return DeviceWidget(device: item);
31 | },
32 | itemCount: data.length,
33 | );
34 | },
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/view/widget/network_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class NetworkWidget extends StatefulWidget {
6 | final void Function(String addr) onPressed;
7 |
8 | const NetworkWidget({super.key, required this.onPressed});
9 |
10 | @override
11 | State createState() => _NetworkWidgetState();
12 | }
13 |
14 | class _NetworkWidgetState extends State {
15 | List addressList = [];
16 |
17 | Future getInterface() async {
18 | List addressList = [];
19 | List interfaces = await NetworkInterface.list();
20 | for (var interface in interfaces) {
21 | for (var addr in interface.addresses) {
22 | if (addr.isLinkLocal ||
23 | addr.isLoopback ||
24 | addr.isMulticast ||
25 | addr.type != InternetAddressType.IPv4) {
26 | continue;
27 | }
28 | addressList.add(addr.address);
29 | }
30 | }
31 | addressList.sort();
32 | setState(() {
33 | this.addressList = addressList;
34 | });
35 | }
36 |
37 | @override
38 | void initState() {
39 | getInterface();
40 | super.initState();
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return Container(
46 | child: Column(
47 | children: [
48 | TextButton.icon(
49 | onPressed: getInterface,
50 | label: const Text("refresh"),
51 | icon: const Icon(Icons.refresh),
52 | ),
53 | for (final addr in addressList)
54 | TextButton(
55 | child: Text(addr),
56 | onPressed: () {
57 | widget.onPressed(addr);
58 | })
59 | ],
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/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/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | void fl_register_plugins(FlPluginRegistry* registry) {
14 | g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
15 | fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
16 | desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
17 | g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
18 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
19 | screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
20 | g_autoptr(FlPluginRegistrar) window_manager_registrar =
21 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
22 | window_manager_plugin_register_with_registrar(window_manager_registrar);
23 | }
24 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void fl_register_plugins(FlPluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | desktop_drop
7 | screen_retriever
8 | window_manager
9 | )
10 |
11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
12 | rust_builder
13 | )
14 |
15 | set(PLUGIN_BUNDLED_LIBRARIES)
16 |
17 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
22 | endforeach(plugin)
23 |
24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
27 | endforeach(ffi_plugin)
28 |
--------------------------------------------------------------------------------
/linux/main.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | int main(int argc, char** argv) {
4 | g_autoptr(MyApplication) app = my_application_new();
5 | return g_application_run(G_APPLICATION(app), argc, argv);
6 | }
7 |
--------------------------------------------------------------------------------
/linux/my_application.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_MY_APPLICATION_H_
2 | #define FLUTTER_MY_APPLICATION_H_
3 |
4 | #include
5 |
6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
7 | GtkApplication)
8 |
9 | /**
10 | * my_application_new:
11 | *
12 | * Creates a new Flutter-based application.
13 | *
14 | * Returns: a new #MyApplication.
15 | */
16 | MyApplication* my_application_new();
17 |
18 | #endif // FLUTTER_MY_APPLICATION_H_
19 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import desktop_drop
9 | import device_info_plus
10 | import path_provider_foundation
11 | import screen_retriever
12 | import shared_preferences_foundation
13 | import window_manager
14 |
15 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
16 | DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
17 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
18 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
19 | ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
20 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
21 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
22 | }
23 |
--------------------------------------------------------------------------------
/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 true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/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 = localsend_rs
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = github.tom8zds.localsendRs
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2023 github.tom8zds. All rights reserved.
15 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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: 'localsend_rs'
2 | version: '1.0.0+1'
3 | publish_to: none
4 | environment:
5 | sdk: '>=3.2.0 <4.0.0'
6 | description: A Rust Implementation of LocalSend protocl v2 for better performance
7 | dependencies:
8 | animations: ^2.0.11
9 | cupertino_icons: '^1.0.2'
10 | chinese_font_library: ^1.2.0
11 | device_info_plus: '^10.1.0'
12 | dio: '^5.4.0'
13 | equatable: '^2.0.5'
14 | file_picker: ^8.0.6
15 | filesize: ^2.0.1
16 | flutter:
17 | sdk: flutter
18 | flutter_localizations:
19 | sdk: flutter
20 | flutter_riverpod: '^2.4.9'
21 | flutter_rust_bridge: ^2.4.0
22 | freezed_annotation: '^2.4.2'
23 | logger: '^2.3.0'
24 | path_provider: '^2.1.1'
25 | riverpod_annotation: '^2.3.3'
26 | rust_builder:
27 | path: 'rust_builder'
28 | shared_preferences: '^2.2.2'
29 | slang: '^3.31.0'
30 | slang_flutter: '^3.31.0'
31 | uuid: '^4.4.0'
32 | window_manager: ^0.4.2
33 | simple_icons: ^10.1.3
34 | desktop_drop: ^0.5.0
35 | dev_dependencies:
36 | build_runner: '^2.4.8'
37 | custom_lint: '^0.6.4'
38 | ffigen: ^14.0.1
39 | flutter_launcher_icons: ^0.14.0
40 | flutter_lints: '^4.0.0'
41 | flutter_test:
42 | sdk: flutter
43 | freezed: '^2.4.6'
44 | innosetup: '^0.1.3'
45 | integration_test:
46 | sdk: flutter
47 | pubspec_dependency_sorter: '^1.0.5'
48 | riverpod_generator: '^2.3.9'
49 | riverpod_lint: '^2.3.7'
50 | slang_build_runner: '^3.31.0'
51 | version: any
52 | flutter:
53 | uses-material-design: true
54 | assets:
55 | - assets/icon/
56 |
--------------------------------------------------------------------------------
/rust/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | debug/
4 | target/
5 |
6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
8 | Cargo.lock
9 |
10 | # These are backup files generated by rustfmt
11 | **/*.rs.bk
12 |
13 | # MSVC Windows builds of rustc generate these, which store debugging information
14 | *.pdb
--------------------------------------------------------------------------------
/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust_lib"
3 | version = "0.1.0"
4 | edition = "2018"
5 |
6 | [lib]
7 | crate-type = ["cdylib", "staticlib"]
8 |
9 | [dependencies]
10 | flutter_rust_bridge = "=2.4.0"
11 | axum = { version = "0.7.6" }
12 | rand = "0.8.5"
13 | serde = "1.0.210"
14 | serde_derive = "1.0.210"
15 | serde_json = "1.0.128"
16 | tokio = { version = "1.40.0", features = ["full"] }
17 | tokio-util = { version = "0.7.12", features = ["io"] }
18 | futures = "0.3.30"
19 | log = "0.4.22"
20 | lazy_static = "1.5.0"
21 | simplelog = "0.12.2"
22 | pin-project-lite = "0.2.14"
23 | uuid = { version = "1.10.0", features = ["v4"] }
24 | ureq = "2.10.1"
25 | socket2 = "0.5.7"
26 | parking_lot = "0.12.3"
27 | once_cell = "1.19.0"
28 |
--------------------------------------------------------------------------------
/rust/build.cmd:
--------------------------------------------------------------------------------
1 | cd rust
2 | cargo ndk -o ../android/app/src/main/jniLibs -t arm64-v8a -t armeabi-v7a --manifest-path ./Cargo.toml build --release
--------------------------------------------------------------------------------
/rust/src/actor/http.rs:
--------------------------------------------------------------------------------
1 | use std::net::SocketAddr;
2 |
3 | use axum::Router;
4 | use log::info;
5 |
6 | use tokio::sync::{mpsc, watch};
7 |
8 | use crate::api::v2;
9 |
10 | use super::{core::CoreActorHandle, discovery::DiscoverHandle};
11 |
12 | enum ServerMessage {
13 | Shutdown,
14 | }
15 |
16 | struct HttpServerActor {
17 | core: CoreActorHandle,
18 | receiver: mpsc::Receiver,
19 | }
20 |
21 | async fn shutdown_signal(mut actor: HttpServerActor) {
22 | while let Some(msg) = actor.receiver.recv().await {
23 | if matches!(msg, ServerMessage::Shutdown) {
24 | break;
25 | };
26 | actor.handle_message(msg).await;
27 | }
28 | }
29 |
30 | async fn run_http_actor(actor: HttpServerActor, shutdown_callback: watch::Sender) {
31 | let config = actor.core.get_config().await;
32 | let n_port = config.port;
33 |
34 | let discover_handle = DiscoverHandle::new(actor.core.clone());
35 |
36 | let addr = SocketAddr::from(([0, 0, 0, 0], n_port));
37 | let listener = tokio::net::TcpListener::bind(addr)
38 | .await
39 | .expect("failed to listen to port");
40 |
41 | let app = Router::new().nest("/api/localsend/", v2::app(actor.core.clone()));
42 |
43 | info!("http service {} started", n_port);
44 |
45 | axum::serve(listener, app)
46 | .with_graceful_shutdown(shutdown_signal(actor))
47 | .await
48 | .unwrap();
49 |
50 | info!("http service {} shutdown", n_port);
51 |
52 | discover_handle.shutdown().await;
53 |
54 | let _ = shutdown_callback.send(true);
55 | }
56 |
57 | impl HttpServerActor {
58 | pub fn new(receiver: mpsc::Receiver, core: CoreActorHandle) -> Self {
59 | HttpServerActor { receiver, core }
60 | }
61 | async fn handle_message(&mut self, msg: ServerMessage) {
62 | match msg {
63 | ServerMessage::Shutdown => todo!(),
64 | _ => {}
65 | }
66 | }
67 | }
68 |
69 | pub struct HttpServerHandle {
70 | sender: mpsc::Sender,
71 | shutdown_receiver: watch::Receiver,
72 | }
73 |
74 | impl HttpServerHandle {
75 | pub fn new(core: CoreActorHandle) -> Self {
76 | let (sender, receiver) = mpsc::channel(8);
77 | let (s_sender, s_receiver) = watch::channel(true);
78 |
79 | let actor = HttpServerActor::new(receiver, core);
80 |
81 | tokio::spawn(run_http_actor(actor, s_sender));
82 |
83 | Self {
84 | sender,
85 | shutdown_receiver: s_receiver,
86 | }
87 | }
88 |
89 | pub async fn shutdown(mut self) {
90 | let msg = ServerMessage::Shutdown;
91 |
92 | // Ignore send errors. If this send fails, so does the
93 | // recv.await below. There's no reason to check the
94 | // failure twice.
95 | let _ = self.sender.send(msg).await;
96 | self.shutdown_receiver
97 | .changed()
98 | .await
99 | .expect("Actor task has been killed")
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/rust/src/actor/mission.rs:
--------------------------------------------------------------------------------
1 | use lazy_static::lazy_static;
2 |
3 | use crate::api::model::FileInfo;
4 |
5 | use super::model::{MissionState, NodeDevice};
6 |
7 | pub mod notify;
8 | pub mod pending;
9 | pub mod transfer;
10 |
11 | lazy_static! {
12 | pub static ref MISSION_NOTIFY: notify::Handle = notify::Handle::new();
13 | }
14 |
15 | #[derive(Clone)]
16 | pub struct MissionInfo {
17 | pub id: String,
18 | pub sender: NodeDevice,
19 | pub files: Vec,
20 | pub state: MissionState,
21 | }
22 |
23 | #[derive(Debug, Clone)]
24 | pub struct MissionFileInfo {
25 | pub info: FileInfo,
26 | pub state: FileState,
27 | }
28 |
29 | #[derive(Debug, Clone)]
30 | pub enum FileState {
31 | Pending,
32 | Transfer,
33 | Finish,
34 | Skip,
35 | Fail { msg: String },
36 | }
37 |
38 | #[derive(Clone)]
39 | pub struct MissionHandle {
40 | pub pending: pending::Handle,
41 | pub transfer: transfer::Handle,
42 | }
43 |
44 | impl MissionHandle {
45 | pub fn new() -> Self {
46 | let transfer = transfer::Handle::new();
47 | let pending = pending::Handle::new(transfer.clone());
48 |
49 | Self { pending, transfer }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/rust/src/actor/mission/notify.rs:
--------------------------------------------------------------------------------
1 | use log::{debug, trace};
2 | use tokio::sync::{mpsc, oneshot, watch};
3 |
4 | use crate::{
5 | actor::{
6 | mission::MISSION_NOTIFY,
7 | model::{Mission, MissionState, NodeDevice},
8 | },
9 | api::model::FileInfo,
10 | };
11 |
12 | use super::MissionInfo;
13 |
14 | enum Message {
15 | Notify {
16 | mission: Option,
17 | },
18 | Listen {
19 | respond_to: oneshot::Sender>>,
20 | },
21 | Clear,
22 | }
23 |
24 | struct Actor {
25 | receiver: mpsc::Receiver,
26 | notify: watch::Sender