diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a5c4c88 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + # Keep GitHub Actions pinned versions current. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" + + # Keep Dart/Flutter dependencies current. + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..de3e0fd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,142 @@ +name: CI + +# Build-and-test only. Producing distributable (signed/packaged) artifacts is +# the job of release.yml, which runs on version tags. + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + FLUTTER_VERSION: "3.44.2" + +jobs: + # Platform-independent checks run once here, not repeated per platform. + checks: + name: Format, analyze, test + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Set up Flutter + uses: subosito/flutter-action@v2.23.0 + with: + channel: stable + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + pub-cache: true + + - name: Show Flutter version + run: flutter --version + + - name: Install dependencies + run: flutter pub get + + - name: Check formatting + run: dart format --output=none --set-exit-if-changed lib test + + - name: Analyze + run: flutter analyze + + - name: Run tests + run: flutter test + + # Per-platform builds only verify that the native app compiles. They run only + # after `checks` passes, so a formatting/analyze/test failure never spins up + # the (slower, pricier) build runners. + macos: + name: Build macOS + needs: checks + runs-on: macos-latest + timeout-minutes: 45 + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Set up Flutter + uses: subosito/flutter-action@v2.23.0 + with: + channel: stable + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + pub-cache: true + + - name: Install dependencies + run: flutter pub get + + - name: Build macOS app + run: flutter build macos --release + + linux: + name: Build Linux + needs: checks + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Install Linux build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + clang cmake ninja-build pkg-config \ + libgtk-3-dev liblzma-dev libstdc++-12-dev \ + libayatana-appindicator3-dev + + - name: Set up Flutter + uses: subosito/flutter-action@v2.23.0 + with: + channel: stable + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + pub-cache: true + + - name: Install dependencies + run: flutter pub get + + - name: Build Linux bundle + run: flutter build linux --release + + ios: + name: Build iOS + needs: checks + runs-on: macos-latest + timeout-minutes: 45 + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Set up Flutter + uses: subosito/flutter-action@v2.23.0 + with: + channel: stable + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + pub-cache: true + + - name: Install dependencies + run: flutter pub get + + # device_info_plus needs a recent iOS SDK; the runner's default Xcode can + # be older, so select the newest installed. + - name: Select latest Xcode + run: | + sudo xcode-select -s "$(ls -d /Applications/Xcode_*.app | sort -V | tail -1)/Contents/Developer" + xcodebuild -version + + # No codesigning on CI; just verify the iOS app + pure-SPM setup compile. + - name: Build iOS app (no codesign) + run: flutter build ios --release --no-codesign diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..55a3e71 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,266 @@ +name: Release + +# Builds signed, notarized macOS and packaged Linux artifacts and attaches them +# to a GitHub Release. Triggered by pushing a version tag, e.g. `v1.0.0`. +# +# Required repository secrets (macOS signing + notarization): +# MACOS_CERTIFICATE_BASE64 base64 of the Developer ID Application .p12 +# MACOS_CERTIFICATE_PASSWORD password for that .p12 +# MACOS_KEYCHAIN_PASSWORD any password for the temporary CI keychain +# MACOS_SIGN_IDENTITY e.g. "Developer ID Application: Name (TEAMID)" +# AC_API_KEY_BASE64 base64 of the App Store Connect API key (.p8) +# AC_API_KEY_ID App Store Connect API key ID +# AC_API_ISSUER_ID App Store Connect API issuer ID + +on: + push: + tags: + - "v*" + workflow_dispatch: + +# Least privilege: build jobs only read; the publish job grants its own write. +permissions: + contents: read + +env: + APP_NAME: CrossDrop + FLUTTER_VERSION: "3.44.2" + +jobs: + guard: + name: Verify tag matches pubspec version + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Check tag matches pubspec version + if: github.ref_type == 'tag' + env: + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + version="$(grep '^version:' pubspec.yaml | sed 's/version:[[:space:]]*//; s/+.*//')" + expected="v${version}" + if [ "$TAG" != "$expected" ]; then + echo "::error::Tag ${TAG} does not match pubspec version (expected ${expected}). Bump pubspec.yaml or retag." + exit 1 + fi + echo "Tag ${TAG} matches pubspec version ${version}." + + macos: + name: Signed macOS build + needs: guard + runs-on: macos-latest + timeout-minutes: 60 + outputs: + version: ${{ steps.package.outputs.version }} + sha256: ${{ steps.package.outputs.sha256 }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Set up Flutter + uses: subosito/flutter-action@v2.23.0 + with: + channel: stable + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + pub-cache: true + + - name: Install dependencies + run: flutter pub get + + - name: Run tests + run: flutter test + + - name: Import signing certificate + env: + MACOS_CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }} + run: | + set -euo pipefail + keychain="$RUNNER_TEMP/app-signing.keychain-db" + cert="$RUNNER_TEMP/certificate.p12" + + printf '%s' "$MACOS_CERTIFICATE_BASE64" | base64 --decode > "$cert" + + security create-keychain -p "$MACOS_KEYCHAIN_PASSWORD" "$keychain" + security set-keychain-settings -lut 21600 "$keychain" + security unlock-keychain -p "$MACOS_KEYCHAIN_PASSWORD" "$keychain" + security import "$cert" -P "$MACOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$keychain" + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PASSWORD" "$keychain" + security list-keychain -d user -s "$keychain" login.keychain-db + + - name: Build macOS release app + run: flutter build macos --release + + - name: Sign app bundle + env: + IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }} + run: | + set -euo pipefail + app="build/macos/Build/Products/Release/${APP_NAME}.app" + + # Sign nested Mach-O code first (inside-out), then framework bundles. + find "$app/Contents/Frameworks" -type f \( -name "*.dylib" -o -name "*.so" \) -print0 \ + | while IFS= read -r -d '' f; do + codesign --force --timestamp --options runtime --sign "$IDENTITY" "$f" + done + find "$app/Contents/Frameworks" -type d -name "*.framework" -print0 \ + | while IFS= read -r -d '' fw; do + codesign --force --timestamp --options runtime --sign "$IDENTITY" "$fw" + done + + # Sign the app last, applying the sandbox entitlements. + codesign --force --timestamp --options runtime \ + --entitlements macos/Runner/Release.entitlements \ + --sign "$IDENTITY" "$app" + + codesign --verify --deep --strict --verbose=2 "$app" + + - name: Notarize and staple + env: + AC_API_KEY_BASE64: ${{ secrets.AC_API_KEY_BASE64 }} + AC_API_KEY_ID: ${{ secrets.AC_API_KEY_ID }} + AC_API_ISSUER_ID: ${{ secrets.AC_API_ISSUER_ID }} + run: | + set -euo pipefail + app="build/macos/Build/Products/Release/${APP_NAME}.app" + key="$RUNNER_TEMP/ac_api_key.p8" + printf '%s' "$AC_API_KEY_BASE64" | base64 --decode > "$key" + + notarize_zip="$RUNNER_TEMP/notarize.zip" + ditto -c -k --keepParent "$app" "$notarize_zip" + + xcrun notarytool submit "$notarize_zip" \ + --key "$key" --key-id "$AC_API_KEY_ID" --issuer "$AC_API_ISSUER_ID" --wait + + xcrun stapler staple "$app" + spctl --assess --type execute --verbose=4 "$app" + + - name: Package signed app + id: package + run: | + set -euo pipefail + app="build/macos/Build/Products/Release/${APP_NAME}.app" + version="$(grep '^version:' pubspec.yaml | sed 's/version:[[:space:]]*//; s/+.*//')" + mkdir -p dist + zip="dist/${APP_NAME}-${version}-macos-universal.zip" + + ditto -c -k --keepParent "$app" "$zip" + sha="$(shasum -a 256 "$zip" | awk '{print $1}')" + printf '%s %s\n' "$sha" "$(basename "$zip")" > "$zip.sha256" + + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "sha256=${sha}" >> "$GITHUB_OUTPUT" + + - name: Upload macOS artifact + uses: actions/upload-artifact@v7.0.1 + with: + name: macos-dist + path: dist/* + if-no-files-found: error + + linux: + name: Packaged Linux build + needs: guard + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Install Linux build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + clang cmake ninja-build pkg-config \ + libgtk-3-dev liblzma-dev libstdc++-12-dev \ + libayatana-appindicator3-dev + + - name: Set up Flutter + uses: subosito/flutter-action@v2.23.0 + with: + channel: stable + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + pub-cache: true + + - name: Install dependencies + run: flutter pub get + + - name: Build Linux release bundle + run: flutter build linux --release + + - name: Package app + run: | + set -euo pipefail + version="$(grep '^version:' pubspec.yaml | sed 's/version:[[:space:]]*//; s/+.*//')" + bundle="build/linux/x64/release/bundle" + mkdir -p dist + + tarball="dist/${APP_NAME}-${version}-linux-x64.tar.gz" + rm -rf "dist/${APP_NAME}" + cp -r "$bundle" "dist/${APP_NAME}" + tar -C dist -czf "$tarball" "${APP_NAME}" + rm -rf "dist/${APP_NAME}" + + appimage="dist/${APP_NAME}-${version}-linux-x86_64.AppImage" + packaging/linux/build-appimage.sh "$bundle" "$appimage" + + ( cd dist && sha256sum "$(basename "$tarball")" "$(basename "$appimage")" > "${APP_NAME}-${version}-linux.sha256" ) + + - name: Upload Linux artifact + uses: actions/upload-artifact@v7.0.1 + with: + name: linux-dist + path: dist/* + if-no-files-found: error + + publish: + name: Publish GitHub Release + needs: [macos, linux] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.3 + + - name: Download build artifacts + uses: actions/download-artifact@v8.0.1 + with: + path: dist + merge-multiple: true + + - name: Render Homebrew cask + env: + VERSION: ${{ needs.macos.outputs.version }} + SHA256: ${{ needs.macos.outputs.sha256 }} + run: | + set -euo pipefail + sed \ + -e "s|REPLACE_WITH_SHA256|${SHA256}|" \ + -e "s|version \"1.0.0\"|version \"${VERSION}\"|" \ + packaging/homebrew/Casks/crossdrop.rb.template > dist/crossdrop.rb + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + # workflow_dispatch has no tag ref; fall back to the pubspec version. + if [ "${GITHUB_REF_TYPE:-}" != "tag" ]; then + TAG="v$(grep '^version:' pubspec.yaml | sed 's/version:[[:space:]]*//; s/+.*//')" + fi + gh release create "$TAG" dist/* \ + --repo "$GITHUB_REPOSITORY" \ + --title "CrossDrop $TAG" \ + --generate-notes \ + --prerelease diff --git a/.gitignore b/.gitignore index 3c88cc5..4b31cf3 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Local-only publishing notes (not tracked) +docs/publishing.md diff --git a/.metadata b/.metadata index b7e64d9..e1be825 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" + revision: "c9a6c484230f8b5e408ec57be1ef71dee1e77020" channel: "stable" project_type: app @@ -13,17 +13,17 @@ project_type: app migration: platforms: - platform: root - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 - platform: ios - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 - platform: linux - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 - platform: macos - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 # User provided section diff --git a/LICENSE b/LICENSE index 6fd3ac4..782c156 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Florian Rachmann +Copyright (c) 2026 Florian Rachmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ce9b8ef..5591619 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,64 @@ The app lives in your menu bar and saves files to your downloads folder. 📈 Since this project has gotten more attention, I will try very hard to release a working version as soon as possible. While much of the UI is ready so far, I'm currently working on the Quick Share feature itself. After that, I still need to implement notifications. +## Installing + +> [!WARNING] +> Builds are **pre-release** and intended for testing only (see the warning above). + +Grab the latest build from the [Releases](https://github.com/Medformatik/CrossDrop/releases) page. + +### macOS + +- **Homebrew** (once published to the tap): + + ```sh + brew install --cask Medformatik/tap/crossdrop + ``` + +- **Manual:** download `CrossDrop--macos-universal.zip`, unzip, and move `CrossDrop.app` to `/Applications`. + +Published release builds are signed with a Developer ID and notarized by Apple, so Gatekeeper opens them without warnings. Locally built (unsigned) apps require a right-click → **Open**, or `xattr -dr com.apple.quarantine CrossDrop.app`. + +### Linux + +- **AppImage:** download `CrossDrop--linux-x86_64.AppImage`, then `chmod +x` it and run. +- **Tarball:** download `CrossDrop--linux-x64.tar.gz`, extract it, and run `./CrossDrop`. + +### iOS + +No public distribution yet — build and run from source (see below). + +## Building from source + +Requires the [Flutter SDK](https://docs.flutter.dev/get-started/install) **3.44.2 or newer**. + +```sh +git clone https://github.com/Medformatik/CrossDrop.git +cd CrossDrop +flutter pub get +``` + +Run in development: + +```sh +flutter run -d macos # or: -d linux +``` + +Build release binaries: + +```sh +flutter build macos --release # → build/macos/Build/Products/Release/CrossDrop.app +flutter build linux --release # → build/linux/x64/release/bundle/ +flutter build ios --release # requires an Apple Developer account for signing +``` + +On Linux, install the build toolchain first: + +```sh +sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev +``` + ## Limitations * **Receive only**. For now. I haven't yet figured out how to make Android turn on the MDNS service and/or show the "a device nearby is sharing" notification. @@ -29,6 +87,8 @@ The app lives in your menu bar and saves files to your downloads folder. Contributions are welcome! Please open an issue or a pull request. +CI (`.github/workflows/ci.yml`) formats, analyzes, and tests on every push and pull request, then compiles the macOS, Linux, and iOS apps. Pushing a `v*` tag triggers `.github/workflows/release.yml`, which builds a signed + notarized macOS app and packaged Linux artifacts (AppImage + tarball) and attaches them to a GitHub Release. + ## FAQ ### Why does this exist next to NearDrop? diff --git a/analysis_options.yaml b/analysis_options.yaml index f9c6c8f..0d29021 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -21,7 +21,7 @@ linter: # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: - avoid_print: false # Uncomment to disable the `avoid_print` rule + # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at diff --git a/assets/icons/app_icon.ico b/assets/icons/app_icon.ico deleted file mode 100644 index e091fbb..0000000 Binary files a/assets/icons/app_icon.ico and /dev/null differ diff --git a/assets/icons/app_icon.png b/assets/icons/app_icon.png index 54f8023..36393df 100644 Binary files a/assets/icons/app_icon.png and b/assets/icons/app_icon.png differ diff --git a/assets/icons/app_icon_48px.ico b/assets/icons/app_icon_48px.ico deleted file mode 100644 index 7957f1e..0000000 Binary files a/assets/icons/app_icon_48px.ico and /dev/null differ diff --git a/assets/icons/system_tray_icon.ico b/assets/icons/system_tray_icon.ico index 1e0fa67..2e56a77 100644 Binary files a/assets/icons/system_tray_icon.ico and b/assets/icons/system_tray_icon.ico differ diff --git a/assets/icons/system_tray_icon.png b/assets/icons/system_tray_icon.png index 9fc17ca..4a6050e 100644 Binary files a/assets/icons/system_tray_icon.png and b/assets/icons/system_tray_icon.png differ diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index ec97fc6..592ceee 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index c4855bf..592ceee 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile deleted file mode 100644 index ebaf318..0000000 --- a/ios/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -platform :ios, '13.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index f63f944..0000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,60 +0,0 @@ -PODS: - - bonsoir_darwin (0.0.1): - - Flutter - - FlutterMacOS - - device_info_plus (0.0.1): - - Flutter - - file_selector_ios (0.0.1): - - Flutter - - Flutter (1.0.0) - - flutter_local_notifications (0.0.1): - - Flutter - - permission_handler_apple (9.3.0): - - Flutter - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - - url_launcher_ios (0.0.1): - - Flutter - -DEPENDENCIES: - - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`) - - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - - Flutter (from `Flutter`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - -EXTERNAL SOURCES: - bonsoir_darwin: - :path: ".symlinks/plugins/bonsoir_darwin/darwin" - device_info_plus: - :path: ".symlinks/plugins/device_info_plus/ios" - file_selector_ios: - :path: ".symlinks/plugins/file_selector_ios/ios" - Flutter: - :path: Flutter - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" - permission_handler_apple: - :path: ".symlinks/plugins/permission_handler_apple/ios" - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" - -SPEC CHECKSUMS: - bonsoir_darwin: 14bd7429acb51db6a8e51f6574ac3b1ea0037a87 - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe - file_selector_ios: ec57ec07954363dd730b642e765e58f199bb621a - Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: 643a3eda1ce1c0599413ca31672536d423dee214 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b - -PODFILE CHECKSUM: ade96bceabe3919b69c16573938e4268fe3a6c9d - -COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ed13497..d572d2c 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,11 +11,10 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 8A89A0A9EA4F3892A82891C2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F68A78679B121B222F5174A4 /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - C9A3795364630574F23D190D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9DC9BEEA067B0880AED846E /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,17 +43,13 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 1E6D116481226BD103D71752 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 25D4795F0447CABB69B85BE9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - 2F747EA54155E58D76CFDF15 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 3302D212A2E6955B6BF9033A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8D923C48B64B0143E7FB781A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -62,9 +57,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A1EB7AB53A3CC3B072631E61 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - B9DC9BEEA067B0880AED846E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F68A78679B121B222F5174A4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -72,7 +64,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8A89A0A9EA4F3892A82891C2 /* Pods_Runner.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -80,35 +72,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C9A3795364630574F23D190D /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0AA675F26E4E9B44C98F4D29 /* Pods */ = { - isa = PBXGroup; - children = ( - 8D923C48B64B0143E7FB781A /* Pods-Runner.debug.xcconfig */, - 3302D212A2E6955B6BF9033A /* Pods-Runner.release.xcconfig */, - 2F747EA54155E58D76CFDF15 /* Pods-Runner.profile.xcconfig */, - 25D4795F0447CABB69B85BE9 /* Pods-RunnerTests.debug.xcconfig */, - 1E6D116481226BD103D71752 /* Pods-RunnerTests.release.xcconfig */, - A1EB7AB53A3CC3B072631E61 /* Pods-RunnerTests.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - 1CEA07D87421EEF251541F40 /* Frameworks */ = { - isa = PBXGroup; - children = ( - F68A78679B121B222F5174A4 /* Pods_Runner.framework */, - B9DC9BEEA067B0880AED846E /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -120,6 +89,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -135,8 +105,6 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, - 0AA675F26E4E9B44C98F4D29 /* Pods */, - 1CEA07D87421EEF251541F40 /* Frameworks */, ); sourceTree = ""; }; @@ -171,7 +139,6 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 06BD854C7863EE7C414FEF67 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, F0585DF04AC73DC52E47FF43 /* Frameworks */, @@ -190,21 +157,21 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 34C3DBD97BA9774968219D96 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - D9543192E6FBEFA2A9005614 /* [CP] Embed Pods Frameworks */, - 6D53C5CC15DD3DA7DFF39751 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -238,6 +205,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -270,50 +240,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 06BD854C7863EE7C414FEF67 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 34C3DBD97BA9774968219D96 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -330,23 +256,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 6D53C5CC15DD3DA7DFF39751 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -362,23 +271,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - D9543192E6FBEFA2A9005614 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -503,7 +395,6 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 25D4795F0447CABB69B85BE9 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -521,7 +412,6 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1E6D116481226BD103D71752 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -537,7 +427,6 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A1EB7AB53A3CC3B072631E61 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -737,6 +626,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d4..c3fedb2 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + - - diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 2571555..ef7b66b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 0cb6ecd..6dc162a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 6e6b806..a6778d5 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 96a120c..26a3ede 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 7c273c8..d70563f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index adcdcc4..4fccdaa 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index fede419..b6df3c2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 6e6b806..a6778d5 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 7015f9d..a0a6af9 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 58c1760..224d437 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index 3cc5661..ce519a2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index 1ea77f5..371e501 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 341c6c6..4f57903 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index da94bce..ef4074c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 58c1760..224d437 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 0fe2e07..232eeec 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 5c05e32..0b3998b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index 651aac8..7d43e4d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index d8962e0..01f23d8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 604b2b9..c4e5474 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 26aad8e..038131e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/app/app.dart b/lib/app/app.dart index 885ddd7..22fcac6 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -23,6 +23,9 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('app'); enum AnimationPhase { idle, fadeIn, visible, fadeOut } @@ -96,13 +99,13 @@ class _AppState extends State implements NearbyEventsListener { try { await _appSystemTray?.initSystemTray(); } catch (e, s) { - print("Failed to initialize system tray: $e\n$s"); + _log.severe("Failed to initialize system tray: $e\n$s"); } try { - print("Starting broadcasting with name: $currentDeviceName"); + _log.info("Starting broadcasting with name: $currentDeviceName"); await _manager.startBroadcasting(currentDeviceName); } catch (e, s) { - print("Failed to start broadcasting: $e\n$s"); + _log.severe("Failed to start broadcasting: $e\n$s"); } } } @@ -175,30 +178,32 @@ class _AppState extends State implements NearbyEventsListener { _appSystemTray?.updateDeviceName(newName); try { - print("Restarting broadcast with new name: $newName"); + _log.info("Restarting broadcast with new name: $newName"); await _manager.stopBroadcasting(); await _manager.startBroadcasting(newName); } catch (e, s) { - print("Error restarting broadcast after name change: $e\n$s"); + _log.severe("Error restarting broadcast after name change: $e\n$s"); } } @override void onDeviceFound(RemoteDeviceInfo device) { - print("UI Listener: Device Found - ${device.name} (${device.id})"); + _log.info("UI Listener: Device Found - ${device.name} (${device.id})"); setState(() {}); if (device.qrMatched && _outgoingFilePaths.isNotEmpty && _outgoingConnectionId == null && _outgoingTargetDeviceId == null) { - print("UI Listener: QR-matched device ${device.id}; starting transfer"); + _log.info( + "UI Listener: QR-matched device ${device.id}; starting transfer", + ); unawaited(_sendSelectedFilesToDevice(device)); } } @override void onDeviceLost(String deviceId) { - print("UI Listener: Device Lost - $deviceId"); + _log.info("UI Listener: Device Lost - $deviceId"); setState(() {}); } @@ -208,7 +213,7 @@ class _AppState extends State implements NearbyEventsListener { RemoteDeviceInfo device, String connectionId, ) { - print( + _log.info( "UI Listener: Incoming Transfer Request - ID: ${transfer.id} from ${device.name}", ); final pendingTransfer = transfer.copyWith(id: connectionId); @@ -220,14 +225,14 @@ class _AppState extends State implements NearbyEventsListener { }); unawaited( showTransferNotification(pendingTransfer, device).catchError((e, s) { - print("Failed to show transfer notification: $e\n$s"); + _log.severe("Failed to show transfer notification: $e\n$s"); }), ); } @override void onTransferFinished(String connectionId, bool success, Exception? error) { - print( + _log.info( "UI Listener: Transfer Finished - ID: $connectionId, Success: $success, Error: $error", ); var stopOutgoingDiscovery = false; @@ -277,7 +282,7 @@ class _AppState extends State implements NearbyEventsListener { @override void onOutgoingTransferStarted(String deviceId, String connectionId) { - print( + _log.info( "UI Listener: Outgoing Transfer Started - DeviceID: $deviceId, ConnID: $connectionId", ); setState(() { @@ -300,7 +305,7 @@ class _AppState extends State implements NearbyEventsListener { @override void onOutgoingPinAvailable(String connectionId, String pin) { - print("UI Listener: Outgoing PIN - ConnID: $connectionId, PIN: $pin"); + _log.info("UI Listener: Outgoing PIN - ConnID: $connectionId, PIN: $pin"); if (connectionId != _outgoingConnectionId) return; setState(() { _outgoingPin = pin; @@ -345,7 +350,7 @@ class _AppState extends State implements NearbyEventsListener { try { await revealFileInFileManager(localPath); } catch (e, s) { - print('Failed to show received file in file manager: $e\n$s'); + _log.severe('Failed to show received file in file manager: $e\n$s'); } } @@ -356,7 +361,7 @@ class _AppState extends State implements NearbyEventsListener { try { await openFileWithDefaultApp(localPath); } catch (e, s) { - print('Failed to open received file: $e\n$s'); + _log.severe('Failed to open received file: $e\n$s'); } } @@ -370,7 +375,9 @@ class _AppState extends State implements NearbyEventsListener { void _handleNotificationAction(String connectionId, bool accepted) { unawaited( _respondToPendingTransfer(connectionId, accepted).catchError((e, s) { - print("Failed to handle notification action for $connectionId: $e\n$s"); + _log.severe( + "Failed to handle notification action for $connectionId: $e\n$s", + ); }), ); } @@ -381,7 +388,7 @@ class _AppState extends State implements NearbyEventsListener { final files = await openFiles(); await _startOutgoingFileSelection(files.map((file) => file.path)); } catch (e, s) { - print('Failed to pick outgoing files: $e\n$s'); + _log.severe('Failed to pick outgoing files: $e\n$s'); if (!mounted) return; setState(() { _outgoingStatus = 'Could not open file picker'; @@ -415,7 +422,7 @@ class _AppState extends State implements NearbyEventsListener { try { await _manager.startDiscovery(); } catch (e, s) { - print('Failed to start discovery for outgoing transfer: $e\n$s'); + _log.severe('Failed to start discovery for outgoing transfer: $e\n$s'); if (!mounted) return; setState(() { _outgoingStatus = 'Could not start discovery'; @@ -451,7 +458,7 @@ class _AppState extends State implements NearbyEventsListener { await _manager.initiateTransfer(device.id, List.of(_outgoingFilePaths)); await _manager.stopDiscovery(); } catch (e, s) { - print('Failed to start outgoing transfer: $e\n$s'); + _log.severe('Failed to start outgoing transfer: $e\n$s'); if (!mounted) return; setState(() { _outgoingStatus = 'Could not start transfer'; diff --git a/lib/app/file_intents.dart b/lib/app/file_intents.dart index 37dffaa..7853d10 100644 --- a/lib/app/file_intents.dart +++ b/lib/app/file_intents.dart @@ -3,6 +3,9 @@ import 'dart:async'; import 'package:crossdrop/app/file_paths.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('file_intents'); const MethodChannel _fileIntentChannel = MethodChannel( 'crossdrop/file_intents', @@ -47,7 +50,7 @@ class FileIntentController { } on MissingPluginException { // The channel is only implemented on platforms with native open-file hooks. } catch (e, s) { - print('Failed to notify native file intent bridge: $e\n$s'); + _log.severe('Failed to notify native file intent bridge: $e\n$s'); } } diff --git a/lib/app/mobile_app.dart b/lib/app/mobile_app.dart index 6642294..b1e9f31 100644 --- a/lib/app/mobile_app.dart +++ b/lib/app/mobile_app.dart @@ -17,6 +17,9 @@ import 'package:crossdrop/notifications.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('mobile_app'); class MobileApp extends StatefulWidget { final List initialOutgoingFilePaths; @@ -109,7 +112,7 @@ class _MobileAppState extends State try { await _manager.startBroadcasting(deviceName ?? await getDeviceName()); } catch (e, s) { - print('Failed to update receive visibility: $e\n$s'); + _log.severe('Failed to update receive visibility: $e\n$s'); if (!mounted) return; setState(() { _receiveError = e.toString(); @@ -171,7 +174,7 @@ class _MobileAppState extends State }); unawaited( showTransferNotification(pendingTransfer, device).catchError((e, s) { - print('Failed to show transfer notification: $e\n$s'); + _log.severe('Failed to show transfer notification: $e\n$s'); }), ); } @@ -287,7 +290,7 @@ class _MobileAppState extends State try { await openFileWithDefaultApp(localPath); } catch (e, s) { - print('Failed to open received file: $e\n$s'); + _log.severe('Failed to open received file: $e\n$s'); } } @@ -307,7 +310,7 @@ class _MobileAppState extends State final files = await openFiles(); await _startOutgoingFileSelection(files.map((file) => file.path)); } catch (e, s) { - print('Failed to pick outgoing files: $e\n$s'); + _log.severe('Failed to pick outgoing files: $e\n$s'); if (!mounted) return; setState(() { _outgoingStatus = 'Could not open file picker'; @@ -340,7 +343,7 @@ class _MobileAppState extends State try { await _manager.startDiscovery(); } catch (e, s) { - print('Failed to start discovery for outgoing transfer: $e\n$s'); + _log.severe('Failed to start discovery for outgoing transfer: $e\n$s'); if (!mounted) return; setState(() { _outgoingStatus = 'Could not start discovery'; @@ -364,7 +367,7 @@ class _MobileAppState extends State await _manager.initiateTransfer(device.id, List.of(_outgoingFilePaths)); await _manager.stopDiscovery(); } catch (e, s) { - print('Failed to start outgoing transfer: $e\n$s'); + _log.severe('Failed to start outgoing transfer: $e\n$s'); if (!mounted) return; setState(() { _outgoingStatus = 'Could not start transfer'; @@ -393,7 +396,9 @@ class _MobileAppState extends State void _handleNotificationAction(String connectionId, bool accepted) { unawaited( _respondToPendingTransfer(connectionId, accepted).catchError((e, s) { - print('Failed to handle notification action for $connectionId: $e\n$s'); + _log.severe( + 'Failed to handle notification action for $connectionId: $e\n$s', + ); }), ); } diff --git a/lib/app/notification_actions.dart b/lib/app/notification_actions.dart index 103efe8..d7a9332 100644 --- a/lib/app/notification_actions.dart +++ b/lib/app/notification_actions.dart @@ -2,6 +2,9 @@ import 'dart:async'; import 'package:crossdrop/nearby_share/manager/nearby_manager.dart'; import 'package:crossdrop/notifications.dart'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('notification_actions'); NotificationActionCallback? _activeNotificationActionHandler; @@ -10,7 +13,9 @@ void registerNotificationActionHandler(NotificationActionCallback? handler) { } void handleNotificationResponse(String connectionId, bool accepted) { - print('Handling notification response: $connectionId, Accepted: $accepted'); + _log.info( + 'Handling notification response: $connectionId, Accepted: $accepted', + ); final activeHandler = _activeNotificationActionHandler; if (activeHandler != null) { activeHandler(connectionId, accepted); diff --git a/lib/app/window_setup.dart b/lib/app/window_setup.dart index 5d75ee7..1386b7d 100644 --- a/lib/app/window_setup.dart +++ b/lib/app/window_setup.dart @@ -5,6 +5,9 @@ import 'package:crossdrop/app_config.dart'; import 'package:crossdrop/window/on_close_window.dart'; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('window_setup'); const Size defaultWindowSize = Size(420, 640); const Size minimumWindowSize = Size(360, 480); @@ -58,7 +61,7 @@ Future ensureWindowSizeAtLeast(Size targetSize) async { await windowManager.setSize(nextSize); } } catch (e, s) { - print('Failed to resize window for outgoing transfer: $e\n$s'); + _log.severe('Failed to resize window for outgoing transfer: $e\n$s'); } } diff --git a/lib/logging_config.dart b/lib/logging_config.dart new file mode 100644 index 0000000..cac9a25 --- /dev/null +++ b/lib/logging_config.dart @@ -0,0 +1,23 @@ +import 'dart:developer' as developer; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; + +/// Configures the root [Logger] for the app. +/// +/// In debug builds every record is emitted; release builds drop anything +/// below [Level.INFO]. Records are forwarded to `dart:developer`'s log, which +/// shows up in `flutter logs`, the IDE, and the macOS Console. +void setupLogging() { + Logger.root.level = kDebugMode ? Level.ALL : Level.INFO; + Logger.root.onRecord.listen((record) { + developer.log( + record.message, + time: record.time, + level: record.level.value, + name: record.loggerName, + error: record.error, + stackTrace: record.stackTrace, + ); + }); +} diff --git a/lib/main.dart b/lib/main.dart index 133d72f..52f1ca5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:crossdrop/app/mobile_app.dart'; import 'package:crossdrop/app/notification_actions.dart'; import 'package:crossdrop/app/window_setup.dart'; import 'package:crossdrop/app_theme.dart'; +import 'package:crossdrop/logging_config.dart'; import 'package:crossdrop/nearby_share/manager/nearby_manager.dart'; import 'package:crossdrop/notifications.dart'; import 'package:flutter/widgets.dart'; @@ -13,6 +14,7 @@ import 'package:provider/provider.dart'; Future main(List args) async { WidgetsFlutterBinding.ensureInitialized(); + setupLogging(); final initialOutgoingFilePaths = filePathsFromArgs(args); final useMobileShell = Platform.isIOS || Platform.isAndroid; diff --git a/lib/nearby_share/connections/inbound_connection.dart b/lib/nearby_share/connections/inbound_connection.dart index 819786d..44b8714 100644 --- a/lib/nearby_share/connections/inbound_connection.dart +++ b/lib/nearby_share/connections/inbound_connection.dart @@ -16,6 +16,9 @@ import 'package:cryptography/cryptography.dart'; import 'package:path_provider/path_provider.dart'; // For downloads directory import 'package:path/path.dart' as p; import 'package:url_launcher/url_launcher.dart'; // For path manipulation +import 'package:logging/logging.dart'; + +final Logger _log = Logger('inbound_connection'); enum InboundState { initial, @@ -73,7 +76,7 @@ class InboundNearbyConnection extends NearbyConnection { if (_currentState != InboundState.disconnected) { _currentState = InboundState.disconnected; _deletePartiallyReceivedFiles().catchError((e, s) { - print("Error deleting partially received files for $id: $e\n$s"); + _log.severe("Error deleting partially received files for $id: $e\n$s"); }); // Use WidgetsBinding.instance.addPostFrameCallback or ensure delegate call is safe Future.microtask(() => delegate?.connectionTerminated(this, lastError)); @@ -82,7 +85,7 @@ class InboundNearbyConnection extends NearbyConnection { @override Future processReceivedFrame(Uint8List frameData) async { - print("Inbound $id: Processing frame in state $_currentState"); + _log.info("Inbound $id: Processing frame in state $_currentState"); try { switch (_currentState) { case InboundState.initial: @@ -116,11 +119,11 @@ class InboundNearbyConnection extends NearbyConnection { await decryptAndProcessReceivedSecureMessage(smsg); break; case InboundState.disconnected: - print("Inbound $id: Received frame while disconnected."); + _log.info("Inbound $id: Received frame while disconnected."); break; } } catch (e, s) { - print( + _log.info( "Inbound $id: Deserialization error in state $_currentState: $e\n$s", ); lastError = (e is Exception) ? e : Exception(e.toString()); @@ -143,7 +146,7 @@ class InboundNearbyConnection extends NearbyConnection { Future processTransferSetupFrame(wire.Frame frame) async { // These frames arrive *after* decryption if (frame.hasV1() && frame.v1.type == wire.V1Frame_FrameType.CANCEL) { - print("Inbound $id: Transfer cancelled by sender."); + _log.info("Inbound $id: Transfer cancelled by sender."); lastError = NearbyCancellationException(CancellationReason.userCanceled); await sendDisconnectionAndDisconnect(); return; @@ -178,7 +181,7 @@ class InboundNearbyConnection extends NearbyConnection { await _processIntroductionFrame(frame); break; default: - print( + _log.info( "Inbound $id: Ignoring unexpected transfer setup frame in state " "$_currentState: ${frame.toProto3Json()}", ); @@ -211,7 +214,7 @@ class InboundNearbyConnection extends NearbyConnection { if (chunk.body.isNotEmpty) { if (fileInfo.fileHandle == null) { // This should ideally not happen if acceptTransfer worked - print( + _log.info( "Warning: File handle for payload $payloadId is null, attempting to reopen.", ); try { @@ -236,13 +239,13 @@ class InboundNearbyConnection extends NearbyConnection { // Check for LAST_CHUNK flag if ((chunk.flags & 1) == 1) { - print("Inbound $id: Received last chunk for payload $payloadId"); + _log.info("Inbound $id: Received last chunk for payload $payloadId"); await fileInfo.fileHandle?.close(); fileInfo.fileHandle = null; // Mark as closed transferredFiles.remove(payloadId); // Remove completed transfer if (transferredFiles.isEmpty) { - print("Inbound $id: All files received, disconnecting."); + _log.info("Inbound $id: All files received, disconnecting."); await sendDisconnectionAndDisconnect(); } } @@ -252,7 +255,7 @@ class InboundNearbyConnection extends NearbyConnection { Future processBytesPayload(Uint8List payload, int payloadId) async { if (payloadId == _textPayloadID && _textPayloadID != 0) { final urlStr = utf8.decode(payload); - print("Inbound $id: Received URL: $urlStr"); + _log.info("Inbound $id: Received URL: $urlStr"); await launchUrl(Uri.parse(urlStr)); await sendDisconnectionAndDisconnect(); return true; // Handled @@ -261,7 +264,7 @@ class InboundNearbyConnection extends NearbyConnection { final fileInfo = transferredFiles[payloadId]; if (fileInfo != null && fileInfo.meta.mimeType == "text/plain") { if (fileInfo.fileHandle == null) { - print( + _log.info( "Warning: File handle for text payload $payloadId is null, attempting to reopen.", ); try { @@ -280,7 +283,7 @@ class InboundNearbyConnection extends NearbyConnection { await fileInfo.fileHandle!.close(); fileInfo.fileHandle = null; transferredFiles.remove(payloadId); - print( + _log.info( "Inbound $id: Finished writing text payload $payloadId to file.", ); if (transferredFiles.isEmpty) { @@ -331,7 +334,7 @@ class InboundNearbyConnection extends NearbyConnection { port: _remotePort, ); - print( + _log.info( "Inbound $id: Received connection request from ${remoteDeviceInfo!.name} (${remoteDeviceInfo!.type}) at $_remoteIpAddress:$_remotePort", ); _currentState = InboundState.receivedConnectionRequest; @@ -385,7 +388,7 @@ class InboundNearbyConnection extends NearbyConnection { : null; if (nextProto != "AES_256_CBC-HMAC_SHA256") { // Note: Swift code checks exact match. Let's be strict too. - print( + _log.info( "Warning: Client proposed next protocol '$nextProto', expected 'AES_256_CBC-HMAC_SHA256'", ); // Allowing for now, but might require alert in stricter implementations. @@ -429,7 +432,9 @@ class InboundNearbyConnection extends NearbyConnection { ); } if (msg.messageType != ukey.Ukey2Message_Type.CLIENT_FINISH) { - print("Inbound $id: Expected UKEY2 ClientFinish, got ${msg.messageType}"); + _log.info( + "Inbound $id: Expected UKEY2 ClientFinish, got ${msg.messageType}", + ); sendUkey2Alert(ukey.Ukey2Alert_AlertType.BAD_MESSAGE_TYPE); throw NearbyUkey2Exception(); } @@ -448,11 +453,11 @@ class InboundNearbyConnection extends NearbyConnection { final digest = await Sha512().hash(rawMsgData); if (!listsEqual(digest.bytes, _cipherCommitment!)) { - print("Cipher commitment mismatch!"); + _log.info("Cipher commitment mismatch!"); sendUkey2Alert(ukey.Ukey2Alert_AlertType.BAD_MESSAGE_DATA); throw NearbyUkey2Exception(); } - print("Cipher commitment verified."); + _log.info("Cipher commitment verified."); try { final clientFinish = ukey.Ukey2ClientFinished.fromBuffer(msg.messageData); @@ -466,13 +471,13 @@ class InboundNearbyConnection extends NearbyConnection { ); await finalizeKeyExchange(clientKeyProto); - print("Inbound $id: UKEY2 Handshake Complete. PIN: $pinCode"); + _log.info("Inbound $id: UKEY2 Handshake Complete. PIN: $pinCode"); _currentState = InboundState.receivedUkeyClientFinish; if (_receivedClientConnectionResponse) { await _sendConnectionResponseAndPairedEncryption(); } } catch (e, s) { - print("Error processing ClientFinish payload: $e\n$s"); + _log.severe("Error processing ClientFinish payload: $e\n$s"); sendUkey2Alert(ukey.Ukey2Alert_AlertType.BAD_MESSAGE_DATA); throw NearbyUkey2Exception(); } @@ -488,7 +493,7 @@ class InboundNearbyConnection extends NearbyConnection { } _validateClientConnectionResponse(frame); _receivedClientConnectionResponse = true; - print("Inbound $id: Received early plaintext ConnectionResponse"); + _log.info("Inbound $id: Received early plaintext ConnectionResponse"); return true; } catch (_) { return false; @@ -536,7 +541,7 @@ class InboundNearbyConnection extends NearbyConnection { v1: v1Frame, ); await sendFrame(offlineFrame.writeToBuffer()); - print("Inbound $id: Sent plaintext ConnectionResponse"); + _log.info("Inbound $id: Sent plaintext ConnectionResponse"); encryptionDone = true; @@ -554,7 +559,7 @@ class InboundNearbyConnection extends NearbyConnection { ); await sendTransferSetupFrame(wireFrame); - print("Inbound $id: Sent encrypted PairedKeyEncryption"); + _log.info("Inbound $id: Sent encrypted PairedKeyEncryption"); _currentState = InboundState.sentConnectionResponse; } @@ -568,7 +573,7 @@ class InboundNearbyConnection extends NearbyConnection { // Handles the encrypted PAIRED_KEY_ENCRYPTION frame from the client Future _processPairedKeyEncryptionFrame(wire.Frame frame) async { - print("Inbound $id: Processing PairedKeyEncryption"); + _log.info("Inbound $id: Processing PairedKeyEncryption"); // We don't actually *use* the data, just send back UNABLE result like Swift final pairedResultFrame = wire.PairedKeyResultFrame( status: wire.PairedKeyResultFrame_Status.UNABLE, @@ -583,22 +588,22 @@ class InboundNearbyConnection extends NearbyConnection { ); await sendTransferSetupFrame(wireFrame); - print("Inbound $id: Sent PairedKeyResult (UNABLE)"); + _log.info("Inbound $id: Sent PairedKeyResult (UNABLE)"); _currentState = InboundState.sentPairedKeyResult; } // Handles the encrypted PAIRED_KEY_RESULT frame from the client Future _processPairedKeyResultFrame(wire.Frame frame) async { - print("Inbound $id: Processing PairedKeyResult"); + _log.info("Inbound $id: Processing PairedKeyResult"); // We don't care about the result, just move to the next state // where we expect the Introduction frame. _currentState = InboundState.receivedPairedKeyResult; - print("Inbound $id: Ready to receive Introduction frame"); + _log.info("Inbound $id: Ready to receive Introduction frame"); } // Handles the Introduction frame (list of files/text) Future _processIntroductionFrame(wire.Frame frame) async { - print("Inbound $id: Processing Introduction"); + _log.info("Inbound $id: Processing Introduction"); if (!frame.hasV1() || !frame.v1.hasIntroduction()) { throw NearbyRequiredFieldMissingException("frame.v1.introduction"); } @@ -625,7 +630,7 @@ class InboundNearbyConnection extends NearbyConnection { final payloadIdInt = file.payloadId.toInt(); if (payloadIdInt == 0) { // Should not happen with random IDs - print( + _log.info( "Warning: Received file metadata with payload ID 0 for ${file.name}", ); continue; // Skip this file potentially @@ -658,7 +663,7 @@ class InboundNearbyConnection extends NearbyConnection { final textMeta = introduction.textMetadata.first; final payloadIdInt = textMeta.payloadId.toInt(); if (payloadIdInt == 0) { - print( + _log.info( "Warning: Received text metadata with payload ID 0 for ${textMeta.textTitle}", ); // Decide how to handle this - maybe reject? @@ -729,7 +734,7 @@ class InboundNearbyConnection extends NearbyConnection { } if (filesMeta.isEmpty && textDesc == null) { - print("Inbound $id: Introduction frame has no actionable content."); + _log.info("Inbound $id: Introduction frame has no actionable content."); await rejectTransfer(reason: wire.ConnectionResponseFrame_Status.REJECT); return; } @@ -756,7 +761,7 @@ class InboundNearbyConnection extends NearbyConnection { // Called by the UI/Manager to accept or reject the transfer Future submitUserConsent(bool accepted) async { if (_currentState != InboundState.waitingForUserConsent) { - print( + _log.info( "Inbound $id: submitUserConsent called in unexpected state: $_currentState", ); return; @@ -772,7 +777,7 @@ class InboundNearbyConnection extends NearbyConnection { // --- Private Helper Methods --- Future acceptTransfer() async { - print("Inbound $id: Transfer accepted by user."); + _log.info("Inbound $id: Transfer accepted by user."); try { // Create files and open handles *before* sending ACCEPT for (final entry in transferredFiles.entries) { @@ -785,7 +790,7 @@ class InboundNearbyConnection extends NearbyConnection { // The destination path has already been uniqued; start with a clean file. fileInfo.fileHandle = await file.open(mode: FileMode.write); fileInfo.created = true; - print( + _log.info( "Inbound $id: Opened file handle for payload $payloadId at " "${fileInfo.destinationPath}", ); @@ -794,7 +799,7 @@ class InboundNearbyConnection extends NearbyConnection { lastError = NearbyIOException( "Failed to create received file ${fileInfo.destinationPath}: $e", ); - print( + _log.info( "Inbound $id: Failed to create/open file for payload $payloadId " "at ${fileInfo.destinationPath}: $e\n$s", ); @@ -822,11 +827,11 @@ class InboundNearbyConnection extends NearbyConnection { await sendTransferSetupFrame(wireFrame); _currentState = InboundState.receivingFiles; - print( + _log.info( "Inbound $id: Sent ACCEPT response, entering receivingFiles state.", ); } catch (e, s) { - print("Inbound $id: Error during acceptTransfer: $e\n$s"); + _log.severe("Inbound $id: Error during acceptTransfer: $e\n$s"); lastError = (e is Exception) ? e : Exception(e.toString()); // Attempt to clean up and disconnect await _cleanupFailedAccept(); @@ -841,7 +846,7 @@ class InboundNearbyConnection extends NearbyConnection { try { await File(fileInfo.destinationPath).delete(); } catch (e) { - print( + _log.info( "Failed to delete partially created file ${fileInfo.destinationPath}: $e", ); } @@ -865,7 +870,7 @@ class InboundNearbyConnection extends NearbyConnection { lastError ??= NearbyCancellationException( wireStatusToCancellationReason(reason) ?? CancellationReason.userRejected, ); - print("Inbound $id: Rejecting transfer with reason: $reason"); + _log.info("Inbound $id: Rejecting transfer with reason: $reason"); final responseFrame = wire.ConnectionResponseFrame(status: reason); final v1WireFrame = wire.V1Frame( type: wire.V1Frame_FrameType.RESPONSE, @@ -880,14 +885,14 @@ class InboundNearbyConnection extends NearbyConnection { await sendTransferSetupFrame(wireFrame); await sendDisconnectionAndDisconnect(); // Disconnect after sending rejection } catch (e, s) { - print("Inbound $id: Error sending rejection/disconnection: $e\n$s"); + _log.severe("Inbound $id: Error sending rejection/disconnection: $e\n$s"); protocolError(); // Force disconnect on error } // No need to change state here, sendDisconnection handles it } Future _deletePartiallyReceivedFiles() async { - print("Inbound $id: Cleaning up partially received files..."); + _log.info("Inbound $id: Cleaning up partially received files..."); for (final fileInfo in transferredFiles.values) { await fileInfo.fileHandle?.close(); // Close handle if open if (fileInfo.created) { @@ -896,10 +901,10 @@ class InboundNearbyConnection extends NearbyConnection { final file = File(fileInfo.destinationPath); if (await file.exists()) { await file.delete(); - print("Deleted partial file: ${fileInfo.destinationPath}"); + _log.info("Deleted partial file: ${fileInfo.destinationPath}"); } } catch (e) { - print( + _log.info( "Error deleting partially received file ${fileInfo.destinationPath}: $e", ); } diff --git a/lib/nearby_share/connections/nearby_connection.dart b/lib/nearby_share/connections/nearby_connection.dart index 8ccc6c8..7762472 100644 --- a/lib/nearby_share/connections/nearby_connection.dart +++ b/lib/nearby_share/connections/nearby_connection.dart @@ -17,6 +17,9 @@ import 'package:crossdrop/nearby_share/crypto/crypto_utils.dart'; import 'package:cryptography/cryptography.dart'; import 'package:fixnum/fixnum.dart'; import 'package:pointycastle/export.dart' as pc; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('nearby_connection'); // Constants const int saneFrameLength = 5 * 1024 * 1024; // 5 MiB @@ -58,7 +61,7 @@ abstract class NearbyConnection { Uint8List? authStringBytes; NearbyConnection(this._socket, this.id) { - print( + _log.info( 'NearbyConnection created for $id with ${_socket.remoteAddress}:${_socket.remotePort}', ); } @@ -76,21 +79,21 @@ abstract class NearbyConnection { // Called when the socket is ready (or assumed ready after creation) void connectionReady() { - print('Connection $id is ready.'); + _log.info('Connection $id is ready.'); } // Handles incoming raw data from the socket final List _receiveBuffer = []; void _onDataReceived(Uint8List data) { _receiveBuffer.addAll(data); - // print('Connection $id received ${data.length} bytes. Buffer size: ${_receiveBuffer.length}'); + // _log.info('Connection $id received ${data.length} bytes. Buffer size: ${_receiveBuffer.length}'); while (_receiveBuffer.length >= 4) { // Read length prefix (4 bytes, big-endian) final lengthBytes = Uint8List.fromList(_receiveBuffer.sublist(0, 4)); final length = ByteData.view(lengthBytes.buffer).getUint32(0, Endian.big); - // print('Connection $id: Expecting frame length: $length'); + // _log.info('Connection $id: Expecting frame length: $length'); if (length > saneFrameLength) { lastError = NearbyProtocolException( @@ -107,18 +110,18 @@ abstract class NearbyConnection { _receiveBuffer.sublist(4, 4 + length), ); _receiveBuffer.removeRange(0, 4 + length); - // print('Connection $id: Processing frame of length ${frameData.length}. Remaining buffer: ${_receiveBuffer.length}'); + // _log.info('Connection $id: Processing frame of length ${frameData.length}. Remaining buffer: ${_receiveBuffer.length}'); try { _enqueueReceivedFrame(frameData); } catch (e, s) { - print('Connection $id: Error queuing frame: $e\n$s'); + _log.severe('Connection $id: Error queuing frame: $e\n$s'); lastError = (e is Exception) ? e : Exception(e.toString()); _reportError(lastError!); protocolError(); } } else { // Need more data for the current frame - // print('Connection $id: Incomplete frame, need ${4 + length}, have ${_receiveBuffer.length}'); + // _log.info('Connection $id: Incomplete frame, need ${4 + length}, have ${_receiveBuffer.length}'); break; // Exit the loop, wait for more data } } @@ -130,7 +133,7 @@ abstract class NearbyConnection { try { await processReceivedFrame(frameData); } catch (e, s) { - print('Connection $id: Error processing frame: $e\n$s'); + _log.severe('Connection $id: Error processing frame: $e\n$s'); lastError = (e is Exception) ? e : Exception(e.toString()); _reportError(lastError!); protocolError(); @@ -141,7 +144,7 @@ abstract class NearbyConnection { // Called when an error occurs on the socket void _onError(Object error, StackTrace stackTrace) { if (connectionClosed) return; - print('Connection $id socket error: $error\n$stackTrace'); + _log.severe('Connection $id socket error: $error\n$stackTrace'); lastError = (error is Exception) ? error : Exception(error.toString()); connectionClosed = true; // Mark as closed due to error _reportError(lastError!); // Report specific error @@ -155,7 +158,7 @@ abstract class NearbyConnection { // Called when the socket is closed by the remote peer void _onDone() { if (connectionClosed) return; - print('Connection $id closed by remote peer.'); + _log.info('Connection $id closed by remote peer.'); connectionClosed = true; handleConnectionClosure(); // General closure handling if (!_closedCompleter.isCompleted) { @@ -189,7 +192,7 @@ abstract class NearbyConnection { // Sends a raw frame with length prefix Future sendFrame(List frame, {void Function()? completion}) { if (connectionClosed) { - print('Attempted to send on closed connection $id'); + _log.info('Attempted to send on closed connection $id'); return Future.value(); } final length = frame.length; @@ -204,7 +207,7 @@ abstract class NearbyConnection { await _socket.flush(); completion?.call(); } catch (e, s) { - print('Connection $id: Error sending frame: $e\n$s'); + _log.severe('Connection $id: Error sending frame: $e\n$s'); _onError(e, s); } }); @@ -402,7 +405,7 @@ abstract class NearbyConnection { payloadTransfer.payloadHeader.hasId() ? payloadTransfer.payloadHeader.id.toString() : 'unknown'; - print("Received payload ACK for payload $payloadId"); + _log.info("Received payload ACK for payload $payloadId"); return; } @@ -417,7 +420,9 @@ abstract class NearbyConnection { : offline .PayloadTransferFrame_ControlMessage_EventType .UNKNOWN_EVENT_TYPE; - print("Received payload control frame for payload $payloadId: $event"); + _log.info( + "Received payload control frame for payload $payloadId: $event", + ); if (event == offline .PayloadTransferFrame_ControlMessage_EventType @@ -489,7 +494,9 @@ abstract class NearbyConnection { final innerFrame = wire.Frame.fromBuffer(fullPayload); await processTransferSetupFrame(innerFrame); } catch (e, s) { - print('Failed to parse bytes payload as wire.Frame: $e\n$s'); + _log.severe( + 'Failed to parse bytes payload as wire.Frame: $e\n$s', + ); // Rethrow or handle as appropriate throw NearbyProtocolException( "Failed to process BYTES payload $payloadId as setup frame", @@ -503,7 +510,7 @@ abstract class NearbyConnection { } } else if (offlineFrame.hasV1() && offlineFrame.v1.type == offline.V1Frame_FrameType.KEEP_ALIVE) { - print("Received keep-alive"); + _log.info("Received keep-alive"); if (offlineFrame.v1.hasKeepAlive() && offlineFrame.v1.keepAlive.ack) { // This is an ack to our keep-alive, no action needed maybe? } else { @@ -511,13 +518,13 @@ abstract class NearbyConnection { sendKeepAlive(ack: true); } } else { - print( + _log.info( "Unhandled encrypted offline frame: ${offlineFrame.toProto3Json()}", ); // Potentially handle other frame types like DISCONNECTION if needed if (offlineFrame.hasV1() && offlineFrame.v1.type == offline.V1Frame_FrameType.DISCONNECTION) { - print("Received disconnection frame"); + _log.info("Received disconnection frame"); disconnect(); // Close our end } } @@ -573,7 +580,7 @@ abstract class NearbyConnection { authStringBytes = Uint8List.fromList(await authStringKey.extractBytes()); _pinCode = await derivePinCode(authStringKey); - print("Derived PIN Code: $_pinCode"); + _log.info("Derived PIN Code: $_pinCode"); // Derive D2D Keys const d2dSalt = [ @@ -668,22 +675,22 @@ abstract class NearbyConnection { sendHmacKey = clientSigKey; } - print('Connection $id: Key exchange finalized.'); + _log.info('Connection $id: Key exchange finalized.'); } // Gracefully disconnects the socket Future disconnect() async { if (connectionClosed) return; - print('Connection $id: Disconnecting...'); + _log.info('Connection $id: Disconnecting...'); connectionClosed = true; await _socketSubscription?.cancel(); _socketSubscription = null; try { // Socket.close() sends FIN and waits for acknowledgment. await _socket.close(); - print('Connection $id: Socket closed gracefully.'); + _log.info('Connection $id: Socket closed gracefully.'); } catch (e, s) { - print( + _log.info( 'Connection $id: Error during socket close: $e\n$s. Destroying socket.', ); // Fallback to destroy if close fails @@ -698,7 +705,7 @@ abstract class NearbyConnection { // Closes the connection immediately due to a protocol error void protocolError() { - print('Connection $id: Protocol Error. Last error: $lastError'); + _log.severe('Connection $id: Protocol Error. Last error: $lastError'); if (connectionClosed) return; connectionClosed = true; _socketSubscription?.cancel(); @@ -735,22 +742,22 @@ abstract class NearbyConnection { try { if (encryptionDone) { await encryptAndSendOfflineFrame(offlineFrame); - print('Connection $id: Sent encrypted DISCONNECTION'); + _log.info('Connection $id: Sent encrypted DISCONNECTION'); } else { await sendFrame(offlineFrame.writeToBuffer()); - print('Connection $id: Sent unencrypted DISCONNECTION'); + _log.info('Connection $id: Sent unencrypted DISCONNECTION'); } sent = true; } catch (e, s) { - print('Connection $id: Failed to send disconnection frame: $e\n$s'); + _log.severe('Connection $id: Failed to send disconnection frame: $e\n$s'); } if (sent && waitForRemoteClose) { - print('Connection $id: Waiting for remote close after DISCONNECTION'); + _log.info('Connection $id: Waiting for remote close after DISCONNECTION'); unawaited( Future.delayed(remoteCloseTimeout, () async { if (connectionClosed) return; - print( + _log.info( 'Connection $id: Remote close timed out after DISCONNECTION; closing locally.', ); await disconnect(); @@ -767,7 +774,7 @@ abstract class NearbyConnection { // Sends a UKEY2 Alert message and disconnects void sendUkey2Alert(ukey.Ukey2Alert_AlertType type) { if (connectionClosed) return; - print('Connection $id: Sending UKEY2 Alert: $type'); + _log.info('Connection $id: Sending UKEY2 Alert: $type'); final alert = ukey.Ukey2Alert(type: type); final msg = ukey.Ukey2Message( @@ -798,13 +805,13 @@ abstract class NearbyConnection { try { if (encryptionDone) { unawaited(encryptAndSendOfflineFrame(offlineFrame)); - print('Connection $id: Sent encrypted KEEP_ALIVE (ack: $ack)'); + _log.info('Connection $id: Sent encrypted KEEP_ALIVE (ack: $ack)'); } else { unawaited(sendFrame(offlineFrame.writeToBuffer())); - print('Connection $id: Sent unencrypted KEEP_ALIVE (ack: $ack)'); + _log.info('Connection $id: Sent unencrypted KEEP_ALIVE (ack: $ack)'); } } catch (e, s) { - print('Connection $id: Error sending KEEP_ALIVE: $e\n$s'); + _log.severe('Connection $id: Error sending KEEP_ALIVE: $e\n$s'); _onError(e, s); // Treat send failure as a connection error } } @@ -812,7 +819,7 @@ abstract class NearbyConnection { // Called when the connection is fully closed (either normally or via error) // Subclasses should override this for cleanup. void handleConnectionClosure() { - print( + _log.info( 'Connection $id: handleConnectionClosure called. Closed: $connectionClosed', ); connectionClosed = true; // Ensure flag is set @@ -835,6 +842,6 @@ abstract class NearbyConnection { // Subclasses might override or use a StreamController for this. void _reportError(Exception error) { // Default implementation does nothing, subclasses/manager should handle. - print("Connection $id reported error: $error"); + _log.severe("Connection $id reported error: $error"); } } diff --git a/lib/nearby_share/connections/outbound_connection.dart b/lib/nearby_share/connections/outbound_connection.dart index cdfb230..f6c7ab1 100644 --- a/lib/nearby_share/connections/outbound_connection.dart +++ b/lib/nearby_share/connections/outbound_connection.dart @@ -18,6 +18,10 @@ import 'package:cryptography/cryptography.dart'; import 'package:fixnum/fixnum.dart'; import 'package:mime/mime.dart'; // For MIME type lookup import 'package:path/path.dart' as p; // For path operations +import 'package:logging/logging.dart'; + +final Logger _log = Logger('outbound_connection'); + // To request storage permission if needed enum OutboundState { @@ -99,7 +103,7 @@ class OutboundNearbyConnection extends NearbyConnection { void cancel() { if (_currentState == OutboundState.disconnected) return; - print("Outbound $id: Cancelling transfer."); + _log.info("Outbound $id: Cancelling transfer."); _cancelled = true; // Try to send CANCEL frame if handshake is complete enough @@ -111,12 +115,12 @@ class OutboundNearbyConnection extends NearbyConnection { ); sendTransferSetupFrame(wireFrame) .catchError((e, s) { - print("Outbound $id: Error sending CANCEL frame: $e\n$s"); + _log.severe("Outbound $id: Error sending CANCEL frame: $e\n$s"); }) .whenComplete(() { // Proceed with disconnection regardless of CANCEL success sendDisconnectionAndDisconnect().catchError((e, s) { - print( + _log.info( "Outbound $id: Error during disconnect after cancel: $e\n$s", ); protocolError(); // Force close if disconnect fails @@ -125,7 +129,9 @@ class OutboundNearbyConnection extends NearbyConnection { } else { // If handshake not done, just disconnect sendDisconnectionAndDisconnect().catchError((e, s) { - print("Outbound $id: Error during disconnect on early cancel: $e\n$s"); + _log.severe( + "Outbound $id: Error during disconnect on early cancel: $e\n$s", + ); protocolError(); }); } @@ -172,9 +178,9 @@ class OutboundNearbyConnection extends NearbyConnection { @override Future processReceivedFrame(Uint8List frameData) async { - print("Outbound $id: Processing frame in state $_currentState"); + _log.info("Outbound $id: Processing frame in state $_currentState"); if (_cancelled) { - print("Outbound $id: Ignoring frame because transfer is cancelled."); + _log.info("Outbound $id: Ignoring frame because transfer is cancelled."); return; } try { @@ -200,11 +206,11 @@ class OutboundNearbyConnection extends NearbyConnection { await decryptAndProcessReceivedSecureMessage(smsg); break; case OutboundState.disconnected: - print("Outbound $id: Received frame while disconnected."); + _log.info("Outbound $id: Received frame while disconnected."); break; } } catch (e, s) { - print( + _log.info( "Outbound $id: Deserialization error in state $_currentState: $e\n$s", ); lastError = (e is Exception) ? e : Exception(e.toString()); @@ -224,7 +230,7 @@ class OutboundNearbyConnection extends NearbyConnection { Null _handleAsyncError(Object error, StackTrace stackTrace) { if (connectionClosed || _cancelled) return null; - print("Outbound $id: Async error: $error\n$stackTrace"); + _log.severe("Outbound $id: Async error: $error\n$stackTrace"); lastError = (error is Exception) ? error : Exception(error.toString()); protocolError(); return null; @@ -235,13 +241,13 @@ class OutboundNearbyConnection extends NearbyConnection { // Handle decrypted setup frames received from the server if (_cancelled) return; if (frame.hasV1() && frame.v1.type == wire.V1Frame_FrameType.CANCEL) { - print("Outbound $id: Transfer canceled by receiver."); + _log.info("Outbound $id: Transfer canceled by receiver."); lastError = NearbyCancellationException(CancellationReason.userCanceled); await sendDisconnectionAndDisconnect(); return; } - print( + _log.info( "Outbound $id: Processing setup frame in state $_currentState: ${frame.toProto3Json()}", ); @@ -274,7 +280,7 @@ class OutboundNearbyConnection extends NearbyConnection { break; case OutboundState.sendingFiles: // Shouldn't receive setup frames while sending files, maybe keep-alive? - print( + _log.info( "Outbound $id: Received unexpected setup frame while sending files: ${frame.toProto3Json()}", ); // Ignore for now, or handle specific cases if needed. @@ -311,7 +317,7 @@ class OutboundNearbyConnection extends NearbyConnection { v1: v1Frame, ); await sendFrame(offlineFrame.writeToBuffer()); - print("Outbound $id: Sent ConnectionRequest"); + _log.info("Outbound $id: Sent ConnectionRequest"); } Future _sendUkey2ClientInit() async { @@ -354,7 +360,7 @@ class OutboundNearbyConnection extends NearbyConnection { .writeToBuffer(); // Store raw bytes for HKDF await sendFrame(ukeyClientInitMsgData!); _currentState = OutboundState.sentUkeyClientInit; - print("Outbound $id: Sent UKEY2 ClientInit"); + _log.info("Outbound $id: Sent UKEY2 ClientInit"); } // --- Private Processing Methods --- @@ -372,7 +378,9 @@ class OutboundNearbyConnection extends NearbyConnection { ); } if (msg.messageType != ukey.Ukey2Message_Type.SERVER_INIT) { - print("Outbound $id: Expected UKEY2 ServerInit, got ${msg.messageType}"); + _log.info( + "Outbound $id: Expected UKEY2 ServerInit, got ${msg.messageType}", + ); sendUkey2Alert(ukey.Ukey2Alert_AlertType.BAD_MESSAGE_TYPE); throw NearbyUkey2Exception(); } @@ -412,22 +420,22 @@ class OutboundNearbyConnection extends NearbyConnection { serverInit.publicKey, ); await finalizeKeyExchange(serverKeyProto); - print("Outbound $id: UKEY2 Handshake Complete. PIN: $pinCode"); + _log.info("Outbound $id: UKEY2 Handshake Complete. PIN: $pinCode"); // Send ClientFinish if (_ukeyClientFinishMsgData == null) { throw StateError("ClientFinish message data is null"); } await sendFrame(_ukeyClientFinishMsgData!); - print("Outbound $id: Sent UKEY2 ClientFinish"); + _log.info("Outbound $id: Sent UKEY2 ClientFinish"); await _sendPlaintextConnectionResponse(); _currentState = OutboundState.sentUkeyClientFinish; encryptionDone = true; - print("Outbound $id: Sent plaintext ConnectionResponse"); + _log.info("Outbound $id: Sent plaintext ConnectionResponse"); // Notify delegate that connection is established (PIN available) Future.microtask(() => delegate?.outboundConnectionEstablished(this)); } catch (e, s) { - print("Error processing ServerInit payload: $e\n$s"); + _log.severe("Error processing ServerInit payload: $e\n$s"); sendUkey2Alert(ukey.Ukey2Alert_AlertType.BAD_PUBLIC_KEY); throw NearbyUkey2Exception(); } @@ -466,7 +474,7 @@ class OutboundNearbyConnection extends NearbyConnection { "Expected ConnectionResponse ACK, got ${frame.v1.type}", ); } - print("Outbound $id: Processing ConnectionResponse ACK"); + _log.info("Outbound $id: Processing ConnectionResponse ACK"); if (!frame.v1.hasConnectionResponse()) { throw NearbyRequiredFieldMissingException("frame.v1.connectionResponse"); } @@ -479,7 +487,7 @@ class OutboundNearbyConnection extends NearbyConnection { } // Connection is accepted. Subsequent setup frames are encrypted. - print("Outbound $id: Connection accepted by server."); + _log.info("Outbound $id: Connection accepted by server."); encryptionDone = true; await _sendPairedKeyEncryption(); } @@ -504,11 +512,11 @@ class OutboundNearbyConnection extends NearbyConnection { ); await sendTransferSetupFrame(wireFrame); _currentState = OutboundState.sentPairedKeyEncryption; - print("Outbound $id: Sent PairedKeyEncryption"); + _log.info("Outbound $id: Sent PairedKeyEncryption"); } Future _processPairedKeyEncryption(wire.Frame frame) async { - print("Outbound $id: Processing PairedKeyEncryption from receiver"); + _log.info("Outbound $id: Processing PairedKeyEncryption from receiver"); final pairedResultFrame = wire.PairedKeyResultFrame( status: wire.PairedKeyResultFrame_Status.UNABLE, ); @@ -522,18 +530,18 @@ class OutboundNearbyConnection extends NearbyConnection { ); await sendTransferSetupFrame(wireFrame); _currentState = OutboundState.sentPairedKeyResult; - print("Outbound $id: Sent our PairedKeyResult (UNABLE)"); + _log.info("Outbound $id: Sent our PairedKeyResult (UNABLE)"); } // Process the server's PairedKeyResult frame Future _processPairedKeyResult(wire.Frame frame) async { // We don't care about the result (UNABLE usually), just proceed - print("Outbound $id: Processing PairedKeyResult from receiver"); + _log.info("Outbound $id: Processing PairedKeyResult from receiver"); await _prepareAndSendIntroduction(); } Future _prepareAndSendIntroduction() async { - print("Outbound $id: Preparing Introduction frame"); + _log.info("Outbound $id: Preparing Introduction frame"); final introduction = wire.IntroductionFrame(); _totalBytesToSend = 0; _transferQueue = []; @@ -556,7 +564,7 @@ class OutboundNearbyConnection extends NearbyConnection { try { final file = File(url); if (!await file.exists()) { - print("Warning: File not found: $url"); + _log.info("Warning: File not found: $url"); continue; } final fileStat = await file.stat(); @@ -572,7 +580,7 @@ class OutboundNearbyConnection extends NearbyConnection { type: fileType, payloadId: Int64(generatePositivePayloadId()), ); - print( + _log.info( "Outbound $id: Queued file ${fileMeta.name} ($mimeType, ${fileType.name}) payload ${fileMeta.payloadId} size $fileSize", ); @@ -588,7 +596,7 @@ class OutboundNearbyConnection extends NearbyConnection { ); _totalBytesToSend += fileSize; } catch (e, s) { - print("Error processing file $url: $e\n$s"); + _log.severe("Error processing file $url: $e\n$s"); // Should we abort the whole transfer? Or just skip this file? // For now, just skip. } @@ -609,18 +617,18 @@ class OutboundNearbyConnection extends NearbyConnection { await sendTransferSetupFrame(wireFrame); _currentState = OutboundState.sentIntroduction; - print( + _log.info( "Outbound $id: Sent Introduction frame. Total bytes: $_totalBytesToSend", ); } // Process the ACCEPT/REJECT response from the receiver Future _processConsentResponse(wire.Frame frame) async { - print("Outbound $id: Processing ConnectionResponse (Consent)"); + _log.info("Outbound $id: Processing ConnectionResponse (Consent)"); final status = frame.v1.connectionResponse.status; if (status == wire.ConnectionResponseFrame_Status.ACCEPT) { - print("Outbound $id: Transfer accepted by receiver."); + _log.info("Outbound $id: Transfer accepted by receiver."); _currentState = OutboundState.sendingFiles; Future.microtask(() => delegate?.outboundTransferAccepted(this)); @@ -633,7 +641,9 @@ class OutboundNearbyConnection extends NearbyConnection { final reason = wireStatusToCancellationReason(status) ?? CancellationReason.userRejected; - print("Outbound $id: Transfer rejected by receiver with status $status."); + _log.info( + "Outbound $id: Transfer rejected by receiver with status $status.", + ); lastError = NearbyCancellationException(reason); await sendDisconnectionAndDisconnect(); // Delegate notified via handleConnectionClosure @@ -642,12 +652,12 @@ class OutboundNearbyConnection extends NearbyConnection { Future _sendUrlPayload() async { if (_cancelled) return; - print("Outbound $id: Sending URL payload."); + _log.info("Outbound $id: Sending URL payload."); await sendBytesPayload( data: utf8.encode(_urlsToSend[0]), payloadId: _textPayloadID, ); - print("Outbound $id: URL payload sent."); + _log.info("Outbound $id: URL payload sent."); _awaitingRemoteCompletion = true; await sendDisconnectionAndDisconnect( waitForRemoteClose: true, @@ -665,7 +675,7 @@ class OutboundNearbyConnection extends NearbyConnection { _currentTransfer?.handle = null; if (_transferQueue.isEmpty) { - print("Outbound $id: All files transferred."); + _log.info("Outbound $id: All files transferred."); _awaitingRemoteCompletion = true; await sendDisconnectionAndDisconnect( waitForRemoteClose: true, @@ -680,11 +690,11 @@ class OutboundNearbyConnection extends NearbyConnection { _currentTransfer!.handle = await File( _currentTransfer!.sourcePath, ).open(mode: FileMode.read); - print( + _log.info( "Outbound $id: Started sending file: ${_currentTransfer!.sourcePath}", ); } catch (e, s) { - print( + _log.info( "Outbound $id: Failed to open file ${_currentTransfer!.sourcePath}: $e\n$s", ); lastError = NearbyIOException(); @@ -699,7 +709,7 @@ class OutboundNearbyConnection extends NearbyConnection { try { fileBuffer = await _currentTransfer!.handle!.read(chunkSize); } catch (e, s) { - print( + _log.info( "Outbound $id: Error reading from file ${_currentTransfer!.sourcePath}: $e\n$s", ); lastError = NearbyIOException(); @@ -710,7 +720,7 @@ class OutboundNearbyConnection extends NearbyConnection { if (fileBuffer.isEmpty && _currentTransfer!.currentOffset != _currentTransfer!.totalBytes) { // This indicates an unexpected EOF or read error - print( + _log.info( "Outbound $id: Read empty buffer before expected EOF for ${_currentTransfer!.sourcePath}", ); lastError = NearbyIOException(); @@ -757,7 +767,7 @@ class OutboundNearbyConnection extends NearbyConnection { // Send the data chunk await encryptAndSendOfflineFrame(offlineFrame); - print( + _log.info( "Outbound $id: Sent chunk ${_currentTransfer!.currentOffset}/${_currentTransfer!.totalBytes} for payload ${_currentTransfer!.payloadID}", ); @@ -773,7 +783,7 @@ class OutboundNearbyConnection extends NearbyConnection { // Send EOF frame if this was the last chunk of the file if (isLastChunkOfFile) { - print( + _log.info( "Outbound $id: Sending EOF for payload ${_currentTransfer!.payloadID}", ); final isFinalFileOfTransfer = _transferQueue.isEmpty; diff --git a/lib/nearby_share/manager/nearby_manager.dart b/lib/nearby_share/manager/nearby_manager.dart index 0f60f82..f165419 100644 --- a/lib/nearby_share/manager/nearby_manager.dart +++ b/lib/nearby_share/manager/nearby_manager.dart @@ -11,6 +11,9 @@ import 'package:crossdrop/nearby_share/platform/fast_init_advertiser.dart'; import 'package:crossdrop/nearby_share/utils/data_extension.dart'; import 'package:flutter/foundation.dart'; // For ChangeNotifier import 'package:uuid/uuid.dart'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('nearby_manager'); // --- Interfaces (similar to Swift protocols) --- @@ -96,24 +99,24 @@ class NearbyConnectionManager extends ChangeNotifier _handleIncomingConnection, onError: (e, s) { if (!identical(_serverSocket, serverSocket)) { - print("Ignoring stale server socket error: $e"); + _log.severe("Ignoring stale server socket error: $e"); return; } - print("Active server socket error: $e\n$s"); + _log.severe("Active server socket error: $e\n$s"); unawaited(stopBroadcasting()); }, onDone: () { if (!identical(_serverSocket, serverSocket)) { - print("Stale server socket closed"); + _log.info("Stale server socket closed"); return; } - print("Active server socket closed"); + _log.info("Active server socket closed"); unawaited(stopBroadcasting()); }, ); final port = serverSocket.port; - print("TCP Listener started on port $port"); + _log.info("TCP Listener started on port $port"); final service = BonsoirService( name: _generateBonsoirName(_endpointId), @@ -127,12 +130,12 @@ class NearbyConnectionManager extends ChangeNotifier await _bonsoirBroadcast!.start(); _isBroadcasting = true; - print( + _log.info( "Started broadcasting Nearby Share service as $_deviceName ($_endpointId) on port $port", ); notifyListeners(); } catch (e, s) { - print("Error starting broadcasting: $e\n$s"); + _log.severe("Error starting broadcasting: $e\n$s"); await _stopBroadcasting(); rethrow; } @@ -161,7 +164,7 @@ class NearbyConnectionManager extends ChangeNotifier _inboundConnections.values.map((conn) => conn.disconnect()), ); _inboundConnections.clear(); - print("Stopped broadcasting Nearby Share service"); + _log.info("Stopped broadcasting Nearby Share service"); notifyListeners(); } @@ -184,13 +187,13 @@ class NearbyConnectionManager extends ChangeNotifier try { if (event is BonsoirDiscoveryServiceFoundEvent) { final service = event.service; - if (service.host == null) { - print("Service ${service.name} found, resolving..."); + if (service.hostAddress == null) { + _log.info("Service ${service.name} found, resolving..."); unawaited( service .resolve(_bonsoirDiscovery!.serviceResolver) .catchError((Object e, StackTrace s) { - print( + _log.info( "Error resolving service ${service.name}: $e\n$s", ); }), @@ -206,18 +209,18 @@ class NearbyConnectionManager extends ChangeNotifier _handleLostService(event.service); } } catch (e, s) { - print("Error handling discovery event $event: $e\n$s"); + _log.severe("Error handling discovery event $event: $e\n$s"); } }, onError: (e, s) { - print("Discovery stream error: $e\n$s"); + _log.severe("Discovery stream error: $e\n$s"); stopDiscovery(); // Stop discovery on stream error }, ); await _bonsoirDiscovery!.start(); _isDiscovering = true; - print("Started Nearby Share discovery"); + _log.info("Started Nearby Share discovery"); notifyListeners(); } catch (_) { await _fastInitAdvertiser.stop(); @@ -236,7 +239,7 @@ class NearbyConnectionManager extends ChangeNotifier _discoveredDevices.clear(); _discoveryQrCode = null; _isDiscovering = false; - print("Stopped Nearby Share discovery"); + _log.info("Stopped Nearby Share discovery"); notifyListeners(); } @@ -262,12 +265,12 @@ class NearbyConnectionManager extends ChangeNotifier throw ArgumentError("File paths list cannot be empty."); } - print("Initiating transfer to ${device.name} ($endpointId)"); + _log.info("Initiating transfer to ${device.name} ($endpointId)"); if (_outboundConnections.values.any( (conn) => conn.remoteDeviceInfo?.id == endpointId, )) { - print("Outgoing connection to $endpointId already exists."); + _log.info("Outgoing connection to $endpointId already exists."); // Optionally notify UI or just return return; } @@ -281,7 +284,7 @@ class NearbyConnectionManager extends ChangeNotifier port, timeout: const Duration(seconds: 10), ); - print("Socket connected to $ip:$port"); + _log.info("Socket connected to $ip:$port"); final connectionId = const Uuid().v4(); final connection = OutboundNearbyConnection( @@ -300,7 +303,7 @@ class NearbyConnectionManager extends ChangeNotifier } connection.start(); } catch (e, s) { - print("Failed to initiate transfer to $endpointId: $e\n$s"); + _log.severe("Failed to initiate transfer to $endpointId: $e\n$s"); for (var l in _nearbyListeners) { // Notify failure if connection couldn't even start // Need a way to map back to the UI element expecting this transfer @@ -317,7 +320,7 @@ class NearbyConnectionManager extends ChangeNotifier Future respondToTransfer(String connectionId, bool accept) async { final connection = _inboundConnections[connectionId]; if (connection == null) { - print( + _log.info( "Cannot respond to transfer: Connection ID $connectionId not found.", ); return; @@ -329,14 +332,14 @@ class NearbyConnectionManager extends ChangeNotifier void _handleIncomingConnection(Socket socket) { final remoteAddr = socket.remoteAddress.address; final remotePort = socket.remotePort; - print("Incoming connection from $remoteAddr:$remotePort"); + _log.info("Incoming connection from $remoteAddr:$remotePort"); final connectionId = const Uuid().v4(); final connection = InboundNearbyConnection(socket, connectionId); connection.delegate = this; _inboundConnections[connectionId] = connection; connection.onClose.whenComplete(() { - print("Inbound connection $connectionId closed completely."); + _log.info("Inbound connection $connectionId closed completely."); _inboundConnections.remove(connectionId); }); connection.start(); @@ -351,22 +354,26 @@ class NearbyConnectionManager extends ChangeNotifier _deviceLossTimers.remove(endpointId)?.cancel(); final txt = service.attributes; - final ip = service.host; // Use host from ResolvedBonsoirService + final ip = service.hostAddress; // First host address from resolved service final port = service.port; if (ip == null) { - print("Ignoring resolved service ${service.name}: missing host/IP"); + _log.info("Ignoring resolved service ${service.name}: missing host/IP"); return; } final endpointInfoEncoded = txt['n']; if (endpointInfoEncoded == null) { - print("Ignoring service ${service.name}: missing 'n' attribute in TXT"); + _log.info( + "Ignoring service ${service.name}: missing 'n' attribute in TXT", + ); return; } final endpointInfoBytes = endpointInfoEncoded.fromUrlSafeBase64(); if (endpointInfoBytes == null) { - print("Ignoring service ${service.name}: failed to decode 'n' attribute"); + _log.severe( + "Ignoring service ${service.name}: failed to decode 'n' attribute", + ); return; } try { @@ -378,7 +385,7 @@ class NearbyConnectionManager extends ChangeNotifier final keychain = await qrCode.deriveHiddenKeychain(); if (listEquals(qrRecord, keychain.advertisingToken)) { qrMatched = true; - print( + _log.info( "Service ${service.name} matched QR advertising token for $endpointId", ); } else if (!endpointInfo.visible && qrRecord.length > 28) { @@ -390,11 +397,11 @@ class NearbyConnectionManager extends ChangeNotifier records: endpointInfo.records, ); qrMatched = true; - print( + _log.info( "Service ${service.name} matched encrypted QR record for $endpointId", ); } else if (!endpointInfo.visible) { - print( + _log.info( "Ignoring hidden service ${service.name}: QR record did not match current QR key (${qrRecord.length} bytes)", ); return; @@ -403,13 +410,13 @@ class NearbyConnectionManager extends ChangeNotifier if (!endpointInfo.visible) { if (qrCode == null) { - print( + _log.info( "Ignoring hidden service ${service.name}: discovery has no QR context", ); return; } if (qrRecord == null) { - print( + _log.info( "Ignoring hidden service ${service.name}: missing QR record; available records: ${endpointInfo.records.keys.join(', ')}", ); return; @@ -429,7 +436,7 @@ class NearbyConnectionManager extends ChangeNotifier bool needsNotify = false; if (existingDevice == null) { - print( + _log.info( "Device Found & Resolved: ${newDeviceInfo.name} ($endpointId) at $ip:$port", ); _discoveredDevices[endpointId] = newDeviceInfo; @@ -440,7 +447,7 @@ class NearbyConnectionManager extends ChangeNotifier existingDevice.qrMatched != newDeviceInfo.qrMatched || !existingDevice.isResolved) { // Update if name changed or if it wasn't resolved before - print( + _log.info( "Device Updated: ${newDeviceInfo.name} ($endpointId) at $ip:$port", ); _discoveredDevices[endpointId] = newDeviceInfo; @@ -454,7 +461,9 @@ class NearbyConnectionManager extends ChangeNotifier notifyListeners(); } } catch (e, s) { - print("Error parsing endpoint info for service ${service.name}: $e\n$s"); + _log.severe( + "Error parsing endpoint info for service ${service.name}: $e\n$s", + ); } } @@ -462,11 +471,11 @@ class NearbyConnectionManager extends ChangeNotifier final endpointId = _getEndpointIdFromService(service); if (endpointId != null && _discoveredDevices.containsKey(endpointId)) { _deviceLossTimers[endpointId]?.cancel(); - print("Device Lost pending: $endpointId"); + _log.info("Device Lost pending: $endpointId"); _deviceLossTimers[endpointId] = Timer(_deviceLostDebounce, () { _deviceLossTimers.remove(endpointId); if (_discoveredDevices.remove(endpointId) != null) { - print("Device Lost: $endpointId"); + _log.info("Device Lost: $endpointId"); for (var l in _nearbyListeners) { l.onDeviceLost(endpointId); } @@ -490,7 +499,7 @@ class NearbyConnectionManager extends ChangeNotifier RemoteDeviceInfo device, InboundNearbyConnection connection, ) { - print( + _log.info( "Manager: Received transfer request ${transfer.id} from ${device.name}", ); for (var l in _nearbyListeners) { @@ -503,7 +512,7 @@ class NearbyConnectionManager extends ChangeNotifier InboundNearbyConnection connection, Exception? error, ) { - print( + _log.info( "Manager: Inbound connection ${connection.id} terminated. Error: $error", ); _inboundConnections.remove(connection.id); @@ -524,7 +533,7 @@ class NearbyConnectionManager extends ChangeNotifier @override void outboundConnectionEstablished(OutboundNearbyConnection connection) { - print( + _log.info( "Manager: Outbound connection ${connection.id} established. PIN: ${connection.pinCode}", ); if (connection.remoteDeviceInfo != null && connection.pinCode != null) { @@ -539,7 +548,7 @@ class NearbyConnectionManager extends ChangeNotifier OutboundNearbyConnection connection, Exception error, ) { - print("Manager: Outbound connection ${connection.id} failed: $error"); + _log.severe("Manager: Outbound connection ${connection.id} failed: $error"); _outboundConnections.remove(connection.id); for (var l in _nearbyListeners) { l.onTransferFinished(connection.id, false, error); @@ -548,7 +557,9 @@ class NearbyConnectionManager extends ChangeNotifier @override void outboundTransferAccepted(OutboundNearbyConnection connection) { - print("Manager: Outbound transfer ${connection.id} accepted by receiver."); + _log.info( + "Manager: Outbound transfer ${connection.id} accepted by receiver.", + ); } @override @@ -563,7 +574,9 @@ class NearbyConnectionManager extends ChangeNotifier @override void outboundTransferFinished(OutboundNearbyConnection connection) { - print("Manager: Outbound transfer ${connection.id} finished successfully."); + _log.info( + "Manager: Outbound transfer ${connection.id} finished successfully.", + ); _outboundConnections.remove(connection.id); for (var l in _nearbyListeners) { l.onTransferFinished(connection.id, true, null); @@ -596,7 +609,9 @@ class NearbyConnectionManager extends ChangeNotifier return ascii.decode(nameData.sublist(1, 5)); } } catch (e) { - print("Error parsing endpoint ID from service name ${service.name}: $e"); + _log.severe( + "Error parsing endpoint ID from service name ${service.name}: $e", + ); } return null; } diff --git a/lib/nearby_share/platform/fast_init_advertiser.dart b/lib/nearby_share/platform/fast_init_advertiser.dart index 5ce8123..a53d63b 100644 --- a/lib/nearby_share/platform/fast_init_advertiser.dart +++ b/lib/nearby_share/platform/fast_init_advertiser.dart @@ -1,6 +1,9 @@ import 'dart:io'; import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('fast_init_advertiser'); class FastInitAdvertiser { static const MethodChannel _channel = MethodChannel('crossdrop/fast_init'); @@ -13,14 +16,18 @@ class FastInitAdvertiser { final started = await _channel.invokeMethod('start') ?? false; _started = started; if (started) { - print('Requested Quick Share Fast Init BLE advertising'); + _log.info('Requested Quick Share Fast Init BLE advertising'); } else { - print('Quick Share Fast Init BLE advertising is not available'); + _log.info('Quick Share Fast Init BLE advertising is not available'); } } on MissingPluginException { - print('Quick Share Fast Init BLE advertising is not implemented here'); + _log.info( + 'Quick Share Fast Init BLE advertising is not implemented here', + ); } catch (e, s) { - print('Failed to start Quick Share Fast Init BLE advertising: $e\n$s'); + _log.severe( + 'Failed to start Quick Share Fast Init BLE advertising: $e\n$s', + ); } } @@ -29,11 +36,13 @@ class FastInitAdvertiser { _started = false; try { await _channel.invokeMethod('stop'); - print('Stopped Quick Share Fast Init BLE advertising'); + _log.info('Stopped Quick Share Fast Init BLE advertising'); } on MissingPluginException { // Platform does not implement the channel. } catch (e, s) { - print('Failed to stop Quick Share Fast Init BLE advertising: $e\n$s'); + _log.severe( + 'Failed to stop Quick Share Fast Init BLE advertising: $e\n$s', + ); } } } diff --git a/lib/nearby_share/utils/data_extension.dart b/lib/nearby_share/utils/data_extension.dart index 7ffcea2..b41a965 100644 --- a/lib/nearby_share/utils/data_extension.dart +++ b/lib/nearby_share/utils/data_extension.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; +import 'package:logging/logging.dart'; + +final Logger _log = Logger('data_extension'); const String _endpointIdAlphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; @@ -60,7 +63,7 @@ Uint8List? urlSafeBase64Decode(String str) { } return base64Decode(regularB64); } catch (e) { - print('Error decoding base64 string: $e'); + _log.severe('Error decoding base64 string: $e'); return null; } } diff --git a/lib/notifications.dart b/lib/notifications.dart index e020e98..46a1ded 100644 --- a/lib/notifications.dart +++ b/lib/notifications.dart @@ -1,6 +1,9 @@ import 'dart:convert'; // For jsonEncode/Decode import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:crossdrop/nearby_share/api/models.dart'; // For TransferMetadata +import 'package:logging/logging.dart'; + +final Logger _log = Logger('notifications'); const String channelId = 'nearby_share_channel'; const String channelName = 'Nearby Share Transfers'; @@ -215,7 +218,7 @@ void onDidReceiveNotificationResponse( ) async { final String? payload = notificationResponse.payload; final String? actionId = notificationResponse.actionId; - print('Notification response: action="$actionId", payload="$payload"'); + _log.info('Notification response: action="$actionId", payload="$payload"'); if (payload != null && actionId != null) { try { @@ -223,13 +226,13 @@ void onDidReceiveNotificationResponse( final connectionId = data['connectionId'] as String?; if (connectionId != null) { final accepted = actionId == 'ACCEPT'; - print( + _log.info( 'Calling notification action callback for $connectionId, accepted: $accepted', ); _onNotificationAction?.call(connectionId, accepted); } } catch (e) { - print('Error decoding notification payload: $e'); + _log.severe('Error decoding notification payload: $e'); } } } @@ -240,7 +243,7 @@ void notificationTapBackground(NotificationResponse notificationResponse) { // This runs in a separate isolate. Basic handling is possible, // but complex state updates require inter-isolate communication. // For simplicity, we'll primarily rely on onDidReceiveNotificationResponse. - print( + _log.info( 'Notification tapped from background/terminated state: ${notificationResponse.payload}', ); // Potentially store the payload/action and process when the main app starts. diff --git a/lib/window/system_tray.dart b/lib/window/system_tray.dart index 1ae7954..dcd9b85 100644 --- a/lib/window/system_tray.dart +++ b/lib/window/system_tray.dart @@ -19,7 +19,13 @@ class AppSystemTray { final path = Platform.isWindows ? 'assets/icons/system_tray_icon.ico' : 'assets/icons/system_tray_icon.png'; - await _systemTray.initSystemTray(iconPath: path, toolTip: 'CrossDrop'); + await _systemTray.initSystemTray( + iconPath: path, + toolTip: 'CrossDrop', + // On macOS, mark the icon as a template image so the menu bar tints it + // automatically: white on a dark menu bar, black on a light one. + isTemplate: Platform.isMacOS, + ); // handle system tray event _systemTray.registerSystemTrayEventHandler((eventName) { diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 3e9b24b..bba7551 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -1,5 +1,5 @@ # Project-level configuration. -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change @@ -54,26 +54,8 @@ add_subdirectory(${FLUTTER_MANAGED_DIR}) find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") - -# Define the application target. To change its name, change BINARY_NAME above, -# not the value here, or `flutter run` will no longer work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} - "fast_init_advertiser.cc" - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add dependency libraries. Add any application-specific dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e9d6633 --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "fast_init_advertiser.cc" + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/fast_init_advertiser.cc b/linux/runner/fast_init_advertiser.cc similarity index 100% rename from linux/fast_init_advertiser.cc rename to linux/runner/fast_init_advertiser.cc diff --git a/linux/fast_init_advertiser.h b/linux/runner/fast_init_advertiser.h similarity index 100% rename from linux/fast_init_advertiser.h rename to linux/runner/fast_init_advertiser.h diff --git a/linux/main.cc b/linux/runner/main.cc similarity index 100% rename from linux/main.cc rename to linux/runner/main.cc diff --git a/linux/my_application.cc b/linux/runner/my_application.cc similarity index 100% rename from linux/my_application.cc rename to linux/runner/my_application.cc diff --git a/linux/my_application.h b/linux/runner/my_application.h similarity index 100% rename from linux/my_application.h rename to linux/runner/my_application.h diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 77f9e9b..96d813f 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,71 +1,21 @@ PODS: - - bonsoir_darwin (0.0.1): - - Flutter - - FlutterMacOS - - device_info_plus (0.0.1): - - FlutterMacOS - - file_selector_macos (0.0.1): - - FlutterMacOS - - flutter_local_notifications (0.0.1): - - FlutterMacOS - FlutterMacOS (1.0.0) - - screen_retriever_macos (0.0.1): - - FlutterMacOS - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - system_tray (0.0.1): - FlutterMacOS - - url_launcher_macos (0.0.1): - - FlutterMacOS - - window_manager (0.5.0): - - FlutterMacOS DEPENDENCIES: - - bonsoir_darwin (from `Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin`) - - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - system_tray (from `Flutter/ephemeral/.symlinks/plugins/system_tray/macos`) - - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) EXTERNAL SOURCES: - bonsoir_darwin: - :path: Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin - device_info_plus: - :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos - file_selector_macos: - :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos - flutter_local_notifications: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos FlutterMacOS: :path: Flutter/ephemeral - screen_retriever_macos: - :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos - shared_preferences_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin system_tray: :path: Flutter/ephemeral/.symlinks/plugins/system_tray/macos - url_launcher_macos: - :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos - window_manager: - :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - bonsoir_darwin: 14bd7429acb51db6a8e51f6574ac3b1ea0037a87 - device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 - file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 - flutter_local_notifications: 1fc7ffb10a83d6a2eeeeddb152d43f1944b0aad0 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb system_tray: 53f0cdb020c3fbee711d3fe45ae7ce730e033d2b - url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd - window_manager: b729e31d38fb04905235df9ea896128991cad99e PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 908c02c..ec92d64 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 4E39F227D5B28385D1AF76F0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA055E102483971541B6AD22 /* Pods_RunnerTests.framework */; }; 524813FDDF5439A7729579C4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DDB2762352C6A66758FD279 /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -88,6 +89,7 @@ C10585D7C8FB309052BF453D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; EF0DB2EEC10E39F403EC2362 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; FA055E102483971541B6AD22 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -103,6 +105,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 524813FDDF5439A7729579C4 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -164,6 +167,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -230,6 +234,9 @@ productType = "com.apple.product-type.bundle.unit-test"; }; 33CC10EC2044A3C60003C045 /* Runner */ = { + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( @@ -255,6 +262,9 @@ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; @@ -717,6 +727,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -788,6 +799,18 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 0aaffc9..c7374f0 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + = :catalina" + + app "CrossDrop.app" +end diff --git a/packaging/linux/build-appimage.sh b/packaging/linux/build-appimage.sh new file mode 100755 index 0000000..767d387 --- /dev/null +++ b/packaging/linux/build-appimage.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# Build a CrossDrop AppImage from a Flutter Linux release bundle. +# +# Usage: +# packaging/linux/build-appimage.sh +# +# Example: +# flutter build linux --release +# packaging/linux/build-appimage.sh \ +# build/linux/x64/release/bundle \ +# dist/CrossDrop-1.0.0-linux-x86_64.AppImage +# +# Must be run from the repository root so the icon and .desktop file can be +# located. Downloads appimagetool on first use. +set -euo pipefail + +BUNDLE_DIR="${1:?usage: build-appimage.sh }" +OUTPUT="${2:?usage: build-appimage.sh }" + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +DESKTOP_FILE="${REPO_ROOT}/linux/de.medformatik.crossdrop.desktop" +ICON_FILE="${REPO_ROOT}/assets/icons/app_icon.png" + +if [[ ! -d "${BUNDLE_DIR}" ]]; then + echo "error: bundle directory '${BUNDLE_DIR}' not found (run 'flutter build linux --release' first)" >&2 + exit 1 +fi + +work_dir="$(mktemp -d)" +trap 'rm -rf "${work_dir}"' EXIT +appdir="${work_dir}/CrossDrop.AppDir" + +# Lay out the AppDir: the Flutter bundle lives in usr/bin so the executable +# keeps its data/ and lib/ siblings; AppRun launches it. +mkdir -p "${appdir}/usr/bin" +cp -r "${BUNDLE_DIR}/." "${appdir}/usr/bin/" + +# Icon must be named after the .desktop "Icon=" key (CrossDrop). +install -Dm644 "${ICON_FILE}" "${appdir}/CrossDrop.png" +install -Dm644 "${ICON_FILE}" "${appdir}/usr/share/icons/hicolor/512x512/apps/CrossDrop.png" + +# Desktop entry at the AppDir root (and the standard location). +install -Dm644 "${DESKTOP_FILE}" "${appdir}/de.medformatik.crossdrop.desktop" +install -Dm644 "${DESKTOP_FILE}" "${appdir}/usr/share/applications/de.medformatik.crossdrop.desktop" + +cat > "${appdir}/AppRun" <<'APPRUN' +#!/usr/bin/env bash +HERE="$(dirname "$(readlink -f "${0}")")" +exec "${HERE}/usr/bin/CrossDrop" "$@" +APPRUN +chmod +x "${appdir}/AppRun" + +# Fetch appimagetool if it is not already cached next to this script. +tool="${APPIMAGETOOL:-${work_dir}/appimagetool}" +if [[ ! -x "${tool}" ]]; then + echo "Downloading appimagetool..." + curl -fsSL \ + "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" \ + -o "${tool}" + chmod +x "${tool}" +fi + +mkdir -p "$(dirname "${OUTPUT}")" +# --appimage-extract-and-run avoids needing FUSE on CI runners. +ARCH=x86_64 "${tool}" --appimage-extract-and-run "${appdir}" "${OUTPUT}" +echo "Built ${OUTPUT}" diff --git a/pubspec.lock b/pubspec.lock index 031e897..cab9578 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,50 +29,50 @@ packages: dependency: "direct main" description: name: bonsoir - sha256: "1b112a966302a739d253c8dbc7ea4c1cb3eca6d8110dc59e36d76f7cf0b6edf2" + sha256: c0e84f696767d67f14fc05f2d67656487aa86934dfaa9462e1eed34c016deb52 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.1.1" bonsoir_android: dependency: transitive description: name: bonsoir_android - sha256: bfa3ab7e2f65473cb369bce639dbdbdc75064848c01ac11b3c2bc3e16031ff3a + sha256: "152966e6b4db2bfd5a8a9cc3234a30d694e596f61c9cc868daa1644707df7873" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "7.1.1" bonsoir_darwin: dependency: transitive description: name: bonsoir_darwin - sha256: d62fd62ed433aa09ec99f71f95dae53ffa0adf788c9051ff3d6b903463045d3c + sha256: "04425a8657e3131683c7966689d4e65e114cb5181de94aac89d2fba84cfaaca4" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.1.0" bonsoir_linux: dependency: transitive description: name: bonsoir_linux - sha256: a49d5f328a197b27a3901b833f92c93366f2cd7085dcb495fa11ee6d9f2509a9 + sha256: "9afae7bb9509c5bd20a0ea2ca02a0f2b1ef09cca1ceb2ea288ed58a0f03d1e11" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "7.1.0" bonsoir_platform_interface: dependency: transitive description: name: bonsoir_platform_interface - sha256: ba1cc30daaa172dfc76f88e4fee8d090674179439201997ba2b3bd9e1cca84c0 + sha256: "6c55c786b01ad279f675ae72cea3a885aa746e416d2e8c1f0c46e9d2c06420b0" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.0" bonsoir_windows: dependency: transitive description: name: bonsoir_windows - sha256: "01aba2516b776eb1deb68845124dc0a41095da108276d4b307bf19c4c3e2d9b1" + sha256: "656aea056e48adc93b535339efe2f5f4e5eeec38a7d5c7aa6eb7566b919cdec1" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "7.1.0" boolean_selector: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.2.1" collection: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: dbus - sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" url: "https://pub.dev" source: hosted - version: "0.7.12" + version: "0.7.14" device_info_plus: dependency: "direct main" description: @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: "89243030ea4b3463fb402b44d5eeacc4ccb1c46a88870cb2a5080d693200c1ed" + sha256: "6a26687fa65cbc28a5345c7ae6f227e89f0b47740978a4c475b1a625da7a331b" url: "https://pub.dev" source: hosted - version: "0.5.2+6" + version: "0.5.2+8" file_selector_ios: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: file_selector_web - sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7 + sha256: "73181fbc5257776d8ecaa6a94ab3c8e920ad143b9132a6d984a9271dfc6928d3" url: "https://pub.dev" source: hosted - version: "0.9.4+2" + version: "0.9.5" file_selector_windows: dependency: transitive description: @@ -314,34 +314,42 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "0d9035862236fe38250fe1644d7ed3b8254e34a21b2c837c9f539fbb3bba5ef1" + sha256: be38e3854d2baabcda8e16966a5fe8748cebb655bb94701494da0f052c2fc352 url: "https://pub.dev" source: hosted - version: "21.0.0" + version: "22.0.0" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: e0f25e243c6c44c825bbbc6b2b2e76f7d9222362adcfe9fd780bf01923c840bd + sha256: "9ca97e63776f29ab1b955725c09999fc2c150523269db150c39274f2a43c5a8b" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.0.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: e7db3d5b49c2b7ecc68deba4aaaa67a348f92ee0fef34c8e4b4459dbef0d7307 + sha256: ff0013eae795e8dc8fad4a8992a209e64d3ba2fbd8bf5e43c36bf448f95bd814 url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "12.0.0" + flutter_local_notifications_web: + dependency: transitive + description: + name: flutter_local_notifications_web + sha256: "516afaf97a2d1e67a036c6617321b00d205d72f7a67b6eccf936cd565f985878" + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_local_notifications_windows: dependency: transitive description: name: flutter_local_notifications_windows - sha256: "3a2654ba104fbb52c618ebed9def24ef270228470718c43b3a6afcd5c81bef0c" + sha256: "5aeed973a0c1480706784fad05c5c3a911335ebb561b2274b47fe80b375201e1" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -352,22 +360,14 @@ packages: description: flutter source: sdk version: "0.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" hooks: dependency: transitive description: name: hooks - sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.2" http: dependency: transitive description: @@ -412,10 +412,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: @@ -449,7 +449,7 @@ packages: source: hosted version: "6.1.0" logging: - dependency: transitive + dependency: "direct main" description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 @@ -476,10 +476,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mime: dependency: "direct main" description: @@ -488,14 +488,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.dev" - source: hosted - version: "0.17.6" nested: dependency: transitive description: @@ -508,10 +500,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.4.1" package_config: dependency: transitive description: @@ -580,10 +572,10 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6 url: "https://pub.dev" source: hosted - version: "12.0.1" + version: "12.0.3" permission_handler_android: dependency: transitive description: @@ -596,10 +588,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420" url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.4.10" permission_handler_html: dependency: transitive description: @@ -716,42 +708,42 @@ packages: dependency: "direct main" description: name: screen_retriever - sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + sha256: "42cc3b402a0f67d2455a0d067553d0f13453f6a008d98eababf8b63958d506bd" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" screen_retriever_linux: dependency: transitive description: name: screen_retriever_linux - sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + sha256: "2a476f1a5538065bc5badf376cfdc83d6ecf07d77eb2391b9c2bff5a76970048" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" screen_retriever_macos: dependency: transitive description: name: screen_retriever_macos - sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + sha256: b5abb900fcb86614ff10b738b34e37b9e1d03b0447280668e2bc8a98bdc7bd59 url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" screen_retriever_platform_interface: dependency: transitive description: name: screen_retriever_platform_interface - sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + sha256: "3af22d926bedf20c2caa308eea376776451a3af125919ce072e56525fded8901" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" screen_retriever_windows: dependency: transitive description: name: screen_retriever_windows - sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + sha256: c44b38a4c4bab34af259180a70a4eee1e29384e7b82e627c9faa68afcdab2e73 url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" shared_preferences: dependency: "direct main" description: @@ -764,10 +756,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + sha256: "93ae5884a9df5d3bb696825bceb3a17590754548b5d740eba51500afc8d088f5" url: "https://pub.dev" source: hosted - version: "2.4.23" + version: "2.4.26" shared_preferences_foundation: dependency: transitive description: @@ -865,10 +857,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" timezone: dependency: transitive description: @@ -897,10 +889,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 url: "https://pub.dev" source: hosted - version: "6.3.29" + version: "6.3.32" url_launcher_ios: dependency: transitive description: @@ -985,10 +977,10 @@ packages: dependency: transitive description: name: win32 - sha256: a1fc9eb9248baa05dfc12ed5b66e377b3e23f095eec078e0371622b9033810d9 + sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.0" win32_registry: dependency: transitive description: @@ -1030,5 +1022,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.11.5 <4.0.0" - flutter: ">=3.41.0" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index cdfe901..d59b055 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,7 @@ name: crossdrop -description: "Android Nearby Share implementation for Flutter" +description: "Quick Share (Nearby Share) implementation in Flutter for macOS, iOS, and Linux." publish_to: "none" +repository: "https://github.com/Medformatik/CrossDrop" version: 1.0.0+1 @@ -8,29 +9,31 @@ environment: sdk: ^3.11.5 dependencies: - bonsoir: ^6.1.0 + bonsoir: ^7.1.1 cryptography: ^2.9.0 device_info_plus: ^13.1.0 file_selector: ^1.1.0 fixnum: ^1.1.1 flutter: sdk: flutter - flutter_local_notifications: ^21.0.0 + flutter_local_notifications: ^22.0.0 + logging: ^1.3.0 mime: ^2.0.0 path: ^1.9.1 path_provider: ^2.1.5 - permission_handler: ^12.0.1 + permission_handler: ^12.0.3 pointycastle: ^4.0.0 protobuf: ^6.0.0 provider: ^6.1.5+1 qr_flutter: ^4.1.0 - screen_retriever: ^0.2.0 + screen_retriever: ^0.2.1 shared_preferences: ^2.5.5 system_tray: ^2.0.3 url_launcher: ^6.3.2 uuid: ^4.5.3 window_manager: ^0.5.1 +# system_tray 2.0.3 pins uuid ^3.0.6; override forces the app-wide uuid ^4.5.3. dependency_overrides: uuid: ^4.5.3 @@ -46,17 +49,14 @@ flutter: - assets/sf_symbols/ - assets/icons/ +# Generates iOS and macOS icon sets from a single 1024x1024 source image. +# Run after changing the source: dart run flutter_launcher_icons +# (Android/web/Windows are not targeted; Linux uses the same source via the +# AppImage packaging + .desktop file, since this tool has no Linux support.) flutter_launcher_icons: - android: false ios: true remove_alpha_ios: true image_path: "assets/icons/app_icon.png" - web: - generate: false - windows: - generate: true - icon_size: 128 # min:48, max:256, default: 48 - image_path: "assets/icons/app_icon.png" macos: generate: true image_path: "assets/icons/app_icon.png"