diff --git a/.env.example b/.env.example index 170eef2..9751f28 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,35 @@ # Example .env file for empower_flutter # Copy this file to .env and fill in your actual values +# ======================================== +# Sentry Core Configuration +# ======================================== SENTRY_AUTH_TOKEN=your_auth_token SENTRY_DSN=your_dsn -SENTRY_RELEASE=your_release_version_or_git_sha +SENTRY_RELEASE=empower_flutter@9.14.0+1 # Format: appname@version+build SENTRY_ENVIRONMENT=your_environment + +# ======================================== +# Sentry Organization & Project +# ======================================== +SENTRY_ORG=your-org-slug +SENTRY_PROJECT=your-project-slug + +# ======================================== +# Optional: Size Analysis +# ======================================== +# Enable automatic build size analysis uploads during release builds +# When enabled, APK/AAB files are automatically uploaded to Sentry +# for size tracking and optimization insights +# Requires sentry-cli to be installed (brew install sentry-cli) +# Set to 'false' or comment out to disable (useful for faster local builds) +SENTRY_SIZE_ANALYSIS_ENABLED=true + +# ======================================== +# Optional: CI/CD Metadata +# ======================================== +# These are typically auto-detected in CI environments +# CI_COMMIT_SHA=abc123 +# CI_MERGE_REQUEST_DIFF_BASE_SHA=def456 +# CI_COMMIT_REF_NAME=feature-branch +# GITHUB_PR_NUMBER=42 diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md new file mode 100644 index 0000000..0f0dc88 --- /dev/null +++ b/BUILD_GUIDE.md @@ -0,0 +1,377 @@ +# Build Guide - Multi-Platform Flutter with Sentry + +This guide explains how to use the unified `demo.sh` script to build your Flutter application for all platforms with automatic Sentry release management and instrumentation. + +## Quick Start + +```bash +# Verify setup +./demo.sh verify + +# Build for Android APK +./demo.sh build android + +# Build for Android App Bundle (preferred) +./demo.sh build aab + +# Build for iOS +./demo.sh build ios + +# Build for Web +./demo.sh build web + +# Run the app +./demo.sh run android +``` + +## Complete Usage + +```bash +./demo.sh build [platform] [build-type] +./demo.sh run [platform] +./demo.sh verify +``` + +### Supported Platforms + +| Platform | Command | OS Requirement | Output Location | +|----------|---------|----------------|-----------------| +| Android APK | `./demo.sh build android` | Any | `build/app/outputs/flutter-apk/app-release.apk` | +| Android AAB | `./demo.sh build aab` | Any | `build/app/outputs/bundle/release/app-release.aab` | +| iOS | `./demo.sh build ios` | macOS only | `build/ios/iphoneos/Runner.app` | +| Web | `./demo.sh build web` | Any | `build/web/` | +| macOS | `./demo.sh build macos` | macOS only | `build/macos/Build/Products/Release/empower_flutter.app` | +| Linux | `./demo.sh build linux` | Linux preferred | `build/linux/x64/release/bundle/` | +| Windows | `./demo.sh build windows` | Windows only | `build/windows/x64/runner/Release/` | + +### Build Types + +| Type | Command | Description | Obfuscation | Release Management | +|------|---------|-------------|-------------|-------------------| +| Release | `./demo.sh build android` | Production build | ✅ Yes | ✅ Yes | +| Profile | `./demo.sh build android profile` | Performance profiling | ❌ No | ❌ No | +| Debug | `./demo.sh build android debug` | Development build | ❌ No | ❌ No | + +**Default:** Release build with obfuscation, symbol upload, and automatic release management + +## What the Script Does + +### 1. Environment Setup +- Checks for Flutter installation +- Loads environment variables from `.env` file +- Validates platform compatibility + +### 2. Dependency Management +- Runs `flutter pub get` to ensure all dependencies are up to date + +### 3. Platform Build +- Executes platform-specific build command +- For **release builds**: + - Enables code obfuscation (`--obfuscate`) + - Generates debug symbols (`--split-debug-info`) + - Includes Sentry environment variables +- For **debug/profile builds**: + - Standard build without obfuscation + +### 4. Sentry Integration (Release Only) +- Automatically uploads debug symbols to Sentry +- Uploads source maps (for web) +- Associates symbols with release version +- **Optional:** Uploads builds for size analysis (when enabled) + +## Platform-Specific Examples + +### Android Development + +```bash +# Debug build for testing +./demo.sh build android debug + +# Release build for distribution +./demo.sh build android release +``` + +After building, install on emulator: +```bash +# Find the APK +find . -name '*.apk' + +# Drag and drop to emulator, or use adb: +adb install build/app/outputs/flutter-apk/app-release.apk +``` + +### iOS Development + +```bash +# Debug build +./demo.sh build ios debug + +# Release build +./demo.sh build ios release +``` + +Then open in Xcode: +```bash +open ios/Runner.xcworkspace +``` + +### Web Development + +```bash +# Build for web +./demo.sh build web release + +# Serve locally for testing +cd build/web +python3 -m http.server 8000 +``` + +Open browser to `http://localhost:8000` + +### macOS Desktop + +```bash +# Build macOS app +./demo.sh build macos release +``` + +Run the app: +```bash +open build/macos/Build/Products/Release/empower_flutter.app +``` + +### Building for All Platforms + +```bash +# Build everything available on your OS +./demo.sh build all release +``` + +**On macOS:** Builds Android, iOS, Web, and macOS +**On Linux:** Builds Android, Web, and Linux +**On Windows:** Builds Android, Web, and Windows + +## Configuring Sentry Symbol Upload + +The script automatically uploads symbols for release builds if Sentry is configured. + +### Option 1: Using .env (Recommended) + +Add to your `.env` file: +```bash +SENTRY_DSN=https://your-key@o0.ingest.sentry.io/0000000 +SENTRY_RELEASE=myapp@9.14.0+1 +SENTRY_ENVIRONMENT=production +``` + +### Option 2: Using sentry.properties + +Create `sentry.properties`: +```properties +org=your-org-slug +project=your-project-name +auth_token=your-sentry-auth-token +``` + +### Creating a Sentry Auth Token + +1. Visit: https://sentry.io/settings/account/api/auth-tokens/ +2. Click "Create New Token" +3. Name: "Flutter Debug Symbol Upload" +4. Scopes: `project:releases` (or `project:write`) and `org:read` +5. Copy the token to `sentry.properties` + +## Size Analysis + +Monitor your app's build sizes to prevent regressions and optimize downloads. + +### Enable Size Analysis + +**Prerequisites:** +- Install Sentry CLI: `curl -sL https://sentry.io/get-cli/ | bash` +- Configure Sentry auth token (see above) + +**Configuration:** + +Add to your `.env` file: +```bash +SENTRY_SIZE_ANALYSIS_ENABLED=true +SENTRY_ORG=your-org-slug +SENTRY_PROJECT=your-project-slug +``` + +**Usage:** + +```bash +# Build with size analysis +./demo.sh build android release +``` + +The script will automatically: +1. Build your app +2. Upload debug symbols +3. **Upload build for size analysis** + +**Output:** +``` +✓ Android APK built successfully +ℹ APK location: build/app/outputs/flutter-apk/app-release.apk + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Uploading Android Build for Size Analysis +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +✓ Size analysis data uploaded successfully +ℹ View results: https://sentry.io/organizations/your-org/projects/your-project/size-analysis/ +``` + +**Supported Platforms:** +- ✅ **Android** - APK/AAB automatic upload +- ⚠️ **iOS** - Requires manual IPA creation and upload (see [SIZE_ANALYSIS_GUIDE.md](SIZE_ANALYSIS_GUIDE.md)) + +For detailed instructions and CI/CD integration, see the [Size Analysis Guide](SIZE_ANALYSIS_GUIDE.md). + +## Troubleshooting + +### Flutter Not Found + +``` +✗ Flutter is not installed or not in PATH +``` + +**Solution:** Install Flutter or add to PATH: +```bash +export PATH="$PATH:/path/to/flutter/bin" +``` + +### iOS Build on Non-macOS + +``` +✗ iOS builds require macOS +``` + +**Solution:** iOS and macOS builds can only be performed on macOS systems. + +### Symbol Upload Failed + +``` +✗ Failed to upload debug symbols +``` + +**Solutions:** +1. Check `sentry.properties` configuration +2. Verify auth token has correct scopes +3. Ensure `SENTRY_DSN` is set in `.env` +4. Check network connectivity + +### Missing Dependencies + +``` +Error: Package not found +``` + +**Solution:** +```bash +flutter pub get +flutter clean +./demo.sh build android +``` + +## Build Script Features + +### ✅ Automatic Features + +- Dependency installation +- Environment variable loading +- Platform compatibility checking +- Colored output for easy reading +- Error handling and validation +- Build location reporting + +### ✅ Release Build Features + +- Code obfuscation +- Debug symbol generation +- Sentry symbol upload +- Source map generation (web) +- Release version tagging + +### ✅ Multi-Platform Support + +- Cross-platform compatible +- Platform-specific optimizations +- Intelligent fallback handling +- OS detection and validation + +## Advanced Usage + +### Custom Build Flags + +Edit `run.sh` to add custom flags: + +```bash +# Example: Add custom build name +flutter build android \ + --$BUILD_TYPE \ + --build-name=2.0.0 \ + --build-number=42 \ + --obfuscate \ + --split-debug-info=$DEBUG_INFO_PATH +``` + +### Environment Variables + +Available environment variables: +- `SENTRY_DSN` - Sentry Data Source Name +- `SENTRY_RELEASE` - Release version +- `SENTRY_ENVIRONMENT` - Environment (production, staging, etc.) + +### CI/CD Integration + +Use in CI/CD pipelines: + +```yaml +# GitHub Actions example +- name: Build Android Release + run: ./demo.sh build android release + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_RELEASE: ${{ github.ref_name }} + SENTRY_ENVIRONMENT: production +``` + +## Performance Tips + +### Build Speed + +- Use `debug` builds during development +- Use `profile` builds for performance testing +- Use `release` builds for distribution only + +### Build Artifacts + +Clean build artifacts periodically: +```bash +flutter clean +rm -rf build/ +./demo.sh build android +``` + +### Parallel Builds + +On multi-core systems, Flutter automatically uses parallel builds. For faster builds: +```bash +# Clear cache and rebuild +flutter clean +flutter pub get +./demo.sh build all +``` + +## Summary + +The enhanced `run.sh` script provides a unified, automated build system for Flutter applications with full Sentry integration. It handles all platform-specific configurations, symbol uploads, and build optimizations automatically. + +For questions or issues, refer to: +- [Flutter Documentation](https://docs.flutter.dev) +- [Sentry Flutter Documentation](https://docs.sentry.io/platforms/flutter/) +- Project README.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e2a8762 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,546 @@ +# Empower Plant - Flutter Sentry Demo Application + +## Project Overview + +**Application:** Empower Plant (`empower_flutter`) +**Version:** 9.14.0+1 (matches Sentry SDK version) +**Purpose:** Production-ready Flutter e-commerce app with comprehensive Sentry instrumentation demonstrating best practices for error monitoring, performance tracking, session replay, and user feedback. + +**Type:** Full-featured plant shopping app + Sentry demo platform +**Platforms:** iOS, Android, Web, macOS, Linux, Windows + +--- + +## Critical Information + +### Branch & Git Status +- **Current Branch:** `feature/comprehensive-sentry-integration` +- **Main Branch:** `main` +- **Latest Commit:** `bf17e76` - Refactor TTFD tracking to use SentryDisplayWidget strategically + +### Environment Configuration +- Secrets in `.env` (git-ignored, based on `.env.example`) +- Engineer ID in `lib/se_config.dart` for event separation +- Sentry config: DSN, ORG, PROJECT, AUTH_TOKEN required + +### Version Management +- **Format:** `package@version+build` (e.g., `com.example.empower_flutter@9.14.0+1`) +- Version source: `pubspec.yaml` +- Must match Sentry SDK version for consistency +- Distribution set to build number (currently `'1'`) + +--- + +## Architecture Overview + +### Directory Structure +``` +lib/ +├── main.dart # Entry point, home page, global setup +├── sentry_setup.dart # Comprehensive Sentry init (280+ lines) +├── se_config.dart # Engineer identifier +├── navbar_destination.dart # Navigation drawer + error triggers +├── product_list.dart # Home screen, catalog, demo triggers (750+ lines) +├── product_details.dart # Product detail view +├── cart.dart # Shopping cart +├── checkout.dart # Checkout flow with metrics/logging +└── models/ + └── cart_state_model.dart # Provider-based cart state + +assets/ +├── images/ # Plant product images +├── config/ # Feature configuration JSONs +└── docs/ # Dummy documentation files + +demo.sh # Unified build script for all platforms +``` + +### Key Files & Purposes + +| File | Lines | Purpose | +|------|-------|---------| +| `lib/main.dart` | ~500 | App initialization, home page, navigation, TTFD setup | +| `lib/sentry_setup.dart` | 280+ | Complete Sentry configuration, all features enabled | +| `lib/product_list.dart` | 750+ | Product catalog, API fetching, error/perf triggers | +| `lib/checkout.dart` | ~500 | Checkout flow, metrics, structured logging | +| `demo.sh` | ~940 | Build automation, release management, symbol upload | + +--- + +## Technology Stack + +### Core Dependencies +```yaml +Flutter SDK: >= 3.22.0 < 4.0.0 +Dart SDK: >= 3.5.0 < 4.0.0 + +# Sentry +sentry_flutter: ^9.14.0 # Main SDK +sentry_dio: ^9.14.0 # HTTP client integration +sentry_file: ^9.14.0 # File I/O tracking +sentry_logging: ^9.14.0 # Logging integration + +# State Management & Utils +provider: ^6.1.5 # State management +flutter_dotenv: ^6.0.0 # Environment variables +dio: ^5.9.1 # HTTP client +logging: ^1.3.0 # Structured logging +``` + +### External Services +- **Backend API:** `https://flask.empower-plant.com/` (products, checkout) +- **Sentry:** Error monitoring, performance, session replay +- **Sentry CLI:** Optional for symbol upload and size analysis + +--- + +## Sentry Integration - Complete Reference + +### Initialization Flow +1. `SentryWidgetsFlutterBinding.ensureInitialized()` - Set up bindings +2. `dotenv.load()` - Load environment variables +3. `initSentry()` - Full Sentry initialization from `sentry_setup.dart` +4. Global scope tags: app name, platform, OS, locale, screen size + +### Configuration - All Features Enabled (Demo Mode) + +```dart +// Sampling (100% for demo) +sampleRate: 1.0 # All errors captured +tracesSampleRate: 1.0 # All performance traces +profilesSampleRate: 1.0 # All profiling (iOS/macOS/Android) + +// Session Replay (100% for demo) +replay.onErrorSampleRate: 1.0 # All error sessions +replay.sessionSampleRate: 1.0 # All normal sessions + +// Performance Tracking +enableAutoPerformanceTracing: true # Automatic spans +enableTimeToFullDisplayTracing: true # TTFD/TTID tracking +enableUserInteractionTracing: true # Tap/swipe tracking +enableUserInteractionBreadcrumbs: true # User action breadcrumbs + +// Attachments +attachStacktrace: true # Stack traces on all events +attachScreenshot: false # Disabled for demo +attachViewHierarchy: true # UI hierarchy +attachThreads: true # Thread info + +// Crash Handling +enableNativeCrashHandling: true # Native crash capture +enableNdkScopeSync: true # Android NDK scope sync +reportSilentFlutterErrors: true # Report ErrorWidget errors +anrEnabled: true # Android ANR detection (5s) +anrTimeoutInterval: Duration(seconds: 5) +appHangTimeoutInterval: Duration(seconds: 2) # iOS/macOS hang detection +enableWatchdogTerminationTracking: true # iOS/macOS watchdog + +// Logging & Metrics +enableLogs: true # Structured logs to Sentry +enableMetrics: true # Custom metrics support +maxBreadcrumbs: 100 # Breadcrumb limit +enableAutoNativeBreadcrumbs: true # Native breadcrumbs + +// Privacy (Demo settings) +sendDefaultPii: true # Send PII for demo environment +``` + +### Custom Hooks & Privacy + +**beforeSend Hook:** +- Adds `se` tag from engineer config for per-developer separation +- Sets fingerprint: `['{{ default }}', 'se:$se']` + +**Session Replay Privacy Masking:** +- Masks Text widgets containing BOTH a financial label AND dollar sign +- Financial labels: `items (`, `shipping & handling`, `total before tax`, `estimated tax`, `order total`, `subtotal` +- Ensures labels remain visible, only values with $ are masked +- Everything else in replays is visible + +### Integrations Enabled + +1. **SentryNavigatorObserver** - Navigation tracking, automatic TTID +2. **SentryHttpClient** - HTTP request tracing +3. **SentryDio** - Dio HTTP client integration (`dio.addSentry()`) +4. **SentryFile** - File I/O instrumentation (`.sentryTrace()`) +5. **LoggingIntegration** - Captures `Logger()` calls as breadcrumbs/events +6. **Spotlight** - Local debugging UI (enabled in `kDebugMode`) + +### TTFD/TTID Implementation + +**TTID (Time to Initial Display):** +- Automatic via `SentryNavigatorObserver` +- Triggered when route becomes visible + +**TTFD (Time to Full Display):** +- Manual via `SentryDisplayWidget.of(context).reportFullyDisplayed()` +- Wrap screen in `SentryDisplayWidget` widget +- Call `reportFullyDisplayed()` when content is fully loaded +- Currently implemented on home page product list + +### Engineer Separation Pattern + +- Each developer sets their name in `lib/se_config.dart`: `const se = 'your-name';` +- Added as tag on all events: `se: your-name` +- Added to fingerprint for per-engineer issue grouping +- Allows multiple engineers to use same Sentry project without interference + +--- + +## Code Patterns & Conventions + +### State Management +- **Pattern:** Provider with `ChangeNotifier` +- **Cart State:** `CartModel` extends `ChangeNotifier` +- **UI Updates:** `Consumer` for reactive updates +- **Global Keys:** `navigatorKey` for programmatic navigation + +### Navigation +- **Named Routes:** `/productDetails`, `/checkout` +- **Arguments:** Custom classes (`ProductArguments`, `CheckoutArguments`) +- **Observer:** `SentryNavigatorObserver` tracks all navigation + +### Error Handling +```dart +try { + // Operation +} catch (error, stackTrace) { + await Sentry.captureException(error, stackTrace: stackTrace); + // Show user feedback dialog if needed +} +``` + +### Performance Instrumentation +```dart +// Transaction +final transaction = Sentry.startTransaction('operation_name', 'operation_type'); + +// Span +final span = transaction.startChild('child_operation', description: 'Details'); +// ... work ... +await span.finish(); + +await transaction.finish(status: SpanStatus.ok()); +``` + +### Structured Logging +```dart +// Setup +final log = Logger('ComponentName'); + +// Usage with Sentry.logger.fmt (captures to Sentry) +Sentry.logger.fmt.info('User %s clicked button', [userId], + attributes: { + 'user_id': SentryLogAttribute.string(userId), + 'button_name': SentryLogAttribute.string('checkout'), + } +); + +// Standard logging (captured as breadcrumbs via LoggingIntegration) +log.info('Standard log message'); +log.severe('Error occurred', error, stackTrace); +``` + +### Metrics +```dart +// Counter +Sentry.metrics.count('promo_code_attempts', 1, + attributes: {'code': code, 'result': 'failed'} +); + +// Gauge (current value) +Sentry.metrics.gauge('order_value', total, + unit: SentryMetricUnit.none +); + +// Distribution (statistical measurement) +Sentry.metrics.distribution('api_latency', latencyMs, + unit: SentryMetricUnit.millisecond +); +``` + +### HTTP Requests +```dart +// Dio integration (automatic tracing) +final dio = Dio(); +dio.addSentry(); // Done in sentry_setup.dart + +// SentryHttpClient (automatic tracing) +final client = SentryHttpClient(); +await client.get(Uri.parse('https://example.com')); +``` + +--- + +## Build System - demo.sh + +### Commands + +```bash +# Build for platform (default: release) +./demo.sh build [platform] [build-type] + +# Run on device and create Sentry deploy +./demo.sh run [platform] + +# Verify setup (Flutter, Sentry CLI, .env) +./demo.sh verify + +# Show help +./demo.sh help +``` + +### Supported Platforms + +| Platform | Command | Output Location | +|----------|---------|-----------------| +| Android APK | `./demo.sh build android` | `build/app/outputs/flutter-apk/app-release.apk` | +| Android AAB | `./demo.sh build aab` | `build/app/outputs/bundle/release/app-release.aab` | +| iOS | `./demo.sh build ios` | `build/ios/iphoneos/Runner.app` | +| Web | `./demo.sh build web` | `build/web/` | +| macOS | `./demo.sh build macos` | `build/macos/Build/Products/Release/` | +| Linux | `./demo.sh build linux` | `build/linux/x64/release/bundle/` | +| Windows | `./demo.sh build windows` | `build/windows/x64/runner/Release/` | + +### Build Types +- `release` (default) - Obfuscation, symbol upload, release management +- `profile` - No obfuscation, no release management +- `debug` - No obfuscation, no release management + +### Build Process (Release Mode) + +1. Validates environment (.env variables) +2. Checks Flutter and Sentry CLI installation +3. Runs `flutter pub get` +4. Builds with obfuscation and debug symbol generation +5. Creates Sentry release: `sentry-cli releases new` +6. Uploads debug symbols and source maps +7. (Optional) Uploads ProGuard mapping for Android +8. (Optional) Uploads build for size analysis +9. Finalizes release: `sentry-cli releases finalize` + +### Environment Variables Required + +```bash +SENTRY_AUTH_TOKEN=sntryu_xxx # Sentry auth token +SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx +SENTRY_RELEASE=com.example.empower_flutter@9.14.0+1 +SENTRY_ENVIRONMENT=development # development/staging/production +SENTRY_ORG=your-org-slug +SENTRY_PROJECT=your-project-slug +SENTRY_SIZE_ANALYSIS_ENABLED=true # Optional: enable size tracking +``` + +--- + +## Demo Features & Error Triggers + +### Available Error Types (via Drawer Menu) + +**Dart Errors:** +- Dart Exception - Simple throw +- Timeout Exception - Simulated timeout +- Platform Exception - Native communication error +- Missing Plugin Exception - Plugin not found +- Assertion Error - Failed assertion +- State Error - Invalid state +- Range Error - Out of bounds +- Type Error - Type mismatch + +**Native Errors:** +- C++ Segfault - Native crash via method channel +- Kotlin Exception - Android-only native exception + +### Performance Issues (Auto-triggered on Home) + +**Main Thread Blocking:** +- Database query simulation (2M iterations) +- File I/O on main thread +- JSON decoding (15k items) +- Image decoding on main thread +- Complex regex operations + +**Async Issues:** +- N+1 API calls (15 sequential requests) +- Function regression (500ms delay) +- Frame drops (rapid setState calls) + +**ANR/Hangs:** +- Android ANR: 10-second freeze +- iOS/macOS App Hang: 3-second freeze + +### Checkout Flow Instrumentation + +- Promo code validation with metrics +- API latency tracking with distributions +- Structured logging with attributes +- Order value gauge metrics +- User feedback collection on errors + +--- + +## Development Workflow + +### Initial Setup + +1. Clone repository +2. Copy `.env.example` to `.env` +3. Fill in Sentry credentials (DSN, ORG, PROJECT, AUTH_TOKEN) +4. Update engineer name in `lib/se_config.dart` +5. Run `./demo.sh verify` to check configuration +6. Run `./demo.sh build android` (or your target platform) + +### Daily Development + +```bash +# Get dependencies +flutter pub get + +# Run in debug mode (no Sentry release management) +flutter run -d + +# Verify code quality +flutter analyze + +# Build for testing +./demo.sh build android debug + +# Build and run with release management +./demo.sh build android +./demo.sh run android +``` + +### Testing Sentry Features + +1. **Error Tracking:** Use drawer menu to trigger errors +2. **Performance:** Navigate app, check Performance tab in Sentry +3. **Session Replay:** Perform actions, check replay in Sentry Issues +4. **TTFD:** Navigate to home, wait for products to load +5. **Metrics:** Complete checkout flow, check Metrics in Sentry +6. **Logs:** Check Logs section in Sentry Issues +7. **Spotlight:** Run in debug mode, visit `http://localhost:8969` for local Sentry events + +### Common Tasks + +**Update Version:** +1. Edit `pubspec.yaml` - change version +2. Update `.env` - change `SENTRY_RELEASE` +3. Commit changes +4. Build: `./demo.sh build android` + +**Add New Error Trigger:** +1. Add method in `navbar_destination.dart` +2. Add drawer item with trigger +3. Implement error scenario +4. Use `Sentry.captureException()` to capture + +**Add Performance Instrumentation:** +1. Start transaction: `Sentry.startTransaction()` +2. Add spans for sub-operations +3. Finish with appropriate status +4. Add metrics if needed + +--- + +## Important Conventions + +### File Editing +- **NEVER** use Bash commands for file operations (cat, sed, awk, echo) +- **ALWAYS** use dedicated tools: Read, Edit, Write for files +- **NEVER** run `grep` or `find` commands - use Grep and Glob tools + +### Git Workflow +- **Check with user** before destructive operations (force push, reset --hard, etc.) +- **Create NEW commits** after hook failures (not amend) +- **Add files by name** when staging, avoid `git add -A` +- **Never skip hooks** (--no-verify) unless explicitly requested + +### Code Style +- Follow `analysis_options.yaml` linting rules +- Keep functions focused and single-purpose +- Use meaningful variable names +- Add comments only where logic isn't self-evident +- Don't add features beyond what's requested + +### Sentry Best Practices +- Set `se` tag consistently from `se_config.dart` +- Use structured logging with attributes for searchability +- Add context to transactions with tags/data +- Use appropriate span operations for clarity +- Capture user feedback on critical errors + +--- + +## Size Analysis (Optional Feature) + +**Purpose:** Track APK/AAB file size over time in Sentry + +**Enable:** +```bash +# In .env +SENTRY_SIZE_ANALYSIS_ENABLED=true +``` + +**Requirements:** +- Sentry CLI installed (`brew install sentry-cli`) +- Auth token configured +- Release build (not debug/profile) + +**Automatic Uploads:** +- APK: `build/app/outputs/flutter-apk/app-release.apk` +- AAB: `build/app/outputs/bundle/release/app-release.aab` +- ProGuard mapping: `build/app/outputs/mapping/release/mapping.txt` + +**Disable:** Set to `false` or comment out in `.env` for faster local builds + +--- + +## Troubleshooting + +### Build Fails +- Run `./demo.sh verify` to check setup +- Ensure `.env` exists with valid credentials +- Check Flutter installation: `flutter doctor` +- Clean build: `flutter clean && flutter pub get` + +### Sentry Events Not Appearing +- Verify DSN in `.env` +- Check Sentry project settings +- Verify sampling rates (should be 1.0 for demo) +- Check Spotlight in debug mode: `http://localhost:8969` + +### Symbol Upload Fails +- Verify `SENTRY_AUTH_TOKEN` has upload permissions +- Check `SENTRY_ORG` and `SENTRY_PROJECT` are correct +- Ensure release was created before symbol upload + +### TTFD Not Working +- Ensure screen is wrapped in `SentryDisplayWidget` +- Call `reportFullyDisplayed()` when content is ready +- Check that `enableTimeToFullDisplayTracing = true` + +--- + +## Quick Reference + +### Key URLs +- **Backend API:** `https://flask.empower-plant.com/` +- **Spotlight (Debug):** `http://localhost:8969/` +- **GitHub (Sentry SDK):** `https://github.com/getsentry/sentry-dart` + +### Important Version Numbers +- **App Version:** 9.14.0+1 +- **Flutter SDK:** >= 3.22.0 +- **Sentry SDK:** ^9.14.0 + +### File Locations +- **Config:** `.env`, `lib/se_config.dart`, `pubspec.yaml` +- **Sentry Setup:** `lib/sentry_setup.dart` +- **Build Output:** `build/` directory +- **Debug Symbols:** `build/debug-info/`, `build/app/obfuscation.map.json` + +--- + +*Last Updated: Session creating this CLAUDE.md* +*Current Branch: feature/comprehensive-sentry-integration* +*App Version: 9.14.0+1* diff --git a/README.md b/README.md index 8f1126c..70c9b7f 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,363 @@ -# empower_flutter +# Empower Plant - Flutter Sentry Demo -A demo Flutter application with full Sentry instrumentation and best practices for error monitoring, performance, and user feedback. +A Flutter e-commerce application showcasing comprehensive Sentry instrumentation for error monitoring, performance tracking, session replay, and user feedback. -## Getting Started +**For Solution Engineers:** This guide focuses on **Android** setup, which has been fully tested. iOS and other platforms have not been validated yet. + +--- + +## Quick Start for Solution Engineers ### Prerequisites -- [Flutter SDK >= 3.24.0 (Dart >= 3.5.0)](https://docs.flutter.dev/get-started/install/macos/desktop) -- Xcode (for iOS simulator) -- Android Studio (for Android emulator) -- Sentry account (for error monitoring) +1. **Flutter SDK** >= 3.22.0 ([Install Guide](https://docs.flutter.dev/get-started/install)) +2. **Android Studio** or Android SDK command-line tools +3. **Android Emulator** or physical device +4. **Sentry Account** with a project created +5. **(Optional) Sentry CLI** for size analysis: `brew install sentry-cli` (macOS) or [other platforms](https://docs.sentry.io/cli/installation/) -### Setup +### Initial Setup (5 minutes) -1. **Clone the repository:** - ```sh +1. **Clone and navigate:** + ```bash git clone https://github.com/sentry-demos/flutter.git cd flutter ``` -2. **Install dependencies:** - ```sh - flutter pub get + +2. **Configure your Sentry credentials:** + ```bash + cp .env.example .env + ``` + + Edit `.env` with your Sentry project details: + ```bash + SENTRY_AUTH_TOKEN=sntryu_your_token_here + SENTRY_DSN=https://your_key@o123456.ingest.us.sentry.io/123456 + SENTRY_RELEASE=com.example.empower_flutter@9.14.0+1 + SENTRY_ENVIRONMENT=development + SENTRY_ORG=your-org-slug + SENTRY_PROJECT=your-project-slug + SENTRY_SIZE_ANALYSIS_ENABLED=true # Optional ``` -3. **Configure Sentry credentials:** - - Copy `.env.example` to `.env` and set your SENTRY_DSN, SENTRY_RELEASE, SENTRY_ENVIRONMENT. - - Each engineer should set their identifier in `lib/se_config.dart`: - ```dart - const String se = 'YourName'; - ``` -### Building and Running with `run.sh` +3. **Set your engineer identifier:** -You can use the provided `run.sh` script to build the app and upload debug symbols/source context to Sentry automatically. + Edit `lib/se_config.dart`: + ```dart + const String se = 'your-name'; // Replace with your name + ``` -**Important:** For readable stacktraces in Sentry, do NOT use `flutter run` to launch the app in the simulator/emulator. Instead, build the release APK and install it manually: + This tags all your Sentry events with your identifier, allowing multiple SEs to use the same project without interference. -- **To build for Android:** - ```sh - ./run.sh - ``` - After the build completes, run: - ``` - find . -name '*.apk' - ``` - to locate the apk, e.g.: - `android/build/app/outputs/apk/release/app.release.apk` - Drag and drop this APK into your Android emulator to install and run the app. +4. **Verify setup:** + ```bash + ./demo.sh verify + ``` -This ensures Sentry receives proper symbol files and can display readable stacktraces for errors. +### Building for Android (Recommended Method) -### Running on iOS Simulator (manual) +Use the unified build script which handles everything automatically: -For iOS, you can still use Xcode or the simulator, but symbolication may be limited unless you build and deploy a release build with symbols. +```bash +# Build Android APK with full Sentry integration +./demo.sh build android -1. Open the project in Xcode or run: - ```sh - flutter run -d ios - ``` -2. If you see build errors, ensure CocoaPods are installed and run: - ```sh - cd ios && pod install && cd .. - flutter clean && flutter pub get - flutter run -d ios +# Output location: +# build/app/outputs/flutter-apk/app-release.apk +``` + +**What the script does:** +- ✅ Creates Sentry release +- ✅ Builds APK with obfuscation +- ✅ Uploads debug symbols for readable stack traces +- ✅ Uploads ProGuard mapping (Android) +- ✅ (Optional) Uploads build for size analysis +- ✅ Finalizes release + +### Installing on Android Emulator + +**Method 1: Drag & Drop (Easiest)** +1. Start your Android emulator (via Android Studio or `emulator -avd `) +2. Locate the APK: `build/app/outputs/flutter-apk/app-release.apk` +3. Drag and drop the APK file into the emulator window +4. App will install automatically + +**Method 2: ADB Install** +```bash +adb install build/app/outputs/flutter-apk/app-release.apk +``` + +**Method 3: Run Script (Build + Install + Create Deploy)** +```bash +./demo.sh run android +``` +This builds, installs, launches the app, and creates a Sentry deploy marker. + +### Installing on Physical Android Device + +1. Enable USB debugging on your device: + - Settings → About Phone → Tap "Build Number" 7 times + - Settings → Developer Options → Enable USB Debugging + +2. Connect device via USB and run: + ```bash + ./demo.sh run android ``` -### Running on Android Emulator (manual) +--- + +## Testing Sentry Features + +### 1. Error Tracking + +All errors appear in your Sentry Issues dashboard with: +- Full stack traces (readable thanks to uploaded symbols) +- Session replay showing what led to the error +- Device context and breadcrumbs +- Your engineer tag (`se:your-name`) + +### 2. Performance Monitoring + +- **TTID (Time to Initial Display)** - Automatic on all screens +- **TTFD (Time to Full Display)** - Manual, measured when content fully loads +- **HTTP Requests** - API calls with timing +- **User Interactions** - Taps, swipes, navigation +- **Database Operations** - Simulated performance issues + +Navigate through the app (Home → Product Details → Cart → Checkout) and check the Performance tab in Sentry. + +### 3. Session Replay + +- Trigger any error from the drawer menu +- Go to Sentry Issues → Click on the error +- View the attached session replay showing user actions leading up to the error +- Financial information (prices in checkout) is automatically masked for privacy + +### 4. Metrics & Logging + +Complete a checkout flow to see: +- **Structured logs** with searchable attributes +- **Custom metrics** (counters, gauges, distributions) +- **API latency tracking** +- **Promo code validation attempts** + +View in Sentry → Metrics and Logs sections. + +### 5. ANR Detection (Android-Specific) + +Trigger from drawer menu: **ANR (Android)** +- Freezes main thread for 10 seconds +- Creates ANR event in Sentry with thread states +- Shows which operations were blocking + +--- + +## Demo Features Reference -**Recommended:** Use the APK from the release build as described above for best Sentry stacktrace results. +### Error Triggers (All Platforms) +- Dart Exception, Timeout, Platform, State, Range, Type errors +- Assertion failures +- Missing plugin exceptions -## Sentry Instrumentation & Features +### Performance Issues (Auto-triggered on Home Screen) +- File I/O on main thread -- **Sentry SDK Integration:** +### Platform-Specific (Android) +- C++ segfault via method channel +- Kotlin exceptions +- ANR detection (5-second threshold) +- ProGuard obfuscation with symbol upload - - Uses `sentry_flutter` for Dart/Flutter error and performance monitoring. - - Sentry is initialized in `lib/sentry_setup.dart` with best practices. - - Credentials and environment are loaded from `.env` and Dart environment variables. +--- -- **Error Tagging & Grouping:** +## Build Outputs & Artifacts - - All Sentry events are tagged with an engineer identifier (`se` tag) from `lib/se_config.dart`. - - Events are fingerprinted for per-engineer grouping. +After running `./demo.sh build android`: -- **User Feedback Popup:** +``` +build/app/outputs/ +├── flutter-apk/ +│ └── app-release.apk # Install this on emulator/device +├── bundle/release/ +│ └── app-release.aab # For Google Play Store +└── mapping/release/ + └── mapping.txt # ProGuard mapping (auto-uploaded) +``` - - Custom feedback dialog appears for Dart exceptions and checkout errors. - - Feedback is sent to Sentry and associated with the event. +Debug symbols location: +- Obfuscation map: `build/app/obfuscation.map.json` +- Debug info: `build/debug-info/` -- **Native Exception Filtering:** +All symbols are automatically uploaded to Sentry by the build script. - - Native exceptions (C++ segfaults, Kotlin exceptions, ANRs) are filtered and do not trigger the feedback popup. +## Size Analysis (Optional) -- **Performance Monitoring:** +Track your app's build size over time in Sentry. - - Tracing is enabled (`tracesSampleRate = 1.0`). - - Profiling is enabled for iOS/macOS (`profilesSampleRate = 1.0`). +**Setup:** +1. Install Sentry CLI: `brew install sentry-cli` (or [other methods](https://docs.sentry.io/cli/installation/)) +2. Ensure `SENTRY_SIZE_ANALYSIS_ENABLED=true` in `.env` +3. Build: `./demo.sh build android` -- **Release Health & Environment:** +View results at: +``` +https://sentry.io/organizations/your-org/projects/your-project/size-analysis/ +``` - - Release and environment are set for Sentry events. +Both APK and AAB are uploaded with detailed DEX breakdown (thanks to ProGuard mapping). -- **Screenshot & View Hierarchy Attachments:** +For more details, see [SIZE_ANALYSIS_GUIDE.md](SIZE_ANALYSIS_GUIDE.md). - - Errors include screenshots and view hierarchy for better debugging. +--- -- **Breadcrumbs & Device Data:** +## Troubleshooting - - Automatic breadcrumbs and device data are captured. +### Build fails with "Flutter not found" +```bash +# Ensure Flutter is in PATH +flutter doctor +``` -- **Debug Symbol & Source Context Upload:** +### "No devices found" when running +```bash +# List available devices +flutter devices - - Uses `sentry_dart_plugin` and `run.sh` for automated symbol/source uploads. +# Start Android emulator +emulator -list-avds +emulator -avd +``` -- **PII & Logging:** - - `sendDefaultPii = true` and `enableLogs = true` for richer event context. +### Events not appearing in Sentry +1. Verify DSN in `.env` is correct +2. Check Sentry project settings +3. Ensure you're on the correct environment filter in Sentry UI +4. Look for errors in app logs: `adb logcat | grep Sentry` + +### Spotlight debugging (development mode) +Spotlight is a local Sentry event viewer — events sent in debug mode appear here without going to the cloud. + +**Install (one-time):** +```bash +npm install -g @spotlightjs/spotlight +``` + +**Usage:** +```bash +# 1. Start the Spotlight sidecar server (in a separate terminal): +spotlight + +# 2. Run the app in debug mode: +flutter run -d emulator-5554 + +# 3. Open in browser: +# http://localhost:8969/ +``` + +The Sentry SDK automatically forwards events to Spotlight when running in `kDebugMode`. + +### Stack traces are not readable +Make sure you used `./demo.sh build android` (not `flutter build`), which uploads debug symbols automatically. + +--- ## Project Structure -- `lib/main.dart` — App entrypoint -- `lib/sentry_setup.dart` — Sentry initialization and configuration -- `lib/se_config.dart` — Engineer tag configuration -- `lib/navbar_destination.dart`, `lib/checkout.dart` — Demo error triggers and feedback +``` +lib/ +├── main.dart # App entry point, home page +├── sentry_setup.dart # Comprehensive Sentry configuration +├── se_config.dart # Engineer identifier (EDIT THIS) +├── navbar_destination.dart # Navigation drawer + error triggers +├── product_list.dart # Product catalog, performance demos +├── product_details.dart # Product detail view +├── cart.dart # Shopping cart +├── checkout.dart # Checkout with metrics/logging +└── models/ + └── cart_state_model.dart # Shopping cart state (Provider) + +demo.sh # Unified build script (USE THIS) +.env # Sentry credentials (CONFIGURE THIS) +.env.example # Configuration template +pubspec.yaml # Dependencies and version +``` + +--- + +## Key Configuration Files + +**`.env`** - Sentry credentials and configuration (git-ignored) +**`lib/se_config.dart`** - Your engineer identifier for event tagging +--- + +## Sentry SDK Features Enabled + +This demo showcases **all** Sentry Flutter features: + +✅ Error tracking (Dart, native crashes) +✅ Performance monitoring (transactions, spans, TTFD/TTID) +✅ Session replay (100% capture for demo) +✅ User interactions tracing +✅ HTTP request tracking (SentryHttpClient, Dio) +✅ File I/O instrumentation +✅ Structured logging with attributes +✅ Custom metrics (counters, gauges, distributions) +✅ ANR detection (Android) +✅ App hang detection (iOS/macOS - not tested) +✅ Profiling (iOS/macOS/Android) +✅ User feedback collection +✅ Breadcrumbs and context +✅ Screenshot capture +✅ View hierarchy attachments +✅ Thread information +✅ Spotlight debugging (development) + +All features are configured at 100% sampling for demo purposes. Adjust in `lib/sentry_setup.dart` for production. + +--- + +## Additional Documentation + +- **[CLAUDE.md](CLAUDE.md)** - Comprehensive project context for AI assistance +- **[BUILD_GUIDE.md](BUILD_GUIDE.md)** - Detailed build instructions for all platforms +- **[SIZE_ANALYSIS_GUIDE.md](SIZE_ANALYSIS_GUIDE.md)** - Size analysis setup and usage +- **[SENTRY_FEATURES.md](SENTRY_FEATURES.md)** - Complete Sentry feature documentation + +--- + +## iOS & Other Platforms (Untested) + +This demo has **only been validated on Android**. iOS, Web, macOS, Linux, and Windows builds may work but have not been tested by the team. + +If you want to try other platforms: + +```bash +# iOS (requires macOS + Xcode) +./demo.sh build ios + +# Web +./demo.sh build web + +# Others +./demo.sh build macos +./demo.sh build linux +./demo.sh build windows +``` + +Platform-specific documentation exists in the codebase but **is not guaranteed to be accurate**. + +--- + +## Support & Resources + +- **Sentry Flutter Docs:** https://docs.sentry.io/platforms/flutter/ +- **Sentry CLI Docs:** https://docs.sentry.io/cli/ +- **Flutter Docs:** https://docs.flutter.dev/ -## Notes +For issues with this demo, check existing documentation or reach out to the SE team. -- To test Sentry integration, use the navigation drawer or checkout flow to trigger errors and send feedback. -- For production, update Sentry DSN and environment variables as needed. -- Use `run.sh` for automated build and Sentry symbol/source uploads. +--- -For more details, see [Sentry Flutter documentation](https://docs.sentry.io/platforms/flutter/). +**Current Version:** 9.14.0+1 (matches Sentry SDK) +**Tested Platform:** Android only +**App Name:** Empower Plant (com.example.empower_flutter) diff --git a/SENTRY_FEATURES.md b/SENTRY_FEATURES.md new file mode 100644 index 0000000..97b55fd --- /dev/null +++ b/SENTRY_FEATURES.md @@ -0,0 +1,428 @@ +# Sentry Features - Complete Integration Guide + +This document provides an overview of all Sentry features integrated into this Flutter application. + +## Overview + +This application demonstrates comprehensive Sentry integration with the latest SDK (9.14.0) and best practices for: +- ✅ Error & Exception Tracking +- ✅ Performance Monitoring (TTID, TTFD) +- ✅ Session Replay +- ✅ User Feedback +- ✅ Debug Symbol Upload +- ✅ **Size Analysis** (NEW) +- ✅ Multi-Platform Support + +## Feature Matrix + +| Feature | Status | Platforms | Configuration | +|---------|--------|-----------|---------------| +| **Error Tracking** | ✅ Enabled | All | Automatic | +| **Performance Monitoring** | ✅ Enabled | All | `tracesSampleRate: 1.0` | +| **TTID Tracking** | ✅ Enabled | Mobile | Automatic | +| **TTFD Tracking** | ✅ Enabled | Mobile | Manual reporting | +| **Session Replay** | ✅ Enabled | Mobile | `replay.sessionSampleRate: 1.0` | +| **Profiling** | ✅ Enabled | iOS/macOS | `profilesSampleRate: 1.0` | +| **User Feedback** | ✅ Enabled | All | Custom dialog | +| **Breadcrumbs** | ✅ Enabled | All | Automatic + Manual | +| **Screenshots** | ✅ Enabled | Mobile | `attachScreenshot: true` | +| **View Hierarchy** | ✅ Enabled | Mobile | `attachViewHierarchy: true` | +| **ANR Detection** | ✅ Enabled | Android | 5-second threshold | +| **Native Crashes** | ✅ Enabled | iOS/Android | NDK/Crashlytics | +| **HTTP Tracking** | ✅ Enabled | All | `SentryHttpClient` | +| **File I/O Tracking** | ✅ Enabled | All | `sentry_file` | +| **Structured Logging** | ✅ Enabled | All | `sentry_logging` | +| **Debug Symbols** | ✅ Enabled | Mobile | `sentry_dart_plugin` | +| **Size Analysis** | ⚙️ Optional | Mobile | `sentry-cli` | + +## 1. Error & Exception Tracking + +### What It Does +- Captures all unhandled exceptions +- Reports native crashes (iOS/Android) +- Tracks handled exceptions +- Groups similar errors automatically + +### Configuration +```dart +// lib/sentry_setup.dart +options.sampleRate = 1.0; // 100% error capture +options.enableNativeCrashHandling = true; +options.reportSilentFlutterErrors = true; +``` + +### Usage +```dart +// Automatic capture +throw Exception('Something went wrong'); + +// Manual capture +try { + riskyOperation(); +} catch (error, stackTrace) { + await Sentry.captureException(error, stackTrace: stackTrace); +} +``` + +### Demo Features +All error types are testable via the drawer menu: +- Dart exceptions +- Platform exceptions +- Timeout exceptions +- Native crashes (C++ segfault, Kotlin exception) +- ANR simulation + +## 2. Performance Monitoring + +### Time to Initial Display (TTID) +**Automatic** - Tracks time until first frame is rendered. + +```dart +// Automatically captured by SentryNavigatorObserver +MaterialApp( + navigatorObservers: [SentryNavigatorObserver()], +) +``` + +### Time to Full Display (TTFD) +**Manual** - Tracks time until all content is loaded. + +```dart +@override +void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + SentryDisplayWidget.of(context).reportFullyDisplayed(); + } + }); +} +``` + +**Configured in:** [lib/main.dart](lib/main.dart), [lib/product_details.dart](lib/product_details.dart), [lib/checkout.dart](lib/checkout.dart), [lib/product_list.dart](lib/product_list.dart) + +### Custom Transactions +```dart +final transaction = Sentry.startTransaction( + 'operation_name', + 'operation_type', + bindToScope: true, +); + +// Do work +await doSomething(); + +await transaction.finish(status: SpanStatus.ok()); +``` + +### HTTP Tracking +```dart +// Automatic with SentryHttpClient +final client = SentryHttpClient(); +await client.get(Uri.parse('https://api.example.com')); + +// Automatic with Dio integration +final dio = Dio(); +dio.addSentry(); +``` + +## 3. Session Replay + +### What It Does +- Records user sessions as video-like replays +- Captures interactions leading to errors +- Provides visual context for debugging + +### Configuration +```dart +// lib/sentry_setup.dart +options.replay.onErrorSampleRate = 1.0; // 100% error sessions +options.replay.sessionSampleRate = 1.0; // 100% normal sessions +options.attachScreenshot = true; +options.screenshotQuality = SentryScreenshotQuality.high; +``` + +### How to View +1. Trigger any error from the drawer menu +2. Go to Sentry Issues +3. Click on the error +4. See "Replays" tab with recorded session + +## 4. User Feedback + +### What It Does +- Shows custom feedback dialog after errors +- Associates feedback with specific events +- Captures user descriptions of issues + +### Implementation +```dart +// lib/sentry_setup.dart +void showUserFeedbackDialog(BuildContext context, SentryId eventId) async { + // ... dialog implementation + await Sentry.captureFeedback( + SentryFeedback( + message: description, + associatedEventId: eventId, + name: userName, + ), + ); +} +``` + +### Demo +Trigger any Dart exception → Feedback dialog appears automatically + +## 5. Debug Symbol Upload + +### What It Does +- Uploads obfuscation maps for readable stack traces +- Uploads source files for source context +- Associates symbols with releases + +### Configuration +**Via pubspec.yaml:** +```yaml +sentry: + upload_debug_symbols: true + upload_sources: true + project: your-project + org: your-org +``` + +**Via sentry.properties:** +```properties +org=your-org-slug +project=your-project-name +auth_token=your-auth-token +``` + +### Usage +```bash +# Automatic via build script +./demo.sh build android + +# Manual upload +flutter pub run sentry_dart_plugin +``` + +**See:** [sentry.properties.example](sentry.properties.example) + +## 6. Size Analysis (NEW) + +### What It Does +- Monitors app build sizes over time +- Detects size regressions before release +- Provides size breakdown by component +- Tracks trends across builds + +### Setup + +1. **Install Sentry CLI:** + ```bash + curl -sL https://sentry.io/get-cli/ | bash + ``` + +2. **Configure .env:** + ```bash + SENTRY_ORG=your-org-slug + SENTRY_PROJECT=your-project-slug + SENTRY_SIZE_ANALYSIS_ENABLED=true + ``` + +3. **Build and upload:** + ```bash + ./demo.sh build android + ``` + +### Supported Formats +- **Android:** APK, AAB +- **iOS:** IPA (requires manual archive) + +### View Results +``` +https://sentry.io/organizations//projects//size-analysis/ +``` + +### Manual Upload +```bash +# Android +./demo.sh upload-size build/app/outputs/flutter-apk/app-release.apk android + +# iOS +./demo.sh upload-size YourApp.ipa ios +``` + +**See:** [SIZE_ANALYSIS_GUIDE.md](SIZE_ANALYSIS_GUIDE.md) + +## 7. Platform-Specific Features + +### iOS/macOS +- ✅ Profiling enabled +- ✅ Watchdog termination tracking +- ✅ Native crash symbolication + +```dart +if (Platform.isIOS || Platform.isMacOS) { + options.profilesSampleRate = 1.0; +} +``` + +### Android +- ✅ ANR detection (5-second threshold) +- ✅ NDK crash handling +- ✅ Proguard/R8 symbol mapping + +```dart +options.anrEnabled = true; +options.anrTimeoutInterval = const Duration(seconds: 5); +``` + +### Web +- ✅ Source maps for error reporting +- ✅ Debug IDs (SDK 9.1.0+) +- ✅ Breadcrumb tracking + +### Desktop (Linux/Windows) +- ✅ Error tracking +- ✅ Performance monitoring +- ✅ Platform-specific crash handling + +## 8. Breadcrumbs + +### Automatic Breadcrumbs +- Navigation events +- User interactions (taps, swipes) +- HTTP requests +- Console logs +- Lifecycle events + +### Manual Breadcrumbs +```dart +Sentry.addBreadcrumb( + Breadcrumb( + category: 'user.action', + message: 'User clicked checkout', + level: SentryLevel.info, + ), +); +``` + +## 9. Context & Tagging + +### Automatic Context +```dart +// lib/main.dart +Sentry.configureScope((scope) { + scope.setTag('app.name', 'Empower Plant'); + scope.setTag('platform', 'flutter'); + scope.setTag('os', Platform.operatingSystem); + // ... more tags +}); +``` + +### User Context +```dart +Sentry.configureScope((scope) => + scope.setUser(SentryUser(id: email)) +); +``` + +### Custom Context +```dart +transaction.setData('custom_key', 'custom_value'); +span.setData('operation', 'database_query'); +``` + +## Configuration Files + +| File | Purpose | +|------|---------| +| [pubspec.yaml](pubspec.yaml) | Dependency versions, sentry_dart_plugin config | +| [lib/sentry_setup.dart](lib/sentry_setup.dart) | Main Sentry initialization | +| [.env](.env) | Runtime configuration (DSN, release, etc.) | +| [sentry.properties](sentry.properties) | Build-time configuration (symbols, size) | +| [demo.sh](demo.sh) | Unified build, run, and release management script | + +## Build Scripts + +| Script | Purpose | +|--------|---------| +| [demo.sh](demo.sh) | Multi-platform build with Sentry integration and release management | + +## Documentation + +| Document | Contents | +|----------|----------| +| [README.md](README.md) | Quick start and overview | +| [BUILD_GUIDE.md](BUILD_GUIDE.md) | Detailed build instructions | +| [SIZE_ANALYSIS_GUIDE.md](SIZE_ANALYSIS_GUIDE.md) | Size monitoring setup | +| [SENTRY_FEATURES.md](SENTRY_FEATURES.md) | This document | + +## Testing Checklist + +### Error Tracking +- [ ] Test Dart exception (drawer menu) +- [ ] Test platform exception +- [ ] Test timeout exception +- [ ] Test native crash (C++/Kotlin) +- [ ] Test ANR simulation +- [ ] Verify errors appear in Sentry Issues +- [ ] Check stack traces are readable + +### Performance Monitoring +- [ ] Navigate between screens +- [ ] Check TTID metrics in Sentry Performance +- [ ] Check TTFD metrics for all screens +- [ ] Verify HTTP requests are tracked +- [ ] Test N+1 API calls demo + +### Session Replay +- [ ] Trigger an error +- [ ] View replay in Sentry +- [ ] Verify interactions are captured +- [ ] Check screenshot quality + +### User Feedback +- [ ] Trigger Dart exception +- [ ] Submit feedback via dialog +- [ ] Verify feedback in Sentry + +### Size Analysis +- [ ] Enable size analysis +- [ ] Build release +- [ ] Verify upload success +- [ ] View size dashboard +- [ ] Check trend data + +## Production Checklist + +Before deploying to production: + +- [ ] Adjust sampling rates in [lib/sentry_setup.dart](lib/sentry_setup.dart): + - `sampleRate`: Consider 0.1-1.0 (10-100%) + - `tracesSampleRate`: Consider 0.1-0.5 (10-50%) + - `replay.sessionSampleRate`: Consider 0.01-0.1 (1-10%) + - `replay.onErrorSampleRate`: Keep at 1.0 (100%) + - `profilesSampleRate`: Consider 0.1-0.5 (10-50%) + +- [ ] Configure proper release versioning +- [ ] Set up alerts for critical errors +- [ ] Enable size analysis in CI/CD +- [ ] Test on all target platforms +- [ ] Verify symbol upload in production builds +- [ ] Review privacy settings (`sendDefaultPii`) + +## Resources + +- [Sentry Flutter Documentation](https://docs.sentry.io/platforms/flutter/) +- [Sentry Size Analysis](https://docs.sentry.io/product/insights/size-analysis/) +- [Flutter Performance Best Practices](https://docs.flutter.dev/perf) +- [GitHub: sentry-dart](https://github.com/getsentry/sentry-dart) + +## Support + +For issues or questions: +- [GitHub Issues](https://github.com/getsentry/sentry-dart/issues) +- [Sentry Discord](https://discord.gg/sentry) +- [Sentry Documentation](https://docs.sentry.io/) diff --git a/SIZE_ANALYSIS_GUIDE.md b/SIZE_ANALYSIS_GUIDE.md new file mode 100644 index 0000000..918e9fc --- /dev/null +++ b/SIZE_ANALYSIS_GUIDE.md @@ -0,0 +1,479 @@ +# Size Analysis Guide - Flutter App Size Monitoring with Sentry + +This guide explains how to set up and use Sentry's Size Analysis feature to monitor your Flutter application's build sizes and prevent size regressions. + +## What is Size Analysis? + +Size Analysis helps you: +- **Monitor app size trends** across builds +- **Detect size regressions** before they reach users +- **Understand size impact** of code changes +- **Optimize download and installation** rates + +Smaller app sizes lead to better installation rates, especially for users with limited storage or slower connections. + +## Prerequisites + +### 1. Install Sentry CLI + +The Sentry CLI (version 2.58.2 or later) is required for size analysis uploads. + +**Install on macOS/Linux:** +```bash +curl -sL https://sentry.io/get-cli/ | bash +``` + +**Or via Homebrew:** +```bash +brew install getsentry/tools/sentry-cli +``` + +**Or via npm:** +```bash +npm install -g @sentry/cli +``` + +**Verify installation:** +```bash +sentry-cli --version +``` + +### 2. Authenticate Sentry CLI + +Create an auth token with the required scopes: + +1. Visit: https://sentry.io/settings/account/api/auth-tokens/ +2. Click "Create New Token" +3. Name: "Size Analysis Upload" +4. Scopes: `project:write`, `org:read` +5. Copy the token + +**Set the token in your environment:** +```bash +export SENTRY_AUTH_TOKEN=your-token-here +``` + +Or add to your `.env` file: +```bash +SENTRY_AUTH_TOKEN=your-token-here +``` + +## Configuration + +### Option 1: Using .env (Recommended) + +Add the following to your `.env` file: + +```bash +# Sentry Configuration +SENTRY_ORG=your-org-slug +SENTRY_PROJECT=your-project-slug +SENTRY_AUTH_TOKEN=your-auth-token + +# Enable Size Analysis +SENTRY_SIZE_ANALYSIS_ENABLED=true + +# Optional: Git metadata (usually auto-detected) +# CI_COMMIT_SHA=abc123 +# CI_MERGE_REQUEST_DIFF_BASE_SHA=def456 +# CI_COMMIT_REF_NAME=feature-branch +# GITHUB_PR_NUMBER=42 +``` + +### Option 2: Using sentry.properties + +Add to your `sentry.properties` file: + +```properties +# Required +org=your-org-slug +project=your-project-slug +auth_token=your-auth-token + +# Size analysis (handled by build script) +# Note: Set SENTRY_SIZE_ANALYSIS_ENABLED=true in environment +``` + +## Usage + +### Building with Size Analysis + +The unified `demo.sh` script automatically uploads builds for size analysis when enabled. + +#### Android + +```bash +# Enable size analysis +export SENTRY_SIZE_ANALYSIS_ENABLED=true + +# Build and upload +./demo.sh build android +``` + +The script will: +1. Build the Android APK with obfuscation +2. Upload debug symbols +3. **Upload APK for size analysis** +4. Display the Sentry URL to view results + +**Build output:** +``` +✓ Android APK built successfully +ℹ APK location: build/app/outputs/flutter-apk/app-release.apk + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Uploading Android Build for Size Analysis +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +ℹ Uploading: build/app/outputs/flutter-apk/app-release.apk +ℹ Organization: your-org +ℹ Project: your-project +✓ Size analysis data uploaded successfully +ℹ View results: https://sentry.io/organizations/your-org/projects/your-project/size-analysis/ +``` + +#### iOS + +For iOS, Flutter doesn't directly create IPA files. You need to create an archive first: + +**Option 1: Use Xcode** +1. Build with the script: + ```bash + export SENTRY_SIZE_ANALYSIS_ENABLED=true + ./demo.sh build ios + ``` +2. Open the project in Xcode: + ```bash + open ios/Runner.xcworkspace + ``` +3. Archive the app: Product → Archive +4. Export the IPA +5. Upload manually: + ```bash + sentry-cli build upload YourApp.ipa \ + --org your-org \ + --project your-project \ + --build-configuration Release + ``` + +**Option 2: Use flutter build ipa (requires Xcode setup)** +```bash +# Build IPA +flutter build ipa --release --obfuscate --split-debug-info=build/debug-info + +# Upload for size analysis +sentry-cli build upload build/ios/ipa/Runner.ipa \ + --org your-org \ + --project your-project \ + --build-configuration Release +``` + +### Viewing Size Analysis Results + +After uploading, view your size analysis dashboard: + +``` +https://sentry.io/organizations//projects//size-analysis/ +``` + +The dashboard shows: +- **Size trends** over time +- **Size breakdown** by component +- **Size comparisons** between builds +- **Regression detection** alerts + +## Build Metadata + +The build script automatically detects and includes metadata: + +| Field | Source | Description | +|-------|--------|-------------| +| `org` | `SENTRY_ORG` | Organization slug (required) | +| `project` | `SENTRY_PROJECT` | Project slug (required) | +| `build-configuration` | Hardcoded | Always "Release" | +| `head-sha` | Git or `CI_COMMIT_SHA` | Current commit SHA | +| `base-sha` | Git merge-base or `CI_MERGE_REQUEST_DIFF_BASE_SHA` | Base commit for comparison | +| `head-ref` | Git branch or `CI_COMMIT_REF_NAME` | Current branch name | +| `base-ref` | `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | Base branch (default: main) | +| `pr-number` | `CI_MERGE_REQUEST_IID` or `GITHUB_PR_NUMBER` | Pull request number | + +## CI/CD Integration + +### GitHub Actions + +```yaml +name: Build and Size Analysis + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Required for git metadata + + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.22.0' + + - name: Install Sentry CLI + run: | + curl -sL https://sentry.io/get-cli/ | bash + + - name: Build and Upload + env: + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_SIZE_ANALYSIS_ENABLED: true + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + run: ./demo.sh build android +``` + +### GitLab CI + +```yaml +build-android: + stage: build + image: ghcr.io/cirruslabs/flutter:stable + before_script: + - curl -sL https://sentry.io/get-cli/ | bash + script: + - export SENTRY_SIZE_ANALYSIS_ENABLED=true + - ./demo.sh build android + variables: + SENTRY_ORG: your-org + SENTRY_PROJECT: your-project + SENTRY_AUTH_TOKEN: $SENTRY_AUTH_TOKEN + only: + - merge_requests + - main +``` + +## Manual Upload + +If you need to upload builds manually: + +### Android APK + +```bash +sentry-cli build upload build/app/outputs/flutter-apk/app-release.apk \ + --org your-org \ + --project your-project \ + --build-configuration Release \ + --head-sha $(git rev-parse HEAD) \ + --base-sha $(git merge-base HEAD origin/main) \ + --head-ref $(git branch --show-current) \ + --base-ref main +``` + +### Android AAB (Preferred) + +```bash +# Build AAB +flutter build appbundle --release --obfuscate --split-debug-info=build/debug-info + +# Upload +sentry-cli build upload build/app/outputs/bundle/release/app-release.aab \ + --org your-org \ + --project your-project \ + --build-configuration Release +``` + +### iOS IPA + +```bash +sentry-cli build upload YourApp.ipa \ + --org your-org \ + --project your-project \ + --build-configuration Release \ + --head-sha $(git rev-parse HEAD) \ + --base-sha $(git merge-base HEAD origin/main) +``` + +## Troubleshooting + +### Sentry CLI Not Found + +``` +⚠ sentry-cli not found - size analysis uploads will be skipped +``` + +**Solution:** Install the Sentry CLI: +```bash +curl -sL https://sentry.io/get-cli/ | bash +``` + +### Authentication Failed + +``` +✗ Failed to upload size analysis data +``` + +**Solutions:** +1. Check that `SENTRY_AUTH_TOKEN` is set correctly +2. Verify the token has `project:write` and `org:read` scopes +3. Test authentication: + ```bash + sentry-cli login + ``` + +### Missing Organization or Project + +``` +⚠ SENTRY_ORG and SENTRY_PROJECT must be set for size analysis +``` + +**Solution:** Add to `.env`: +```bash +SENTRY_ORG=your-org-slug +SENTRY_PROJECT=your-project-slug +``` + +### Build File Not Found + +``` +⚠ Build file not found: build/app/outputs/flutter-apk/app-release.apk +``` + +**Solution:** Ensure the build completed successfully. Check: +```bash +find . -name "*.apk" +``` + +### Size Analysis Disabled + +``` +ℹ Size analysis disabled. Set SENTRY_SIZE_ANALYSIS_ENABLED=true to enable +``` + +**Solution:** Enable in your environment: +```bash +export SENTRY_SIZE_ANALYSIS_ENABLED=true +./demo.sh build android +``` + +## Best Practices + +### 1. Enable in CI/CD Only + +Size analysis is most valuable in CI/CD pipelines. Enable it for: +- Pull request builds (to catch regressions before merge) +- Main branch builds (to track trends) + +```bash +# In CI environment +if [ -n "$CI" ]; then + export SENTRY_SIZE_ANALYSIS_ENABLED=true +fi +``` + +### 2. Use Consistent Build Configurations + +Always use the same build configuration for comparisons: +- iOS: Use "Release" configuration +- Android: Use "release" build type + +### 3. Include Git Metadata + +Accurate comparisons require git metadata: +- Ensure `git` is available in CI +- Use `fetch-depth: 0` in GitHub Actions checkout +- Set base branch correctly for PR builds + +### 4. Upload AAB for Android (When Possible) + +AAB provides more accurate size estimates: +```bash +flutter build appbundle --release +``` + +### 5. Monitor Trends Regularly + +- Set up alerts for size increases > 5% +- Review size analysis dashboard weekly +- Investigate unexpected size increases promptly + +## Advanced Configuration + +### Custom Metadata + +Override auto-detected metadata: + +```bash +sentry-cli build upload app.apk \ + --org your-org \ + --project your-project \ + --build-configuration Release \ + --head-sha custom-sha \ + --base-sha base-sha \ + --head-ref feature-branch \ + --base-ref main \ + --pr-number 123 \ + --head-repo-name org/repo \ + --base-repo-name org/repo \ + --vcs-provider github +``` + +### Different Build Configurations + +Track different build configurations separately: + +```bash +# Release build +sentry-cli build upload app-release.apk \ + --build-configuration Release + +# Debug build (if needed) +sentry-cli build upload app-debug.apk \ + --build-configuration Debug +``` + +## Size Optimization Tips + +When size analysis detects regressions, consider: + +1. **Code Analysis** + - Remove unused dependencies + - Use tree shaking + - Enable code minification + +2. **Asset Optimization** + - Compress images (WebP format) + - Use vector graphics (SVG) + - Remove unused assets + +3. **Build Optimization** + - Enable obfuscation: `--obfuscate` + - Split debug info: `--split-debug-info` + - Use app bundles (Android) + +4. **Dependency Audit** + ```bash + flutter pub deps --no-dev + ``` + +5. **Size Analysis** + ```bash + flutter build apk --analyze-size + ``` + +## Summary + +Sentry Size Analysis integrated into your Flutter build pipeline provides: +- ✅ Automated size tracking +- ✅ Regression detection +- ✅ Historical trends +- ✅ Comparative analysis +- ✅ CI/CD integration + +For more information: +- [Sentry Size Analysis Documentation](https://docs.sentry.io/product/insights/size-analysis/) +- [Sentry CLI Documentation](https://docs.sentry.io/product/cli/) +- [Flutter Build Optimization](https://docs.flutter.dev/perf/app-size) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 67c73bb..85d2520 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -8,7 +8,7 @@ plugins { android { namespace = "com.example.empower_flutter" compileSdk = flutter.compileSdkVersion - ndkVersion = "27.0.12077973" + ndkVersion = "28.2.13676358" compileOptions { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f71329c..41739a7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,8 @@ + android:icon="@mipmap/ic_launcher" + android:networkSecurityConfig="@xml/network"> + + + + + + + + + + + + + + + + 10.0.2.2 + + diff --git a/assets/audio/background_music.mp3 b/assets/audio/background_music.mp3 new file mode 100644 index 0000000..69113cb Binary files /dev/null and b/assets/audio/background_music.mp3 differ diff --git a/assets/audio/notification_sound.wav b/assets/audio/notification_sound.wav new file mode 100644 index 0000000..a352908 Binary files /dev/null and b/assets/audio/notification_sound.wav differ diff --git a/assets/config/feature_1.json b/assets/config/feature_1.json new file mode 100644 index 0000000..1191b15 --- /dev/null +++ b/assets/config/feature_1.json @@ -0,0 +1 @@ +{"id": 1, "enabled": true} diff --git a/assets/config/feature_10.json b/assets/config/feature_10.json new file mode 100644 index 0000000..18d8511 --- /dev/null +++ b/assets/config/feature_10.json @@ -0,0 +1 @@ +{"id": 10, "enabled": true} diff --git a/assets/config/feature_11.json b/assets/config/feature_11.json new file mode 100644 index 0000000..b1896a2 --- /dev/null +++ b/assets/config/feature_11.json @@ -0,0 +1 @@ +{"id": 11, "enabled": true} diff --git a/assets/config/feature_12.json b/assets/config/feature_12.json new file mode 100644 index 0000000..78edcb9 --- /dev/null +++ b/assets/config/feature_12.json @@ -0,0 +1 @@ +{"id": 12, "enabled": true} diff --git a/assets/config/feature_13.json b/assets/config/feature_13.json new file mode 100644 index 0000000..dd8be36 --- /dev/null +++ b/assets/config/feature_13.json @@ -0,0 +1 @@ +{"id": 13, "enabled": true} diff --git a/assets/config/feature_14.json b/assets/config/feature_14.json new file mode 100644 index 0000000..3cac915 --- /dev/null +++ b/assets/config/feature_14.json @@ -0,0 +1 @@ +{"id": 14, "enabled": true} diff --git a/assets/config/feature_15.json b/assets/config/feature_15.json new file mode 100644 index 0000000..48144b1 --- /dev/null +++ b/assets/config/feature_15.json @@ -0,0 +1 @@ +{"id": 15, "enabled": true} diff --git a/assets/config/feature_2.json b/assets/config/feature_2.json new file mode 100644 index 0000000..8e95cf9 --- /dev/null +++ b/assets/config/feature_2.json @@ -0,0 +1 @@ +{"id": 2, "enabled": true} diff --git a/assets/config/feature_3.json b/assets/config/feature_3.json new file mode 100644 index 0000000..52b2b71 --- /dev/null +++ b/assets/config/feature_3.json @@ -0,0 +1 @@ +{"id": 3, "enabled": true} diff --git a/assets/config/feature_4.json b/assets/config/feature_4.json new file mode 100644 index 0000000..ce6b74e --- /dev/null +++ b/assets/config/feature_4.json @@ -0,0 +1 @@ +{"id": 4, "enabled": true} diff --git a/assets/config/feature_5.json b/assets/config/feature_5.json new file mode 100644 index 0000000..bced2a2 --- /dev/null +++ b/assets/config/feature_5.json @@ -0,0 +1 @@ +{"id": 5, "enabled": true} diff --git a/assets/config/feature_6.json b/assets/config/feature_6.json new file mode 100644 index 0000000..bb5bb29 --- /dev/null +++ b/assets/config/feature_6.json @@ -0,0 +1 @@ +{"id": 6, "enabled": true} diff --git a/assets/config/feature_7.json b/assets/config/feature_7.json new file mode 100644 index 0000000..3e00915 --- /dev/null +++ b/assets/config/feature_7.json @@ -0,0 +1 @@ +{"id": 7, "enabled": true} diff --git a/assets/config/feature_8.json b/assets/config/feature_8.json new file mode 100644 index 0000000..0688335 --- /dev/null +++ b/assets/config/feature_8.json @@ -0,0 +1 @@ +{"id": 8, "enabled": true} diff --git a/assets/config/feature_9.json b/assets/config/feature_9.json new file mode 100644 index 0000000..256cc25 --- /dev/null +++ b/assets/config/feature_9.json @@ -0,0 +1 @@ +{"id": 9, "enabled": true} diff --git a/assets/docs/ARCHITECTURE.md b/assets/docs/ARCHITECTURE.md new file mode 100644 index 0000000..20140be --- /dev/null +++ b/assets/docs/ARCHITECTURE.md @@ -0,0 +1,64 @@ +# Architecture Documentation + +This is an unnecessarily large documentation file that should not be bundled with the app. +It's only useful for developers during development, not for end users. + +## Overview + +This document describes the architecture of our Flutter application. + +## Component Structure + +### Frontend +- Main app widget tree +- State management with Provider +- Custom widgets and components + +### Backend Integration +- REST API calls +- WebSocket connections +- Data persistence + +### Performance Optimization +- Image caching +- Lazy loading +- Code splitting + +## Design Patterns + +We use various design patterns including: +- Singleton for app-wide services +- Factory for object creation +- Observer for state management +- Repository for data access + +## Development Guidelines + +### Code Style +- Follow Dart style guide +- Use meaningful variable names +- Add comments for complex logic + +### Testing +- Write unit tests for business logic +- Write widget tests for UI components +- Write integration tests for user flows + +### Deployment +- Build for release +- Test on multiple devices +- Submit to app stores + +This documentation continues for many more sections... + +## API Reference +[Extensive API documentation that doesn't need to be in the app bundle] + +## Troubleshooting +[Debugging guides that are only for developers] + +## Contributing +[Guidelines for contributors] + +## License +[License information] diff --git a/assets/docs/CHANGELOG.md b/assets/docs/CHANGELOG.md new file mode 100644 index 0000000..23e8f26 --- /dev/null +++ b/assets/docs/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +All notable changes to this project will be documented in this file. +This file should not be bundled with the production app as it's only relevant for developers. + +## [1.0.0] - 2025-01-01 +### Added +- Initial release +- User authentication +- Product catalog +- Shopping cart +- Checkout flow + +### Changed +- Updated dependencies +- Improved performance + +### Fixed +- Various bug fixes + +## [0.9.0] - 2024-12-15 +### Added +- Beta testing features +- Analytics integration + +## [0.8.0] - 2024-12-01 +### Added +- Alpha testing features + +[Many more changelog entries that add unnecessary bloat...] diff --git a/assets/docs/CONTRIBUTING.md b/assets/docs/CONTRIBUTING.md new file mode 100644 index 0000000..d57549c --- /dev/null +++ b/assets/docs/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing Guidelines + +This file contains guidelines for contributors and should not be in the production app. + +## How to Contribute +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## Code Review Process +All pull requests must be reviewed before merging. + +## Development Setup +Follow the instructions in README.md to set up your development environment. diff --git a/assets/docs/build.sh b/assets/docs/build.sh new file mode 100755 index 0000000..2674555 --- /dev/null +++ b/assets/docs/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# This build script should not be included in the app bundle +# It's only used during development + +echo "Building Flutter app..." +flutter build apk --release +echo "Build complete!" diff --git a/assets/docs/build_config.json b/assets/docs/build_config.json new file mode 100644 index 0000000..5a738f9 --- /dev/null +++ b/assets/docs/build_config.json @@ -0,0 +1,18 @@ +{ + "build": { + "environment": "production", + "version": "9.13.0", + "build_number": 1, + "compiler_flags": ["-O2", "-g"], + "dependencies": { + "flutter": "3.x", + "dart": "3.x" + }, + "scripts": { + "prebuild": "echo 'Starting build'", + "build": "flutter build", + "postbuild": "echo 'Build complete'" + } + }, + "developer_notes": "This config file is only needed during build time, not at runtime" +} diff --git a/assets/images/badges/badge_1.png b/assets/images/badges/badge_1.png new file mode 100644 index 0000000..7528cce Binary files /dev/null and b/assets/images/badges/badge_1.png differ diff --git a/assets/images/badges/badge_10.png b/assets/images/badges/badge_10.png new file mode 100644 index 0000000..da92c2e --- /dev/null +++ b/assets/images/badges/badge_10.png @@ -0,0 +1 @@ +:]DFFn`>Cұ1֖#vW92!! ,tI5yts\0}8 9^r~s@}a*~ݯTn ,giii _0< ~d)KC"l#[8nLw[a=򀐶X5?&g*^TdU'ȭ>]LX8nԚd?K&\!.V'IIO5m:*a|ޒ A \ No newline at end of file diff --git a/assets/images/badges/badge_11.png b/assets/images/badges/badge_11.png new file mode 100644 index 0000000..4c6ad33 --- /dev/null +++ b/assets/images/badges/badge_11.png @@ -0,0 +1,2 @@ +)GUC|jS/FuIZED* !$fy)VAWLcf҉W<}s9lomwZ*8^$P-<ǵWͶuvqYg, +#TS6W*[uvVV};b2-YR$`% Eɢ4W#S".ΒT$`:+N?cdY@P8_]5"<$X~ \ No newline at end of file diff --git a/assets/images/badges/badge_12.png b/assets/images/badges/badge_12.png new file mode 100644 index 0000000..386cdff Binary files /dev/null and b/assets/images/badges/badge_12.png differ diff --git a/assets/images/badges/badge_13.png b/assets/images/badges/badge_13.png new file mode 100644 index 0000000..f6b384e Binary files /dev/null and b/assets/images/badges/badge_13.png differ diff --git a/assets/images/badges/badge_14.png b/assets/images/badges/badge_14.png new file mode 100644 index 0000000..da3ca9b Binary files /dev/null and b/assets/images/badges/badge_14.png differ diff --git a/assets/images/badges/badge_15.png b/assets/images/badges/badge_15.png new file mode 100644 index 0000000..82cb143 --- /dev/null +++ b/assets/images/badges/badge_15.png @@ -0,0 +1 @@ +/S4{].#L\Bz] &~pcTE|dTg6"jnvS6j+d%f(ܪD?+Z"jWhYj5ϛDr ]aUF4~Ass"6~k.'j5(axU`L&iT,?0 u|G*/wwIgME \ No newline at end of file diff --git a/assets/images/badges/badge_16.png b/assets/images/badges/badge_16.png new file mode 100644 index 0000000..2e3e6a6 --- /dev/null +++ b/assets/images/badges/badge_16.png @@ -0,0 +1,2 @@ +h'6IPΓڸ%_1)`^.q9C7+ʶQcKNA٭tkT`a^1VJR ׋?!V7 =|`kdhHWKըbl{j'G'٘W;\Q,/*P[OV-zYT&iqtaķo^ 1yu^,?㷥tZ`5gr_ttOarBQM>a&0MhZJs:55$< +Ī4, \ No newline at end of file diff --git a/assets/images/badges/badge_17.png b/assets/images/badges/badge_17.png new file mode 100644 index 0000000..0507a86 Binary files /dev/null and b/assets/images/badges/badge_17.png differ diff --git a/assets/images/badges/badge_18.png b/assets/images/badges/badge_18.png new file mode 100644 index 0000000..28098d1 Binary files /dev/null and b/assets/images/badges/badge_18.png differ diff --git a/assets/images/badges/badge_19.png b/assets/images/badges/badge_19.png new file mode 100644 index 0000000..6a90a69 Binary files /dev/null and b/assets/images/badges/badge_19.png differ diff --git a/assets/images/badges/badge_2.png b/assets/images/badges/badge_2.png new file mode 100644 index 0000000..b9203ca Binary files /dev/null and b/assets/images/badges/badge_2.png differ diff --git a/assets/images/badges/badge_20.png b/assets/images/badges/badge_20.png new file mode 100644 index 0000000..4bea7c2 --- /dev/null +++ b/assets/images/badges/badge_20.png @@ -0,0 +1,4 @@ +|c)7{A&i)|skǔ(~^DHylWBA(_ԫ6jigK +RPJ4ysmt|~ (8 +,K4Eh +Nf'`9<}Q!xE }ܴq܏è7)^?OֱGPe lN>۲K7F ޡ}: +,P1_$^ h/ܕ \ No newline at end of file diff --git a/assets/images/badges/badge_3.png b/assets/images/badges/badge_3.png new file mode 100644 index 0000000..b3a6ebf Binary files /dev/null and b/assets/images/badges/badge_3.png differ diff --git a/assets/images/badges/badge_4.png b/assets/images/badges/badge_4.png new file mode 100644 index 0000000..25ef5c9 Binary files /dev/null and b/assets/images/badges/badge_4.png differ diff --git a/assets/images/badges/badge_5.png b/assets/images/badges/badge_5.png new file mode 100644 index 0000000..7f01c7a Binary files /dev/null and b/assets/images/badges/badge_5.png differ diff --git a/assets/images/badges/badge_6.png b/assets/images/badges/badge_6.png new file mode 100644 index 0000000..060c3a3 --- /dev/null +++ b/assets/images/badges/badge_6.png @@ -0,0 +1 @@ +S@KRCaan2CMkdGV9/Ƶ [EIm/<3TBә|GeE=#J&6ڵLW7z|YQ\KDCjGO'^B m`O_B]Iea; Œ.>~H7hUPBǭ `cg`Ykp! 5HEѦaZm0kl 3T0 \ No newline at end of file diff --git a/assets/images/badges/badge_7.png b/assets/images/badges/badge_7.png new file mode 100644 index 0000000..3d0e1ea Binary files /dev/null and b/assets/images/badges/badge_7.png differ diff --git a/assets/images/badges/badge_8.png b/assets/images/badges/badge_8.png new file mode 100644 index 0000000..14fccc8 --- /dev/null +++ b/assets/images/badges/badge_8.png @@ -0,0 +1,3 @@ +W:VJ b2{bP 7`e& 𤈊 3q/kw"K8]N,s$,m/a OV9GaL9q*CfTs=\\_{ͻiQU ata6M.5X] G& s'.QN_\|v{!z=c@ҒFM\yXƟId4@s=[NaK 7\[ԉ[^M;ύ4ȁ;cڼ702gxm@xX +vECu=Cd3Xt걶9w2;n + U}BxF01(cvԨݑqAܛ /r5 STcWNnB`5E7'n8:0Q, xb_Q+!T9դ  \ No newline at end of file diff --git a/assets/images/icons/icon_10.png b/assets/images/icons/icon_10.png new file mode 100644 index 0000000..ca3c23c Binary files /dev/null and b/assets/images/icons/icon_10.png differ diff --git a/assets/images/icons/icon_11.png b/assets/images/icons/icon_11.png new file mode 100644 index 0000000..a4b4461 Binary files /dev/null and b/assets/images/icons/icon_11.png differ diff --git a/assets/images/icons/icon_12.png b/assets/images/icons/icon_12.png new file mode 100644 index 0000000..2d9679b --- /dev/null +++ b/assets/images/icons/icon_12.png @@ -0,0 +1,4 @@ +mƞ9 +w*@7?JT-\IY@MGj2ɆbvIq,-#ZܡH4=+3$fI"$YysPNA ɉ\|"D)`}J`` + KVtݴB527+Q/=0Ih8@ +?!%nlh$WQv1KJuEؑJ;꺸F@=*D}2vH,I먷B%[b4nØ^r|p {Q;{Ϥ! 3mr[¯WjIj3 'HZ,WȮ[L*4SZg;*1fT#wl R ~qFsTtڻ=JJqڴ#{>$`XM"_p֨̇vh$=OxGN}WdÅ^$;*:BMz* zp(#QaKƇh˂? \ No newline at end of file diff --git a/assets/images/icons/icon_13.png b/assets/images/icons/icon_13.png new file mode 100644 index 0000000..bdeff43 Binary files /dev/null and b/assets/images/icons/icon_13.png differ diff --git a/assets/images/icons/icon_14.png b/assets/images/icons/icon_14.png new file mode 100644 index 0000000..9c61310 Binary files /dev/null and b/assets/images/icons/icon_14.png differ diff --git a/assets/images/icons/icon_15.png b/assets/images/icons/icon_15.png new file mode 100644 index 0000000..1940c96 Binary files /dev/null and b/assets/images/icons/icon_15.png differ diff --git a/assets/images/icons/icon_16.png b/assets/images/icons/icon_16.png new file mode 100644 index 0000000..fc49794 Binary files /dev/null and b/assets/images/icons/icon_16.png differ diff --git a/assets/images/icons/icon_17.png b/assets/images/icons/icon_17.png new file mode 100644 index 0000000..081c2ed Binary files /dev/null and b/assets/images/icons/icon_17.png differ diff --git a/assets/images/icons/icon_18.png b/assets/images/icons/icon_18.png new file mode 100644 index 0000000..aa23005 Binary files /dev/null and b/assets/images/icons/icon_18.png differ diff --git a/assets/images/icons/icon_19.png b/assets/images/icons/icon_19.png new file mode 100644 index 0000000..176e5ff Binary files /dev/null and b/assets/images/icons/icon_19.png differ diff --git a/assets/images/icons/icon_2.png b/assets/images/icons/icon_2.png new file mode 100644 index 0000000..3b8b55c Binary files /dev/null and b/assets/images/icons/icon_2.png differ diff --git a/assets/images/icons/icon_20.png b/assets/images/icons/icon_20.png new file mode 100644 index 0000000..c2ef686 Binary files /dev/null and b/assets/images/icons/icon_20.png differ diff --git a/assets/images/icons/icon_21.png b/assets/images/icons/icon_21.png new file mode 100644 index 0000000..12c8f21 --- /dev/null +++ b/assets/images/icons/icon_21.png @@ -0,0 +1,4 @@ +{H,-)~me==7͠!m b$&eȐCSi2ct6Z!v;հZ>\_|d28Ym}L#RVJRIH5$:O⼻qfe Ed}x_W +ݳ8=iΏrS-WĹ\mcQ4Z3ʇV܏ +$55HC|HhL xk.Zzy4odXA4tAgJb84R) \c S<|x'B_PoD*Y`&~y! ð٦(M5֚w5Ȧ‚?FVB׉U /oUfuDìLZ!/P@bwr DzSM.H؄6:m +X=G~gW]{9Jc\JaГWojjy \ No newline at end of file diff --git a/assets/images/icons/icon_22.png b/assets/images/icons/icon_22.png new file mode 100644 index 0000000..704e47d Binary files /dev/null and b/assets/images/icons/icon_22.png differ diff --git a/assets/images/icons/icon_23.png b/assets/images/icons/icon_23.png new file mode 100644 index 0000000..75e5d36 Binary files /dev/null and b/assets/images/icons/icon_23.png differ diff --git a/assets/images/icons/icon_24.png b/assets/images/icons/icon_24.png new file mode 100644 index 0000000..61c18a8 Binary files /dev/null and b/assets/images/icons/icon_24.png differ diff --git a/assets/images/icons/icon_25.png b/assets/images/icons/icon_25.png new file mode 100644 index 0000000..aefe081 Binary files /dev/null and b/assets/images/icons/icon_25.png differ diff --git a/assets/images/icons/icon_26.png b/assets/images/icons/icon_26.png new file mode 100644 index 0000000..e2f4be3 Binary files /dev/null and b/assets/images/icons/icon_26.png differ diff --git a/assets/images/icons/icon_27.png b/assets/images/icons/icon_27.png new file mode 100644 index 0000000..0cfb382 Binary files /dev/null and b/assets/images/icons/icon_27.png differ diff --git a/assets/images/icons/icon_28.png b/assets/images/icons/icon_28.png new file mode 100644 index 0000000..bcb4898 Binary files /dev/null and b/assets/images/icons/icon_28.png differ diff --git a/assets/images/icons/icon_29.png b/assets/images/icons/icon_29.png new file mode 100644 index 0000000..d2a13f5 --- /dev/null +++ b/assets/images/icons/icon_29.png @@ -0,0 +1,2 @@ +WFxX S,]\| Mq=2HdqbOs;K|rO-p$ d`:k:w)WiAl 97,  +ͯKm7Yhz'.gBǜC^ NVӾqH2nCᾒ4-x6ڞ:E.j]N%5-ކH8aYVNhPk5om'L\["эninpt4@ v5ꔉw6npuoLVn(WǙZ:6fƆ_XcOH2i0t%ƈjjtE%FGԌ"_$̮5MmG}G 7.qw?9N}31>8 v,% +9Ǫx6GF~Wn7 )3c>]&Cyi1L.4\?U2.j/~DԻCKR^~ ǏnHdECuxw*5/A^HеMs-]B~rYЩ²׉top1Z//K&ΰ*۟jJ ?- 8m"a6xDT$d؛vKJj Ry" \ No newline at end of file diff --git a/assets/images/icons/icon_30.png b/assets/images/icons/icon_30.png new file mode 100644 index 0000000..e4b4a67 Binary files /dev/null and b/assets/images/icons/icon_30.png differ diff --git a/assets/images/icons/icon_4.png b/assets/images/icons/icon_4.png new file mode 100644 index 0000000..f288014 Binary files /dev/null and b/assets/images/icons/icon_4.png differ diff --git a/assets/images/icons/icon_5.png b/assets/images/icons/icon_5.png new file mode 100644 index 0000000..5cc2259 Binary files /dev/null and b/assets/images/icons/icon_5.png differ diff --git a/assets/images/icons/icon_6.png b/assets/images/icons/icon_6.png new file mode 100644 index 0000000..ef328e5 Binary files /dev/null and b/assets/images/icons/icon_6.png differ diff --git a/assets/images/icons/icon_7.png b/assets/images/icons/icon_7.png new file mode 100644 index 0000000..b36e607 Binary files /dev/null and b/assets/images/icons/icon_7.png differ diff --git a/assets/images/icons/icon_8.png b/assets/images/icons/icon_8.png new file mode 100644 index 0000000..8ae69e1 Binary files /dev/null and b/assets/images/icons/icon_8.png differ diff --git a/assets/images/icons/icon_9.png b/assets/images/icons/icon_9.png new file mode 100644 index 0000000..5427a37 Binary files /dev/null and b/assets/images/icons/icon_9.png differ diff --git a/assets/videos/onboarding_tutorial.mp4 b/assets/videos/onboarding_tutorial.mp4 new file mode 100644 index 0000000..e15ee77 Binary files /dev/null and b/assets/videos/onboarding_tutorial.mp4 differ diff --git a/assets/videos/product_demo.mp4 b/assets/videos/product_demo.mp4 new file mode 100644 index 0000000..6c3ef8f Binary files /dev/null and b/assets/videos/product_demo.mp4 differ diff --git a/demo.sh b/demo.sh new file mode 100755 index 0000000..a33187a --- /dev/null +++ b/demo.sh @@ -0,0 +1,1022 @@ +#!/bin/bash +# Unified Sentry Demo Script for Flutter +# Consolidates build, run, size analysis, and release management +# +# Usage: +# ./demo.sh build [platform] [build-type] - Build with release management +# ./demo.sh run [platform] - Run app and create deploy +# ./demo.sh upload-size [file] [platform] - Upload size analysis +# ./demo.sh verify - Verify setup +# ./demo.sh help - Show help +# +# Examples: +# ./demo.sh build android # Build Android APK with release +# ./demo.sh build aab # Build Android AAB with release +# ./demo.sh run android # Run Android app and create deploy +# ./demo.sh upload-size build/app.apk android +# ./demo.sh verify + +set -e + +# ============================================================================ +# CONFIGURATION & COLORS +# ============================================================================ + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +NC='\033[0m' + +DEBUG_INFO_PATH="build/debug-info" + +# ============================================================================ +# UTILITY FUNCTIONS +# ============================================================================ + +print_info() { echo -e "${BLUE}ℹ ${NC}$1"; } +print_success() { echo -e "${GREEN}✓${NC} $1"; } +print_warning() { echo -e "${YELLOW}⚠${NC} $1"; } +print_error() { echo -e "${RED}✗${NC} $1"; } +print_header() { + echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" +} + +# Load environment variables from .env +load_env() { + if [ -f .env ]; then + print_info "Loading environment variables from .env" + export $(grep -v '^#' .env | xargs) + print_success "Environment variables loaded" + else + print_warning ".env file not found, using system environment variables" + fi +} + +# Check if Flutter is installed +check_flutter() { + if command -v flutter &> /dev/null; then + FLUTTER_CMD="flutter" + print_success "Flutter found: $(flutter --version | head -n 1)" + else + HOMEBREW_FLUTTER="/opt/homebrew/Caskroom/flutter/3.38.7/flutter/bin/flutter" + if [ -f "$HOMEBREW_FLUTTER" ]; then + FLUTTER_CMD="$HOMEBREW_FLUTTER" + print_success "Flutter found (Homebrew): $($FLUTTER_CMD --version | head -n 1)" + else + print_error "Flutter is not installed or not in PATH" + print_info "Install: https://flutter.dev/docs/get-started/install" + exit 1 + fi + fi +} + +# Check if Sentry CLI is installed +check_sentry_cli() { + if ! command -v sentry-cli &> /dev/null; then + print_warning "sentry-cli not found" + print_info "Install: brew install sentry-cli" + return 1 + fi + return 0 +} + +# Extract version from pubspec.yaml +get_version() { + if [ ! -f pubspec.yaml ]; then + print_error "pubspec.yaml not found" + exit 1 + fi + + VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: *//' | tr -d '\r\n ') + + if [ -z "$VERSION" ]; then + print_error "Could not extract version from pubspec.yaml" + exit 1 + fi + + echo "$VERSION" +} + +# Get package name from pubspec.yaml +get_package_name() { + if [ ! -f pubspec.yaml ]; then + print_error "pubspec.yaml not found" + exit 1 + fi + + PACKAGE_NAME=$(grep '^name:' pubspec.yaml | sed 's/name: *//' | tr -d '\r\n ') + + if [ -z "$PACKAGE_NAME" ]; then + print_error "Could not extract package name from pubspec.yaml" + exit 1 + fi + + echo "$PACKAGE_NAME" +} + +# Get full release name for Sentry +get_release_name() { + local package_name=$(get_package_name) + local version=$(get_version) + echo "com.example.${package_name}@${version}" +} + +# Get dependencies +get_dependencies() { + print_info "Getting Flutter dependencies..." + $FLUTTER_CMD pub get + print_success "Dependencies installed" +} + +# ============================================================================ +# RELEASE MANAGEMENT FUNCTIONS +# ============================================================================ + +create_release() { + if ! check_sentry_cli; then + print_warning "Skipping release creation (sentry-cli not found)" + return 1 + fi + + local release_name=$(get_release_name) + + print_header "Creating Sentry Release" + print_info "Release: $release_name" + print_info "Organization: ${SENTRY_ORG}" + print_info "Project: ${SENTRY_PROJECT}" + + # Create release + if sentry-cli releases new "$release_name" \ + --org "${SENTRY_ORG}" \ + --project "${SENTRY_PROJECT}"; then + print_success "Release created: $release_name" + else + # Release might already exist + print_warning "Release may already exist (continuing)" + fi + + # Set commits + print_info "Associating commits with release..." + if sentry-cli releases set-commits "$release_name" --auto \ + --org "${SENTRY_ORG}"; then + print_success "Commits associated with release" + else + print_warning "Could not associate commits (continuing)" + print_info "Ensure your auth token has 'org:read' scope" + fi + + return 0 +} + +finalize_release() { + if ! check_sentry_cli; then + return 1 + fi + + local release_name=$(get_release_name) + + print_header "Finalizing Sentry Release" + print_info "Release: $release_name" + + if sentry-cli releases finalize "$release_name" \ + --org "${SENTRY_ORG}" \ + --project "${SENTRY_PROJECT}"; then + print_success "Release finalized: $release_name" + else + print_warning "Could not finalize release" + return 1 + fi + + return 0 +} + +create_deploy() { + if ! check_sentry_cli; then + return 1 + fi + + local release_name=$(get_release_name) + local environment="${SENTRY_ENVIRONMENT:-production}" + + print_header "Creating Deploy Record" + print_info "Release: $release_name" + print_info "Environment: $environment" + + if sentry-cli releases deploys "$release_name" new \ + -e "$environment" \ + --org "${SENTRY_ORG}" \ + --project "${SENTRY_PROJECT}"; then + print_success "Deploy created for $environment" + else + print_warning "Could not create deploy" + return 1 + fi + + return 0 +} + +# ============================================================================ +# BUILD FUNCTIONS +# ============================================================================ + +build_with_release() { + local platform="$1" + local build_type="${2:-release}" + + print_header "Building $platform ($build_type)" + print_info "Version: $(get_version)" + + # Load environment + load_env + + # Check Flutter + check_flutter + + # Get dependencies + get_dependencies + + # Create release (for release builds only) + if [ "$build_type" = "release" ]; then + create_release + fi + + # Build based on platform + case "$platform" in + android) + build_android "$build_type" + ;; + aab) + build_aab "$build_type" + ;; + ios) + build_ios "$build_type" + ;; + web) + build_web "$build_type" + ;; + macos) + build_macos "$build_type" + ;; + linux) + build_linux "$build_type" + ;; + windows) + build_windows "$build_type" + ;; + *) + print_error "Unknown platform: $platform" + exit 1 + ;; + esac + + # Upload debug symbols (for release builds) + if [ "$build_type" = "release" ]; then + upload_symbols + finalize_release + fi + + print_header "Build Complete!" + print_success "All tasks completed successfully" +} + +build_android() { + local build_type="${1:-release}" + + print_info "Building Android APK..." + + if [ "$build_type" = "release" ]; then + $FLUTTER_CMD build apk \ + --$build_type \ + --obfuscate \ + --split-debug-info=$DEBUG_INFO_PATH \ + --extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json \ + --dart-define=SENTRY_DSN="$SENTRY_DSN" \ + --dart-define=SENTRY_RELEASE="$(get_release_name)" \ + --dart-define=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" + else + $FLUTTER_CMD build apk --$build_type + fi + + local apk_path="build/app/outputs/flutter-apk/app-$build_type.apk" + print_success "Android APK built: $apk_path" + + # Show file size + if [ -f "$apk_path" ]; then + local size=$(du -h "$apk_path" | cut -f1) + print_info "APK size: $size" + fi + + # Note: Size analysis is only uploaded for AAB builds (see build_aab function) + # APK builds skip size analysis to avoid duplicate uploads +} + +build_aab() { + local build_type="${1:-release}" + + print_info "Building Android AAB (App Bundle)..." + + if [ "$build_type" = "release" ]; then + $FLUTTER_CMD build appbundle \ + --$build_type \ + --obfuscate \ + --split-debug-info=$DEBUG_INFO_PATH \ + --extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json \ + --dart-define=SENTRY_DSN="$SENTRY_DSN" \ + --dart-define=SENTRY_RELEASE="$(get_release_name)" \ + --dart-define=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" + else + $FLUTTER_CMD build appbundle --$build_type + fi + + local aab_path="build/app/outputs/bundle/${build_type}/app-${build_type}.aab" + print_success "Android AAB built: $aab_path" + + # Show file size + if [ -f "$aab_path" ]; then + local size=$(du -h "$aab_path" | cut -f1) + print_info "AAB size: $size" + fi + + # Upload ProGuard mapping BEFORE size analysis for detailed DEX breakdown + if [ "$build_type" = "release" ] && [ "${SENTRY_SIZE_ANALYSIS_ENABLED}" = "true" ]; then + upload_proguard_mapping + fi + + # Upload size analysis for release builds + if [ "$build_type" = "release" ] && [ "${SENTRY_SIZE_ANALYSIS_ENABLED}" = "true" ]; then + upload_size_analysis "$aab_path" "Android" + fi +} + +build_ios() { + local build_type="${1:-release}" + + if [ "$(uname)" != "Darwin" ]; then + print_error "iOS builds require macOS" + exit 1 + fi + + print_info "Building iOS app..." + + if [ "$build_type" = "release" ]; then + $FLUTTER_CMD build ios \ + --$build_type \ + --obfuscate \ + --split-debug-info=$DEBUG_INFO_PATH \ + --extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json \ + --dart-define=SENTRY_DSN="$SENTRY_DSN" \ + --dart-define=SENTRY_RELEASE="$(get_release_name)" \ + --dart-define=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" + else + $FLUTTER_CMD build ios --$build_type + fi + + print_success "iOS app built: build/ios/iphoneos/Runner.app" + + if [ "$build_type" = "release" ]; then + print_info "For size analysis, create IPA via Xcode and upload with:" + print_info " ./demo.sh upload-size YourApp.ipa ios" + fi +} + +build_web() { + local build_type="${1:-release}" + + print_info "Building web app..." + + if [ "$build_type" = "release" ]; then + $FLUTTER_CMD build web \ + --$build_type \ + --source-maps \ + --dart-define=SENTRY_DSN="$SENTRY_DSN" \ + --dart-define=SENTRY_RELEASE="$(get_release_name)" \ + --dart-define=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" + else + $FLUTTER_CMD build web --$build_type + fi + + print_success "Web app built: build/web/" +} + +build_macos() { + local build_type="${1:-release}" + + if [ "$(uname)" != "Darwin" ]; then + print_error "macOS builds require macOS" + exit 1 + fi + + print_info "Building macOS app..." + + if [ "$build_type" = "release" ]; then + $FLUTTER_CMD build macos \ + --$build_type \ + --obfuscate \ + --split-debug-info=$DEBUG_INFO_PATH \ + --extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json \ + --dart-define=SENTRY_DSN="$SENTRY_DSN" \ + --dart-define=SENTRY_RELEASE="$(get_release_name)" \ + --dart-define=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" + else + $FLUTTER_CMD build macos --$build_type + fi + + print_success "macOS app built: build/macos/Build/Products/Release/empower_flutter.app" +} + +build_linux() { + local build_type="${1:-release}" + + print_info "Building Linux app..." + + if [ "$build_type" = "release" ]; then + $FLUTTER_CMD build linux \ + --$build_type \ + --obfuscate \ + --split-debug-info=$DEBUG_INFO_PATH \ + --extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json \ + --dart-define=SENTRY_DSN="$SENTRY_DSN" \ + --dart-define=SENTRY_RELEASE="$(get_release_name)" \ + --dart-define=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" + else + $FLUTTER_CMD build linux --$build_type + fi + + print_success "Linux app built: build/linux/x64/release/bundle/" +} + +build_windows() { + local build_type="${1:-release}" + + if [ "$(uname)" = "Darwin" ]; then + print_error "Windows builds cannot be performed on macOS" + exit 1 + fi + + print_info "Building Windows app..." + + if [ "$build_type" = "release" ]; then + $FLUTTER_CMD build windows \ + --$build_type \ + --obfuscate \ + --split-debug-info=$DEBUG_INFO_PATH \ + --extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json \ + --dart-define=SENTRY_DSN="$SENTRY_DSN" \ + --dart-define=SENTRY_RELEASE="$(get_release_name)" \ + --dart-define=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" + else + $FLUTTER_CMD build windows --$build_type + fi + + print_success "Windows app built: build/windows/x64/runner/Release/" +} + +# Upload ProGuard mapping for Android builds (must be called before size analysis) +upload_proguard_mapping() { + local proguard_mapping="build/app/outputs/mapping/release/mapping.txt" + if [ -f "$proguard_mapping" ]; then + print_info "Uploading ProGuard mapping for detailed DEX breakdown..." + if check_sentry_cli; then + if sentry-cli upload-proguard --org "${SENTRY_ORG}" --project "${SENTRY_PROJECT}" "$proguard_mapping" > /tmp/proguard_upload.log 2>&1; then + print_success "ProGuard mapping uploaded ($(du -h "$proguard_mapping" | cut -f1))" + else + print_warning "ProGuard mapping upload failed" + cat /tmp/proguard_upload.log + fi + fi + else + print_info "ProGuard mapping not found, skipping" + fi +} + +# Upload debug symbols to Sentry +upload_symbols() { + print_header "Uploading Debug Symbols" + + if [ -z "$SENTRY_DSN" ] && [ ! -f "sentry.properties" ]; then + print_warning "Sentry not configured. Skipping symbol upload." + return + fi + + print_info "Running sentry_dart_plugin..." + + set +e + $FLUTTER_CMD pub run sentry_dart_plugin --include-sources > /tmp/sentry_upload.log 2>&1 + local exit_code=$? + set -e + + cat /tmp/sentry_upload.log + + if [ $exit_code -eq 0 ]; then + print_success "Debug symbols uploaded to Sentry" + elif grep -q "succeeded=" /tmp/sentry_upload.log || grep -q "Nothing to upload" /tmp/sentry_upload.log; then + print_success "Debug symbols uploaded to Sentry" + if grep -q "Unknown repo\|403" /tmp/sentry_upload.log; then + print_warning "Repository integration requires 'org:read' scope" + fi + else + print_warning "Symbol upload completed with warnings (exit code: $exit_code)" + fi + + # Also upload ProGuard mapping (if not already uploaded before size analysis) + upload_proguard_mapping +} + +# ============================================================================ +# RUN FUNCTIONS +# ============================================================================ + +run_app() { + local platform="${1:-android}" + + print_header "Running $platform App" + + # Load environment + load_env + + # Check Flutter + check_flutter + + # Create deploy record + create_deploy + + case "$platform" in + android) + run_android + ;; + ios) + run_ios + ;; + web) + run_web + ;; + macos) + run_macos + ;; + linux) + run_linux + ;; + windows) + run_windows + ;; + *) + print_error "Unknown platform: $platform" + exit 1 + ;; + esac +} + +run_android() { + local ADB_CMD="adb" + + if ! command -v adb &> /dev/null; then + if [ -f "$HOME/Library/Android/sdk/platform-tools/adb" ]; then + ADB_CMD="$HOME/Library/Android/sdk/platform-tools/adb" + elif [ -f "$HOME/Android/Sdk/platform-tools/adb" ]; then + ADB_CMD="$HOME/Android/Sdk/platform-tools/adb" + else + print_error "adb not found" + exit 1 + fi + fi + + local device_count=$($ADB_CMD devices | grep -w "device" | wc -l) + if [ "$device_count" -eq 0 ]; then + print_error "No Android device/emulator connected" + exit 1 + fi + + local apk_path="build/app/outputs/flutter-apk/app-release.apk" + + if [ ! -f "$apk_path" ]; then + print_error "APK not found: $apk_path" + print_info "Build first with: ./demo.sh build android" + exit 1 + fi + + print_info "Installing APK..." + $ADB_CMD install -r "$apk_path" + + print_info "Launching app..." + $ADB_CMD shell am start -n com.example.empower_flutter/.MainActivity + + print_success "Android app launched" +} + +run_ios() { + if [ "$(uname)" != "Darwin" ]; then + print_error "iOS apps can only be run on macOS" + exit 1 + fi + + print_info "Launching iOS simulator..." + $FLUTTER_CMD run -d ios --release + + print_success "iOS app launched" +} + +run_web() { + print_info "Starting web server on port 8080..." + + if [ ! -d "build/web" ]; then + print_error "Web build not found" + print_info "Build first with: ./demo.sh build web" + exit 1 + fi + + cd build/web + + if command -v python3 &> /dev/null; then + python3 -m http.server 8080 + elif command -v python &> /dev/null; then + python -m SimpleHTTPServer 8080 + else + print_error "Python not found" + exit 1 + fi +} + +run_macos() { + if [ "$(uname)" != "Darwin" ]; then + print_error "macOS apps can only be run on macOS" + exit 1 + fi + + local app_path="build/macos/Build/Products/Release/empower_flutter.app" + + if [ ! -d "$app_path" ]; then + print_error "App not found: $app_path" + print_info "Build first with: ./demo.sh build macos" + exit 1 + fi + + print_info "Launching macOS app..." + open "$app_path" + + print_success "macOS app launched" +} + +run_linux() { + local exe_path="build/linux/x64/release/bundle/empower_flutter" + + if [ ! -f "$exe_path" ]; then + print_error "Executable not found: $exe_path" + print_info "Build first with: ./demo.sh build linux" + exit 1 + fi + + print_info "Launching Linux app..." + "$exe_path" & + + print_success "Linux app launched" +} + +run_windows() { + local exe_path="build/windows/x64/runner/Release/empower_flutter.exe" + + if [ ! -f "$exe_path" ]; then + print_error "Executable not found: $exe_path" + print_info "Build first with: ./demo.sh build windows" + exit 1 + fi + + print_info "Launching Windows app..." + start "$exe_path" + + print_success "Windows app launched" +} + +# ============================================================================ +# SIZE ANALYSIS FUNCTIONS +# ============================================================================ + +upload_size_analysis() { + local build_file="$1" + local platform="$2" + + if ! check_sentry_cli; then + print_warning "Cannot upload size analysis (sentry-cli not found)" + return 1 + fi + + if [ ! -f "$build_file" ]; then + print_error "Build file not found: $build_file" + exit 1 + fi + + print_header "Uploading Size Analysis" + + # Detect build format + local build_format="" + if [[ "$build_file" == *.aab ]]; then + build_format="AAB" + elif [[ "$build_file" == *.apk ]]; then + build_format="APK" + elif [[ "$build_file" == *.xcarchive ]]; then + build_format="XCArchive" + elif [[ "$build_file" == *.ipa ]]; then + build_format="IPA" + fi + + print_info "File: $build_file" + print_info "Format: $build_format" + print_info "Platform: $platform" + print_info "Organization: ${SENTRY_ORG}" + print_info "Project: ${SENTRY_PROJECT}" + + # Get git metadata + local head_sha=$(git rev-parse HEAD 2>/dev/null || echo "") + local base_sha=$(git merge-base HEAD origin/main 2>/dev/null || echo "") + local head_ref=$(git branch --show-current 2>/dev/null || echo "") + local base_ref="main" + + # Auto-detect VCS info + local git_remote=$(git config --get remote.origin.url 2>/dev/null || echo "") + local head_repo_name="" + local vcs_provider="" + + if [[ "$git_remote" =~ github\.com[:/](.+)(\.git)?$ ]]; then + head_repo_name="${BASH_REMATCH[1]}" + head_repo_name="${head_repo_name%.git}" + vcs_provider="github" + fi + + # Build command + local cmd="sentry-cli build upload \"$build_file\" --org \"$SENTRY_ORG\" --project \"$SENTRY_PROJECT\" --build-configuration Release" + + # Add metadata + [ -n "$head_sha" ] && cmd="$cmd --head-sha \"$head_sha\"" + [ -n "$base_sha" ] && cmd="$cmd --base-sha \"$base_sha\"" + [ -n "$head_ref" ] && cmd="$cmd --head-ref \"$head_ref\"" + [ -n "$base_ref" ] && cmd="$cmd --base-ref \"$base_ref\"" + [ -n "$vcs_provider" ] && cmd="$cmd --vcs-provider \"$vcs_provider\"" + [ -n "$head_repo_name" ] && cmd="$cmd --head-repo-name \"$head_repo_name\"" + + # Execute + if eval "$cmd"; then + print_success "Size analysis uploaded" + print_info "View: https://sentry.io/organizations/$SENTRY_ORG/projects/$SENTRY_PROJECT/size-analysis/" + else + print_error "Upload failed" + exit 1 + fi +} + +# ============================================================================ +# VERIFY FUNCTION +# ============================================================================ + +verify_setup() { + local issues=0 + + print_header "Verifying Setup" + + # Check Flutter + print_info "Checking Flutter..." + if command -v flutter &> /dev/null; then + print_success "Flutter: $(flutter --version | head -n 1)" + else + print_error "Flutter not found" + issues=$((issues + 1)) + fi + + # Check .env + print_info "Checking .env..." + if [ -f .env ]; then + print_success ".env file exists" + + if grep -q "SENTRY_DSN=" .env && [ -n "$(grep SENTRY_DSN= .env | cut -d= -f2)" ]; then + print_success "SENTRY_DSN is set" + else + print_warning "SENTRY_DSN not set" + issues=$((issues + 1)) + fi + + if grep -q "SENTRY_ORG=" .env && [ -n "$(grep SENTRY_ORG= .env | cut -d= -f2)" ]; then + print_success "SENTRY_ORG is set" + else + print_warning "SENTRY_ORG not set" + issues=$((issues + 1)) + fi + + if grep -q "SENTRY_PROJECT=" .env && [ -n "$(grep SENTRY_PROJECT= .env | cut -d= -f2)" ]; then + print_success "SENTRY_PROJECT is set" + else + print_warning "SENTRY_PROJECT not set" + issues=$((issues + 1)) + fi + else + print_error ".env file not found" + issues=$((issues + 1)) + fi + + # Check sentry-cli + print_info "Checking sentry-cli..." + if command -v sentry-cli &> /dev/null; then + print_success "sentry-cli: $(sentry-cli --version | head -n 1)" + else + print_warning "sentry-cli not found (optional for size analysis)" + print_info "Install: brew install sentry-cli" + fi + + # Check pubspec.yaml + print_info "Checking pubspec.yaml..." + if [ -f pubspec.yaml ]; then + local version=$(get_version) + print_success "Version: $version" + + if grep -q "sentry_flutter:" pubspec.yaml; then + local sdk_version=$(grep "sentry_flutter:" pubspec.yaml | awk '{print $2}') + print_success "Sentry SDK: $sdk_version" + fi + else + print_error "pubspec.yaml not found" + issues=$((issues + 1)) + fi + + # Check key files + print_info "Checking project files..." + local required_files=("lib/main.dart" "lib/sentry_setup.dart" "lib/checkout.dart") + + for file in "${required_files[@]}"; do + if [ -f "$file" ]; then + print_success "$file" + else + print_error "$file not found" + issues=$((issues + 1)) + fi + done + + # Summary + print_header "Verification Summary" + + if [ $issues -eq 0 ]; then + print_success "All checks passed! ✨" + echo "" + print_info "Next steps:" + echo " 1. ./demo.sh build android # Build with release management" + echo " 2. ./demo.sh run android # Run and create deploy" + echo "" + else + print_warning "Found $issues issue(s)" + print_info "Please fix the issues above" + fi + + exit 0 +} + +# ============================================================================ +# HELP FUNCTION +# ============================================================================ + +show_help() { + cat << 'EOF' +Unified Sentry Demo Script for Flutter +===================================== + +Consolidates build, run, size analysis, and release management. + +USAGE: + ./demo.sh [options] + +COMMANDS: + build [build-type] Build app with release management + run Run app and create deploy + upload-size Upload size analysis to Sentry + verify Verify setup configuration + help Show this help message + +PLATFORMS: + android - Build Android APK + aab - Build Android App Bundle (preferred) + ios - Build iOS app (macOS only) + web - Build web app + macos - Build macOS app (macOS only) + linux - Build Linux app + windows - Build Windows app + +BUILD TYPES: + debug - Debug build (no obfuscation) + profile - Profile build + release - Release build with obfuscation (default) + +EXAMPLES: + # Build Android APK with release management + ./demo.sh build android + + # Build Android AAB (preferred for Play Store) + ./demo.sh build aab + + # Build iOS release + ./demo.sh build ios + + # Build debug version + ./demo.sh build android debug + + # Run Android app and create deploy + ./demo.sh run android + + # Upload size analysis manually + ./demo.sh upload-size build/app/outputs/bundle/release/app-release.aab android + + # Verify setup + ./demo.sh verify + +RELEASE MANAGEMENT: + This script automatically manages Sentry releases: + 1. Extracts version from pubspec.yaml (e.g., 1.0.0+2) + 2. Creates release: com.example.empower_flutter@1.0.0+2 + 3. Associates commits with release (--auto) + 4. Builds the application + 5. Uploads debug symbols + 6. Finalizes the release + 7. Creates deploy record when app runs + +ENVIRONMENT: + Configure in .env file: + - SENTRY_DSN + - SENTRY_ORG + - SENTRY_PROJECT + - SENTRY_AUTH_TOKEN + - SENTRY_ENVIRONMENT + - SENTRY_SIZE_ANALYSIS_ENABLED + +REQUIREMENTS: + - Flutter SDK + - sentry-cli (brew install sentry-cli) + - Git (for release metadata) + +EOF +} + +# ============================================================================ +# MAIN ENTRY POINT +# ============================================================================ + +main() { + local command="${1:-help}" + + case "$command" in + build) + if [ -z "$2" ]; then + print_error "Platform required" + echo "Usage: ./demo.sh build [build-type]" + echo "Run './demo.sh help' for more information" + exit 1 + fi + build_with_release "$2" "${3:-release}" + ;; + + run) + if [ -z "$2" ]; then + print_error "Platform required" + echo "Usage: ./demo.sh run " + echo "Run './demo.sh help' for more information" + exit 1 + fi + run_app "$2" + ;; + + upload-size) + if [ -z "$2" ] || [ -z "$3" ]; then + print_error "Build file and platform required" + echo "Usage: ./demo.sh upload-size " + echo "Example: ./demo.sh upload-size build/app.aab android" + exit 1 + fi + load_env + upload_size_analysis "$2" "$3" + ;; + + verify) + verify_setup + ;; + + help|--help|-h) + show_help + ;; + + *) + print_error "Unknown command: $command" + echo "" + echo "Usage: ./demo.sh [options]" + echo "" + echo "Commands:" + echo " build - Build app with release management" + echo " run - Run app and create deploy" + echo " upload-size - Upload size analysis" + echo " verify - Verify setup" + echo " help - Show detailed help" + echo "" + echo "Run './demo.sh help' for detailed usage information" + exit 1 + ;; + esac +} + +# Run main function +main "$@" diff --git a/lib/checkout.dart b/lib/checkout.dart index 807b3f1..0ef2862 100644 --- a/lib/checkout.dart +++ b/lib/checkout.dart @@ -1,9 +1,13 @@ import 'dart:convert'; +import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; // ignore: depend_on_referenced_packages import 'package:sentry/sentry.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:logging/logging.dart'; import 'sentry_setup.dart'; +import 'se_config.dart'; class CheckoutView extends StatefulWidget { static const String routeName = "/checkout"; @@ -16,8 +20,87 @@ class CheckoutView extends StatefulWidget { } class _CheckoutViewState extends State { - final _uri = - "https://application-monitoring-flask-dot-sales-engineering-sf.appspot.com/checkout"; + final _uri = "https://flask.empower-plant.com/checkout"; + final _promoCodeController = TextEditingController(text: 'SAVE20'); + final _log = Logger('CheckoutLogger'); + String? _promoErrorMessage; + + @override + void dispose() { + _promoCodeController.dispose(); + super.dispose(); + } + + Future applyPromoCode(String code) async { + // Clear any previous error + setState(() { + _promoErrorMessage = null; + }); + + if (code.isEmpty) return; + + // Track promo code application attempt with metrics + Sentry.metrics.count( + 'promo_code_attempts', + 1, + attributes: { + 'code': SentryAttribute.string(code), + 'code_length': SentryAttribute.int(code.length), + }, + ); + + // Log info message with new Sentry.logger API + Sentry.logger.fmt.info('Applying promo code: %s', [code], attributes: { + 'promo_code': SentryAttribute.string(code), + 'action': SentryAttribute.string('promo_apply'), + }); + + // Also use legacy logger for backwards compatibility + _log.info("applying promo code '$code'..."); + + // Simulate API delay + await Future.delayed(const Duration(milliseconds: 500)); + + // Always fail with error - simulate expired promo code + final errorBody = jsonEncode({ + "error": { + "code": "expired", + "message": "Provided coupon code has expired." + } + }); + + // Track failure with metrics + Sentry.metrics.count( + 'promo_code_failures', + 1, + attributes: { + 'error_code': SentryAttribute.string('expired'), + 'code': SentryAttribute.string(code), + }, + ); + + // Log error with new Sentry.logger API including structured attributes + Sentry.logger.fmt.error( + 'Failed to apply promo code %s: HTTP 410 | Error: %s', + [code, 'expired'], + attributes: { + 'promo_code': SentryAttribute.string(code), + 'http_status': SentryAttribute.int(410), + 'error_code': SentryAttribute.string('expired'), + 'error_message': SentryAttribute.string('Provided coupon code has expired.'), + 'response_body': SentryAttribute.string(errorBody), + }, + ); + + // Also use legacy logger for backwards compatibility (info level since this is expected behavior) + _log.info("failed to apply promo code: HTTP 410 | body: $errorBody"); + + // Update UI with error message + setState(() { + _promoErrorMessage = "Unknown error applying promo code"; + }); + } + @override Widget build(BuildContext context) { var key = GlobalKey(); @@ -33,9 +116,29 @@ class _CheckoutViewState extends State { var client = SentryHttpClient(); void completeCheckout(var key) async { + // Track checkout attempt with metrics + Sentry.metrics.count('checkout_attempts', 1, attributes: { + 'num_items': SentryAttribute.int(args?.numItems ?? 0), + }); + + // Log checkout start + Sentry.logger.fmt.info( + 'Starting checkout: %s items, total %s', + [args?.numItems ?? 0, subTotal?.toStringAsFixed(2) ?? '0.00'], + attributes: { + 'num_items': SentryAttribute.int(args?.numItems ?? 0), + 'subtotal': SentryAttribute.double(subTotal ?? 0.0), + 'action': SentryAttribute.string('checkout_start'), + }, + ); + if (kDebugMode) { print(orderPayload); } + + // Track API latency + final startTime = DateTime.now(); + try { final checkoutResult = await client.post( Uri.parse(_uri), @@ -60,7 +163,35 @@ class _CheckoutViewState extends State { }), ); + // Track API response time + final latency = DateTime.now().difference(startTime).inMilliseconds; + Sentry.metrics.distribution( + 'checkout_api_latency', + latency.toDouble(), + unit: SentryMetricUnit.millisecond, + attributes: { + 'status_code': SentryAttribute.int(checkoutResult.statusCode), + }, + ); + if (checkoutResult.statusCode != 200) { + // Track checkout failure + Sentry.metrics.count('checkout_failures', 1, attributes: { + 'status_code': SentryAttribute.int(checkoutResult.statusCode), + 'error_type': SentryAttribute.string('http_error'), + }); + + // Log checkout failure with details + Sentry.logger.fmt.error( + 'Checkout failed with status %s', + [checkoutResult.statusCode], + attributes: { + 'http_status': SentryAttribute.int(checkoutResult.statusCode), + 'num_items': SentryAttribute.int(args?.numItems ?? 0), + 'subtotal': SentryAttribute.double(subTotal ?? 0.0), + 'latency_ms': SentryAttribute.int(latency), + }, + ); Sentry.runZonedGuarded( () async { // Show error to user @@ -106,6 +237,22 @@ class _CheckoutViewState extends State { } } } catch (error, stackTrace) { + // Track exception + Sentry.metrics.count('checkout_exceptions', 1, attributes: { + 'error_type': SentryAttribute.string(error.runtimeType.toString()), + }); + + // Log exception with context + Sentry.logger.fmt.error( + 'Checkout exception: %s', + [error.toString()], + attributes: { + 'error_type': SentryAttribute.string(error.runtimeType.toString()), + 'num_items': SentryAttribute.int(args?.numItems ?? 0), + 'subtotal': SentryAttribute.double(subTotal ?? 0.0), + }, + ); + Sentry.runZonedGuarded( () async { ScaffoldMessenger.of(context).showSnackBar( @@ -228,6 +375,64 @@ class _CheckoutViewState extends State { ), ), SizedBox(height: 30), + // Promo Code Section + Container( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Have a promo code?", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 10), + Row( + children: [ + Expanded( + child: TextField( + controller: _promoCodeController, + decoration: InputDecoration( + hintText: "Enter promo code", + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + ), + ), + ), + SizedBox(width: 10), + ElevatedButton( + onPressed: () { + applyPromoCode(_promoCodeController.text); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + ), + child: Text('Apply'), + ), + ], + ), + if (_promoErrorMessage != null) ...[ + SizedBox(height: 8), + Text( + _promoErrorMessage!, + style: TextStyle( + color: Colors.red, + fontSize: 14, + ), + ), + ], + ], + ), + ), + SizedBox(height: 30), ElevatedButton( onPressed: () { Sentry.addBreadcrumb( @@ -249,10 +454,25 @@ class _CheckoutViewState extends State { } } -// Returns a randomized email address for demo/testing +// Returns an email address for demo/testing. +// - If se is set (not 'tda'): returns se@example.com for easy attribution. +// - If se is 'tda' (default): returns john.logs@example.com ~10 times per day +// (once per 144-minute window, seeded by day+window so it's deterministic), +// otherwise returns a unique timestamped address. String getRandomEmail() { - final timestamp = DateTime.now().millisecondsSinceEpoch; - return 'user_$timestamp@example.com'; + if (se != 'tda') { + return '$se@example.com'; + } + final now = DateTime.now(); + // Divide the day into 10 equal windows of 144 minutes each (1440 min / 10). + final minuteOfDay = now.hour * 60 + now.minute; + final windowIndex = minuteOfDay ~/ 144; // 0–9 + // Seed is unique per calendar day + window, so result is fixed within a window. + final seed = now.year * 10000 + now.month * 100 + now.day * 10 + windowIndex; + if (Random(seed).nextBool()) { + return 'john.logs@example.com'; + } + return 'user_${now.millisecondsSinceEpoch}@example.com'; } class CheckoutArguments { diff --git a/lib/main.dart b/lib/main.dart index bfae94c..1876b68 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,13 +26,13 @@ Future main() async { scope.setTag('dart.version', Platform.version); scope.setTag('os', Platform.operatingSystem); scope.setTag('os.version', Platform.operatingSystemVersion); - // ignore: deprecated_member_use - scope.setTag('locale', WidgetsBinding.instance.window.locale.toString()); - // ignore: deprecated_member_use + + // Get platform dispatcher for locale and screen size (replaces deprecated window API) + final view = WidgetsBinding.instance.platformDispatcher.views.first; + scope.setTag('locale', view.platformDispatcher.locale.toString()); scope.setTag( 'screen.size', - // ignore: deprecated_member_use - '${WidgetsBinding.instance.window.physicalSize.width}x${WidgetsBinding.instance.window.physicalSize.height}', + '${view.physicalSize.width}x${view.physicalSize.height}', ); }); await initSentry( @@ -64,7 +64,14 @@ class MyApp extends StatelessWidget { log.info('Building MyApp'); return MaterialApp( navigatorKey: navigatorKey, - navigatorObservers: [SentryNavigatorObserver()], + navigatorObservers: [ + SentryNavigatorObserver( + // Enable automatic breadcrumb tracking for navigation + enableAutoTransactions: true, + // Auto-finish transactions after 3 seconds (default) + autoFinishAfter: const Duration(seconds: 3), + ), + ], routes: { "/productDetails": (context) { log.info('Navigating to ProductDetails'); @@ -75,7 +82,7 @@ class MyApp extends StatelessWidget { return CheckoutView(); }, }, - home: SentryDisplayWidget(child: HomePage()), + home: HomePage(), ); } } @@ -107,7 +114,12 @@ class _HomePageState extends State { int _currentIndex = 0; List allDestinations = [ - Destination.withChild(Icons.home, "Home", ItemsList()), + // Mark the ItemsList with TTFD because this is the first screen displayed when opening the app + Destination.withChild( + Icons.home, + "Home", + SentryDisplayWidget(child: ItemsList()), + ), Destination.withChild(Icons.shopping_bag, "Cart", CartView()), ]; @@ -164,11 +176,6 @@ class _HomePageState extends State { } catch (e) { log.warning('Frame drop computation error: $e'); } - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - SentryDisplayWidget.of(context).reportFullyDisplayed(); - } - }); } @override diff --git a/lib/navbar_destination.dart b/lib/navbar_destination.dart index 3cd4f8b..b5f1ac1 100644 --- a/lib/navbar_destination.dart +++ b/lib/navbar_destination.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; import 'package:flutter/services.dart'; // ignore: depend_on_referenced_packages import 'package:sentry/sentry.dart'; +import 'package:sentry_file/sentry_file.dart'; import 'sentry_setup.dart'; class Destination { @@ -53,326 +56,52 @@ class _DestinationViewState extends State { await transaction.finish(); }, ), - ListTile( - title: Text('ANR'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.anr', - 'ui.action', - ); - final span = transaction.startChild( - 'main.thread.block', - description: 'Simulate ANR: Watering plant too long', - ); - transaction.setData('plant_action', 'simulate_anr'); - span.setData('duration', 10); - Navigator.pop(context); - final start = DateTime.now(); - while (DateTime.now().difference(start).inSeconds < 10) {} - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('C++ Segfault'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.cpp_segfault', - 'ui.action', - ); - final span = transaction.startChild( - 'native.crash', - description: 'Trigger C++ Segfault: Plant root failure', - ); - transaction.setData('plant_action', 'cpp_segfault'); - span.setData('drawer', 'cpp_segfault_button'); - try { - await channel.invokeMethod('cppSegfault'); - } catch (error, stackTrace) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException( - error, - stackTrace: stackTrace, + // Platform-specific: ANR for Android, App Hang for iOS/macOS + if (Platform.isAndroid) + ListTile( + title: Text('ANR (Android)'), + onTap: () async { + final transaction = Sentry.startTransaction( + 'drawer.anr', + 'ui.action', ); - } - // ignore: use_build_context_synchronously - Navigator.pop(context); - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Kotlin Exception'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.kotlin_exception', - 'ui.action', - ); - final span = transaction.startChild( - 'native.exception', - description: 'Trigger Kotlin Exception: Plant leaf error', - ); - transaction.setData('plant_action', 'kotlin_exception'); - span.setData('drawer', 'kotlin_exception_button'); - try { - await channel.invokeMethod('kotlinException'); - } catch (error, stackTrace) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException( - error, - stackTrace: stackTrace, + final span = transaction.startChild( + 'main.thread.block', + description: 'Simulate ANR: Watering plant too long', ); - } - // ignore: use_build_context_synchronously - Navigator.pop(context); - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Dart Exception'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.dart_exception', - 'ui.action', - ); - final span = transaction.startChild( - 'dart.exception', - description: 'Trigger Dart Exception: Plant soil error', - ); - transaction.setData('plant_action', 'dart_exception'); - span.setData('drawer', 'dart_exception_button'); - Navigator.pop(context); - try { - throw Exception('Simulated Dart Exception'); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Timeout Exception'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.timeout_exception', - 'ui.action', - ); - final span = transaction.startChild( - 'dart.timeout', - description: 'Trigger Timeout: Plant growth timeout', - ); - transaction.setData('plant_action', 'timeout_exception'); - span.setData('drawer', 'timeout_exception_button'); - Navigator.pop(context); - try { - throw TimeoutException( - 'Operation timed out', - Duration(seconds: 2), + transaction.setData('plant_action', 'simulate_anr'); + span.setData('duration', 10); + Navigator.pop(context); + // Block main thread for 10 seconds to trigger ANR + final start = DateTime.now(); + while (DateTime.now().difference(start).inSeconds < 10) {} + await span.finish(); + await transaction.finish(); + }, + ), + if (Platform.isIOS || Platform.isMacOS) + ListTile( + title: Text('App Hang (iOS/macOS)'), + onTap: () async { + final transaction = Sentry.startTransaction( + 'drawer.app_hang', + 'ui.action', ); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Platform Exception'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.platform_exception', - 'ui.action', - ); - final span = transaction.startChild( - 'platform.exception', - description: 'Trigger Platform Exception: Plant pot error', - ); - transaction.setData('plant_action', 'platform_exception'); - span.setData('drawer', 'platform_exception_button'); - Navigator.pop(context); - try { - throw PlatformException( - code: 'PLATFORM_ERROR', - message: 'Simulated platform error', + final span = transaction.startChild( + 'main.thread.block', + description: 'Simulate App Hang: Plant processing stuck', ); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Missing Plugin Exception'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.missing_plugin_exception', - 'ui.action', - ); - final span = transaction.startChild( - 'plugin.exception', - description: - 'Trigger Missing Plugin: Plant fertilizer missing', - ); - transaction.setData( - 'plant_action', - 'missing_plugin_exception', - ); - span.setData('drawer', 'missing_plugin_exception_button'); - Navigator.pop(context); - try { - throw MissingPluginException('Simulated missing plugin'); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Assertion Error'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.assertion_error', - 'ui.action', - ); - final span = transaction.startChild( - 'dart.assertion', - description: - 'Trigger Assertion Error: Plant sunlight assertion', - ); - transaction.setData('plant_action', 'assertion_error'); - span.setData('drawer', 'assertion_error_button'); - Navigator.pop(context); - try { - assert(false, 'Simulated assertion error'); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('State Error'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.state_error', - 'ui.action', - ); - final span = transaction.startChild( - 'dart.state', - description: 'Trigger State Error: Plant state error', - ); - transaction.setData('plant_action', 'state_error'); - span.setData('drawer', 'state_error_button'); - Navigator.pop(context); - try { - throw StateError('Simulated state error'); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Range Error'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.range_error', - 'ui.action', - ); - final span = transaction.startChild( - 'dart.range', - description: 'Trigger Range Error: Plant root range error', - ); - transaction.setData('plant_action', 'range_error'); - span.setData('drawer', 'range_error_button'); - Navigator.pop(context); - try { - throw RangeError('Simulated range error'); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - ListTile( - title: Text('Type Error'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.type_error', - 'ui.action', - ); - final span = transaction.startChild( - 'dart.type', - description: 'Trigger Type Error: Plant type error', - ); - transaction.setData('plant_action', 'type_error'); - span.setData('drawer', 'type_error_button'); - Navigator.pop(context); - try { - throw TypeError(); - } catch (error) { - span.throwable = error; - span.status = SpanStatus.internalError(); - await Sentry.captureException(error); - } - await span.finish(); - await transaction.finish(); - }, - ), - // N+1 API Calls demo button - ListTile( - title: Text('N+1 API Calls'), - onTap: () async { - final transaction = Sentry.startTransaction( - 'drawer.n_plus_one_api_calls', - 'ui.action', - ); - final span = transaction.startChild( - 'http.client', - description: 'Trigger N+1 API Calls: Plant fetch demo', - ); - transaction.setData('plant_action', 'n_plus_one_api_calls'); - span.setData('drawer', 'n_plus_one_api_calls_button'); - Navigator.pop(context); - final url = - 'https://application-monitoring-flask-dot-sales-engineering-sf.appspot.com/products'; - final client = SentryHttpClient(); - final futures = List.generate(15, (i) async { - final uri = Uri.parse('$url?id=$i'); - try { - await client.get(uri); - } catch (e, st) { - await Sentry.captureException(e, stackTrace: st); - } - }); - await Future.wait(futures); - await span.finish(); - await transaction.finish(); - }, - ), + transaction.setData('plant_action', 'simulate_app_hang'); + span.setData('duration', 3); + Navigator.pop(context); + // Block main thread for 3 seconds to trigger App Hang + // (App hang threshold is 2 seconds by default) + final start = DateTime.now(); + while (DateTime.now().difference(start).inSeconds < 3) {} + await span.finish(); + await transaction.finish(); + }, + ), ], ).toList(), ), @@ -386,9 +115,23 @@ class _DestinationViewState extends State { } Future execute(String method) async { + final transaction = Sentry.startTransaction( + 'execute.$method', + 'ui.action', + ); + final span = transaction.startChild( + 'method.channel.invoke', + description: 'Execute method: $method', + ); + transaction.setData('method', method); + span.setData('channel', 'example.flutter.sentry.io'); + try { await channel.invokeMethod(method); + span.status = SpanStatus.ok(); } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); final eventId = await Sentry.captureException( error, stackTrace: stackTrace, @@ -397,6 +140,9 @@ class _DestinationViewState extends State { if (mounted && error is! PlatformException) { showUserFeedbackDialog(context, eventId); } + } finally { + await span.finish(); + await transaction.finish(); } } } diff --git a/lib/product_details.dart b/lib/product_details.dart index a1f7446..5e6b162 100644 --- a/lib/product_details.dart +++ b/lib/product_details.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; // ignore: depend_on_referenced_packages import 'package:sentry/sentry.dart'; -import 'package:flutter/services.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; class ProductDetails extends StatefulWidget { static const routeName = '/productDetails'; diff --git a/lib/product_list.dart b/lib/product_list.dart index a860e8c..06d8b02 100644 --- a/lib/product_list.dart +++ b/lib/product_list.dart @@ -1,11 +1,16 @@ import 'dart:convert'; +import 'dart:async'; +import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'models/cart_state_model.dart'; import 'product_details.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_file/sentry_file.dart'; import 'dart:io'; +import 'se_config.dart'; // ignore: depend_on_referenced_packages class ItemsList extends StatefulWidget { @@ -17,86 +22,506 @@ class ItemsList extends StatefulWidget { } class _ItemListState extends State { - final String _uri = - 'https://application-monitoring-flask-dot-sales-engineering-sf.appspot.com/products'; + final String _uri = 'https://flask.empower-plant.com/products'; late Future shopItems; var client = SentryHttpClient(); + final channel = const MethodChannel('example.flutter.sentry.io'); Future fetchShopItems() async { - final transaction = Sentry.startTransaction( - 'GET /products', - 'http.client', - bindToScope: true, - ); try { final response = await client.get(Uri.parse(_uri)); // Simulate full response processing final data = ResponseData.fromJson((jsonDecode(response.body))); - transaction.finish(status: SpanStatus.ok()); return data; } catch (e) { - transaction.finish(status: SpanStatus.internalError()); rethrow; } } @override void initState() { - // Intentionally perform a slow database query on the main thread to trigger Sentry performance issue + super.initState(); + + final email = getRandomEmail(); + Sentry.configureScope((scope) => scope.setUser(SentryUser(id: email))); + + // PERFORMANCE ISSUES (triggered synchronously on main thread) + // 1. Simulate Database Query on Main Thread (triggers DB on Main Thread issue) + _performDatabaseQuery(); + + // 2. Simulate File I/O on Main Thread (triggers File I/O issue) + _performFileIO(); + + // 3. Simulate JSON Decoding on Main Thread (triggers JSON Decoding issue) + _performJSONDecoding(); + + // ERROR DEMONSTRATIONS (triggered asynchronously to not block UI) + // Trigger various error types for Sentry demo + Future.microtask(() async { + await _triggerCppSegfault(); + await _triggerKotlinException(); // Android only + await _triggerDartException(); + await _triggerTimeoutException(); + await _triggerPlatformException(); + await _triggerMissingPluginException(); + await _triggerAssertionError(); + await _triggerStateError(); + await _triggerRangeError(); + await _triggerTypeError(); + }); + + // ADDITIONAL PERFORMANCE ISSUES (triggered asynchronously) + Future.microtask(() async { + await _triggerImageDecoding(); + await _triggerRegex(); + await _triggerNPlusOneAPICalls(); + await _triggerFrameDrop(); + await _triggerFunctionRegression(); + }); + + // Fetch products from API and report TTFD when complete + shopItems = fetchShopItems().whenComplete(() { + // Report TTFD as soon as fetching the shop items is done + if (mounted) { + SentryDisplayWidget.of(context).reportFullyDisplayed(); + } + }); + } + + // Simulate slow database query on main thread + void _performDatabaseQuery() { try { - // Simulate a slow database query by performing a large in-memory search - final stopwatch = Stopwatch()..start(); + // Perform heavy computation to simulate slow DB query (>16ms) int sum = 0; - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < 2000000; i++) { sum += i; } - stopwatch.stop(); if (kDebugMode) { - print( - 'Simulated DB query on main thread duration: ${stopwatch.elapsedMilliseconds}ms, result: $sum', - ); + print('DB query completed on main thread, result: $sum'); } } catch (e) { if (kDebugMode) { print('DB query error: $e'); } } - super.initState(); - - final email = getRandomEmail(); - Sentry.configureScope((scope) => scope.setUser(SentryUser(id: email))); + } - // Intentionally perform slow file I/O on the main thread to trigger Sentry performance issue + // Simulate slow file I/O on main thread + void _performFileIO() { try { - final file = File('main_thread_io.txt'); - // Write a large file to ensure duration exceeds 16ms - file.writeAsStringSync(List.filled(500000, 'A').join()); + // Use system temp directory for cross-platform compatibility + final tempDir = Directory.systemTemp; + final file = File('${tempDir.path}/plant_cache.txt').sentryTrace(); + + // Write large file synchronously on main thread (>16ms) + file.writeAsStringSync(List.filled(500000, 'Plant data ').join()); + final content = file.readAsStringSync(); + if (kDebugMode) { - print('File I/O on main thread content length: ${content.length}'); + print('File I/O on main thread, size: ${content.length}'); } } catch (e) { if (kDebugMode) { print('File I/O error: $e'); } } - // Start a Sentry span for the full display lifecycle + } + + // Simulate JSON decoding on main thread + void _performJSONDecoding() { + try { + // Create and decode large JSON (>40ms for profiler detection) + final largeJson = jsonEncode(List.generate( + 15000, + (i) => { + 'id': i, + 'name': 'Plant $i', + 'description': 'A beautiful plant with detailed information $i', + 'price': i * 10.0, + 'stock': i % 100, + 'category': 'category_${i % 10}', + }, + )); + final decoded = jsonDecode(largeJson); + if (kDebugMode) { + print('JSON decoded ${decoded.length} items on main thread'); + } + } catch (e) { + if (kDebugMode) { + print('JSON decode error: $e'); + } + } + } + + // Trigger C++ Segfault on startup + Future _triggerCppSegfault() async { final transaction = Sentry.startTransaction( - 'products.full_display', - 'ui.load.full_display', + 'startup.cpp_segfault', + 'error', + ); + final span = transaction.startChild( + 'native.crash', + description: 'Trigger C++ Segfault: Plant root failure', + ); + transaction.setData('plant_action', 'cpp_segfault'); + span.setData('triggered_on', 'startup'); + try { + await channel.invokeMethod('cppSegfault'); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Kotlin Exception on startup (Android only) + Future _triggerKotlinException() async { + if (!Platform.isAndroid) return; + + final transaction = Sentry.startTransaction( + 'startup.kotlin_exception', + 'error', + ); + final span = transaction.startChild( + 'native.exception', + description: 'Trigger Kotlin Exception: Plant leaf error', + ); + transaction.setData('plant_action', 'kotlin_exception'); + span.setData('triggered_on', 'startup'); + try { + await channel.invokeMethod('kotlinException'); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Dart Exception on startup + Future _triggerDartException() async { + final transaction = Sentry.startTransaction( + 'startup.dart_exception', + 'error', + ); + final span = transaction.startChild( + 'dart.exception', + description: 'Trigger Dart Exception: Plant soil error', + ); + transaction.setData('plant_action', 'dart_exception'); + span.setData('triggered_on', 'startup'); + try { + throw Exception('Simulated Dart Exception'); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Timeout Exception on startup + Future _triggerTimeoutException() async { + final transaction = Sentry.startTransaction( + 'startup.timeout_exception', + 'error', + ); + final span = transaction.startChild( + 'dart.timeout', + description: 'Trigger Timeout: Plant growth timeout', + ); + transaction.setData('plant_action', 'timeout_exception'); + span.setData('triggered_on', 'startup'); + try { + throw TimeoutException('Operation timed out', Duration(seconds: 2)); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Platform Exception on startup + Future _triggerPlatformException() async { + final transaction = Sentry.startTransaction( + 'startup.platform_exception', + 'error', + ); + final span = transaction.startChild( + 'platform.exception', + description: 'Trigger Platform Exception: Plant pot error', + ); + transaction.setData('plant_action', 'platform_exception'); + span.setData('triggered_on', 'startup'); + try { + throw PlatformException( + code: 'PLATFORM_ERROR', + message: 'Simulated platform error', + ); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Missing Plugin Exception on startup + Future _triggerMissingPluginException() async { + final transaction = Sentry.startTransaction( + 'startup.missing_plugin_exception', + 'error', + ); + final span = transaction.startChild( + 'plugin.exception', + description: 'Trigger Missing Plugin: Plant fertilizer missing', + ); + transaction.setData('plant_action', 'missing_plugin_exception'); + span.setData('triggered_on', 'startup'); + try { + throw MissingPluginException('Simulated missing plugin'); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Assertion Error on startup + Future _triggerAssertionError() async { + final transaction = Sentry.startTransaction( + 'startup.assertion_error', + 'error', + ); + final span = transaction.startChild( + 'dart.assertion', + description: 'Trigger Assertion Error: Plant sunlight assertion', + ); + transaction.setData('plant_action', 'assertion_error'); + span.setData('triggered_on', 'startup'); + try { + assert(false, 'Simulated assertion error'); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger State Error on startup + Future _triggerStateError() async { + final transaction = Sentry.startTransaction( + 'startup.state_error', + 'error', + ); + final span = transaction.startChild( + 'dart.state', + description: 'Trigger State Error: Plant state error', + ); + transaction.setData('plant_action', 'state_error'); + span.setData('triggered_on', 'startup'); + try { + throw StateError('Simulated state error'); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Range Error on startup + Future _triggerRangeError() async { + final transaction = Sentry.startTransaction( + 'startup.range_error', + 'error', + ); + final span = transaction.startChild( + 'dart.range', + description: 'Trigger Range Error: Plant root range error', + ); + transaction.setData('plant_action', 'range_error'); + span.setData('triggered_on', 'startup'); + try { + throw RangeError('Simulated range error'); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Type Error on startup + Future _triggerTypeError() async { + final transaction = Sentry.startTransaction( + 'startup.type_error', + 'error', + ); + final span = transaction.startChild( + 'dart.type', + description: 'Trigger Type Error: Plant type error', + ); + transaction.setData('plant_action', 'type_error'); + span.setData('triggered_on', 'startup'); + try { + throw TypeError(); + } catch (error, stackTrace) { + span.throwable = error; + span.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + } + await span.finish(); + await transaction.finish(); + } + + // Trigger Image Decoding on Main Thread + Future _triggerImageDecoding() async { + final transaction = Sentry.startTransaction( + 'startup.image_decode_main_thread', + 'performance', + bindToScope: true, + ); + final span = transaction.startChild( + 'image.decode.main_thread', + description: 'Large Image Decoding on Main Thread', + ); + transaction.setData('plant_action', 'image_decode_main_thread'); + span.setData('triggered_on', 'startup'); + + // Simulate image decoding by creating large in-memory data + final imageData = List.filled(1000000, 255); + imageData.reduce((a, b) => a + b); + + await span.finish(); + await transaction.finish(); + } + + // Trigger Regex on Main Thread + Future _triggerRegex() async { + final transaction = Sentry.startTransaction( + 'startup.regex_main_thread', + 'performance', + bindToScope: true, + ); + final span = transaction.startChild( + 'regex.main_thread', + description: 'Complex Regex on Main Thread', + ); + transaction.setData('plant_action', 'regex_main_thread'); + span.setData('triggered_on', 'startup'); + + // Perform complex regex operations on main thread + final text = List.filled(10000, 'Plant name: Monstera deliciosa, Price: \$25.99. ').join(); + final regex = RegExp(r'\b[A-Z][a-z]+ [a-z]+\b'); + regex.allMatches(text).toList(); + + await span.finish(); + await transaction.finish(); + } + + // Trigger N+1 API Calls + Future _triggerNPlusOneAPICalls() async { + final transaction = Sentry.startTransaction( + 'startup.n_plus_one_api_calls', + 'performance', bindToScope: true, ); - shopItems = fetchShopItems() - .then((data) { - // Finish the span after products are fetched and ready to render - transaction.finish(status: SpanStatus.ok()); - return data; - }) - .catchError((e) { - transaction.finish(status: SpanStatus.internalError()); - throw e; - }); + transaction.setData('plant_action', 'n_plus_one_api_calls'); + + // Make sequential calls to trigger N+1 detection + // SentryHttpClient automatically creates http.client spans + final url = 'https://httpbin.org/delay/0'; // Use faster endpoint for demo + final apiClient = SentryHttpClient(); + + try { + // Make 15 sequential GET requests (required for N+1 detection) + for (int i = 0; i < 15; i++) { + final uri = Uri.parse('$url?id=$i'); + try { + await apiClient.get(uri); + } catch (e, st) { + await Sentry.captureException(e, stackTrace: st); + } + } + } finally { + apiClient.close(); + await transaction.finish(status: SpanStatus.ok()); + } + } + + // Trigger Frame Drop + Future _triggerFrameDrop() async { + final transaction = Sentry.startTransaction( + 'startup.frame_drop', + 'performance', + bindToScope: true, + ); + final span = transaction.startChild( + 'ui.frame_drop', + description: 'Trigger Frame Drops', + ); + transaction.setData('plant_action', 'frame_drop'); + span.setData('triggered_on', 'startup'); + + // Perform heavy computation to cause frame drops + for (int i = 0; i < 5; i++) { + final start = DateTime.now(); + // Block for ~20ms to cause dropped frames (target is 16ms for 60fps) + while (DateTime.now().difference(start).inMilliseconds < 20) { + // Busy wait + } + await Future.delayed(Duration(milliseconds: 10)); + } + + await span.finish(); + await transaction.finish(); + } + + // Trigger Function Regression + Future _triggerFunctionRegression() async { + final transaction = Sentry.startTransaction( + 'startup.function_regression', + 'performance', + bindToScope: true, + ); + final span = transaction.startChild( + 'function.slow', + description: 'Slow Function Performance Regression', + ); + transaction.setData('plant_action', 'function_regression'); + span.setData('triggered_on', 'startup'); + span.setData('duration_ms', 500); + + // Simulate a slow function that has regressed in performance + await Future.delayed(Duration(milliseconds: 500)); + + // Do some computation + int sum = 0; + for (int i = 0; i < 100000; i++) { + sum += i; + } + if (kDebugMode) { + print('Function regression completed, sum: $sum'); + } + + await span.finish(); + await transaction.finish(); } @override @@ -106,20 +531,15 @@ class _ItemListState extends State { /*24 is for notification bar on Android*/ final double itemHeight = (size.height - kToolbarHeight - 24) / 2; final double itemWidth = size.width * 1.3; - return SentryDisplayWidget( - child: FutureBuilder( - future: shopItems, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - throw Exception("Error fetching shop data"); - } - WidgetsBinding.instance.addPostFrameCallback((_) { - if (context.mounted) { - SentryDisplayWidget.of(context).reportFullyDisplayed(); - } - }); - return SingleChildScrollView( + + return FutureBuilder( + future: shopItems, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + throw Exception("Error fetching shop data"); + } + return SingleChildScrollView( child: Column( children: [ Container( @@ -177,11 +597,10 @@ class _ItemListState extends State { ], ), ); - } else { - return CircularProgressIndicator(); - } - }, - ), + } else { + return CircularProgressIndicator(); + } + }, ); } @@ -327,8 +746,23 @@ class ResponseItem { } } -// Returns a randomized email address for demo/testing +// Returns an email address for demo/testing. +// - If se is set (not 'tda'): returns se@example.com for easy attribution. +// - If se is 'tda' (default): returns john.logs@example.com ~10 times per day +// (once per 144-minute window, seeded by day+window so it's deterministic), +// otherwise returns a unique timestamped address. String getRandomEmail() { - final timestamp = DateTime.now().millisecondsSinceEpoch; - return 'user_$timestamp@example.com'; + if (se != 'tda') { + return '$se@example.com'; + } + final now = DateTime.now(); + // Divide the day into 10 equal windows of 144 minutes each (1440 min / 10). + final minuteOfDay = now.hour * 60 + now.minute; + final windowIndex = minuteOfDay ~/ 144; // 0–9 + // Seed is unique per calendar day + window, so result is fixed within a window. + final seed = now.year * 10000 + now.month * 100 + now.day * 10 + windowIndex; + if (Random(seed).nextBool()) { + return 'john.logs@example.com'; + } + return 'user_${now.millisecondsSinceEpoch}@example.com'; } diff --git a/lib/se_config.dart b/lib/se_config.dart index 5022f88..22b1356 100644 --- a/lib/se_config.dart +++ b/lib/se_config.dart @@ -1,2 +1,3 @@ // Each engineer should set their name or identifier here. -const String se = 'Prithvi'; // <-- Change this to your name or ID +// This tags all Sentry events with your identifier for separation. +const String se = 'tda'; // <-- Change this to your name or ID diff --git a/lib/sentry_setup.dart b/lib/sentry_setup.dart index 0fe8249..14eccc6 100644 --- a/lib/sentry_setup.dart +++ b/lib/sentry_setup.dart @@ -1,7 +1,9 @@ import 'dart:async'; +import 'dart:io'; +import 'dart:math'; import 'package:flutter/foundation.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:flutter/material.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:dio/dio.dart'; import 'package:sentry_dio/sentry_dio.dart'; @@ -9,10 +11,29 @@ import 'package:sentry_file/sentry_file.dart'; import 'package:sentry_logging/sentry_logging.dart'; import 'package:logging/logging.dart'; import 'se_config.dart'; -import 'dart:io'; final GlobalKey navigatorKey = GlobalKey(); +/// Generates a random customer type based on specified distribution +/// - 40% enterprise +/// - 20% small-plan +/// - 20% medium-plan +/// - 20% large-plan +String getRandomCustomerType() { + final random = Random(); + final value = random.nextDouble(); // 0.0 to 1.0 + + if (value < 0.40) { + return 'enterprise'; + } else if (value < 0.60) { + return 'small-plan'; + } else if (value < 0.80) { + return 'medium-plan'; + } else { + return 'large-plan'; + } +} + FutureOr beforeSend(SentryEvent event, Hint? hint) async { // Add the se tag for engineer separation final tags = {}; @@ -27,79 +48,198 @@ FutureOr beforeSend(SentryEvent event, Hint? hint) async { } Future initSentry({required VoidCallback appRunner}) async { + // Get configuration from dart-define (for release builds) or dotenv (for debug) + const dartDefineDsn = String.fromEnvironment('SENTRY_DSN', defaultValue: ''); + const dartDefineRelease = String.fromEnvironment('SENTRY_RELEASE', defaultValue: ''); + const dartDefineEnvironment = String.fromEnvironment('SENTRY_ENVIRONMENT', defaultValue: ''); + + final String? sentryDsn = dartDefineDsn.isNotEmpty ? dartDefineDsn : dotenv.env['SENTRY_DSN']; + final String? sentryRelease = dartDefineRelease.isNotEmpty ? dartDefineRelease : dotenv.env['SENTRY_RELEASE']; + final String? sentryEnvironment = dartDefineEnvironment.isNotEmpty ? dartDefineEnvironment : dotenv.env['SENTRY_ENVIRONMENT']; + await SentryFlutter.init((options) { - options.addIntegration(LoggingIntegration()); - // Ensure the se tag is added to all events - options.beforeSend = beforeSend; - // Core options - options.dsn = dotenv.env['SENTRY_DSN']; - options.enableTimeToFullDisplayTracing = true; - options.release = dotenv.env['SENTRY_RELEASE']; - options.environment = dotenv.env['SENTRY_ENVIRONMENT']; - options.debug = false; // Set true for local debugging + // ======================================== + // Core Configuration + // ======================================== + options.dsn = sentryDsn; + + // Release must match exactly with uploaded debug symbols + // CRITICAL: This must be in format "appname@version+build" (e.g., "empower_flutter@9.14.0+1") + options.release = sentryRelease; + options.environment = sentryEnvironment; + + // Set distribution to match build number for better symbol matching + options.dist = '1'; // Matches version 9.14.0+1 + + // Debug settings (disabled for production to ensure proper symbol resolution) + // Set to true only when debugging Sentry configuration issues + options.debug = false; options.diagnosticLevel = SentryLevel.error; - // options.dist = null; // Set build number if available - - // Event sampling - options.sampleRate = 1.0; // 100% errors - options.tracesSampleRate = 1.0; // 100% performance traces - options.profilesSampleRate = - 1.0; // Enable profiling for all sampled transactions - // Enable Session Replay - options.replay.sessionSampleRate = 1.0; + + // ======================================== + // Sampling Configuration + // ======================================== + // Capture 100% of errors (recommended for demo, adjust in production) + options.sampleRate = 1.0; + + // Capture 100% of performance traces (adjust in production based on volume) + options.tracesSampleRate = 1.0; + + // Enable profiling (relative to tracesSampleRate) + // Required for JSON Decoding, Image Decoding, and Frame Drop detection + // Profiling is available on iOS, macOS, and Android + options.profilesSampleRate = 1.0; + + // ======================================== + // Session Replay Configuration + // ======================================== + // Capture 100% of error sessions with replay options.replay.onErrorSampleRate = 1.0; - // Enable structured logs - options.enableLogs = true; + // Capture 100% of normal sessions with replay (adjust in production) + options.replay.sessionSampleRate = 1.0; + + // ======================================== + // Performance Tracking + // ======================================== + // Enable automatic performance tracking + options.enableAutoPerformanceTracing = true; - // Breadcrumbs & cache + // Enable Time to Initial Display (TTID) and Time to Full Display (TTFD) tracking + options.enableTimeToFullDisplayTracing = true; + + // Enable user interaction tracing (tap, swipe, etc.) + options.enableUserInteractionTracing = true; + options.enableUserInteractionBreadcrumbs = true; + + // ======================================== + // Breadcrumbs & Context + // ======================================== options.maxBreadcrumbs = 100; - options.maxCacheItems = 30; + options.enableAutoNativeBreadcrumbs = true; - // Attachments + // ======================================== + // Attachments & Screenshots + // ======================================== options.attachStacktrace = true; - options.attachScreenshot = true; - options.screenshotQuality = SentryScreenshotQuality.high; + options.attachScreenshot = false; // Disabled for demo + // options.screenshotQuality = SentryScreenshotQuality.high; // Not needed when screenshots are disabled options.attachViewHierarchy = true; + options.attachThreads = true; - // Privacy - options.sendDefaultPii = false; // Enable if you want PII (see docs) - // inAppInclude/inAppExclude not supported in Flutter SDK - - // Session & crash tracking + // ======================================== + // Crash & Error Handling + // ======================================== options.enableNativeCrashHandling = true; - options.enableAutoSessionTracking = true; options.enableNdkScopeSync = true; - options.attachThreads = true; - options.enableScopeSync = true; - options.enableAutoPerformanceTracing = true; - options.enableWatchdogTerminationTracking = true; - options.reportPackages = true; + options.reportSilentFlutterErrors = true; + + // ANR (Application Not Responding) detection for Android options.anrEnabled = true; options.anrTimeoutInterval = const Duration(seconds: 5); - options.reportSilentFlutterErrors = true; - options.enableAutoNativeBreadcrumbs = true; - options.enableUserInteractionBreadcrumbs = true; - options.enableUserInteractionTracing = true; - // HTTP request capture + // App Hang tracking for iOS/macOS + // Enabled by default in Sentry Flutter SDK 9.0.0+ + // Watchdog termination tracking (iOS/macOS) + options.enableWatchdogTerminationTracking = true; + + // Configure App Hang tracking for iOS/macOS + if (Platform.isIOS || Platform.isMacOS) { + // App hang timeout interval (default is 2 seconds) + options.appHangTimeoutInterval = const Duration(seconds: 2); + // Note: App Hang Tracking V2 is enabled by default in SDK 9.0.0+ + // It automatically measures duration and differentiates between + // fully-blocking and non-fully-blocking app hangs + } + + // ======================================== + // Session Tracking + // ======================================== + options.enableAutoSessionTracking = true; + options.enableScopeSync = true; + + // ======================================== + // HTTP Request Tracking + // ======================================== options.captureFailedRequests = true; options.maxRequestBodySize = MaxRequestBodySize.medium; - // Transport & client reports + // ======================================== + // Logging Integration + // ======================================== + // Enable structured logs to be sent to Sentry + options.enableLogs = true; + // Integrate with dart logging package to capture Logger() calls + options.addIntegration(LoggingIntegration()); + + // ======================================== + // Metrics Configuration + // ======================================== + // Enable metrics to track counters, gauges, and distributions + options.enableMetrics = true; + + // ======================================== + // Privacy & PII + // ======================================== + // Set to true to capture personally identifiable information + // (user IP, request headers, user.id, user.name, user.email in logs/metrics) + // Enable for maximum telemetry in demo environment + options.sendDefaultPii = true; + + // ======================================== + // Session Replay Privacy Configuration + // ======================================== + // Disable default masking - everything is visible in session replays + // WARNING: Only use this for demo environments without sensitive data + options.privacy.maskAllText = false; + options.privacy.maskAllImages = false; + + // To enable masking again, set the above to true and add custom rules: + // options.privacy.mask(); + // options.privacy.unmask(); + // options.privacy.maskCallback( + // (element, widget) { + // final text = widget.data?.toLowerCase() ?? ''; + // // Add your masking logic here + // return SentryMaskingDecision.continueProcessing; + // }, + // ); + + // ======================================== + // Additional Configuration + // ======================================== + options.maxCacheItems = 30; options.sendClientReports = true; + options.reportPackages = true; - // Platform-specific (Android) - // options.proguardUuid = 'YOUR_PROGUARD_UUID'; // If using Proguard + // Sends the envelope to both Sentry and Spotlight which is helpful for debugging + // https://spotlightjs.com/setup/headless/ + options.spotlight = Spotlight(enabled: kDebugMode); - // Hooks (replace with your actual functions if needed) - // options.beforeSend = yourBeforeSendFunction; + // ======================================== + // Custom Hooks + // ======================================== + // Ensure the se tag is added to all events for engineer separation + options.beforeSend = beforeSend; // options.beforeBreadcrumb = yourBeforeBreadcrumbFunction; - - // Add LoggingIntegration to Sentry options - options.addIntegration(LoggingIntegration()); }, appRunner: appRunner); - // Sentry Dio integration + // ======================================== + // Customer Type Tag Configuration + // ======================================== + // Set a random customer type tag for all events to demonstrate tag filtering + // Distribution: 40% enterprise, 20% small-plan, 20% medium-plan, 20% large-plan + final customerType = getRandomCustomerType(); + Sentry.configureScope((scope) { + scope.setTag('customerType', customerType); + if (kDebugMode) { + print('Sentry: Set customerType tag to: $customerType'); + } + }); + + // ======================================== + // HTTP Client Integration (Dio) + // ======================================== + // Create a global Dio instance with Sentry integration final dio = Dio(); dio.addSentry(); // You can now use this dio instance throughout your app for HTTP requests @@ -156,25 +296,32 @@ void showUserFeedbackDialog(BuildContext context, SentryId eventId) async { } // Sentry file I/O instrumentation example +// Use this to automatically track file operations performance Future sentryFileExample() async { final file = File('my_file.txt'); final sentryFile = file.sentryTrace(); final transaction = Sentry.startTransaction( - 'MyFileExample', - 'file', + 'file_operations_example', + 'file.io', bindToScope: true, ); - await sentryFile.create(); - await sentryFile.writeAsString('Hello World'); - final text = await sentryFile.readAsString(); - if (kDebugMode) { - print(text); + try { + await sentryFile.create(); + await sentryFile.writeAsString('Hello World'); + final text = await sentryFile.readAsString(); + if (kDebugMode) { + print(text); + } + await sentryFile.delete(); + await transaction.finish(status: SpanStatus.ok()); + } catch (error, stackTrace) { + transaction.throwable = error; + transaction.status = SpanStatus.internalError(); + await Sentry.captureException(error, stackTrace: stackTrace); + await transaction.finish(); } - await sentryFile.delete(); - - await transaction.finish(status: SpanStatus.ok()); } // Example logger usage diff --git a/macos/Podfile b/macos/Podfile index 29c8eb3..ff5ddb3 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..f0a939e --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,36 @@ +PODS: + - FlutterMacOS (1.0.0) + - package_info_plus (0.0.1): + - FlutterMacOS + - Sentry/HybridSDK (8.56.2) + - sentry_flutter (9.12.0): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (= 8.56.2) + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`) + +SPEC REPOS: + trunk: + - Sentry + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + sentry_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos + +SPEC CHECKSUMS: + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7 + sentry_flutter: 26f4615de8bdc741318397fdc78ba3e4e08df07f + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 9c13be3..54237a6 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 7F7E80F68E32F6A3AF61EE2A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12FF902E44F004F7CE8EF91 /* Pods_Runner.framework */; }; + C9E4809A8CA7976529BE63BB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A9E2C1374F34AEDC661F6CE /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +62,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0654D93895D80200DF86F9E1 /* 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 = ""; }; + 15A1D2792EB60CDBBFBC7DA1 /* 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 = ""; }; + 21AE2CA1E314A5BBAA9E605F /* 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 = ""; }; + 2A9E2C1374F34AEDC661F6CE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* empower_flutter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "empower_flutter.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* empower_flutter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = empower_flutter.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -77,7 +83,11 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 81949A52B6A51A49120A8397 /* 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 97AB30BA1B0288D6937000B8 /* 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 = ""; }; + C12FF902E44F004F7CE8EF91 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D374192F42F2251B44554268 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C9E4809A8CA7976529BE63BB /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7F7E80F68E32F6A3AF61EE2A /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 814FCA6DC85FFE40C57DD0AC /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 814FCA6DC85FFE40C57DD0AC /* Pods */ = { + isa = PBXGroup; + children = ( + 15A1D2792EB60CDBBFBC7DA1 /* Pods-Runner.debug.xcconfig */, + 97AB30BA1B0288D6937000B8 /* Pods-Runner.release.xcconfig */, + 0654D93895D80200DF86F9E1 /* Pods-Runner.profile.xcconfig */, + D374192F42F2251B44554268 /* Pods-RunnerTests.debug.xcconfig */, + 81949A52B6A51A49120A8397 /* Pods-RunnerTests.release.xcconfig */, + 21AE2CA1E314A5BBAA9E605F /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + C12FF902E44F004F7CE8EF91 /* Pods_Runner.framework */, + 2A9E2C1374F34AEDC661F6CE /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + CDB9305F3596C6F4C49BF170 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + B5B5024A46C6005AF1B70467 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + AB3245E1AABDFC8ED7191262 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + AB3245E1AABDFC8ED7191262 /* [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; + }; + B5B5024A46C6005AF1B70467 /* [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; + }; + CDB9305F3596C6F4C49BF170 /* [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 */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D374192F42F2251B44554268 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81949A52B6A51A49120A8397 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 21AE2CA1E314A5BBAA9E605F /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -461,7 +557,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -543,7 +639,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -593,7 +689,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.lock b/pubspec.lock index 7cd5e9a..ed406a1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -85,10 +85,10 @@ packages: dependency: "direct main" description: name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.9.1" dio_web_adapter: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" file: dependency: transitive description: @@ -138,10 +138,10 @@ packages: dependency: "direct main" description: name: flutter_dotenv - sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + sha256: d4130c4a43e0b13fefc593bc3961f2cb46e30cb79e253d4a526b1b5d24ae1ce4 url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "6.0.0" flutter_lints: dependency: "direct dev" description: @@ -172,10 +172,10 @@ packages: dependency: transitive description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.0" http_parser: dependency: transitive description: @@ -204,34 +204,34 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" logging: dependency: "direct main" description: @@ -260,10 +260,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -292,18 +292,18 @@ packages: dependency: transitive description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "9.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" path: dependency: transitive description: @@ -348,58 +348,58 @@ packages: dependency: "direct main" description: name: provider - sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.1.5+1" sentry: dependency: transitive description: name: sentry - sha256: d9f3dcf1ecdd600cf9ce134f622383adde5423ecfdaf0ca9b20fbc1c44849337 + sha256: "605ad1f6f1ae5b72018cbe8fc20f490fa3bd53e58882e5579566776030d8c8c1" url: "https://pub.dev" source: hosted - version: "9.6.0" + version: "9.14.0" sentry_dart_plugin: dependency: "direct dev" description: name: sentry_dart_plugin - sha256: "8c0c9fe19368890381101f5000759b2ae203a0bf6158a25584b55d197e847f53" + sha256: "514cd5cc5c022bed9c232d08dc126a081b8a965dbad78b819ae91bf3a06e622c" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.1" sentry_dio: dependency: "direct main" description: name: sentry_dio - sha256: "136ea2ac41291862b5433e1f1725322a851c2b082fe5ecaeec7904c5cae1d5f5" + sha256: bd2b3010bae9facba9bd75054148acfe1013e87c841fefaa0240b8086ee0b327 url: "https://pub.dev" source: hosted - version: "9.6.0" + version: "9.14.0" sentry_file: dependency: "direct main" description: name: sentry_file - sha256: ed1b899125a20d567a78fb7a6d2f56208b4844c0be4b21d9f97a6b999ae9247d + sha256: "59b9a5ed89eef44fdb596e5e921a12b4b697e3338cf31658436714fdd74bb889" url: "https://pub.dev" source: hosted - version: "9.6.0" + version: "9.14.0" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: "37deb4ef8837d10b5c1f527ec18591f8d2d2da9c34f19b3d97ccbbe7f84077c0" + sha256: "7fd0fb80050c1f6a77ae185bda997a76d384326d6777cf5137a6c38952c4ac7d" url: "https://pub.dev" source: hosted - version: "9.6.0" + version: "9.14.0" sentry_logging: dependency: "direct main" description: name: sentry_logging - sha256: "040046d5fe79b94b1c73069031547c066ab37bcbd18c029dc3ceeb9b5d0c67c5" + sha256: e6754237940822f048844cfd74686ccede12211a5815e25977e15a9c425d66a0 url: "https://pub.dev" source: hosted - version: "9.6.0" + version: "9.14.0" sky_engine: dependency: transitive description: flutter @@ -409,18 +409,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "1.10.2" stack_trace: dependency: transitive description: @@ -449,10 +441,10 @@ packages: dependency: transitive description: name: system_info2 - sha256: "65206bbef475217008b5827374767550a5420ce70a04d2d7e94d1d2253f3efc9" + sha256: b937736ecfa63c45b10dde1ceb6bb30e5c0c340e14c441df024150679d65ac43 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.0" term_glyph: dependency: transitive description: @@ -465,10 +457,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" typed_data: dependency: transitive description: @@ -481,26 +473,26 @@ packages: dependency: transitive description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.5.2" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" web: dependency: transitive description: @@ -513,10 +505,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.15.0" yaml: dependency: transitive description: @@ -526,5 +518,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.1 <4.0.0" + dart: ">=3.8.0 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 914d261..ea181e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: empower_flutter -description: "A new Flutter project." +description: "A new Flutter project for Sentry." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev @@ -16,10 +16,10 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +version: 9.14.0+1 environment: - sdk: ">=3.8.1 <4.0.0" + sdk: ">=3.5.0 <4.0.0" flutter: ">=3.22.0 <4.0.0" # Dependencies specify other packages that your package needs in order to work. @@ -36,13 +36,13 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 provider: ^6.1.5 - sentry_flutter: ^9.5.0 - flutter_dotenv: ^5.2.1 - sentry_dio: ^9.5.0 - dio: ^5.9.0 - sentry_file: ^9.5.0 - sentry_logging: ^9.5.0 - logging: ^1.0.2 + sentry_flutter: ^9.14.0 + flutter_dotenv: ^6.0.0 + sentry_dio: ^9.14.0 + dio: ^5.9.1 + sentry_file: ^9.14.0 + sentry_logging: ^9.14.0 + logging: ^1.3.0 dev_dependencies: flutter_test: @@ -63,6 +63,14 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/images/ + - assets/images/bloat/ + - assets/images/duplicates/ + - assets/images/icons/ + - assets/images/badges/ + - assets/videos/ + - assets/audio/ + - assets/docs/ + - assets/config/ - .env # An image asset can refer to one or more resolution-specific "variants", see @@ -93,6 +101,13 @@ flutter: sentry: upload_debug_symbols: true + upload_source_maps: true upload_sources: true - project: flutter - org: demo + # Organization and project are read from environment variables: + # SENTRY_ORG and SENTRY_PROJECT (set in .env file) + # This is automatically handled by demo.sh script + # Path to the Dart obfuscation map file for readable stack traces + # Generated with --extra-gen-snapshot-options=--save-obfuscation-map + dart_symbol_map_path: build/app/obfuscation.map.json + # Path where web build files are stored + web_build_path: build/web diff --git a/run.sh b/run.sh deleted file mode 100755 index 6e044a9..0000000 --- a/run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Usage: ./run.sh -# This script builds the Flutter app with obfuscation and uploads debug symbols to Sentry. -# It loads Sentry secrets from .env if present, otherwise uses sentry.properties or env vars. - -set -e - -# Load .env if it exists -if [ -f .env ]; then - echo "Loading environment variables from .env" - export $(grep -v '^#' .env | xargs) -fi - -# Build the app (Android example) -echo "Building Flutter APK with obfuscation and split-debug-info..." -flutter build apk --obfuscate --split-debug-info=build/debug-info - -echo "Uploading debug symbols to Sentry..." -flutter pub run sentry_dart_plugin --include-sources - -echo "Done."