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).
|
||||