chore: Initial commit
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPovRHpejR4zRxuhjY4Qs3
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a reproducible bug in MeshCore SAR App
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: [bug]
|
||||||
|
assignees: []
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
A short description of the problem.
|
||||||
|
|
||||||
|
## Reported context
|
||||||
|
Where this was observed (build/version, environment, device model, logs, links).
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
## Expected behavior
|
||||||
|
What should happen.
|
||||||
|
|
||||||
|
## Actual behavior
|
||||||
|
What actually happens.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
Why this matters (user impact, risk, severity).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- [ ] Repro case is fixed.
|
||||||
|
- [ ] Behavior matches expected result.
|
||||||
|
- [ ] Regression coverage is added/updated.
|
||||||
434
.github/workflows/build-artifacts.yml
vendored
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
name: Build And Upload Release Assets
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
FLUTTER_VERSION: "3.41.4"
|
||||||
|
APP_NAME: meshcore-sar
|
||||||
|
ANDROID_NDK_VERSION: "27.0.12077973"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-android:
|
||||||
|
name: Build Android APK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "17"
|
||||||
|
|
||||||
|
- name: Setup Gradle cache
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Setup Ruby
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: "3.3"
|
||||||
|
bundler-cache: true
|
||||||
|
working-directory: android
|
||||||
|
|
||||||
|
- name: Cache Android NDK
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
/usr/local/lib/android/sdk/ndk/${{ env.ANDROID_NDK_VERSION }}
|
||||||
|
/usr/local/lib/android/sdk/licenses
|
||||||
|
key: ${{ runner.os }}-android-ndk-${{ env.ANDROID_NDK_VERSION }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Validate Android release secrets
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
env:
|
||||||
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
|
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
|
||||||
|
run: |
|
||||||
|
test -n "$ANDROID_KEYSTORE_BASE64"
|
||||||
|
test -n "$ANDROID_KEYSTORE_PASSWORD"
|
||||||
|
test -n "$ANDROID_KEY_ALIAS"
|
||||||
|
test -n "$ANDROID_KEY_PASSWORD"
|
||||||
|
test -n "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON"
|
||||||
|
|
||||||
|
- name: Create Android signing files
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
env:
|
||||||
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/release-keystore.jks
|
||||||
|
cat <<EOF > android/key.properties
|
||||||
|
storePassword=$ANDROID_KEYSTORE_PASSWORD
|
||||||
|
keyPassword=$ANDROID_KEY_PASSWORD
|
||||||
|
keyAlias=$ANDROID_KEY_ALIAS
|
||||||
|
storeFile=../release-keystore.jks
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Build APK
|
||||||
|
run: flutter build apk --release
|
||||||
|
|
||||||
|
- name: Build Play release AAB
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
run: flutter build appbundle --release
|
||||||
|
|
||||||
|
- name: Prepare Android artifacts
|
||||||
|
run: |
|
||||||
|
RAW_TAG="${{ github.event.release.tag_name || github.ref_name }}"
|
||||||
|
TAG="${RAW_TAG//\//-}"
|
||||||
|
cp build/app/outputs/flutter-apk/app-release.apk "${APP_NAME}-${TAG}-android.apk"
|
||||||
|
if [ -f build/app/outputs/bundle/release/app-release.aab ]; then
|
||||||
|
cp build/app/outputs/bundle/release/app-release.aab "${APP_NAME}-${TAG}-play.aab"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Android artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-android
|
||||||
|
path: |
|
||||||
|
${{ env.APP_NAME }}-*-android.apk
|
||||||
|
${{ env.APP_NAME }}-*-play.aab
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Play release to internal testing
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
env:
|
||||||
|
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
|
||||||
|
SKIP_ANDROID_BUILD: "1"
|
||||||
|
run: cd android && bundle exec fastlane android internal
|
||||||
|
|
||||||
|
- name: Upload Play release to production
|
||||||
|
if: github.event_name == 'release' && !github.event.release.prerelease
|
||||||
|
env:
|
||||||
|
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
|
||||||
|
SKIP_ANDROID_BUILD: "1"
|
||||||
|
run: cd android && bundle exec fastlane android production
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
name: Build Linux App
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- 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 \
|
||||||
|
libgstreamer1.0-dev \
|
||||||
|
libgstreamer-plugins-base1.0-dev
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Enable Linux desktop
|
||||||
|
run: flutter config --enable-linux-desktop
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Build Linux release
|
||||||
|
run: flutter build linux --release
|
||||||
|
|
||||||
|
- name: Package Linux artifact
|
||||||
|
run: |
|
||||||
|
RAW_TAG="${{ github.event.release.tag_name || github.ref_name }}"
|
||||||
|
TAG="${RAW_TAG//\//-}"
|
||||||
|
tar -C build/linux/x64/release -czf "${APP_NAME}-${TAG}-linux.tar.gz" bundle
|
||||||
|
|
||||||
|
- name: Upload Linux artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-linux
|
||||||
|
path: "${{ env.APP_NAME }}-*-linux.tar.gz"
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
name: Build Windows App
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Enable Windows desktop
|
||||||
|
shell: pwsh
|
||||||
|
run: flutter config --enable-windows-desktop
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
shell: pwsh
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Build Windows release
|
||||||
|
shell: pwsh
|
||||||
|
run: flutter build windows --release
|
||||||
|
|
||||||
|
- name: Package Windows artifact
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$RawTag = "${{ github.event.release.tag_name || github.ref_name }}"
|
||||||
|
$Tag = $RawTag -replace '/', '-'
|
||||||
|
$Output = "${{ env.APP_NAME }}-$Tag-windows.zip"
|
||||||
|
Compress-Archive -Path "build/windows/x64/runner/Release/*" -DestinationPath $Output
|
||||||
|
|
||||||
|
- name: Upload Windows artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-windows
|
||||||
|
path: "${{ env.APP_NAME }}-*-windows.zip"
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-macos:
|
||||||
|
name: Build macOS DMG
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Enable macOS desktop
|
||||||
|
run: flutter config --enable-macos-desktop
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Install macOS native build tools
|
||||||
|
run: brew install automake libtool
|
||||||
|
|
||||||
|
- name: Build macOS release
|
||||||
|
run: flutter build macos --release
|
||||||
|
|
||||||
|
- name: Create DMG
|
||||||
|
run: |
|
||||||
|
RAW_TAG="${{ github.event.release.tag_name || github.ref_name }}"
|
||||||
|
TAG="${RAW_TAG//\//-}"
|
||||||
|
APP_PATH=$(find build/macos/Build/Products/Release -maxdepth 1 -name "*.app" -print -quit)
|
||||||
|
if [ -z "$APP_PATH" ]; then
|
||||||
|
echo "No .app bundle found in build output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
hdiutil create -volname "MeshCore SAR" -srcfolder "$APP_PATH" -ov -format UDZO "${APP_NAME}-${TAG}-macos.dmg"
|
||||||
|
|
||||||
|
- name: Upload macOS artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-macos
|
||||||
|
path: "${{ env.APP_NAME }}-*-macos.dmg"
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-ios:
|
||||||
|
name: Build iOS Archive (Manual Signing)
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Build iOS app (no codesign)
|
||||||
|
run: flutter build ios --release --no-codesign
|
||||||
|
|
||||||
|
- name: Package iOS unsigned artifacts
|
||||||
|
run: |
|
||||||
|
RAW_TAG="${{ github.event.release.tag_name || github.ref_name }}"
|
||||||
|
TAG="${RAW_TAG//\//-}"
|
||||||
|
APP_PATH="build/ios/iphoneos/Runner.app"
|
||||||
|
IPA_NAME="${APP_NAME}-${TAG}-ios-unsigned.ipa"
|
||||||
|
RUNNER_ZIP_NAME="${APP_NAME}-${TAG}-ios-runner-app.zip"
|
||||||
|
if [ ! -d "$APP_PATH" ]; then
|
||||||
|
echo "Runner.app not found at $APP_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p ios_payload/Payload
|
||||||
|
cp -R "$APP_PATH" ios_payload/Payload/
|
||||||
|
(
|
||||||
|
cd ios_payload
|
||||||
|
zip -qry "../${IPA_NAME}" Payload
|
||||||
|
)
|
||||||
|
rm -rf ios_payload
|
||||||
|
|
||||||
|
zip -qry "${RUNNER_ZIP_NAME}" "$APP_PATH"
|
||||||
|
|
||||||
|
echo "IOS_IPA=${IPA_NAME}" >> "$GITHUB_ENV"
|
||||||
|
echo "IOS_RUNNER_ZIP=${RUNNER_ZIP_NAME}" >> "$GITHUB_ENV"
|
||||||
|
ls -lah "${IPA_NAME}" "${RUNNER_ZIP_NAME}"
|
||||||
|
|
||||||
|
- name: Upload iOS artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-ios
|
||||||
|
path: |
|
||||||
|
${{ env.IOS_IPA }}
|
||||||
|
${{ env.IOS_RUNNER_ZIP }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-web:
|
||||||
|
name: Build Web App
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Enable web
|
||||||
|
run: flutter config --enable-web
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Build web release
|
||||||
|
run: flutter build web --wasm --release --base-href /meshcore-sar/
|
||||||
|
|
||||||
|
- name: Upload pages artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: build/web
|
||||||
|
|
||||||
|
deploy-pages:
|
||||||
|
name: Deploy to GitHub Pages
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-web
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
upload-release-assets:
|
||||||
|
name: Upload Assets To Release
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build-android
|
||||||
|
- build-linux
|
||||||
|
- build-windows
|
||||||
|
- build-macos
|
||||||
|
- build-ios
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download all build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: release-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Upload assets to published release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.event.release.tag_name || github.ref_name }}
|
||||||
|
body: |
|
||||||
|
Release assets are published for all supported build targets:
|
||||||
|
- Android (`.apk`)
|
||||||
|
- Google Play (`.aab`)
|
||||||
|
- Linux (`.tar.gz`)
|
||||||
|
- macOS (`.dmg`)
|
||||||
|
- Windows (`.zip`)
|
||||||
|
- iOS unsigned (`-ios-unsigned.ipa`, `-ios-runner-app.zip`)
|
||||||
|
|
||||||
|
Windows status: currently unusable. The BLE stack on Windows is broken, so BLE features do not work.
|
||||||
|
|
||||||
|
iOS manual signing and install:
|
||||||
|
1. Download `*-ios-unsigned.ipa` (or `*-ios-runner-app.zip`) from this release.
|
||||||
|
2. Re-sign it with your Apple Development/Distribution certificate and provisioning profile.
|
||||||
|
3. If you used `*-ios-runner-app.zip`, create a signed `.ipa` from `Runner.app` during signing.
|
||||||
|
4. Install the signed `.ipa` on your iPhone using Apple Configurator 2 or Xcode (Devices and Simulators).
|
||||||
|
files: |
|
||||||
|
dist/*.apk
|
||||||
|
dist/*.aab
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/*.zip
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*-ios-unsigned.ipa
|
||||||
|
dist/*-ios-runner-app.zip
|
||||||
|
fail_on_unmatched_files: true
|
||||||
41
.github/workflows/build-multiplatform.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Flutter Analyze
|
||||||
|
|
||||||
|
# This workflow runs flutter analyze to check for code issues.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
FLUTTER_VERSION: '3.41.4'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Flutter Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: 'stable'
|
||||||
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Generate localizations
|
||||||
|
run: flutter gen-l10n
|
||||||
|
|
||||||
|
- name: Run flutter analyze
|
||||||
|
run: flutter analyze
|
||||||
99
.gitignore
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
pubspec_overrides.yaml
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
/coverage/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
|
||||||
|
# Android signing
|
||||||
|
android/key.properties
|
||||||
|
android/app/*.keystore
|
||||||
|
android/app/*.jks
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
*.zip
|
||||||
|
*.ipa
|
||||||
|
ios/Runner.app.dSYM.zip
|
||||||
|
ios/Runner.ipa
|
||||||
|
ios/build/ios/XCBuildData/PIFCache
|
||||||
|
|
||||||
|
# iOS signing & certificates
|
||||||
|
*.p8
|
||||||
|
*.p12
|
||||||
|
*.cer
|
||||||
|
*.mobileprovision
|
||||||
|
*.certSigningRequest
|
||||||
|
|
||||||
|
# Firebase / Google services (contain API keys)
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Fastlane credentials — store credentials in CI env vars, not files
|
||||||
|
**/fastlane/Appfile
|
||||||
|
**/fastlane/.env
|
||||||
|
**/fastlane/.env.*
|
||||||
|
fastlane/README.md
|
||||||
|
ios/.bundle/
|
||||||
|
ios/vendor/
|
||||||
|
|
||||||
|
# Play Store release assets & scripts
|
||||||
|
play-store/
|
||||||
|
create_feature_graphic.py
|
||||||
|
|
||||||
|
# Tool caches and local state
|
||||||
|
.devenv/
|
||||||
|
.cachebro/
|
||||||
|
.osgrep/
|
||||||
|
third_party/lpcnet_flutter/
|
||||||
|
worker/node_modules/
|
||||||
|
worker/.wrangler/
|
||||||
|
worker/.bun/
|
||||||
|
worker/.astro/
|
||||||
|
worker/dist/
|
||||||
|
|
||||||
|
# Claude Code local settings (permissions, personal config)
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# Dart code coverage
|
||||||
|
coverage/
|
||||||
|
lcov.info
|
||||||
45
.metadata
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "9f455d2486bcb28cad87b062475f42edc959f636"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
- platform: android
|
||||||
|
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
- platform: web
|
||||||
|
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
- platform: windows
|
||||||
|
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
BIN
AppIcon.icon/Assets/icon.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
38
AppIcon.icon/icon.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"fill" : {
|
||||||
|
"automatic-gradient" : "extended-srgb:0.00000,0.53333,1.00000,1.00000"
|
||||||
|
},
|
||||||
|
"groups" : [
|
||||||
|
{
|
||||||
|
"layers" : [
|
||||||
|
{
|
||||||
|
"blend-mode" : "soft-light",
|
||||||
|
"glass" : true,
|
||||||
|
"image-name" : "icon.png",
|
||||||
|
"name" : "icon",
|
||||||
|
"position" : {
|
||||||
|
"scale" : 1.4,
|
||||||
|
"translation-in-points" : [
|
||||||
|
2.625,
|
||||||
|
5.3203125
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shadow" : {
|
||||||
|
"kind" : "neutral",
|
||||||
|
"opacity" : 0.5
|
||||||
|
},
|
||||||
|
"translucency" : {
|
||||||
|
"enabled" : true,
|
||||||
|
"value" : 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"supported-platforms" : {
|
||||||
|
"circles" : [
|
||||||
|
"watchOS"
|
||||||
|
],
|
||||||
|
"squares" : "shared"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
CHANGELOG.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-15 to 2026-03-22
|
||||||
|
|
||||||
|
- Device connectivity and routing: added Android USB serial companion support, serial compass support, GPS module controls, per-contact path size selection, and favourites support via firmware flags.
|
||||||
|
- Location and map intelligence: added RSSI-based repeater trilateration and contact location estimates, isolated MET history, matched the official advert import/discovery flow, and refreshed map presentation by removing contact trails.
|
||||||
|
- Telemetry and device info: surfaced self telemetry and sensor defaults, opened device info from signal and battery taps, improved signal chip behavior, fixed duplicate telemetry delivery, and restored telemetry refresh behavior.
|
||||||
|
- Contacts and messaging: added meshcore:// contact sharing and repeater neighbour views, supported MeshCore message links, fixed DM retry behavior, corrected room poster naming, and compacted the recipient selector.
|
||||||
|
- UI polish and platform upkeep: improved device settings layout, tightened sensor and contact presentation, expanded localization coverage, and refreshed platform dependencies and build numbers.
|
||||||
|
|
||||||
|
## Key PRs
|
||||||
|
|
||||||
|
- [#26](https://github.com/dz0ny/meshcore-sar/pull/26) Merge pull request #26 from `dz0ny/feat/sensor-telemetry-preview`
|
||||||
|
|
||||||
|
## 2026-03-08 to 2026-03-14
|
||||||
|
|
||||||
|
- Discovery and contacts: finished the discovery flow, removed artificial UI delays, added profile storage and sync, and allowed contact name overrides.
|
||||||
|
- Messaging and channels: fixed channel sync, deduplication, overwrite and deletion issues, cleared message input after send, and added delayed-message warning tracking.
|
||||||
|
- Voice and transport: improved voice playback quality, improved scan and advert parsing, and kept route path data visible in logs.
|
||||||
|
- Maps and field operations: enabled iOS background GPS updates, added GPX trail import/export, introduced a fullscreen repeater map, and added live mesh traffic views.
|
||||||
|
- Localization and UI polish: added multilingual localization coverage, updated locale strings, fixed onboarding layout issues, and refined channel and contacts presentation.
|
||||||
|
|
||||||
|
## Key PRs
|
||||||
|
|
||||||
|
- [#16](https://github.com/dz0ny/meshcore-sar/pull/16) Merge pull request #16 from `MGJ520/main`
|
||||||
144
Makefile
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# MeshCore SAR Makefile
|
||||||
|
# Version format: YYYY.MMDD.N+build (Flutter-compatible semver with date)
|
||||||
|
# Example: 2025.1205.1+1
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
APP_NAME := meshcore-sar
|
||||||
|
PUBSPEC := pubspec.yaml
|
||||||
|
BUILD_DIR := build/app/outputs/flutter-apk
|
||||||
|
|
||||||
|
# Get current date parts
|
||||||
|
YEAR := $(shell date +%Y)
|
||||||
|
MMDD := $(shell date +%m%d)
|
||||||
|
|
||||||
|
# Get current version info from pubspec.yaml
|
||||||
|
CURRENT_VERSION := $(shell grep '^version:' $(PUBSPEC) | sed 's/version: //')
|
||||||
|
CURRENT_BUILD_NUMBER := $(shell echo $(CURRENT_VERSION) | cut -d'+' -f2)
|
||||||
|
|
||||||
|
# Extract current MMDD from version (middle part)
|
||||||
|
CURRENT_MMDD := $(shell echo $(CURRENT_VERSION) | cut -d'.' -f2)
|
||||||
|
|
||||||
|
# Calculate new build number
|
||||||
|
# IMPORTANT: Build number (version code) must ALWAYS increment for Android updates
|
||||||
|
# Never reset to 1, even on date changes
|
||||||
|
NEW_BUILD_NUMBER := $(shell echo $$(($(CURRENT_BUILD_NUMBER) + 1)))
|
||||||
|
|
||||||
|
# Calculate daily sequence number (resets each day for version name readability)
|
||||||
|
DAILY_BUILD := $(shell \
|
||||||
|
if [ "$(CURRENT_MMDD)" = "$(MMDD)" ]; then \
|
||||||
|
echo $(CURRENT_VERSION) | cut -d'.' -f3 | cut -d'+' -f1 | awk '{print $$1 + 1}'; \
|
||||||
|
else \
|
||||||
|
echo 1; \
|
||||||
|
fi)
|
||||||
|
|
||||||
|
# Version format: YYYY.MMDD.DAILY+BUILD
|
||||||
|
# DAILY resets each day (for readability), BUILD always increments (for Android)
|
||||||
|
NEW_VERSION := $(YEAR).$(MMDD).$(DAILY_BUILD)+$(NEW_BUILD_NUMBER)
|
||||||
|
NEW_BUILD_NAME := $(shell echo $(NEW_VERSION) | cut -d'+' -f1)
|
||||||
|
|
||||||
|
.PHONY: help version bump make-bump sync-ios-version build release release-android release-ios clean deps analyze test icon bundle bundle-no-bump
|
||||||
|
|
||||||
|
help: ## Show this help
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
version: ## Show current and next version
|
||||||
|
@echo "Current version: $(CURRENT_VERSION)"
|
||||||
|
@echo "Current build: $(CURRENT_BUILD_NUMBER)"
|
||||||
|
@echo "---"
|
||||||
|
@echo "Next version: $(NEW_VERSION)"
|
||||||
|
|
||||||
|
bump: ## Bump version in pubspec.yaml
|
||||||
|
@echo "Bumping version to $(NEW_VERSION)..."
|
||||||
|
@sed -i '' 's/^version: .*/version: $(NEW_VERSION)/' $(PUBSPEC)
|
||||||
|
@$(MAKE) sync-ios-version BUILD_NAME=$(NEW_BUILD_NAME) BUILD_NUMBER=$(NEW_BUILD_NUMBER)
|
||||||
|
@echo "Version bumped to $(NEW_VERSION)"
|
||||||
|
|
||||||
|
make-bump: bump ## Alias for bump
|
||||||
|
|
||||||
|
sync-ios-version: ## Sync iOS FLUTTER_BUILD_NAME/NUMBER from pubspec or provided BUILD_NAME/BUILD_NUMBER
|
||||||
|
@$(eval IOS_VERSION := $(shell grep '^version:' $(PUBSPEC) | sed 's/version: //'))
|
||||||
|
@$(eval IOS_BUILD_NAME := $(if $(BUILD_NAME),$(BUILD_NAME),$(shell echo $(IOS_VERSION) | cut -d'+' -f1)))
|
||||||
|
@$(eval IOS_BUILD_NUMBER := $(if $(BUILD_NUMBER),$(BUILD_NUMBER),$(shell echo $(IOS_VERSION) | cut -d'+' -f2)))
|
||||||
|
@echo "Syncing iOS version to $(IOS_BUILD_NAME)+$(IOS_BUILD_NUMBER)..."
|
||||||
|
@flutter build ios --config-only --build-name "$(IOS_BUILD_NAME)" --build-number "$(IOS_BUILD_NUMBER)" > /dev/null
|
||||||
|
@echo "iOS version synced"
|
||||||
|
|
||||||
|
deps: ## Install dependencies
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
analyze: ## Run Flutter analyze
|
||||||
|
flutter analyze
|
||||||
|
|
||||||
|
test: ## Run tests
|
||||||
|
flutter test
|
||||||
|
|
||||||
|
icon: ## Generate app icons from icon.png
|
||||||
|
dart run flutter_launcher_icons
|
||||||
|
|
||||||
|
build: bump ## Build release APK (auto-bumps version)
|
||||||
|
@echo "Building release APK..."
|
||||||
|
flutter build apk --release
|
||||||
|
@echo "APK built: $(BUILD_DIR)/app-release.apk"
|
||||||
|
@ls -lh $(BUILD_DIR)/app-release.apk
|
||||||
|
|
||||||
|
build-no-bump: ## Build release APK without bumping version
|
||||||
|
flutter build apk --release
|
||||||
|
@ls -lh $(BUILD_DIR)/app-release.apk
|
||||||
|
|
||||||
|
bundle: bump ## Build release App Bundle (auto-bumps version)
|
||||||
|
@echo "Building release App Bundle..."
|
||||||
|
flutter build appbundle --release
|
||||||
|
@echo "App Bundle built: build/app/outputs/bundle/release/app-release.aab"
|
||||||
|
@ls -lh build/app/outputs/bundle/release/app-release.aab
|
||||||
|
|
||||||
|
bundle-no-bump: ## Build release App Bundle without bumping version
|
||||||
|
flutter build appbundle --release
|
||||||
|
@ls -lh build/app/outputs/bundle/release/app-release.aab
|
||||||
|
|
||||||
|
release: build release-ios ## Build APK, create GitHub release, and upload iOS to TestFlight
|
||||||
|
$(eval VERSION := $(shell grep '^version:' $(PUBSPEC) | sed 's/version: //' | cut -d'+' -f1))
|
||||||
|
@echo "Creating GitHub release v$(VERSION)..."
|
||||||
|
@if ! command -v gh &> /dev/null; then \
|
||||||
|
echo "Error: GitHub CLI (gh) not installed. Install with: brew install gh"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@cp $(BUILD_DIR)/app-release.apk $(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk
|
||||||
|
gh release create "v$(VERSION)" \
|
||||||
|
"$(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk#MeshCore SAR $(VERSION) APK" \
|
||||||
|
--title "MeshCore SAR v$(VERSION)" \
|
||||||
|
--notes "Release $(VERSION)" \
|
||||||
|
--latest
|
||||||
|
@echo "Release v$(VERSION) created!"
|
||||||
|
|
||||||
|
release-android: build ## Build APK and create GitHub release (Android only)
|
||||||
|
$(eval VERSION := $(shell grep '^version:' $(PUBSPEC) | sed 's/version: //' | cut -d'+' -f1))
|
||||||
|
@echo "Creating GitHub release v$(VERSION)..."
|
||||||
|
@if ! command -v gh &> /dev/null; then \
|
||||||
|
echo "Error: GitHub CLI (gh) not installed. Install with: brew install gh"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@cp $(BUILD_DIR)/app-release.apk $(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk
|
||||||
|
gh release create "v$(VERSION)" \
|
||||||
|
"$(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk#MeshCore SAR $(VERSION) APK" \
|
||||||
|
--title "MeshCore SAR v$(VERSION)" \
|
||||||
|
--notes "Release $(VERSION)" \
|
||||||
|
--latest
|
||||||
|
@echo "Release v$(VERSION) created!"
|
||||||
|
|
||||||
|
release-ios: ## Build iOS and upload to TestFlight
|
||||||
|
@echo "Building iOS and uploading to TestFlight..."
|
||||||
|
@$(MAKE) sync-ios-version
|
||||||
|
cd ios && fastlane release
|
||||||
|
@echo "iOS release uploaded!"
|
||||||
|
|
||||||
|
clean: ## Clean build artifacts
|
||||||
|
flutter clean
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
|
||||||
|
build-web: ## Build web with WebAssembly
|
||||||
|
flutter build web --wasm --release
|
||||||
|
|
||||||
|
# Build for all platforms
|
||||||
|
build-all: bump ## Build for Android and iOS
|
||||||
|
flutter build apk --release
|
||||||
|
flutter build ios --release --no-codesign
|
||||||
101
README.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="./icon.png" alt="MeshCore SAR icon" width="120" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">MeshCore SAR</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Field-ready Search and Rescue coordination over <a href="https://github.com/meshcore-dev">MeshCore</a> + BLE
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img alt="Platform" src="https://img.shields.io/badge/platform-iOS%20%7C%20Android-blue" />
|
||||||
|
<img alt="Mode" src="https://img.shields.io/badge/offline-first-success" />
|
||||||
|
<img alt="Use case" src="https://img.shields.io/badge/use%20case-SAR-orange" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
MeshCore SAR helps teams coordinate in low-connectivity or no-connectivity environments with messaging, voice, images, maps, and live location context in one app.
|
||||||
|
It uses the MeshCore protocol over LoRa for long-range, infrastructure-free communication.
|
||||||
|
`iOS TestFlight:` https://testflight.apple.com/join/tngpPF12
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
- Rapid mesh chat for both 1:1 and group coordination
|
||||||
|
- On-demand voice (Codec2) and image (AVIF) transfer tuned for low-bandwidth links
|
||||||
|
- Offline-first mapping with tactical overlays and SAR incident markers
|
||||||
|
- Live team location, movement trails, and shareable tactical drawings
|
||||||
|
|
||||||
|
## Demo Video
|
||||||
|
|
||||||
|
[<img width="1280" height="720" alt="MeshCore SAR demo preview" src="https://github.com/user-attachments/assets/13ccacee-7306-4976-a408-f31f3336828a" />](https://youtu.be/rLsKeLJBpFg)
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="./screenshots/ios/IMG_2953.PNG" alt="Map view with live team context" width="19%" />
|
||||||
|
<img src="./screenshots/ios/IMG_2954.PNG" alt="Compass and navigation view" width="19%" />
|
||||||
|
<img src="./screenshots/ios/IMG_2955.PNG" alt="Contacts and repeaters list" width="19%" />
|
||||||
|
<img src="./screenshots/ios/IMG_2956.PNG" alt="Messages with voice and image cards" width="19%" />
|
||||||
|
<img src="./screenshots/ios/IMG_2957.PNG" alt="Quick actions sheet for SAR, voice, and image" width="19%" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Feature Overview
|
||||||
|
|
||||||
|
| Area | What you get |
|
||||||
|
|---|---|
|
||||||
|
| Messaging | Direct and group chat over mesh, with contact/room awareness from live telemetry |
|
||||||
|
| Voice | Push-to-talk voice clips (Codec2), fetched on demand when play is pressed, auto-play on completion |
|
||||||
|
| Images | Camera/gallery image sending (AVIF), auto-compression, tap-to-load receiving, full-screen viewer |
|
||||||
|
| Maps | Street/topo/satellite/terrain layers, offline tile downloads, optional MBTiles import |
|
||||||
|
| SAR Operations | Team markers with freshness indicators, SAR markers for incidents and staging points |
|
||||||
|
| Tracking | Continuous GPS updates, personal trails, distance/duration trail stats |
|
||||||
|
| Trail Interop | GPX export/import for trail sharing and reuse |
|
||||||
|
| Tactical Drawing | Line/rectangle drawing, distance measurement, drawing sharing to channel/room |
|
||||||
|
|
||||||
|
## Voice, Image, and Maps
|
||||||
|
|
||||||
|
### Voice
|
||||||
|
- Built for short, urgent field communication
|
||||||
|
- On-demand playback fetch reduces unnecessary mesh traffic
|
||||||
|
- Ideal when typing is impractical during active operations
|
||||||
|
|
||||||
|
### Images
|
||||||
|
- Pre-send optimization for constrained links
|
||||||
|
- Tap-to-load receiving keeps channels lightweight
|
||||||
|
- Full-screen view supports rapid field verification
|
||||||
|
|
||||||
|
### Maps
|
||||||
|
- Works in both online and offline workflows
|
||||||
|
- SAR context with team markers, incident markers, and orientation tools
|
||||||
|
- Combines location, messaging, and tactical layers in one place
|
||||||
|
|
||||||
|
## Built For
|
||||||
|
|
||||||
|
- Search and Rescue field teams
|
||||||
|
- Incident command and coordination roles
|
||||||
|
- Operators working in weak/no cellular coverage
|
||||||
|
|
||||||
|
## Connecting Your Radio (BLE)
|
||||||
|
|
||||||
|
The app pairs with your MeshCore radio over Bluetooth Low Energy.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
On Windows 11 (tested on 25H2), pair the radio through the system Bluetooth menu **before** connecting in the app:
|
||||||
|
|
||||||
|
1. Open **Settings → Bluetooth & devices → Add device → Bluetooth**.
|
||||||
|
2. Select your MeshCore radio and complete pairing, entering the **PIN** when prompted.
|
||||||
|
3. Launch MeshCore SAR and connect — the radio now appears as a paired device.
|
||||||
|
|
||||||
|
If the radio does not show up or fails to connect in-app, remove it from the Windows Bluetooth menu and repeat the pairing step.
|
||||||
|
|
||||||
|
### iOS / Android
|
||||||
|
|
||||||
|
Connect directly from within the app — no separate system pairing step is required.
|
||||||
|
|
||||||
|
## Permissions (App Use)
|
||||||
|
|
||||||
|
- Bluetooth: mesh device communication
|
||||||
|
- Location: team tracking and map position updates
|
||||||
|
- Microphone: voice clip recording
|
||||||
|
- Camera / Photos: image messaging
|
||||||
28
analysis_options.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// 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
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
14
android/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
4
android/Gemfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "fastlane"
|
||||||
|
gem "ostruct"
|
||||||
99
android/app/build.gradle.kts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import java.util.Properties
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("kotlin-android")
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load keystore properties from key.properties file
|
||||||
|
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||||
|
val keystoreProperties = Properties()
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveSigningValue(propertyKey: String, envKey: String): String? {
|
||||||
|
val envValue = System.getenv(envKey)?.takeIf { it.isNotBlank() }
|
||||||
|
if (envValue != null) {
|
||||||
|
return envValue
|
||||||
|
}
|
||||||
|
return (keystoreProperties[propertyKey] as? String)?.takeIf { it.isNotBlank() }
|
||||||
|
}
|
||||||
|
|
||||||
|
val releaseKeyAlias = resolveSigningValue("keyAlias", "ANDROID_KEY_ALIAS")
|
||||||
|
val releaseKeyPassword = resolveSigningValue("keyPassword", "ANDROID_KEY_PASSWORD")
|
||||||
|
val releaseStorePassword = resolveSigningValue("storePassword", "ANDROID_STORE_PASSWORD")
|
||||||
|
val releaseStoreFilePath = resolveSigningValue("storeFile", "ANDROID_STORE_FILE")
|
||||||
|
val hasReleaseSigning =
|
||||||
|
releaseKeyAlias != null &&
|
||||||
|
releaseKeyPassword != null &&
|
||||||
|
releaseStorePassword != null &&
|
||||||
|
releaseStoreFilePath != null
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.meshcore.sar.meshcore_sar_app"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
// Enable BuildConfig generation
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.meshcore.sar.meshcore_sar_app"
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
|
||||||
|
// Inject commit hash from environment variable (set by GitHub Actions)
|
||||||
|
// Falls back to "dev" for local development builds
|
||||||
|
val commitHash = System.getenv("COMMIT_HASH") ?: "dev"
|
||||||
|
buildConfigField("String", "COMMIT_HASH", "\"$commitHash\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (hasReleaseSigning) {
|
||||||
|
create("release") {
|
||||||
|
keyAlias = releaseKeyAlias
|
||||||
|
keyPassword = releaseKeyPassword
|
||||||
|
storeFile = file(releaseStoreFilePath!!)
|
||||||
|
storePassword = releaseStorePassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig =
|
||||||
|
if (hasReleaseSigning) {
|
||||||
|
signingConfigs.getByName("release")
|
||||||
|
} else {
|
||||||
|
// Keep CI release builds working even when private keystore isn't provided.
|
||||||
|
signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||||
|
}
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
84
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Bluetooth permissions -->
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
|
||||||
|
<!-- Location permissions -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
|
||||||
|
<!-- Foreground service for background location -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
|
||||||
|
<!-- Internet for map tiles -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<!-- Network Service Discovery (Bonjour/mDNS) -->
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||||
|
|
||||||
|
<!-- Microphone for voice messages -->
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:label="MeshCore SAR"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/launcher_icon">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
<service
|
||||||
|
android:name="id.flutter.flutter_background_service.BackgroundService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="location"
|
||||||
|
android:stopWithTask="false"
|
||||||
|
tools:replace="android:exported" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.meshcore.sar.meshcore_sar_app
|
||||||
|
|
||||||
|
import io.flutter.plugin.common.MethodCall
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform channel for exposing build information to Flutter
|
||||||
|
* Provides access to BuildConfig values that are injected at build time
|
||||||
|
*/
|
||||||
|
class BuildInfoChannel : MethodCallHandler {
|
||||||
|
companion object {
|
||||||
|
const val CHANNEL_NAME = "com.meshcore.sar/build_info"
|
||||||
|
const val METHOD_GET_COMMIT_HASH = "getCommitHash"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||||
|
when (call.method) {
|
||||||
|
METHOD_GET_COMMIT_HASH -> {
|
||||||
|
try {
|
||||||
|
// Get commit hash from BuildConfig
|
||||||
|
// This value is injected by gradle at build time
|
||||||
|
val commitHash = BuildConfig.COMMIT_HASH
|
||||||
|
result.success(commitHash)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("BUILD_INFO_ERROR", "Failed to get commit hash: ${e.message}", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.meshcore.sar.meshcore_sar_app
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity() {
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
|
// Register BuildInfoChannel to expose build information to Flutter
|
||||||
|
MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger,
|
||||||
|
BuildInfoChannel.CHANNEL_NAME
|
||||||
|
).setMethodCallHandler(BuildInfoChannel())
|
||||||
|
}
|
||||||
|
}
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-hdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-mdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
22
android/app/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">@android:color/white</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
22
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">@android:color/white</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
42
android/build.gradle.kts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newBuildDir: Directory =
|
||||||
|
rootProject.layout.buildDirectory
|
||||||
|
.dir("../../build")
|
||||||
|
.get()
|
||||||
|
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
// flutter_avif_android 3.1.0 (latest) ships duplicate Java and Kotlin
|
||||||
|
// copies of FlutterAvifPlugin. Kotlin 2.2+ rejects the redeclaration. The
|
||||||
|
// plugin's Java source dir contains only this redundant copy, so empty it
|
||||||
|
// and let the identical Kotlin implementation compile. kotlinc reads the
|
||||||
|
// module's Java source roots for joint compilation, so clearing srcDirs
|
||||||
|
// (rather than an exclude filter, which AGP doesn't forward to kotlinc) is
|
||||||
|
// what actually removes the duplicate from its view. Registered before
|
||||||
|
// evaluationDependsOn so it also covers :app, which that call evaluates
|
||||||
|
// eagerly.
|
||||||
|
afterEvaluate {
|
||||||
|
if (project.name == "flutter_avif_android") {
|
||||||
|
extensions.findByName("android")?.let { ext ->
|
||||||
|
(ext as com.android.build.gradle.BaseExtension)
|
||||||
|
.sourceSets.getByName("main").java
|
||||||
|
.setSrcDirs(emptyList<String>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
87
android/fastlane/Fastfile
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# This file contains the fastlane.tools configuration
|
||||||
|
# You can find the documentation at https://docs.fastlane.tools
|
||||||
|
#
|
||||||
|
# For a list of all available actions, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/actions
|
||||||
|
#
|
||||||
|
# For a list of all available plugins, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/plugins/available-plugins
|
||||||
|
#
|
||||||
|
|
||||||
|
# Uncomment the line if you want fastlane to automatically update itself
|
||||||
|
# update_fastlane
|
||||||
|
|
||||||
|
default_platform(:android)
|
||||||
|
|
||||||
|
platform :android do
|
||||||
|
def project_root
|
||||||
|
File.expand_path("../..", __dir__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def play_service_account_json
|
||||||
|
ENV.fetch("GOOGLE_PLAY_SERVICE_ACCOUNT_JSON")
|
||||||
|
end
|
||||||
|
|
||||||
|
def play_build_command
|
||||||
|
"cd #{project_root} && flutter build appbundle --release"
|
||||||
|
end
|
||||||
|
|
||||||
|
def play_aab_path
|
||||||
|
File.join(project_root, "build/app/outputs/bundle/release/app-release.aab")
|
||||||
|
end
|
||||||
|
|
||||||
|
def play_package_name
|
||||||
|
"com.meshcore.sar.meshcore_sar_app"
|
||||||
|
end
|
||||||
|
|
||||||
|
def direct_apk_build_command
|
||||||
|
"cd #{project_root} && flutter build apk --release"
|
||||||
|
end
|
||||||
|
|
||||||
|
def direct_apk_path
|
||||||
|
File.join(project_root, "build/app/outputs/flutter-apk/app-release.apk")
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip_android_build?
|
||||||
|
ENV["SKIP_ANDROID_BUILD"] == "1"
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Runs all the tests"
|
||||||
|
lane :test do
|
||||||
|
gradle(task: "test")
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Build release APK"
|
||||||
|
lane :direct_apk do
|
||||||
|
sh(direct_apk_build_command)
|
||||||
|
|
||||||
|
UI.success("APK built successfully at: #{direct_apk_path}")
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Build Play release AAB and upload to internal testing"
|
||||||
|
lane :internal do
|
||||||
|
sh(play_build_command) unless skip_android_build?
|
||||||
|
|
||||||
|
upload_to_play_store(
|
||||||
|
aab: play_aab_path,
|
||||||
|
package_name: play_package_name,
|
||||||
|
track: "internal",
|
||||||
|
release_status: "draft",
|
||||||
|
json_key_data: play_service_account_json
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Build Play release AAB and upload to production"
|
||||||
|
lane :production do
|
||||||
|
sh(play_build_command) unless skip_android_build?
|
||||||
|
|
||||||
|
upload_to_play_store(
|
||||||
|
aab: play_aab_path,
|
||||||
|
package_name: play_package_name,
|
||||||
|
track: "production",
|
||||||
|
json_key_data: play_service_account_json
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
13
android/gradle.properties
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||||
|
android.builtInKotlin=false
|
||||||
|
# This newDsl flag was added automatically by Flutter migrator
|
||||||
|
android.newDsl=false
|
||||||
|
# Kotlin 2.2+ fails the build when a module's Java and Kotlin compile tasks
|
||||||
|
# target different JVM versions. Several Flutter plugins still pin mismatched
|
||||||
|
# targets (e.g. Java 1.8 vs Kotlin 1.8, or Java 11 vs Kotlin 17). On Android
|
||||||
|
# every class is dexed by D8/R8, so the class-file version mismatch is benign;
|
||||||
|
# downgrade the strict check to a warning so third-party plugins build.
|
||||||
|
kotlin.jvm.target.validation.mode=warning
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||||
26
android/settings.gradle.kts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
pluginManagement {
|
||||||
|
val flutterSdkPath =
|
||||||
|
run {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
|
id("com.android.application") version "8.11.1" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":app")
|
||||||
4
devtools_options.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
|
- provider: true
|
||||||
128
docs/google-play-release.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Google Play Release Runbook
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This runbook prepares and ships the Android release build for `com.meshcore.sar.meshcore_sar_app`.
|
||||||
|
|
||||||
|
## Distribution Modes
|
||||||
|
|
||||||
|
- GitHub release: signed APK for direct install or manual distribution.
|
||||||
|
- Google Play: signed AAB uploaded through Fastlane to Play internal testing and, for non-prerelease GitHub releases, production.
|
||||||
|
|
||||||
|
## Required Secrets and Local Files
|
||||||
|
|
||||||
|
GitHub Actions secrets for `dz0ny/meshcore-sar`:
|
||||||
|
|
||||||
|
- `ANDROID_KEYSTORE_BASE64`
|
||||||
|
- `ANDROID_KEYSTORE_PASSWORD`
|
||||||
|
- `ANDROID_KEY_ALIAS`
|
||||||
|
- `ANDROID_KEY_PASSWORD`
|
||||||
|
- `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON`
|
||||||
|
|
||||||
|
Local files used to seed the secrets:
|
||||||
|
|
||||||
|
- `android/key.properties`
|
||||||
|
- release keystore referenced by `android/key.properties`
|
||||||
|
- `/Users/dz0ny/android-keystores/fastlane-480919-0cb30c62db50.json`
|
||||||
|
|
||||||
|
The Play Console service account must have release access for `com.meshcore.sar.meshcore_sar_app`.
|
||||||
|
|
||||||
|
## CI Release Flow
|
||||||
|
|
||||||
|
The release flow runs from `.github/workflows/build-artifacts.yml` when a GitHub release is published.
|
||||||
|
|
||||||
|
Android release steps:
|
||||||
|
|
||||||
|
1. Validate all Android signing and Google Play secrets.
|
||||||
|
2. Recreate `android/release-keystore.jks` and `android/key.properties` from GitHub secrets.
|
||||||
|
3. Build the signed APK with `flutter build apk --release`.
|
||||||
|
4. Build the signed Play AAB with `flutter build appbundle --release`.
|
||||||
|
5. Upload the APK and AAB as GitHub release assets.
|
||||||
|
6. Upload the AAB to Play internal testing with `bundle exec fastlane android internal`.
|
||||||
|
7. Upload the AAB to Play production when the GitHub release is not marked as prerelease.
|
||||||
|
|
||||||
|
The same workflow also builds Linux, macOS, Windows, iOS unsigned, and web artifacts.
|
||||||
|
|
||||||
|
## Versioning Rules
|
||||||
|
|
||||||
|
- Version source of truth: `pubspec.yaml`.
|
||||||
|
- Android `versionCode` is the number after `+`.
|
||||||
|
- `versionCode` must always increase for Play uploads.
|
||||||
|
- `make bump`, `make build`, and `make bundle` increment the version.
|
||||||
|
- Use `make build-no-bump` or `make bundle-no-bump` only when intentionally rebuilding the same version locally.
|
||||||
|
|
||||||
|
## Manual Build Commands
|
||||||
|
|
||||||
|
Run commands from the repo root.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flutter build apk --release
|
||||||
|
flutter build appbundle --release
|
||||||
|
```
|
||||||
|
|
||||||
|
Repo shortcuts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
make bundle
|
||||||
|
make build-no-bump
|
||||||
|
make bundle-no-bump
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fastlane Lanes
|
||||||
|
|
||||||
|
Run commands from `android/`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bundle exec fastlane android direct_apk
|
||||||
|
bundle exec fastlane android internal
|
||||||
|
bundle exec fastlane android production
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `SKIP_ANDROID_BUILD=1` when an AAB has already been built and Fastlane should only upload it.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SKIP_ANDROID_BUILD=1 GOOGLE_PLAY_SERVICE_ACCOUNT_JSON="$(cat /Users/dz0ny/android-keystores/fastlane-480919-0cb30c62db50.json)" bundle exec fastlane android internal
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Artifacts
|
||||||
|
|
||||||
|
- APK: `build/app/outputs/flutter-apk/app-release.apk`
|
||||||
|
- AAB: `build/app/outputs/bundle/release/app-release.aab`
|
||||||
|
- Release APK asset: `meshcore-sar-<tag>-android.apk`
|
||||||
|
- Release AAB asset: `meshcore-sar-<tag>-play.aab`
|
||||||
|
|
||||||
|
## Internal Testing Release Checklist
|
||||||
|
|
||||||
|
1. Confirm `pubspec.yaml` version is correct and `versionCode` increased.
|
||||||
|
2. Confirm `android/key.properties` points to the release keystore for local builds.
|
||||||
|
3. Confirm all five GitHub Actions secrets exist.
|
||||||
|
4. Publish a prerelease in GitHub to build assets and upload Play internal testing without production promotion.
|
||||||
|
5. Install from the internal testing track on a real Android device.
|
||||||
|
6. Smoke test startup, permissions, map, messaging, telemetry, and offline map behavior.
|
||||||
|
|
||||||
|
## Production Release Checklist
|
||||||
|
|
||||||
|
1. Complete internal testing validation first.
|
||||||
|
2. Confirm Play Console forms are current:
|
||||||
|
- App content
|
||||||
|
- Data safety
|
||||||
|
- App access
|
||||||
|
- Ads declaration
|
||||||
|
- Content rating
|
||||||
|
3. Confirm store assets are current:
|
||||||
|
- app icon
|
||||||
|
- feature graphic
|
||||||
|
- phone screenshots
|
||||||
|
- tablet screenshots if used
|
||||||
|
- support URL
|
||||||
|
- privacy policy URL
|
||||||
|
4. Publish a non-prerelease GitHub release.
|
||||||
|
5. Confirm the GitHub release has the APK and AAB assets.
|
||||||
|
6. Confirm Play Console has the new internal and production release entries.
|
||||||
|
|
||||||
|
## Rollback Rules
|
||||||
|
|
||||||
|
- Never reuse or lower an Android `versionCode`.
|
||||||
|
- If a Play release is bad, halt rollout in Play Console and ship a new higher-version fix.
|
||||||
|
- Keep GitHub direct APK and Play AAB artifacts separate; do not upload APKs to Play.
|
||||||
324
docs/image-mode-technical.md
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
# Image Mode Technical Design
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
Image mode mirrors the voice on-demand architecture exactly, including
|
||||||
|
swarm-assisted recovery for stalled partial transfers:
|
||||||
|
|
||||||
|
- **Control plane (text messages):**
|
||||||
|
- `IE4:` image envelope announces image availability in chat.
|
||||||
|
- **Control plane (raw binary request):**
|
||||||
|
- Binary image fetch request (same raw route as image fragments).
|
||||||
|
- **Data plane (raw binary packets):**
|
||||||
|
- `ImagePacket` binary payload streamed via `cmdSendRawData` / `pushRawData`.
|
||||||
|
|
||||||
|
Images are never broadcast in full to channels. Chat carries only metadata;
|
||||||
|
pixels are fetched on demand when the user taps the image bubble.
|
||||||
|
|
||||||
|
Shared swarm fallback is documented in
|
||||||
|
[Swarm Mode Technical Design](./swarm-mode-technical.md).
|
||||||
|
|
||||||
|
## 2. Key Modules
|
||||||
|
|
||||||
|
- `lib/utils/image_message_parser.dart`
|
||||||
|
- `ImagePacket` (binary fragment format)
|
||||||
|
- `ImageEnvelope` (`IE4`)
|
||||||
|
- `ImageFetchRequest` (binary)
|
||||||
|
- `fragmentImage()` — split compressed bytes into packets
|
||||||
|
- `reassembleImage()` — join received fragments into bytes
|
||||||
|
- `lib/screens/messages_tab.dart`
|
||||||
|
- Pick image from gallery or camera, compress with user settings, cache, send envelope
|
||||||
|
- `lib/providers/image_provider.dart`
|
||||||
|
- Reassembly sessions, outgoing cache, deferred serving
|
||||||
|
- Outgoing sessions also registered as complete incoming sessions for immediate local display
|
||||||
|
- Received partial sessions can be re-served during swarm recovery
|
||||||
|
- `lib/providers/app_provider.dart`
|
||||||
|
- Incoming routing for `IE4`, binary image fetch requests, binary `0x49` packets, and raw swarm control payloads
|
||||||
|
- `lib/widgets/messages/image_message_bubble.dart`
|
||||||
|
- Square cover thumbnail (up to 256 px); tap-to-load for received images;
|
||||||
|
progress ring during fetch; full-screen `InteractiveViewer` on tap
|
||||||
|
- `lib/services/image_codec_service.dart`
|
||||||
|
- `ImageCodecService.compress()` — aspect-ratio-preserving resize + optional
|
||||||
|
grayscale + AVIF encode via `flutter_avif`
|
||||||
|
- `lib/services/image_preferences.dart`
|
||||||
|
- `ImagePreferences` — persists max size, compression level, grayscale toggle
|
||||||
|
|
||||||
|
## 3. Wire Formats
|
||||||
|
|
||||||
|
### 3.1 Image Envelope (`IE4`)
|
||||||
|
|
||||||
|
Prefix: `IE4:` + colon-delimited compact payload (base36 numeric fields)
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|--------------|--------|------------------------------------------------|
|
||||||
|
| `sid` | string | base36 token for 32-bit session ID |
|
||||||
|
| `fmt` | base36 | `ImageFormat.id` (0 = AVIF, 1 = JPEG) |
|
||||||
|
| `total` | base36 | Fragment count (1..255) |
|
||||||
|
| `w` | base36 | Actual image width after compression (pixels) |
|
||||||
|
| `h` | base36 | Actual image height after compression (pixels) |
|
||||||
|
| `bytes` | base36 | Total compressed size in bytes |
|
||||||
|
Compact format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IE4:{sid}:{fmt}:{total}:{w}:{h}:{bytes}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example (256×171 landscape image, 14 fragments):
|
||||||
|
|
||||||
|
```text
|
||||||
|
IE4:a:0:e:74:4r:1mc
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `sid` is base36 on wire and expands to 8-hex internally.
|
||||||
|
`w` and `h` reflect the actual post-compression dimensions, which preserve
|
||||||
|
the source aspect ratio (contain within the configured max size).
|
||||||
|
|
||||||
|
### 3.2 Image Fetch Request (`IR4` + binary)
|
||||||
|
|
||||||
|
Text format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IR4:{sid}:{want}:{requesterKey6}
|
||||||
|
```
|
||||||
|
|
||||||
|
Binary payload format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[magic=0x69][sid:4B][flags:1B][requesterKey6:6B][missingCount:1B][missingIndices...]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|------------------|--------------------------|
|
||||||
|
| `flags` | bit0=1 => request missing indices, else all |
|
||||||
|
| `requesterKey6` | 6-byte requester key prefix |
|
||||||
|
### 3.3 Raw Image Packet (data plane)
|
||||||
|
|
||||||
|
Binary payload structure:
|
||||||
|
|
||||||
|
- Byte 0: magic `0x49` (`'I'`)
|
||||||
|
- Bytes 1..4: session ID (4 bytes)
|
||||||
|
- Byte 5: fragment index (0-based)
|
||||||
|
- Bytes 6..N: image data (max 152 bytes per fragment)
|
||||||
|
|
||||||
|
Header is 6 bytes. Image format and total fragment count come from the `IE4` envelope.
|
||||||
|
|
||||||
|
## 4. Compression Pipeline
|
||||||
|
|
||||||
|
`ImageCodecService.compress(rawBytes, {maxDimension, compression, grayscale})`:
|
||||||
|
|
||||||
|
1. **Probe** — decode source image at original resolution to read `srcW × srcH`.
|
||||||
|
2. **Contain** — compute `dstW × dstH` that fits within `maxDimension × maxDimension`
|
||||||
|
while preserving aspect ratio. Images smaller than the cap are not upscaled.
|
||||||
|
3. **Decode** — `dart:ui.instantiateImageCodec` with `targetWidth: dstW, targetHeight: dstH`.
|
||||||
|
4. **RGBA export** — `image.toByteData(format: rawRgba)`.
|
||||||
|
5. **Grayscale** *(optional, default on)* — luminance `0.299R + 0.587G + 0.114B`
|
||||||
|
applied in-place; if disabled, colour pixels are passed through unchanged.
|
||||||
|
6. **PNG re-encode** — `ui.ImageDescriptor.raw` → `toByteData(format: png)`.
|
||||||
|
Required because `encodeAvif()` takes an encoded image, not raw RGBA.
|
||||||
|
7. **AVIF encode** — `flutter_avif.encodeAvif(pngBytes, maxQuantizer, minQuantizer, speed: 8)`.
|
||||||
|
`compression` (10–90) maps to libavif CQ scale:
|
||||||
|
`maxQ = round(compression/100 × 63)`, `minQ = round(maxQ × 0.65)`.
|
||||||
|
8. **Fragment** — `fragmentImage()` at 152 bytes/packet (≤255 packets).
|
||||||
|
|
||||||
|
Returns `({Uint8List bytes, int width, int height})` with the actual compressed dimensions.
|
||||||
|
|
||||||
|
### User-configurable settings (`ImagePreferences`)
|
||||||
|
|
||||||
|
| Setting | Key | Default | Range / Options |
|
||||||
|
|--------------|--------------------|---------|-----------------|
|
||||||
|
| Max size | `image_max_size` | 256 | 64 / 128 / 256 |
|
||||||
|
| Compression | `image_quality` | 90 | 10–90 |
|
||||||
|
| Grayscale | `image_grayscale` | true | true / false |
|
||||||
|
|
||||||
|
### Expected sizes (grayscale AVIF, compression 90)
|
||||||
|
|
||||||
|
| Max size | Typical size | Fragments |
|
||||||
|
|----------|--------------|-----------|
|
||||||
|
| 64×64 | 100–400 B | 1–3 |
|
||||||
|
| 128×128 | 300–900 B | 2–6 |
|
||||||
|
| 256×256 | 700–3000 B | 5–20 |
|
||||||
|
|
||||||
|
Non-square images produce fewer fragments than the square equivalent because
|
||||||
|
only the shorter axis is padded — no cropping occurs.
|
||||||
|
|
||||||
|
## 5. Outgoing Flow (Send)
|
||||||
|
|
||||||
|
1. User picks image from gallery or camera (`image_picker`).
|
||||||
|
2. `ImagePreferences` is read: max size, compression, grayscale.
|
||||||
|
3. `ImageCodecService.compress()` returns `(bytes, width, height)`.
|
||||||
|
4. `fragmentImage()` splits bytes into `ImagePacket` list (≤255 fragments).
|
||||||
|
5. `ImageProvider.cacheOutgoingSession()` stores fragments in both the outgoing
|
||||||
|
cache (for serving to peers) and the incoming session map (so the local bubble
|
||||||
|
renders the image immediately without a fetch round-trip).
|
||||||
|
6. `ImageEnvelope` built with actual `width`/`height` from step 3.
|
||||||
|
7. Envelope sent via normal message path:
|
||||||
|
- Channel: `sendChannelMessage`
|
||||||
|
- Direct: `sendTextMessage`
|
||||||
|
8. Local placeholder message added (`IE4:` text, `deliveryStatus.sending`).
|
||||||
|
|
||||||
|
## 6. Incoming Flow (Receive)
|
||||||
|
|
||||||
|
### 6.1 `IE4` envelope received
|
||||||
|
|
||||||
|
`AppProvider` calls `imageProvider.registerEnvelope()` and adds the message to
|
||||||
|
chat. The bubble shows a grey square placeholder with a download icon.
|
||||||
|
|
||||||
|
### 6.2 Binary image fetch request received
|
||||||
|
|
||||||
|
`AppProvider` treats it as control-plane only (not added to chat):
|
||||||
|
|
||||||
|
- Resolves requester contact.
|
||||||
|
- Calls `imageProvider.serveSessionTo()` which streams all cached fragments.
|
||||||
|
|
||||||
|
### 6.3 Swarm control messages received
|
||||||
|
|
||||||
|
`AppProvider` also handles shared raw swarm discovery payloads:
|
||||||
|
|
||||||
|
- binary swarm requests advertise which image fragments are still missing
|
||||||
|
- binary swarm availability responses advertise which fragments another peer can relay
|
||||||
|
- swarm payloads arrive via `pushRawData` and are intercepted instead of being added to chat
|
||||||
|
|
||||||
|
Shared discovery and responder semantics are documented in
|
||||||
|
[Swarm Mode Technical Design](./swarm-mode-technical.md).
|
||||||
|
|
||||||
|
### 6.4 Raw packet received (`pushRawData`, magic `0x49`)
|
||||||
|
|
||||||
|
`AppProvider.onRawDataReceived` parses `ImagePacket` binary and calls
|
||||||
|
`imageProvider.addFragment()`. When the session becomes complete, the bubble
|
||||||
|
automatically rebuilds with the full image.
|
||||||
|
|
||||||
|
## 7. Outgoing Cache Details
|
||||||
|
|
||||||
|
`ImageProvider` outgoing cache:
|
||||||
|
|
||||||
|
- key: `sessionId`
|
||||||
|
- value: encoded fragment list + cached envelope + timestamp
|
||||||
|
- TTL: 15 minutes
|
||||||
|
- eviction: lazy on access
|
||||||
|
|
||||||
|
When `cacheOutgoingSession()` is called it also writes all fragments into
|
||||||
|
`_sessions[sessionId]`, so the sender sees the image immediately in the bubble
|
||||||
|
(no tap-to-load required for own messages).
|
||||||
|
|
||||||
|
If a peer later receives only part of an image session, those received fragments
|
||||||
|
can also be served onward to another requester during swarm recovery.
|
||||||
|
|
||||||
|
## 8. Display
|
||||||
|
|
||||||
|
`ImageMessageBubble` (max width 256 px):
|
||||||
|
|
||||||
|
- **Complete session**: `AspectRatio(1.0)` → `AvifImage.memory(fit: cover)`
|
||||||
|
square thumbnail; tap → full-screen `InteractiveViewer` with fade transition.
|
||||||
|
- **Incomplete/missing**: grey square placeholder with download icon;
|
||||||
|
tap → sends binary fetch request, with raw swarm fallback if the original
|
||||||
|
sender path stalls.
|
||||||
|
- **Loading**: circular progress indicator showing `received/total` count.
|
||||||
|
- **Error**: broken-image icon.
|
||||||
|
|
||||||
|
Status line below thumbnail:
|
||||||
|
|
||||||
|
- Sender: `🖼️ {w}×{h} AVIF · {n} seg`
|
||||||
|
- Receiver (complete): `🖼️ {w}×{h} AVIF`
|
||||||
|
- Receiver (pending): `🖼️ Tap to load · {w}×{h}`
|
||||||
|
- Loading: `📥 Loading… {received}/{total}`
|
||||||
|
|
||||||
|
## 9. Transmit Time Estimate (UI)
|
||||||
|
|
||||||
|
Image bubbles and Message Technical Details show an **estimated transmit time** (`~... tx`).
|
||||||
|
|
||||||
|
The estimate is airtime-based (LoRa packet model), not just compressed image size:
|
||||||
|
|
||||||
|
- Source inputs:
|
||||||
|
- `total` fragments and `bytes` from `IE4` envelope
|
||||||
|
- all numeric envelope values are decoded from base36
|
||||||
|
- `pathLen` from message metadata
|
||||||
|
- current radio params from `deviceInfo`: `radioBw`, `radioSf`, `radioCr`
|
||||||
|
- Per-fragment payload model:
|
||||||
|
- `meshHeader(2)` + `pathLen` + `imageHeader(6)` + `fragmentBytes`
|
||||||
|
- LoRa airtime:
|
||||||
|
- standard symbol-time formula (preamble + payload symbols)
|
||||||
|
- Mesh pacing/hops:
|
||||||
|
- multiplied by `(1 + airtimeBudgetFactor)` where default factor is `1.0`
|
||||||
|
- multiplied by hop count `(pathLen + 1)`
|
||||||
|
- Total estimate:
|
||||||
|
- sum over all fragments
|
||||||
|
|
||||||
|
BW handling:
|
||||||
|
|
||||||
|
- If `radioBw` is index `0..9`, app maps it to Hz (`7.8k` .. `500k`)
|
||||||
|
- If `radioBw > 1000`, it is treated as Hz directly
|
||||||
|
|
||||||
|
Fallback defaults are used when radio params are unavailable: `SF10`, `BW250kHz`, `CR5`.
|
||||||
|
|
||||||
|
## 10. Persistence
|
||||||
|
|
||||||
|
`ImageProvider` stores sessions in `SharedPreferences` under key
|
||||||
|
`stored_image_sessions_v1`:
|
||||||
|
|
||||||
|
- Incoming: fragment list serialized as base64 binary packets.
|
||||||
|
- Outgoing: fragment list + envelope text + `cachedAt` timestamp.
|
||||||
|
- Expired outgoing sessions (> 15 min) are not restored on startup.
|
||||||
|
|
||||||
|
## 11. Operational Constraints
|
||||||
|
|
||||||
|
- No firmware changes required (reuses `cmdSendRawData` / `pushRawData`).
|
||||||
|
- On-demand fetch prefers the original sender, but a partial image can also be
|
||||||
|
completed from alternate peers that already hold matching fragments.
|
||||||
|
- Raw return path requires a valid direct route to requester.
|
||||||
|
- Available on iOS and Android (`image_picker` + `flutter_avif`).
|
||||||
|
- Swarm discovery uses the same `cmdSendRawData` / `pushRawData` path as image
|
||||||
|
fetch and fragment delivery.
|
||||||
|
|
||||||
|
### 11.1 Raw Binary Routing Semantics
|
||||||
|
|
||||||
|
- Image fragment payloads use companion command `CMD_SEND_RAW_DATA` (`25` / `0x19`).
|
||||||
|
- Companion push back to the app is `PUSH_CODE_RAW_DATA` (`0x84`).
|
||||||
|
- Over-the-air packet type for this flow is `PAYLOAD_TYPE_RAW_CUSTOM` (`0x0F`).
|
||||||
|
- This flow is direct-route only, not flood/broadcast:
|
||||||
|
- it is sent to one destination path;
|
||||||
|
- only nodes on that path relay it;
|
||||||
|
- it is **not** received by everyone in the mesh.
|
||||||
|
|
||||||
|
## 12. Swarm Fallback Sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant A as Original Sender
|
||||||
|
participant P as Peer With Fragments
|
||||||
|
participant N as Reachable Peers
|
||||||
|
participant B as Receiver App
|
||||||
|
|
||||||
|
A->>M: Send IE4 envelope
|
||||||
|
M->>B: Deliver IE4
|
||||||
|
B->>A: Direct binary image fetch request
|
||||||
|
A->>B: Stream raw ImagePacket fragments (partial)
|
||||||
|
Note over A,B: Sender path stops responding
|
||||||
|
B->>N: Raw swarm requests with missing image fragment indices
|
||||||
|
P->>B: Raw swarm availability response
|
||||||
|
B->>P: Direct binary fetch request for missing subset
|
||||||
|
P->>B: Stream remaining raw ImagePacket fragments
|
||||||
|
B->>B: Reassemble completed image
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. High-Level Sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant A as Sender App
|
||||||
|
participant M as Mesh Chat
|
||||||
|
participant B as Receiver App
|
||||||
|
|
||||||
|
A->>A: Pick image (gallery or camera)
|
||||||
|
A->>A: Compress: contain resize → grayscale → PNG → AVIF
|
||||||
|
A->>A: Fragment into ≤152B packets
|
||||||
|
A->>A: Cache outgoing + populate local session (immediate display)
|
||||||
|
A->>M: Send IE4 envelope (actual w×h, fragment count)
|
||||||
|
M->>B: Deliver IE4
|
||||||
|
B->>B: Render grey placeholder bubble
|
||||||
|
B->>A: Tap → send binary fetch request
|
||||||
|
A->>B: Stream binary ImagePackets
|
||||||
|
B->>B: Reassemble fragments
|
||||||
|
B->>B: Display AVIF image (cover thumbnail)
|
||||||
|
```
|
||||||
148
docs/swarm-mode-technical.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Swarm Mode Technical Design
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
Swarm mode is a shared media-recovery transport used by both voice and image
|
||||||
|
sessions when the original sender path stops responding after some fragments
|
||||||
|
have already propagated through the mesh.
|
||||||
|
|
||||||
|
It adds a lightweight **swarm discovery plane** on top of the existing
|
||||||
|
**direct raw-data transfer plane**:
|
||||||
|
|
||||||
|
- **Discovery plane (raw custom control payloads):**
|
||||||
|
- `MediaSwarmRequest`
|
||||||
|
- `MediaSwarmAvailability`
|
||||||
|
- **Transfer plane (direct raw binary):**
|
||||||
|
- existing `VoiceFetchRequest` / `ImageFetchRequest`
|
||||||
|
- existing `VoicePacket` / `ImagePacket`
|
||||||
|
|
||||||
|
Swarm mode does not broadcast media payloads. It fans out raw control requests
|
||||||
|
to reachable peers, collects raw availability responses, and then fetches media
|
||||||
|
from the best responder.
|
||||||
|
|
||||||
|
## 2. Problem It Solves
|
||||||
|
|
||||||
|
Without swarm mode, media fetch is limited to the original sender's direct raw
|
||||||
|
path. If that sender goes offline, moves, or stops responding, a receiver can
|
||||||
|
be left with a partial session even when other peers already hold useful
|
||||||
|
fragments.
|
||||||
|
|
||||||
|
Swarm mode lets the receiver discover alternate peers and fetch the missing
|
||||||
|
subset directly from them.
|
||||||
|
|
||||||
|
## 3. Control Messages
|
||||||
|
|
||||||
|
### 3.1 Media Swarm Request (binary)
|
||||||
|
|
||||||
|
```text
|
||||||
|
[magic=0x6d][kind=0x01][mediaType:1B][sessionId:4B][requesterKey6:6B][missingCount:1B][missingIndices...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `mediaType` — `voice` or `image`
|
||||||
|
- `sessionId` — 8 hex chars
|
||||||
|
- `requesterKey6` — 12 hex chars identifying the requesting device
|
||||||
|
- `missingCount` — `0` means the requester needs all fragments
|
||||||
|
- `missingIndices` — exact missing fragment indices when `missingCount > 0`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6d 01 02 de ad be ef aa bb cc dd ee ff 03 00 02 09
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Media Swarm Availability (binary)
|
||||||
|
|
||||||
|
```text
|
||||||
|
[magic=0x6d][kind=0x02][mediaType:1B][sessionId:4B][requesterKey6:6B][responderKey6:6B][availableCount:1B][availableIndices...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `mediaType` — `voice` or `image`
|
||||||
|
- `sessionId` — 8 hex chars
|
||||||
|
- `requesterKey6` — copied from the swarm request
|
||||||
|
- `responderKey6` — 12 hex chars identifying the responding peer
|
||||||
|
- `availableCount` — `0` means the responder can satisfy the full request
|
||||||
|
- `availableIndices` — exact fragment indices held when `availableCount > 0`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6d 02 02 de ad be ef aa bb cc dd ee ff 11 22 33 44 55 66 02 02 09
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Requester Behavior
|
||||||
|
|
||||||
|
When a voice or image session is incomplete:
|
||||||
|
|
||||||
|
1. Prefer the original sender if its direct raw route is healthy.
|
||||||
|
2. If the original sender path is unavailable or not responding, send a raw
|
||||||
|
swarm request to reachable peers with the exact missing fragment indices.
|
||||||
|
3. Wait up to **10 seconds** for raw availability responses.
|
||||||
|
4. Rank responders by overlap with the current missing set.
|
||||||
|
5. Skip the original sender when choosing alternate peers.
|
||||||
|
6. Send a direct binary fetch request to the best responder for only the
|
||||||
|
missing subset that responder advertised.
|
||||||
|
|
||||||
|
Actual media transfer uses the same `cmdSendRawData` / `pushRawData` path as the
|
||||||
|
swarm control messages.
|
||||||
|
|
||||||
|
## 5. Responder Behavior
|
||||||
|
|
||||||
|
A peer that receives a swarm request:
|
||||||
|
|
||||||
|
- checks whether it has fragments for the requested media session
|
||||||
|
- replies only if it has at least one requested fragment
|
||||||
|
- advertises only fragments it actually holds
|
||||||
|
- may reply from:
|
||||||
|
- its outgoing cache, or
|
||||||
|
- a partially/fully received incoming session
|
||||||
|
|
||||||
|
This enables torrent-like relay behavior without requiring the peer to be the
|
||||||
|
original sender.
|
||||||
|
|
||||||
|
## 6. Visibility and Routing
|
||||||
|
|
||||||
|
- Swarm control payloads are carried by `cmdSendRawData` and received through
|
||||||
|
`pushRawData`.
|
||||||
|
- They are intercepted by the app and **must not be surfaced in chat**.
|
||||||
|
- Discovery is a direct raw fan-out to reachable peers, not a public-channel
|
||||||
|
broadcast.
|
||||||
|
- Media packets themselves remain direct-route raw packets.
|
||||||
|
|
||||||
|
## 7. Constraints
|
||||||
|
|
||||||
|
- No firmware changes are required.
|
||||||
|
- Swarm mode only helps if at least one peer has already received some useful
|
||||||
|
fragments.
|
||||||
|
- Peers can only relay fragments they actually have.
|
||||||
|
- Alternate peers still need a valid direct raw route back to the requester.
|
||||||
|
- Swarm mode improves recovery from sender-path failure, but it does not change
|
||||||
|
the underlying raw-packet size, airtime, or hop constraints.
|
||||||
|
|
||||||
|
## 8. High-Level Sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant S as Original Sender
|
||||||
|
participant P as Peer With Fragments
|
||||||
|
participant N as Reachable Peers
|
||||||
|
participant R as Requester
|
||||||
|
|
||||||
|
S->>R: Direct raw fragments (partial)
|
||||||
|
Note over S,R: Original sender path stops responding
|
||||||
|
R->>N: Raw swarm requests with exact missing fragments
|
||||||
|
P->>R: Raw swarm availability response
|
||||||
|
R->>P: Direct binary fetch request for missing subset
|
||||||
|
P->>R: Direct raw fragments
|
||||||
|
R->>R: Reassemble completed session
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Integration Points
|
||||||
|
|
||||||
|
- Voice-specific behavior is summarized in `docs/voice-mode-technical.md`.
|
||||||
|
- Image-specific behavior is summarized in `docs/image-mode-technical.md`.
|
||||||
|
- This document is the shared source of truth for swarm discovery and fallback
|
||||||
|
semantics.
|
||||||
255
docs/voice-mode-technical.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# Voice Mode Technical Design
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
Voice mode uses a **two-plane architecture** with optional swarm-assisted
|
||||||
|
recovery:
|
||||||
|
|
||||||
|
- **Control plane (text messages):**
|
||||||
|
- `VE3:` voice envelope announces voice availability in chat.
|
||||||
|
- **Control plane (raw binary request):**
|
||||||
|
- Binary voice fetch request (same raw route as voice packets).
|
||||||
|
- **Data plane (raw binary packets):**
|
||||||
|
- `VoicePacket` payload streamed via `cmdSendRawData` and received through `pushRawData`.
|
||||||
|
|
||||||
|
This design avoids broadcasting full voice payloads to channels/rooms. Chat carries only metadata; audio is fetched on demand when user presses play.
|
||||||
|
|
||||||
|
Swarm fallback is documented in [Swarm Mode Technical Design](./swarm-mode-technical.md).
|
||||||
|
|
||||||
|
## 2. Key Modules
|
||||||
|
|
||||||
|
- `lib/utils/voice_message_parser.dart`
|
||||||
|
- `VoicePacket` (binary direct-packet format)
|
||||||
|
- `VoiceEnvelope` (`VE3`)
|
||||||
|
- `VoiceFetchRequest` (binary)
|
||||||
|
- `lib/screens/messages_tab.dart`
|
||||||
|
- Capture/encode voice, cache encoded packets, send envelope only
|
||||||
|
- `lib/providers/voice_provider.dart`
|
||||||
|
- Reassembly/playback sessions
|
||||||
|
- Outgoing session cache + deferred serving
|
||||||
|
- `lib/providers/app_provider.dart`
|
||||||
|
- Incoming routing for `VE3`, binary voice fetch requests, and raw swarm control payloads
|
||||||
|
- Handles raw packet ingestion
|
||||||
|
- `lib/widgets/messages/voice_message_bubble.dart`
|
||||||
|
- Play behavior (immediate play if complete, otherwise fetch + auto-play)
|
||||||
|
- `lib/providers/messages_provider.dart`
|
||||||
|
- Message-level voice detection (`VE3`)
|
||||||
|
- `lib/services/message_storage_service.dart`
|
||||||
|
- Persists `isVoice` and `voiceId`
|
||||||
|
|
||||||
|
## 3. Wire Formats
|
||||||
|
|
||||||
|
### 3.1 Voice Envelope (`VE3`)
|
||||||
|
|
||||||
|
Prefix: `VE3:` + colon-delimited compact payload (base36 numeric fields)
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `sid` (string): base36 token for 32-bit session ID
|
||||||
|
- `mode` (base36): codec mode ID (`VoicePacketMode.id`)
|
||||||
|
- `total` (base36): packet count (1..255)
|
||||||
|
- `durS` (base36): estimated duration in seconds
|
||||||
|
|
||||||
|
`sid` is base36 on wire and expands to 8-hex internally.
|
||||||
|
|
||||||
|
Compact format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VE3:{sid}:{mode}:{total}:{durS}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VE3:a:1:4:4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Voice Fetch Request (binary)
|
||||||
|
|
||||||
|
Binary payload format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[magic=0x72][sid:4B][flags:1B][requesterKey6:6B][missingCount:1B][missingIndices...]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Raw Voice Packet (data plane)
|
||||||
|
|
||||||
|
Binary payload structure:
|
||||||
|
|
||||||
|
- Byte 0: magic `0x56` (`'V'`)
|
||||||
|
- Bytes 1..4: session ID (4 bytes)
|
||||||
|
- Byte 5: packet index
|
||||||
|
- Bytes 6..N: codec2 data
|
||||||
|
|
||||||
|
Header is 6 bytes. Mode and total packet count come from the `VE3` envelope.
|
||||||
|
|
||||||
|
## 4. Outgoing Flow (Send)
|
||||||
|
|
||||||
|
1. Recorder captures PCM chunks.
|
||||||
|
2. Each chunk is codec2-encoded into `VoicePacket` objects.
|
||||||
|
3. Packets are cached in `VoiceProvider` outgoing cache (TTL 15 min).
|
||||||
|
4. Sender inserts local voice placeholder message (`isVoice=true`, `voiceId=sessionId`).
|
||||||
|
5. Sender sends one envelope (`VE3`) through normal message path:
|
||||||
|
- channel/room: `sendChannelMessage`
|
||||||
|
- direct: `sendTextMessage`
|
||||||
|
6. **No raw audio packets are sent during initial send.**
|
||||||
|
|
||||||
|
## 5. Incoming Routing
|
||||||
|
|
||||||
|
### 5.1 `VE3` envelope received
|
||||||
|
|
||||||
|
`AppProvider` records sender identity from message metadata, registers the voice
|
||||||
|
session envelope, marks the message as voice (`isVoice`, `voiceId`), and adds it to chat.
|
||||||
|
|
||||||
|
### 5.2 Binary voice fetch request received
|
||||||
|
|
||||||
|
`AppProvider` treats it as control-plane only:
|
||||||
|
|
||||||
|
- request is not added to chat
|
||||||
|
- resolves requester contact via key prefix
|
||||||
|
- calls `voiceProvider.serveSessionTo(...)`
|
||||||
|
|
||||||
|
### 5.3 Swarm control messages received
|
||||||
|
|
||||||
|
`AppProvider` also handles raw swarm control payloads:
|
||||||
|
|
||||||
|
- binary swarm requests advertise which voice fragments are still missing
|
||||||
|
- binary swarm availability responses advertise which fragments a peer can relay
|
||||||
|
- swarm control payloads arrive via `pushRawData` and are not added to chat history
|
||||||
|
|
||||||
|
Swarm semantics are shared with image mode and documented in
|
||||||
|
[Swarm Mode Technical Design](./swarm-mode-technical.md).
|
||||||
|
|
||||||
|
### 5.4 Raw packet received (`pushRawData`)
|
||||||
|
|
||||||
|
`AppProvider.onRawDataReceived` parses `VoicePacket` binary and appends to session in `VoiceProvider`.
|
||||||
|
|
||||||
|
## 6. Play / Fetch Behavior
|
||||||
|
|
||||||
|
In `VoiceMessageBubble`:
|
||||||
|
|
||||||
|
- If session already complete: play immediately.
|
||||||
|
- If incomplete/missing:
|
||||||
|
1. Resolve sender contact from message sender metadata
|
||||||
|
2. Prefer a direct fetch from the original sender if its raw route is healthy
|
||||||
|
3. If the sender path does not respond, fan out a raw swarm request with the
|
||||||
|
exact missing packet indices to reachable peers
|
||||||
|
4. Wait up to 10 seconds for raw peer availability responses
|
||||||
|
5. Send a direct binary fetch request to the best alternate peer for the
|
||||||
|
missing subset it advertised
|
||||||
|
6. Show requesting state in UI
|
||||||
|
7. Auto-play when session becomes complete
|
||||||
|
|
||||||
|
If sender cannot be resolved or request cannot be sent, bubble remains and shows: **"Voice unavailable right now"**.
|
||||||
|
|
||||||
|
## 7. Outgoing Cache Details
|
||||||
|
|
||||||
|
`VoiceProvider` outgoing cache:
|
||||||
|
|
||||||
|
- key: `sessionId`
|
||||||
|
- value: encoded packet list + cached timestamp
|
||||||
|
- TTL: 15 minutes (`_outgoingSessionTtl`)
|
||||||
|
- eviction: lazy (on cache access/add/serve)
|
||||||
|
|
||||||
|
Serving prerequisites:
|
||||||
|
|
||||||
|
- session exists in outgoing cache or already-received session state
|
||||||
|
- `sendRawPacketCallback` configured
|
||||||
|
- requester has direct path (`outPathLen >= 0`)
|
||||||
|
|
||||||
|
Received partial sessions can therefore act as relay sources during swarm
|
||||||
|
recovery.
|
||||||
|
|
||||||
|
## 8. Persistence
|
||||||
|
|
||||||
|
`MessageStorageService` now stores and restores:
|
||||||
|
|
||||||
|
- `isVoice`
|
||||||
|
- `voiceId`
|
||||||
|
|
||||||
|
This ensures envelope messages remain voice bubbles across app restart.
|
||||||
|
|
||||||
|
## 9. Validation and Safety
|
||||||
|
|
||||||
|
Parser validation enforces:
|
||||||
|
|
||||||
|
- strict hex lengths for IDs and key prefixes
|
||||||
|
- valid mode range
|
||||||
|
- valid packet counts and duration bounds
|
||||||
|
- compact base36 numeric fields in envelope/request
|
||||||
|
- Binary request flags specify `all` or `missing` indices.
|
||||||
|
- Request payload includes `requesterKey6` to resolve return route.
|
||||||
|
|
||||||
|
## 10. Transmit Time Estimate (UI)
|
||||||
|
|
||||||
|
Voice bubbles and Message Technical Details show an **estimated transmit time** (`~... tx`).
|
||||||
|
|
||||||
|
The estimate is airtime-based (LoRa packet model), not file-duration-only:
|
||||||
|
|
||||||
|
- Source inputs:
|
||||||
|
- `packetCount` and `durationMs` from `VE3` envelope, or
|
||||||
|
- numeric envelope values decoded from base36
|
||||||
|
- actual received `VoicePacket.codec2Data.length` bytes when local session packets exist
|
||||||
|
- `pathLen` from message metadata
|
||||||
|
- current radio params from `deviceInfo`: `radioBw`, `radioSf`, `radioCr`
|
||||||
|
- Per-packet payload model:
|
||||||
|
- `meshHeader(2)` + `pathLen` + `voiceHeader(6)` + `codec2Bytes`
|
||||||
|
- LoRa airtime:
|
||||||
|
- standard symbol-time formula (preamble + payload symbols)
|
||||||
|
- Mesh pacing/hops:
|
||||||
|
- multiplied by `(1 + airtimeBudgetFactor)` where default factor is `1.0`
|
||||||
|
- multiplied by hop count `(pathLen + 1)`
|
||||||
|
- Total estimate:
|
||||||
|
- sum over all packets
|
||||||
|
|
||||||
|
BW handling:
|
||||||
|
|
||||||
|
- If `radioBw` is index `0..9`, app maps it to Hz (`7.8k` .. `500k`)
|
||||||
|
- If `radioBw > 1000`, it is treated as Hz directly
|
||||||
|
|
||||||
|
Fallback defaults are used when radio params are unavailable: `SF10`, `BW250kHz`, `CR5`.
|
||||||
|
|
||||||
|
## 11. Operational Constraints
|
||||||
|
|
||||||
|
- No firmware changes required.
|
||||||
|
- On-demand fetch prefers the original sender, but a partial session can also be
|
||||||
|
completed from alternate peers that already hold packets.
|
||||||
|
- Raw return path needs a currently valid direct route to requester.
|
||||||
|
- Voice capture is available on iOS and Android (`Platform.isIOS || Platform.isAndroid`).
|
||||||
|
- Swarm discovery uses the same `cmdSendRawData` / `pushRawData` path as voice
|
||||||
|
fetch and packet delivery.
|
||||||
|
|
||||||
|
### 11.1 Raw Binary Routing Semantics
|
||||||
|
|
||||||
|
- Voice payload packets use companion command `CMD_SEND_RAW_DATA` (`25` / `0x19`).
|
||||||
|
- Companion push back to the app is `PUSH_CODE_RAW_DATA` (`0x84`).
|
||||||
|
- Over-the-air packet type for this flow is `PAYLOAD_TYPE_RAW_CUSTOM` (`0x0F`).
|
||||||
|
- This flow is direct-route only, not flood/broadcast:
|
||||||
|
- it is sent to one destination path;
|
||||||
|
- only nodes on that path relay it;
|
||||||
|
- it is **not** received by everyone in the mesh.
|
||||||
|
|
||||||
|
## 12. High-Level Sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant A as Sender App
|
||||||
|
participant P as Peer With Packets
|
||||||
|
participant N as Reachable Peers
|
||||||
|
participant B as Receiver App
|
||||||
|
|
||||||
|
A->>A: Record + encode voice packets
|
||||||
|
A->>A: Cache session packets (TTL 15m)
|
||||||
|
A->>M: Send VE3 envelope
|
||||||
|
M->>B: Deliver VE3
|
||||||
|
B->>B: Render voice bubble (metadata only)
|
||||||
|
B->>A: Send binary fetch request on Play
|
||||||
|
A->>B: Stream raw VoicePacket packets (partial)
|
||||||
|
Note over A,B: Sender path stops responding
|
||||||
|
B->>N: Raw swarm requests with missing voice packet indices
|
||||||
|
P->>B: Raw swarm availability response
|
||||||
|
B->>P: Direct binary fetch request for missing subset
|
||||||
|
P->>B: Stream remaining raw VoicePacket packets
|
||||||
|
B->>B: Reassemble session
|
||||||
|
B->>B: Auto-play when complete
|
||||||
|
```
|
||||||
116
integration_test/app_screenshots_test.dart
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:meshcore_sar_app/l10n/app_localizations.dart';
|
||||||
|
import 'package:meshcore_sar_app/main.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/messages_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/screens/home_screen.dart';
|
||||||
|
import 'package:meshcore_sar_app/services/wizard_preferences.dart';
|
||||||
|
import 'package:meshcore_sar_app/utils/sample_data_generator.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
const screenshotPrefix = String.fromEnvironment('SCREENSHOT_PREFIX');
|
||||||
|
|
||||||
|
testWidgets('captures App Store screenshots', (tester) async {
|
||||||
|
SharedPreferences.setMockInitialValues({
|
||||||
|
'wizard_completed': true,
|
||||||
|
'wizard_version': 1,
|
||||||
|
});
|
||||||
|
await WizardPreferences.setWizardCompleted(true);
|
||||||
|
|
||||||
|
await tester.pumpWidget(const MeshCoreSarApp());
|
||||||
|
await _pumpUntilFound(tester, find.byType(HomeScreen));
|
||||||
|
await binding.convertFlutterSurfaceToImage();
|
||||||
|
|
||||||
|
final homeContext = tester.element(find.byType(HomeScreen));
|
||||||
|
_loadSampleData(homeContext);
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
await _takeScreenshot(binding, tester, '${screenshotPrefix}01-messages');
|
||||||
|
|
||||||
|
await _tapTab(tester, 'Contacts');
|
||||||
|
await _takeScreenshot(binding, tester, '${screenshotPrefix}02-contacts');
|
||||||
|
|
||||||
|
await _tapTab(tester, 'Map');
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||||
|
await _takeScreenshot(binding, tester, '${screenshotPrefix}03-map');
|
||||||
|
|
||||||
|
await _openOverflowItem(tester, Icons.more_vert, 'Settings');
|
||||||
|
await _takeScreenshot(binding, tester, '${screenshotPrefix}04-settings');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadSampleData(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
const centerLocation = LatLng(46.0569, 14.5058);
|
||||||
|
final contacts = SampleDataGenerator.generateContacts(
|
||||||
|
centerLocation: centerLocation,
|
||||||
|
l10n: l10n,
|
||||||
|
teamMemberCount: 5,
|
||||||
|
channelCount: 2,
|
||||||
|
);
|
||||||
|
final sampleMessages = SampleDataGenerator.generateAllMessages(
|
||||||
|
centerLocation: centerLocation,
|
||||||
|
l10n: l10n,
|
||||||
|
foundPersonCount: 2,
|
||||||
|
fireCount: 1,
|
||||||
|
stagingCount: 1,
|
||||||
|
objectCount: 1,
|
||||||
|
generalChannelMessages: 8,
|
||||||
|
emergencyChannelMessages: 5,
|
||||||
|
);
|
||||||
|
context.read<ContactsProvider>().addContacts(contacts);
|
||||||
|
for (final message in sampleMessages.messages) {
|
||||||
|
context.read<MessagesProvider>().addMessage(
|
||||||
|
message,
|
||||||
|
contactLocationSnapshot: sampleMessages.contactLocations[message.id],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pumpUntilFound(
|
||||||
|
WidgetTester tester,
|
||||||
|
Finder finder, {
|
||||||
|
Duration timeout = const Duration(seconds: 10),
|
||||||
|
}) async {
|
||||||
|
final end = DateTime.now().add(timeout);
|
||||||
|
while (DateTime.now().isBefore(end)) {
|
||||||
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
|
if (finder.evaluate().isNotEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(finder, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _tapTab(WidgetTester tester, String label) async {
|
||||||
|
final tab = find.text(label).last;
|
||||||
|
await tester.tap(tab);
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openOverflowItem(
|
||||||
|
WidgetTester tester,
|
||||||
|
IconData buttonIcon,
|
||||||
|
String label,
|
||||||
|
) async {
|
||||||
|
await tester.tap(find.byIcon(buttonIcon));
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
|
await tester.tap(find.text(label));
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _takeScreenshot(
|
||||||
|
IntegrationTestWidgetsFlutterBinding binding,
|
||||||
|
WidgetTester tester,
|
||||||
|
String name,
|
||||||
|
) async {
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
|
final bytes = await binding.takeScreenshot(name);
|
||||||
|
expect(bytes.isNotEmpty, isTrue);
|
||||||
|
}
|
||||||
34
ios/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
24
ios/Flutter/AppFrameworkInfo.plist
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
3
ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
|
ENABLE_DEBUG_DYLIB = NO
|
||||||
2
ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
3
ios/Gemfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "fastlane"
|
||||||
228
ios/Gemfile.lock
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
CFPropertyList (3.0.9)
|
||||||
|
abbrev (0.1.2)
|
||||||
|
addressable (2.9.0)
|
||||||
|
public_suffix (>= 2.0.2, < 8.0)
|
||||||
|
artifactory (3.0.17)
|
||||||
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.3.2)
|
||||||
|
aws-partitions (1.1109.0)
|
||||||
|
aws-sdk-core (3.224.1)
|
||||||
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
|
aws-sigv4 (~> 1.9)
|
||||||
|
base64
|
||||||
|
jmespath (~> 1, >= 1.6.1)
|
||||||
|
logger
|
||||||
|
aws-sdk-kms (1.101.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.216.0)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sdk-s3 (1.188.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.224.1)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sigv4 (1.11.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
babosa (1.0.4)
|
||||||
|
base64 (0.3.0)
|
||||||
|
claide (1.1.0)
|
||||||
|
colored (1.2)
|
||||||
|
colored2 (3.1.2)
|
||||||
|
commander (4.6.0)
|
||||||
|
highline (~> 2.0.0)
|
||||||
|
csv (3.3.5)
|
||||||
|
declarative (0.0.20)
|
||||||
|
digest-crc (0.7.0)
|
||||||
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
|
domain_name (0.5.20190701)
|
||||||
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
|
dotenv (2.8.1)
|
||||||
|
emoji_regex (3.2.3)
|
||||||
|
excon (0.109.0)
|
||||||
|
faraday (1.10.5)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-cookie_jar (0.0.8)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
|
http-cookie (>= 1.0.0)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.1)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.2.0)
|
||||||
|
multipart-post (~> 2.0)
|
||||||
|
faraday-net_http (1.0.2)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.4)
|
||||||
|
faraday_middleware (1.2.1)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
fastimage (2.4.1)
|
||||||
|
fastlane (2.229.0)
|
||||||
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
|
abbrev (~> 0.1.2)
|
||||||
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
|
colored (~> 1.2)
|
||||||
|
commander (~> 4.6)
|
||||||
|
csv (~> 3.3)
|
||||||
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
|
faraday_middleware (~> 1.0)
|
||||||
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
fastlane-sirp (>= 1.0.0)
|
||||||
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
|
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||||
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
|
http-cookie (~> 1.0.5)
|
||||||
|
json (< 3.0.0)
|
||||||
|
jwt (>= 2.1.0, < 3)
|
||||||
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
|
mutex_m (~> 0.3.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (>= 0.1.1, < 1.0.0)
|
||||||
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
|
security (= 0.1.5)
|
||||||
|
simctl (~> 1.6.3)
|
||||||
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
|
terminal-table (~> 3)
|
||||||
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
|
word_wrap (~> 1.0.0)
|
||||||
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
|
xcpretty (~> 0.4.1)
|
||||||
|
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||||
|
fastlane-sirp (1.1.0)
|
||||||
|
gh_inspector (1.1.3)
|
||||||
|
google-apis-androidpublisher_v3 (0.54.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-core (0.11.3)
|
||||||
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
representable (~> 3.0)
|
||||||
|
retriable (>= 2.0, < 4.a)
|
||||||
|
rexml
|
||||||
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.13.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.32.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-cloud-core (1.6.1)
|
||||||
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
|
google-cloud-errors (~> 1.0)
|
||||||
|
google-cloud-env (1.6.0)
|
||||||
|
faraday (>= 0.17.3, < 3.0)
|
||||||
|
google-cloud-errors (1.3.1)
|
||||||
|
google-cloud-storage (1.37.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
digest-crc (~> 0.4)
|
||||||
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
|
google-apis-storage_v1 (~> 0.1)
|
||||||
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
googleauth (1.8.1)
|
||||||
|
faraday (>= 0.17.3, < 3.a)
|
||||||
|
jwt (>= 1.4, < 3.0)
|
||||||
|
multi_json (~> 1.11)
|
||||||
|
os (>= 0.9, < 2.0)
|
||||||
|
signet (>= 0.16, < 2.a)
|
||||||
|
highline (2.0.3)
|
||||||
|
http-cookie (1.0.8)
|
||||||
|
domain_name (~> 0.5)
|
||||||
|
httpclient (2.9.0)
|
||||||
|
mutex_m
|
||||||
|
jmespath (1.6.2)
|
||||||
|
json (2.7.6)
|
||||||
|
jwt (2.10.3)
|
||||||
|
base64
|
||||||
|
logger (1.7.0)
|
||||||
|
mini_magick (4.13.2)
|
||||||
|
mini_mime (1.1.5)
|
||||||
|
multi_json (1.15.0)
|
||||||
|
multipart-post (2.4.1)
|
||||||
|
mutex_m (0.3.0)
|
||||||
|
nanaimo (0.4.0)
|
||||||
|
naturally (2.3.0)
|
||||||
|
optparse (0.8.1)
|
||||||
|
os (1.1.4)
|
||||||
|
plist (3.7.2)
|
||||||
|
public_suffix (5.1.1)
|
||||||
|
rake (13.4.2)
|
||||||
|
representable (3.2.0)
|
||||||
|
declarative (< 0.1.0)
|
||||||
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
|
uber (< 0.2.0)
|
||||||
|
retriable (3.8.0)
|
||||||
|
rexml (3.4.4)
|
||||||
|
rouge (3.28.0)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyzip (2.4.1)
|
||||||
|
security (0.1.5)
|
||||||
|
signet (0.18.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
faraday (>= 0.17.5, < 3.a)
|
||||||
|
jwt (>= 1.5, < 3.0)
|
||||||
|
multi_json (~> 1.10)
|
||||||
|
simctl (1.6.10)
|
||||||
|
CFPropertyList
|
||||||
|
naturally
|
||||||
|
terminal-notifier (2.0.0)
|
||||||
|
terminal-table (3.0.2)
|
||||||
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
|
tty-cursor (0.7.1)
|
||||||
|
tty-screen (0.8.2)
|
||||||
|
tty-spinner (0.9.3)
|
||||||
|
tty-cursor (~> 0.7)
|
||||||
|
uber (0.1.0)
|
||||||
|
unf (0.2.0)
|
||||||
|
unicode-display_width (2.6.0)
|
||||||
|
word_wrap (1.0.0)
|
||||||
|
xcodeproj (1.27.0)
|
||||||
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
atomos (~> 0.1.3)
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
nanaimo (~> 0.4.0)
|
||||||
|
rexml (>= 3.3.6, < 4.0)
|
||||||
|
xcpretty (0.4.1)
|
||||||
|
rouge (~> 3.28.0)
|
||||||
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
arm64-darwin-25
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
fastlane
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.4.22
|
||||||
51
ios/Podfile
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# 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!
|
||||||
|
|
||||||
|
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)
|
||||||
|
# codec2_flutter uses ARM-specific register asm("sp") in debug_alloc.h that
|
||||||
|
# fails to compile for iOS simulators. Voice codec support is only required
|
||||||
|
# on real iOS devices, so skip the native Codec2 sources for simulator SDKs.
|
||||||
|
if target.name == 'codec2_flutter'
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['EXCLUDED_SOURCE_FILE_NAMES[sdk=iphonesimulator*]'] = '*.c *.cpp'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
52
ios/Podfile.lock
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
PODS:
|
||||||
|
- codec2_flutter (0.1.0):
|
||||||
|
- Flutter
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- flutter_avif_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- flutter_background_service_ios (0.0.3):
|
||||||
|
- Flutter
|
||||||
|
- flutter_compass (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- nsd_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- permission_handler_apple (9.3.0):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- codec2_flutter (from `.symlinks/plugins/codec2_flutter/ios`)
|
||||||
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_avif_ios (from `.symlinks/plugins/flutter_avif_ios/ios`)
|
||||||
|
- flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`)
|
||||||
|
- flutter_compass (from `.symlinks/plugins/flutter_compass/ios`)
|
||||||
|
- nsd_ios (from `.symlinks/plugins/nsd_ios/ios`)
|
||||||
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
codec2_flutter:
|
||||||
|
:path: ".symlinks/plugins/codec2_flutter/ios"
|
||||||
|
Flutter:
|
||||||
|
:path: Flutter
|
||||||
|
flutter_avif_ios:
|
||||||
|
:path: ".symlinks/plugins/flutter_avif_ios/ios"
|
||||||
|
flutter_background_service_ios:
|
||||||
|
:path: ".symlinks/plugins/flutter_background_service_ios/ios"
|
||||||
|
flutter_compass:
|
||||||
|
:path: ".symlinks/plugins/flutter_compass/ios"
|
||||||
|
nsd_ios:
|
||||||
|
:path: ".symlinks/plugins/nsd_ios/ios"
|
||||||
|
permission_handler_apple:
|
||||||
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
codec2_flutter: 15e24fa897d9d903a2afb1cc5a17ae3ac88b6d6f
|
||||||
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
flutter_avif_ios: 2553d2aafda56339cf924e943da89ad3f40af2dd
|
||||||
|
flutter_background_service_ios: 00d31bdff7b4bfe06d32375df358abe0329cf87e
|
||||||
|
flutter_compass: b236ab69b61545cce89fd58527f401a7587d5cc1
|
||||||
|
nsd_ios: 596ad79109ddd3e52d665f650f36428049e3653e
|
||||||
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 2d56c9747241a29800bf539d978b210bda037878
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
779
ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,779 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
6E07C83B534F125C2CBE6788 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 95A961E69DFB3804DF041D16 /* Pods_Runner.framework */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
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 */; };
|
||||||
|
B117D6FC7E214B5BDF078335 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E125ECD1538D6FD469DAD40 /* Pods_RunnerTests.framework */; };
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
0A6847CB76DAC5A39547F092 /* 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 = "<group>"; };
|
||||||
|
11010AF1B9B58058CB45D836 /* 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 = "<group>"; };
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
2FF0A626EBAC324A9D2CD7C8 /* 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 = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
||||||
|
3B8CF791ABE83A2374700CB3 /* 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 = "<group>"; };
|
||||||
|
5E125ECD1538D6FD469DAD40 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
88D332C6C1988532993F282B /* 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 = "<group>"; };
|
||||||
|
8E8E561633FC42AD799C3EC6 /* 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 = "<group>"; };
|
||||||
|
95A961E69DFB3804DF041D16 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
63128E4122D07543FBC5706D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
B117D6FC7E214B5BDF078335 /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||||
|
6E07C83B534F125C2CBE6788 /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
3C28992F8CC08CA6C1F157CE /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0A6847CB76DAC5A39547F092 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
88D332C6C1988532993F282B /* Pods-Runner.release.xcconfig */,
|
||||||
|
3B8CF791ABE83A2374700CB3 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
8E8E561633FC42AD799C3EC6 /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
|
2FF0A626EBAC324A9D2CD7C8 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
|
11010AF1B9B58058CB45D836 /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
3C28992F8CC08CA6C1F157CE /* Pods */,
|
||||||
|
9C44871016BF8D8E5F823EE9 /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9C44871016BF8D8E5F823EE9 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
95A961E69DFB3804DF041D16 /* Pods_Runner.framework */,
|
||||||
|
5E125ECD1538D6FD469DAD40 /* Pods_RunnerTests.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
E57C8F1FC76A13F8392A3A72 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
63128E4122D07543FBC5706D /* Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
packageProductDependencies = (
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
);
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
142118FEB8CC97859621AD22 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
0F5356670725D838348C6698 /* [CP] Embed Pods Frameworks */,
|
||||||
|
5C71E5FAEF51EAE4BBF3727A /* [CP] Copy Pods Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
packageReferences = (
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||||
|
);
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
0F5356670725D838348C6698 /* [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;
|
||||||
|
};
|
||||||
|
142118FEB8CC97859621AD22 /* [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;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
5C71E5FAEF51EAE4BBF3727A /* [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;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
E57C8F1FC76A13F8392A3A72 /* [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;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 142;
|
||||||
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 8E8E561633FC42AD799C3EC6 /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 142;
|
||||||
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 2FF0A626EBAC324A9D2CD7C8 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 142;
|
||||||
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 11010AF1B9B58058CB45D836 /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 142;
|
||||||
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 142;
|
||||||
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 142;
|
||||||
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
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 = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "dkcamera",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkimagepickercontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "4.3.9",
|
||||||
|
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkphotogallery",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "sdwebimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||||
|
"version" : "5.21.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftygif",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||||
|
"version" : "5.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "tocropviewcontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||||
|
"version" : "2.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
|
}
|
||||||
119
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<PreActions>
|
||||||
|
<ExecutionAction
|
||||||
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
|
<ActionContent
|
||||||
|
title = "Run Prepare Flutter Framework Script"
|
||||||
|
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||||
|
<EnvironmentBuildable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</EnvironmentBuildable>
|
||||||
|
</ActionContent>
|
||||||
|
</ExecutionAction>
|
||||||
|
</PreActions>
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
10
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
59
ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "dkcamera",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkimagepickercontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "4.3.9",
|
||||||
|
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkphotogallery",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "sdwebimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||||
|
"version" : "5.21.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftygif",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||||
|
"version" : "5.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "tocropviewcontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||||
|
"version" : "2.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
|
}
|
||||||
15
ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import UIKit
|
||||||
|
import Flutter
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +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":"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"}}
|
||||||
|
After Width: | Height: | Size: 973 KiB |
|
After Width: | Height: | Size: 589 KiB |
|
After Width: | Height: | Size: 881 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 19 KiB |
6
ios/Runner/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
158
ios/Runner/Info.plist
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.flutter.background.refresh</string>
|
||||||
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>MeshCore SAR</string>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>MBTiles Map File</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.database</string>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>MeshCore SAR</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>142</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs Bluetooth to communicate with MeshCore devices for Search & Rescue operations</string>
|
||||||
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs Bluetooth to communicate with MeshCore devices</string>
|
||||||
|
<key>NSBonjourServices</key>
|
||||||
|
<array>
|
||||||
|
<string>_meshcore-sse._tcp</string>
|
||||||
|
</array>
|
||||||
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs local network access to discover and connect to shared MeshCore devices on your network for team coordination during SAR operations</string>
|
||||||
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs location access for offline map functionality during field operations</string>
|
||||||
|
<key>NSLocationDefaultAccuracyReduced</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSLocationTemporaryPreciseUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs precise location for accurate positioning in SAR operations</string>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs location access to display team members and SAR markers on the map</string>
|
||||||
|
<key>NSMotionUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs access to the compass to show your heading direction on the map</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs microphone access to send voice messages over the mesh radio network during SAR operations</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs camera access to take photos and attach them to SAR messages</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>MeshCore SAR may need access to your photo library to attach images to messages or save map screenshots for documentation during SAR operations</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs permission to save exported screenshots and SAR documentation images to your photo library</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>location</string>
|
||||||
|
<string>bluetooth-central</string>
|
||||||
|
<string>bluetooth-peripheral</string>
|
||||||
|
</array>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportsDocumentBrowser</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIUserNotificationSettings</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIUserNotificationTypesEnabled</key>
|
||||||
|
<array>
|
||||||
|
<string>UIUserNotificationTypeAlert</string>
|
||||||
|
<string>UIUserNotificationTypeBadge</string>
|
||||||
|
<string>UIUserNotificationTypeSound</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>UTImportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.database</string>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>MBTiles Map Archive</string>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.mapbox.mbtiles</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>mbtiles</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
81
ios/fastlane/Fastfile
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# This file contains the fastlane.tools configuration
|
||||||
|
# You can find the documentation at https://docs.fastlane.tools
|
||||||
|
#
|
||||||
|
# For a list of all available actions, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/actions
|
||||||
|
#
|
||||||
|
# For a list of all available plugins, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/plugins/available-plugins
|
||||||
|
#
|
||||||
|
|
||||||
|
# Uncomment the line if you want fastlane to automatically update itself
|
||||||
|
# update_fastlane
|
||||||
|
|
||||||
|
default_platform(:ios)
|
||||||
|
|
||||||
|
platform :ios do
|
||||||
|
def project_root
|
||||||
|
File.expand_path("../..", __dir__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def beta_app_info
|
||||||
|
{
|
||||||
|
"en-US" => {
|
||||||
|
description: "Field-ready SAR coordination over MeshCore with chat, voice, images, offline maps, and live team context.",
|
||||||
|
feedback_email: "hey@dz0ny.dev",
|
||||||
|
marketing_url: "https://github.com/dz0ny/meshcore-sar",
|
||||||
|
privacy_policy_url: "https://github.com/dz0ny/meshcore-sar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def beta_build_info
|
||||||
|
{
|
||||||
|
"en-US" => {
|
||||||
|
whats_new: "Test MeshCore SAR field coordination flows: messaging, maps, contacts, voice, images, and SAR markers."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Push a new release build to the App Store"
|
||||||
|
lane :release do
|
||||||
|
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
||||||
|
build_app(workspace: "Runner.xcworkspace", scheme: "Runner")
|
||||||
|
upload_to_testflight(
|
||||||
|
skip_waiting_for_build_processing: true,
|
||||||
|
localized_app_info: beta_app_info,
|
||||||
|
localized_build_info: beta_build_info,
|
||||||
|
changelog: "Test MeshCore SAR field coordination flows: messaging, maps, contacts, voice, images, and SAR markers."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Push a new beta build to TestFlight"
|
||||||
|
lane :beta do
|
||||||
|
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
||||||
|
build_app(workspace: "Runner.xcworkspace", scheme: "Runner")
|
||||||
|
upload_to_testflight(
|
||||||
|
skip_waiting_for_build_processing: true,
|
||||||
|
localized_app_info: beta_app_info,
|
||||||
|
localized_build_info: beta_build_info,
|
||||||
|
changelog: "Test MeshCore SAR field coordination flows: messaging, maps, contacts, voice, images, and SAR markers."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Capture iOS screenshots for App Store Connect"
|
||||||
|
lane :screenshots do
|
||||||
|
sh("cd #{project_root} && scripts/take_screenshots.sh --ios")
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Upload App Store screenshots"
|
||||||
|
lane :store_assets do
|
||||||
|
upload_to_app_store(
|
||||||
|
skip_binary_upload: true,
|
||||||
|
skip_metadata: true,
|
||||||
|
skip_screenshots: false,
|
||||||
|
submit_for_review: false,
|
||||||
|
force: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
56
ios/fastlane/README.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
fastlane documentation
|
||||||
|
----
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Make sure you have the latest version of the Xcode command line tools installed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xcode-select --install
|
||||||
|
```
|
||||||
|
|
||||||
|
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||||
|
|
||||||
|
# Available Actions
|
||||||
|
|
||||||
|
## iOS
|
||||||
|
|
||||||
|
### ios release
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane ios release
|
||||||
|
```
|
||||||
|
|
||||||
|
Push a new release build to the App Store
|
||||||
|
|
||||||
|
### ios beta
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane ios beta
|
||||||
|
```
|
||||||
|
|
||||||
|
Push a new beta build to TestFlight
|
||||||
|
|
||||||
|
### ios screenshots
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane ios screenshots
|
||||||
|
```
|
||||||
|
|
||||||
|
Capture iOS screenshots for App Store Connect
|
||||||
|
|
||||||
|
### ios store_assets
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane ios store_assets
|
||||||
|
```
|
||||||
|
|
||||||
|
Upload App Store screenshots
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||||
|
|
||||||
|
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||||
|
|
||||||
|
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||||