diff --git a/.gitignore b/.gitignore index 3050550..9480ef2 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/android/build/ lib/generated lib/oss_licenses.dart \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9875308 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,529 @@ +# Codebase Analysis: Just Another Workout Timer + +## Executive Summary + +Flutter-based workout timer app with interval training functionality. Currently **works only in foreground** - timer stops when app is backgrounded or screen locks. No background service or notification implementation exists. + +**Core Issue:** Uses `Timer.periodic()` which is lifecycle-dependent and stops when app is minimized. + +--- + +## Architecture Overview + +### Technology Stack +- **Framework:** Flutter 3.35.7 / Dart 3.9.2 +- **State Management:** Provider pattern with ChangeNotifier +- **UI:** Material Design 3 with Dynamic Color support +- **Platforms:** Android/iOS (focus on Android for background work) + +### Project Structure +``` +lib/ +├── layouts/ # UI screens (home, builder, runner, settings) +├── utils/ # Core business logic and helpers +├── l10n/ # Localization (6 languages) +├── generated/ # Auto-generated code (translations, JSON serialization) +└── main.dart # App entry point +``` + +--- + +## Timer Implementation Deep Dive + +### Current Implementation (`lib/utils/timetable.dart`) + +**Core Class:** `Timetable extends ChangeNotifier` + +**Timer Mechanism:** +```dart +// Single periodic timer, 1-second intervals +_timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _timerTick(); +}); +``` + +**Event Scheduling System:** +- Pre-computes ALL events for entire workout in `buildTimetable()` +- Stores events in `SplayTreeMap` keyed by second +- Events include: TTS announcements, sounds, UI updates, progress tracking + +**State Properties:** +- `currentSecond` - Elapsed time tracker +- `currentSet` / `currentExercise` / `currentReps` - Position in workout +- `remainingSeconds` - Time left in current exercise +- `workoutDone` - Completion flag + +**Lifecycle:** +1. `buildTimetable()` - Initialize event schedule +2. `timerStart()` - Create Timer.periodic +3. `_timerTick()` - Execute scheduled events each second +4. `timerStop()` - Cancel timer +5. `notifyListeners()` - Update UI via Provider + +### Why It Fails in Background + +**Problem:** Flutter's `Timer.periodic()` is tied to the app's lifecycle: +- **App minimized** → Timer pauses +- **Screen locks** → Timer stops +- **Memory cleanup** → Timer destroyed + +**No Existing Mitigation:** +- No foreground service +- No WorkManager tasks +- No isolates for background execution +- No system notifications +- Wakelock only prevents screen sleep while app is visible + +--- + +## Data Model Structure + +### Hierarchy +``` +Workout +├── title: String +├── sets: List +├── version: int +└── position: int + │ + └── Set + ├── id: String (UUID) + ├── repetitions: int + └── exercises: List + │ + └── Exercise + ├── id: String (UUID) + ├── name: String + └── duration: int (seconds) +``` + +### Serialization +- JSON-based with `json_annotation` + code generation +- Files stored: `getExternalStorageDirectory()/workouts/` +- Versioned schema with migration support + +--- + +## Audio & Feedback Systems + +### Text-to-Speech (`flutter_tts` package) +- **Helper:** `lib/utils/tts_helper.dart` +- Announces: Exercise names, countdowns, progress milestones +- Configurable: Engine, voice, language +- **Limitation:** Foreground only + +### Sound Effects (`soundpool` package) +- **Helper:** `lib/utils/sound_helper.dart` +- Assets: `beep_low.wav`, `beep_high.wav`, `tick.wav` +- Usage: Interval transitions, countdown cues, halfway notifications +- Loaded at app startup in `main.dart` + +### Wakelock (`wakelock_plus`) +- Keeps screen on during workout (optional) +- Enabled in `WorkoutPageState.initState()` +- **Does NOT enable background execution** + +--- + +## State Management Details + +### Provider Pattern Implementation + +**Provider Setup:** +```dart +ChangeNotifierProvider( + create: (context) => Timetable(context, workout), + child: Consumer( + builder: (context, timetable, child) => WorkoutPageContent(...) + ), +) +``` + +**Notification Flow:** +``` +Timetable._timerTick() + → Update state properties + → notifyListeners() + → Consumer rebuilds UI + → WorkoutPageContent updates +``` + +### Settings Management +- **Package:** `prefs` + `pref` +- **Wrapper:** `lib/utils/pref_service_shared.dart` +- **Persistence:** SharedPreferences + +**Key Settings:** +- `theme` - System/Light/Dark +- `wakelock` - Screen wake during workout +- `sound` - Output mode (none/tts/beep) +- `tts_*` - Voice configuration +- `halftime` - Mid-exercise notification +- `ticks` - Second-by-second audio + +--- + +## Key Files Reference + +| File | Purpose | Critical for Background Fix | +|------|---------|------------------------------| +| `lib/utils/timetable.dart` | **Core timer logic** | ✅ YES - Needs background service integration | +| `lib/layouts/workout_runner.dart` | Workout execution UI | ✅ YES - Connects to notifications | +| `lib/main.dart` | App initialization | ⚠️ MAYBE - Service initialization | +| `lib/utils/tts_helper.dart` | Text-to-speech | ⚠️ MAYBE - Background TTS considerations | +| `lib/utils/sound_helper.dart` | Audio playback | ⚠️ MAYBE - Background audio | +| `lib/utils/workout.dart` | Data models | ⬜ NO - Just data structures | +| `lib/utils/storage_helper.dart` | File I/O | ⬜ NO - Not involved in timer | +| `android/app/src/main/AndroidManifest.xml` | Android config | ✅ YES - Needs permissions & service declaration | +| `android/app/src/main/kotlin/.../MainActivity.kt` | Android entry | ✅ YES - May need method channel for service | + +--- + +## Dependencies Relevant to Background Work + +**Currently Installed:** +- `provider: ^6.1.2` - State management (keep) +- `flutter_tts: ^4.0.2` - TTS (may need background handling) +- `soundpool: ^2.4.1` - Audio (may need background handling) +- `wakelock_plus: ^1.2.5` - Screen wake (insufficient for background) + +**Will Need to Add:** +- `flutter_local_notifications` - System notifications +- `flutter_foreground_task` or `android_alarm_manager_plus` - Background service +- Or use native Android Foreground Service via method channels + +--- + +## Android Platform Configuration + +### Current State (`android/AndroidManifest.xml`) + +**Permissions:** Minimal (just basic Flutter) +```xml + +``` + +**Services:** None defined (only Flutter's MainActivity) + +**Build Config (`android/app/build.gradle`):** +- Min SDK: 21 (Android 5.0) +- Target SDK: 34 (Android 14) +- Kotlin support enabled + +### What's Missing for Background Execution + +**Permissions Needed:** +```xml + + + + +``` + +**Service Declaration:** +```xml + +``` + +--- + +## Localization Support + +**Languages:** English (default), German, French, Italian, Russian, Turkish + +**Implementation:** +- `intl` package with `.arb` files in `lib/l10n/` +- Generated `S` class for translation access +- Automatic locale detection + +**Note:** Notification text must also be localized + +--- + +## Navigation & UI Flow + +``` +HomePage (workout list) +├── FloatingActionButton → BuilderPage (create workout) +├── Workout tile tap → WorkoutPage (execute with Timetable) +├── AppBar actions → Export/Import +└── Settings icon → SettingsPage + +WorkoutPage (during execution) +├── Provider wraps entire page +├── WorkoutPageContent consumes Timetable state +├── Play/Pause button → timerStart() / timerStop() +├── Skip buttons → skipForward() / skipBackward() +└── Reset button → resetWorkout() +``` + +**Critical for Background:** +- WorkoutPage must handle app lifecycle events +- Need to persist timer state when backgrounded +- Must restore state when foregrounded + +--- + +## Testing Observations + +**Test Structure:** None found (no `/test` directory) + +**Manual Testing Checklist for Background Fix:** +1. ✅ Timer continues when app minimized +2. ✅ Timer continues when screen locks +3. ✅ Notification shows current exercise + time +4. ✅ Notification pause/play works +5. ✅ TTS announcements work in background +6. ✅ Sound effects play in background +7. ✅ App state syncs when foregrounded +8. ✅ Notification dismisses when workout completes +9. ✅ Battery impact is reasonable +10. ✅ Handles phone calls / other interruptions + +--- + +## Implementation Plan for Background Execution + +### Approach 1: Flutter Plugin (Recommended) + +**Use `flutter_local_notifications` + `android_alarm_manager_plus` or `workmanager`** + +**Pros:** +- Pure Dart/Flutter code (mostly) +- Well-maintained packages +- Cross-platform potential + +**Cons:** +- May have limitations on newer Android versions +- Plugin overhead + +### Approach 2: Native Android Foreground Service + +**Custom implementation via Method Channels** + +**Pros:** +- Full control over Android service lifecycle +- Better performance +- Direct access to Android APIs + +**Cons:** +- Requires Kotlin/Java code +- Android-only +- More complex implementation + +### Recommended: Hybrid Approach + +1. **Use `flutter_local_notifications`** for notification UI (media-style controls) +2. **Use `flutter_foreground_task`** or native service for background execution +3. **Keep Timetable logic** but make it background-aware +4. **Add state persistence** (save current second, exercise on background) +5. **Restore state** when foregrounded + +--- + +## State Persistence Strategy + +**What to Save (SharedPreferences or temp file):** +```dart +{ + "workoutInProgress": bool, + "workoutId": String, + "currentSecond": int, + "isPaused": bool, + "startTimestamp": int, // For drift correction +} +``` + +**When to Save:** +- On app background (lifecycle event) +- Every N seconds (periodic checkpoint) +- On pause/resume + +**When to Restore:** +- On app foreground +- On service start (if killed) + +--- + +## Notification Design (Spotify-Style) + +### Required Information Display +1. **Title:** Current exercise name +2. **Text:** Remaining time (e.g., "0:35 remaining") +3. **Subtext:** Current set / total sets (e.g., "Set 2/4, Rep 3/5") + +### Required Actions +1. **Play/Pause** - Toggle timer +2. **Skip Forward** - Next exercise +3. *Optional:* Skip Backward, Stop Workout + +### Notification Style +- `MediaStyle` or `DecoratedMediaCustomViewStyle` +- Persistent notification (non-dismissible during workout) +- Updates every second for countdown + +--- + +## Critical Code Locations for Modification + +### 1. `lib/utils/timetable.dart` +**Changes Needed:** +- Add background service awareness +- Persist state on background +- Sync state on foreground resume +- Handle service callbacks for pause/play + +### 2. `lib/layouts/workout_runner.dart` +**Changes Needed:** +- Listen to app lifecycle changes +- Trigger notification creation +- Handle notification action callbacks +- Update UI from background state + +### 3. New File: `lib/utils/background_service.dart` +**Purpose:** +- Initialize foreground service +- Create/update notifications +- Handle action button callbacks +- Communicate with Timetable + +### 4. `android/app/src/main/AndroidManifest.xml` +**Changes:** +- Add permissions +- Declare foreground service + +### 5. `pubspec.yaml` +**Add Dependencies:** +```yaml +dependencies: + flutter_local_notifications: ^17.0.0 + flutter_foreground_task: ^8.0.0 # or custom native +``` + +--- + +## Potential Challenges & Mitigations + +| Challenge | Impact | Mitigation | +|-----------|--------|------------| +| Android 12+ strict background limits | High | Use foreground service (required) | +| Battery optimization killing service | Medium | Request battery optimization exemption | +| Notification every second = battery drain | Medium | Update only when value changes | +| TTS in background may be throttled | Low | Test on multiple Android versions | +| Audio focus conflicts | Low | Request audio focus properly | +| Timer drift over long workouts | Low | Use timestamp-based correction | +| State sync issues on rapid background/foreground | Low | Use mutex/lock for state updates | + +--- + +## Success Criteria + +✅ **Functional Requirements:** +1. Timer continues running when app is backgrounded +2. Notification appears with current exercise details +3. Notification updates in real-time (remaining time) +4. Pause/Play button in notification works +5. TTS and sounds continue in background +6. App UI syncs when foregrounded +7. Workout completes successfully in background + +✅ **Non-Functional Requirements:** +1. Battery usage < 5% per hour +2. No timer drift > 2 seconds per hour +3. Notification updates with < 500ms lag +4. App remains responsive when foregrounding +5. No crashes or ANRs (Application Not Responding) + +--- + +## Current Package Dependencies + +**State & UI:** +- `provider: ^6.1.2` +- `dynamic_color: ^1.7.0` +- `scrollable_positioned_list: ^0.3.8` +- `numberpicker: ^2.1.2` +- `fluttertoast: ^8.2.5` +- `flutter_phoenix: ^1.1.1` + +**Audio & Feedback:** +- `flutter_tts: ^4.0.2` +- `soundpool: ^2.4.1` +- `wakelock_plus: ^1.2.5` + +**Storage & Files:** +- `prefs: ^2.2.1` +- `pref: ^2.8.0` +- `path_provider: ^2.1.3` +- `flutter_file_dialog: ^3.0.2` +- `share_plus: ^9.0.0` + +**Utilities:** +- `uuid: ^4.4.0` +- `intl: ^0.19.0` +- `json_annotation: ^4.9.0` + +**Dev Dependencies:** +- `build_runner: ^2.4.11` +- `json_serializable: ^6.8.0` +- `flutter_launcher_icons: ^0.13.1` +- `flutter_native_splash: ^2.4.0` + +--- + +## Next Steps for Implementation + +1. ✅ **Analysis Complete** (this document) +2. ⬜ **Add Dependencies** (`flutter_local_notifications`, foreground service package) +3. ⬜ **Update Android Manifest** (permissions, service declaration) +4. ⬜ **Create Background Service** (new utility class) +5. ⬜ **Modify Timetable** (background awareness, state persistence) +6. ⬜ **Update WorkoutPage** (lifecycle handling, notification integration) +7. ⬜ **Implement Notification** (media-style with controls) +8. ⬜ **Test Thoroughly** (background, foreground, edge cases) +9. ⬜ **Optimize Battery** (reduce update frequency if needed) +10. ⬜ **Handle Permissions** (runtime requests for Android 13+) + +--- + +## References + +- **Flutter Background Execution:** https://docs.flutter.dev/platform-integration/android/background-processes +- **Android Foreground Services:** https://developer.android.com/develop/background-work/services/foreground-services +- **Flutter Local Notifications:** https://pub.dev/packages/flutter_local_notifications +- **Flutter Foreground Task:** https://pub.dev/packages/flutter_foreground_task +- **Android 14 Changes:** https://developer.android.com/about/versions/14/changes/fgs-types-required + +--- + +## Future Tasks & Improvements + +### Build Process Automation +**Issue:** Code generation (translations and OSS licenses) must be run manually before builds +**Required commands:** +```bash +flutter pub run intl_utils:generate +flutter pub run flutter_oss_licenses:generate.dart +``` + +**Solution Options:** +1. Add pre-build hook to run code generation automatically +2. Update CI/CD pipeline to include generation step +3. Create wrapper build script that runs generation first +4. Consider switching to Flutter's built-in code generation if possible + +**Priority:** Medium - currently manageable but could cause build failures if forgotten + +### Dependency Replacement +**Package:** `soundpool` (discontinued) +**Current usage:** Playing beep sounds and tick audio during workouts +**Replacement candidates:** `audioplayers`, `just_audio` +**Priority:** Medium - works for now but will become problematic with future Android SDK updates + +--- + +*Last Updated: 2026-01-07* +*Analyzed by: Claude (Sonnet 4.5)* diff --git a/android/app/build.gradle b/android/app/build.gradle index a084df0..041db33 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -36,6 +36,7 @@ android { } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } @@ -82,5 +83,6 @@ flutter { } dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' //implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3d86086..f260b4c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,11 @@ + + + + + + + @@ -35,5 +42,11 @@ + + + diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 1a2b35a..b72954c 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,5 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip -distributionSha256Sum=f8b4f4772d302c8ff580bc40d0f56e715de69b163546944f787c87abf209c961 \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index 5ae9180..df53568 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.4.1' apply false - id "org.jetbrains.kotlin.android" version "2.0.0" apply false + id "com.android.application" version '8.9.1' apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" \ No newline at end of file diff --git a/lib/layouts/oss_license_page.dart b/lib/layouts/oss_license_page.dart index dc50cdc..1da03fa 100644 --- a/lib/layouts/oss_license_page.dart +++ b/lib/layouts/oss_license_page.dart @@ -39,6 +39,7 @@ class OssLicensesPage extends StatelessWidget { isMarkdown: false, isSdk: false, dependencies: [], + devDependencies: [], ), ); } @@ -54,6 +55,7 @@ class OssLicensesPage extends StatelessWidget { isSdk: false, homepage: 'https://freesound.org/people/unfa/sounds/243749/', dependencies: [], + devDependencies: [], ), ); diff --git a/lib/layouts/workout_runner.dart b/lib/layouts/workout_runner.dart index 00de6e2..78e6faa 100644 --- a/lib/layouts/workout_runner.dart +++ b/lib/layouts/workout_runner.dart @@ -4,10 +4,13 @@ import 'package:prefs/prefs.dart'; import 'package:provider/provider.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import '../generated/l10n.dart'; import '../utils/utils.dart'; import '../utils/workout.dart'; +import '../utils/workout_service.dart'; class WorkoutPage extends StatelessWidget { final Workout workout; @@ -15,11 +18,13 @@ class WorkoutPage extends StatelessWidget { const WorkoutPage({super.key, required this.workout}); @override - Widget build(BuildContext context) => ChangeNotifierProvider( - create: (context) => Timetable(context, workout), - child: Consumer( - builder: (context, timetable, child) => - WorkoutPageContent(workout: workout, timetable: timetable), + Widget build(BuildContext context) => WithForegroundTask( + child: ChangeNotifierProvider( + create: (context) => Timetable(context, workout), + child: Consumer( + builder: (context, timetable, child) => + WorkoutPageContent(workout: workout, timetable: timetable), + ), ), ); } @@ -49,13 +54,6 @@ class WorkoutPageState extends State { final ItemPositionsListener _itemPositionsListener = ItemPositionsListener.create(); - @override - void dispose() { - timetable.timerStop(); - WakelockPlus.disable(); - super.dispose(); - } - @override void initState() { super.initState(); @@ -64,11 +62,74 @@ class WorkoutPageState extends State { timetable.itemScrollController = _itemScrollController; if (Prefs.getBool('wakelock', true)) WakelockPlus.enable(); + // Initialize workout service and request notification permission + _initializeWorkoutService(); + + // Set up notification button listener + _setupButtonListener(); + WidgetsBinding.instance.addPostFrameCallback((_) { timetable.buildTimetable(); }); } + void _setupButtonListener() { + // Listen for button presses from the foreground task + FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); + } + + @override + void dispose() { + FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData); + timetable.stopWorkout(); + WakelockPlus.disable(); + super.dispose(); + } + + void _onReceiveTaskData(Object data) { + if (data is Map && data['action'] != null) { + final action = data['action'] as String; + _handleNotificationAction(action); + } + } + + Future _initializeWorkoutService() async { + try { + await WorkoutService.initialize(); + + // Request notification permission for Android 13+ + final notificationPlugin = FlutterLocalNotificationsPlugin(); + await notificationPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestNotificationsPermission(); + } catch (e) { + // Silently handle errors - service will work without permissions + // but user won't see notifications + } + } + + void _handleNotificationAction(String action) { + switch (action) { + case 'play': + case 'pause': + if (timetable.isActive) { + timetable.timerStop(); + } else { + timetable.timerStart(); + } + // Force immediate notification update to reflect new state + timetable.forceNotificationUpdate(); + break; + case 'stop': + timetable.stopWorkout(); + if (mounted) { + Navigator.of(context).pop(); + } + break; + } + } + Widget _buildCurrentSetList(Set? set) { if (set == null) return Container(); diff --git a/lib/main.dart b/lib/main.dart index 78ded61..8d624fd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; @@ -20,6 +21,10 @@ void main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); GestureBinding.instance.resamplingEnabled = true; FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + + // Initialize communication port for foreground task + FlutterForegroundTask.initCommunicationPort(); + await Prefs.init(); PrefServiceShared.init( @@ -84,14 +89,14 @@ class JAWTApp extends StatelessWidget { brightness: Brightness.light, colorScheme: lightDynamic, colorSchemeSeed: lightDynamic != null ? null : Colors.blue, - cardTheme: const CardTheme( + cardTheme: const CardThemeData( elevation: 4, ), ), darkTheme: ThemeData( useMaterial3: true, brightness: Brightness.dark, - cardTheme: const CardTheme(elevation: 4), + cardTheme: const CardThemeData(elevation: 4), colorScheme: darkDynamic, colorSchemeSeed: darkDynamic != null ? null : Colors.blue, ), diff --git a/lib/utils/sound_helper.dart b/lib/utils/sound_helper.dart index 81f8bd3..013e485 100644 --- a/lib/utils/sound_helper.dart +++ b/lib/utils/sound_helper.dart @@ -1,63 +1,54 @@ -import 'package:flutter/services.dart'; +import 'package:audioplayers/audioplayers.dart'; import 'package:prefs/prefs.dart'; -import 'package:soundpool/soundpool.dart'; // ignore: avoid_classes_with_only_static_members class SoundHelper { - static final Soundpool _soundpool = Soundpool.fromOptions( - options: const SoundpoolOptions( - streamType: StreamType.music, - ), - ); - static late int _beepLowId; - static late int _beepHighId; - static late int _tickId; - + static final AudioPlayer _player = AudioPlayer(); static bool useSound = false; static Future loadSounds() async { - await _loadSounds(); useSound = Prefs.getString('sound') == 'beep'; + // Configure audio player for low-latency playback + await _player.setReleaseMode(ReleaseMode.stop); } - static Future _loadSounds() async { - var beepLow = await rootBundle.load('assets/beep_low.wav'); - var beepHigh = await rootBundle.load('assets/beep_high.wav'); - var tick = await rootBundle.load('assets/tick.wav'); - _beepLowId = await _soundpool.load(beepLow); - _beepHighId = await _soundpool.load(beepHigh); - _tickId = await _soundpool.load(tick); - } - - static void playBeepLow() { - if (useSound) _soundpool.play(_beepLowId); + static Future playBeepLow() async { + if (useSound) { + await _player.play(AssetSource('beep_low.wav')); + } } - static void playBeepHigh() { - if (useSound) _soundpool.play(_beepHighId); + static Future playBeepHigh() async { + if (useSound) { + await _player.play(AssetSource('beep_high.wav')); + } } - static void playBeepTick() { - if (Prefs.getBool('ticks')) _soundpool.play(_tickId); + static Future playBeepTick() async { + if (Prefs.getBool('ticks')) { + await _player.play(AssetSource('tick.wav')); + } } - static void playDouble() { + static Future playDouble() async { if (useSound) { - _soundpool.play(_beepLowId); - Future.delayed(const Duration(milliseconds: 200)) - .then((value) => _soundpool.play(_beepLowId)); + await _player.play(AssetSource('beep_low.wav')); + await Future.delayed(const Duration(milliseconds: 200)); + await _player.play(AssetSource('beep_low.wav')); } } - static void playTriple() { + static Future playTriple() async { if (useSound) { - _soundpool.play(_beepHighId); - Future.delayed(const Duration(milliseconds: 150)) - .then((value) => _soundpool.play(_beepHighId)) - .then( - (value) => Future.delayed(const Duration(milliseconds: 150)) - .then((value) => _soundpool.play(_beepHighId)), - ); + await _player.play(AssetSource('beep_high.wav')); + await Future.delayed(const Duration(milliseconds: 150)); + await _player.play(AssetSource('beep_high.wav')); + await Future.delayed(const Duration(milliseconds: 150)); + await _player.play(AssetSource('beep_high.wav')); } } + + static Future dispose() async { + await _player.dispose(); + } } diff --git a/lib/utils/timetable.dart b/lib/utils/timetable.dart index d166852..644b861 100644 --- a/lib/utils/timetable.dart +++ b/lib/utils/timetable.dart @@ -7,6 +7,7 @@ import 'package:just_another_workout_timer/utils/tts_helper.dart'; import 'workout.dart'; import 'package:prefs/prefs.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'workout_service.dart'; import '../generated/l10n.dart'; @@ -28,12 +29,15 @@ class Timetable with ChangeNotifier { Exercise? prevExercise; Exercise? nextExercise; - int remainingSeconds = 10; + int remainingSeconds = 10; // 10 second countdown before workout starts int currentSecond = 0; bool workoutDone = false; bool isInitialized = false; + // Track last notification state to avoid unnecessary updates + int _lastNotificationSecond = -1; + /// timestamps of functions to announce current exercise (among other things) final Map _timetable = SplayTreeMap(); @@ -236,12 +240,12 @@ class Timetable with ChangeNotifier { // announce completed workout _timetable[currentTime] = () { - timerStop(); + workoutDone = true; TTSHelper.speak(S.of(_context).workoutComplete); - workoutDone = true; currentExercise = Exercise(name: S.of(_context).workoutComplete, duration: 1); + timerStop(); // Stop after setting workoutDone so service stops properly notifyListeners(); }; @@ -251,10 +255,18 @@ class Timetable with ChangeNotifier { }); } - void timerStart() { + void timerStart() async { _timer = Timer.periodic(const Duration(seconds: 1), (timer) { _timerTick(); }); + + // Start background service for notifications + try { + await WorkoutService.startService(); + } catch (e) { + // Silently handle errors - timer will still work without background service + } + notifyListeners(); } @@ -267,11 +279,67 @@ class Timetable with ChangeNotifier { } else if (currentSecond > 10 && Prefs.getBool('ticks')) { SoundHelper.playBeepTick(); } + + // Update notification with current workout state + _updateNotification(); + notifyListeners(); } - void timerStop() { + /// Update the notification with current workout progress + void _updateNotification() { + if (currentSecond > 0 && !workoutDone) { + // Update notification every 2 seconds or when there's a timetable event + // This balances responsiveness with reducing flickering + bool shouldUpdate = _lastNotificationSecond != currentSecond && + (currentSecond % 2 == 0 || _timetable.containsKey(currentSecond)); + + if (shouldUpdate) { + _performNotificationUpdate(); + } + } + } + + /// Force an immediate notification update (used when pause/play button is pressed) + void forceNotificationUpdate() { + if (currentSecond > 0 && !workoutDone) { + _performNotificationUpdate(); + } + } + + /// Actually perform the notification update + void _performNotificationUpdate() { + _lastNotificationSecond = currentSecond; + + // Find current set index and rep + int setIndex = _workout.sets.indexOf(currentSet) + 1; + int totalSets = _workout.sets.length; + + WorkoutService.updateNotification( + exerciseName: currentExercise.name, + remainingSeconds: remainingSeconds, + currentSet: setIndex, + totalSets: totalSets, + currentRep: currentReps, + totalReps: currentSet.repetitions, + isPaused: !isActive, + ); + } + + void timerStop() async { _timer?.cancel(); + + // Stop background service if workout is done + if (workoutDone) { + await WorkoutService.stopService(); + } + notifyListeners(); } + + /// Called when workout is completed or manually stopped + void stopWorkout() async { + timerStop(); + await WorkoutService.stopService(); + } } diff --git a/lib/utils/workout_service.dart b/lib/utils/workout_service.dart new file mode 100644 index 0000000..883dbf6 --- /dev/null +++ b/lib/utils/workout_service.dart @@ -0,0 +1,202 @@ +import 'dart:async'; + +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +/// Callback function type for handling notification actions +typedef NotificationActionCallback = void Function(String action); + +/// Manages background workout timer service and notifications +class WorkoutService { + static const String _channelId = 'workout_timer_channel'; + static const String _channelName = 'Workout Timer'; + static const int _notificationId = 1; + + static final FlutterLocalNotificationsPlugin _notifications = + FlutterLocalNotificationsPlugin(); + + static bool _isInitialized = false; + static bool _isRunning = false; + + static NotificationActionCallback? _onNotificationAction; + + /// Initialize the workout service + static Future initialize() async { + if (_isInitialized) return; + + // Initialize local notifications + const androidSettings = + AndroidInitializationSettings('@mipmap/launcher_icon'); + const initSettings = InitializationSettings(android: androidSettings); + + await _notifications.initialize( + initSettings, + onDidReceiveNotificationResponse: (NotificationResponse response) { + if (response.actionId != null && _onNotificationAction != null) { + _onNotificationAction!(response.actionId!); + } + }, + ); + + // Create notification channel + const androidChannel = AndroidNotificationChannel( + _channelId, + _channelName, + description: 'Notifications for workout timer progress', + importance: Importance.low, + playSound: false, + enableVibration: false, + ); + + await _notifications + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(androidChannel); + + // Initialize foreground task + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: _channelId, + channelName: _channelName, + channelDescription: 'Workout timer running in background', + channelImportance: NotificationChannelImportance.LOW, + priority: NotificationPriority.LOW, + ), + iosNotificationOptions: const IOSNotificationOptions(), + foregroundTaskOptions: ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.repeat(1000), // 1 second + autoRunOnBoot: false, + autoRunOnMyPackageReplaced: false, + allowWakeLock: true, + allowWifiLock: false, + ), + ); + + _isInitialized = true; + } + + /// Set the callback for notification actions + static void setNotificationActionCallback( + NotificationActionCallback callback) { + _onNotificationAction = callback; + } + + /// Start the foreground service + static Future startService() async { + if (!_isInitialized) { + await initialize(); + } + + if (_isRunning) { + return true; + } + + await FlutterForegroundTask.startService( + serviceId: 1, + notificationTitle: 'Workout Timer', + notificationText: 'Starting workout...', + notificationIcon: null, + notificationButtons: [ + const NotificationButton(id: 'pause', text: 'Pause'), + const NotificationButton(id: 'stop', text: 'Stop'), + ], + callback: startWorkoutCallback, + ); + + // Assume service started successfully if no exception thrown + _isRunning = true; + return true; + } + + /// Stop the foreground service + static Future stopService() async { + if (!_isRunning) { + return true; + } + + await FlutterForegroundTask.stopService(); + + // Assume service stopped successfully if no exception thrown + _isRunning = false; + return true; + } + + /// Update the notification with current workout state + static Future updateNotification({ + required String exerciseName, + required int remainingSeconds, + required int currentSet, + required int totalSets, + required int currentRep, + required int totalReps, + required bool isPaused, + }) async { + if (!_isInitialized) { + await initialize(); + } + + final minutes = remainingSeconds ~/ 60; + final seconds = remainingSeconds % 60; + final timeStr = '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + + // Update the foreground task notification with current workout state + await FlutterForegroundTask.updateService( + notificationTitle: 'Workout Timer - $timeStr', + notificationText: '$exerciseName\nSet $currentSet/$totalSets, Rep $currentRep/$totalReps', + notificationButtons: [ + NotificationButton( + id: isPaused ? 'play' : 'pause', + text: isPaused ? 'Resume' : 'Pause', + ), + const NotificationButton(id: 'stop', text: 'Stop'), + ], + ); + } + + /// Check if service is currently running + static bool get isRunning => _isRunning; +} + +/// Foreground task callback (runs in isolate) - MUST be top-level function +@pragma('vm:entry-point') +void startWorkoutCallback() { + FlutterForegroundTask.setTaskHandler(WorkoutTaskHandler()); +} + +/// Task handler that runs in the foreground service isolate +class WorkoutTaskHandler extends TaskHandler { + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + // Service started + } + + @override + void onRepeatEvent(DateTime timestamp) { + // The actual timer logic stays in the main app (Timetable class) + // This just keeps the service alive + } + + @override + Future onDestroy(DateTime timestamp) async { + await FlutterForegroundTask.clearAllData(); + } + + @override + void onNotificationButtonPressed(String id) { + // Send button press event to main app + FlutterForegroundTask.sendDataToMain({'action': id}); + // Bring app to foreground + FlutterForegroundTask.launchApp(); + } + + @override + void onNotificationPressed() { + // Bring app to foreground when notification is tapped + FlutterForegroundTask.launchApp(); + } + + @override + void onNotificationDismissed() { + // Notification dismissed + } +} diff --git a/pubspec.lock b/pubspec.lock index 4626522..7027025 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,106 +5,162 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "85.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "7.7.1" ansicolor: dependency: transitive description: name: ansicolor - sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" archive: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.7" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4" + url: "https://pub.dev" + source: hosted + version: "6.5.1" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "4.2.1" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "3.1.0" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.7.1" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "9.3.1" built_collection: dependency: transitive description: @@ -117,154 +173,170 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139" url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.12.1" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.11.1" collection: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" convert: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" cross_file: dependency: transitive description: name: cross_file - sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" url: "https://pub.dev" source: hosted - version: "0.3.4+1" + version: "0.3.5+1" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.7" csslib: dependency: transitive description: name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" dart_pubspec_licenses: - dependency: transitive + dependency: "direct dev" description: name: dart_pubspec_licenses - sha256: a488baa010044452b208bc35c6dd62ac072a1ea918f796e4381d5aa1b21cfa41 + sha256: fa28071ec85d18260949553ab9f7ad33873b8002cd9d71b42b9967b74940ad67 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.15" dart_style: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "3.1.1" dbus: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" dynamic_color: dependency: "direct main" description: name: dynamic_color - sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c" url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -274,26 +346,58 @@ packages: dependency: "direct main" description: name: flutter_file_dialog - sha256: "9344b8f07be6a1b6f9854b723fb0cf84a8094ba94761af1d213589d3cb087488" + sha256: ec904d15e7da3691bb60442a762b0a09afa37ded7265b9fc2088ec202b7d844f url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" + flutter_foreground_task: + dependency: "direct main" + description: + name: flutter_foreground_task + sha256: "206017ee1bf864f34b8d7bce664a172717caa21af8da23f55866470dfe316644" + url: "https://pub.dev" + source: hosted + version: "8.17.0" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.13.1" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 + url: "https://pub.dev" + source: hosted + version: "18.0.1" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" + url: "https://pub.dev" + source: hosted + version: "8.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -303,18 +407,18 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: edf39bcf4d74aca1eb2c1e43c3e445fd9f494013df7f0da752fefe72020eedc0 + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.7" flutter_oss_licenses: dependency: "direct dev" description: name: flutter_oss_licenses - sha256: "1219e0e03faac80e68a554b79447d4936c79081d698abca912cc123fb77094aa" + sha256: b7fd6b417807a5783d7ff75c2f91eeaa8bb304a37cc28d7dd9c9803799406ac9 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.12" flutter_phoenix: dependency: "direct main" description: @@ -332,10 +436,10 @@ packages: dependency: "direct main" description: name: flutter_tts - sha256: aed2a00c48c43af043ed81145fd8503ddd793dafa7088ab137dbef81a703e53d + sha256: ce5eb209b40e95f2f4a1397116c87ab2fcdff32257d04ed7a764e75894c03775 url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.2.5" flutter_web_plugins: dependency: transitive description: flutter @@ -345,10 +449,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "7eae679e596a44fdf761853a706f74979f8dd3cd92cf4e23cae161fda091b847" + sha256: "144ddd74d49c865eba47abe31cbc746c7b311c82d6c32e571fd73c4264b740e2" url: "https://pub.dev" source: hosted - version: "8.2.6" + version: "9.0.0" frontend_server_client: dependency: transitive description: @@ -361,58 +465,58 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" graphs: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" html: dependency: transitive description: name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.4" + version: "0.15.6" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.6.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" image: dependency: transitive description: name: image - sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.7.2" infinite_listview: dependency: transitive description: @@ -425,34 +529,34 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" intl_utils: dependency: "direct dev" description: name: intl_utils - sha256: c2b1f5c72c25512cbeef5ab015c008fc50fe7e04813ba5541c25272300484bf4 + sha256: da67ac187b521445d745f7c68e7254c2090f6c3c9081c93cd515480af9e59569 url: "https://pub.dev" source: hosted - version: "2.8.7" + version: "2.8.11" io: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -465,90 +569,98 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.11.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.3" + 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: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.1.1" logger: dependency: transitive description: name: logger - sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.6.2" logging: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + url: "https://pub.dev" + source: hosted + version: "7.3.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.16.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" nested: dependency: transitive description: @@ -557,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" numberpicker: dependency: "direct main" description: @@ -569,58 +689,66 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" package_info_plus: dependency: transitive description: name: package_info_plus - sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "9.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.2.1" + pana: + dependency: transitive + description: + name: pana + sha256: "424b9e8523030e21ceeb9712a8b09c49e8e0e50d55845af70efd3238f6a73fa2" + url: "https://pub.dev" + source: hosted + version: "0.22.21" path: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e url: "https://pub.dev" source: hosted - version: "2.2.6" + version: "2.2.22" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -641,26 +769,26 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" petitparser: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "7.0.1" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -673,10 +801,18 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "6.0.3" pref: dependency: "direct main" description: @@ -689,26 +825,26 @@ packages: dependency: "direct main" description: name: prefs - sha256: a2bf3bd2b242539a14862fbc994721fb88bd3599255b38b721d16d45e361f30d + sha256: b4139074f9b326d73355f3501a1493190e1362e4569088e0cef611572b3a1b11 url: "https://pub.dev" source: hosted - version: "4.1.0+3" + version: "4.2.0" provider: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5+1" pub_semver: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" pubspec: dependency: transitive description: @@ -729,18 +865,34 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" quiver: dependency: transitive description: name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + safe_url_check: + dependency: transitive + description: + name: safe_url_check + sha256: "49a3e060a7869cbafc8f4845ca1ecbbaaa53179980a32f4fdfeab1607e90f41d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" scrollable_positioned_list: dependency: "direct main" description: @@ -753,231 +905,263 @@ packages: dependency: "direct main" description: name: share_plus - sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "12.0.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "6.1.0" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.4.18" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shelf: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" - shelf_web_socket: + version: "1.4.2" + shelf_packages_handler: dependency: transitive description: - name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" url: "https://pub.dev" source: hosted - version: "2.0.0" - sky_engine: + version: "3.0.2" + shelf_static: dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - soundpool: - dependency: "direct main" description: - name: soundpool - sha256: fe7302005759d6a3561de1711e3ea818b1ba025a62375b469196dda5b654bd38 + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 url: "https://pub.dev" source: hosted - version: "2.4.1" - soundpool_macos: + version: "1.1.3" + shelf_web_socket: dependency: transitive description: - name: soundpool_macos - sha256: e0440a19d4e8f344dace336923b369184e91eebbbd8348266f4434b675bd15db + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.3.0" - soundpool_platform_interface: + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: dependency: transitive description: - name: soundpool_platform_interface - sha256: "7c6666e19319151b2036c4fc9b6da3a83f2ebf4097989e6ba1c2b0bfe3612e9f" + name: source_gen + sha256: "800f12fb87434defa13432ab37e33051b43b290a174e15259563b043cda40c46" url: "https://pub.dev" source: hosted - version: "2.2.0" - soundpool_web: + version: "4.0.0" + source_helper: dependency: transitive description: - name: soundpool_web - sha256: "3d1eb8d6cceb8a0aec38ff9aec4fbd11a9a8101d27b27a6eb29305b83d46aee5" + name: source_helper + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" url: "https://pub.dev" source: hosted - version: "2.3.0" - source_gen: + version: "1.3.8" + source_map_stack_trace: dependency: transitive description: - name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: + version: "2.1.2" + source_maps: dependency: transitive description: - name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "1.3.4" + version: "0.10.13" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + tar: + dependency: transitive + description: + name: tar + sha256: b338bacfd24dae6cf527acb4242003a71fc88ce183a9002376fabbc4ebda30c9 + url: "https://pub.dev" + source: hosted + version: "2.0.2" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + url: "https://pub.dev" + source: hosted + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.6" + test_core: + dependency: transitive + description: + name: test_core + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + url: "https://pub.dev" + source: hosted + version: "0.6.11" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" universal_io: dependency: transitive description: name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.1" uri: dependency: transitive description: @@ -990,42 +1174,42 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" url: "https://pub.dev" source: hosted - version: "6.3.3" + version: "6.3.28" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.6" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -1038,130 +1222,138 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.5" uuid: dependency: "direct main" description: name: uuid - sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.4.0" + 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: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "15.0.2" wakelock_plus: dependency: "direct main" description: name: wakelock_plus - sha256: "14758533319a462ffb5aa3b7ddb198e59b29ac3b02da14173a1715d65d4e6e68" + sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" url: "https://pub.dev" source: hosted - version: "1.2.5" + version: "1.4.0" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.1" web_socket: dependency: transitive description: name: web_socket - sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276 + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" win32: dependency: transitive description: name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.5.1" + version: "5.15.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.6.1" yaml: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" yaml_writer: dependency: transitive description: name: yaml_writer - sha256: f182931a598f9a3fd29ff528f0caab98fffa713583e30c12c7a27ce0b66c1308 + sha256: "69651cd7238411179ac32079937d4aa9a2970150d6b2ae2c6fe6de09402a5dc5" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index a0168fb..4bd14c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,13 +10,15 @@ dependencies: flutter: sdk: flutter flutter_file_dialog: '^3.0.2' - flutter_localizations: + flutter_foreground_task: '^8.14.0' + flutter_local_notifications: '^18.0.1' + flutter_localizations: sdk: flutter flutter_native_splash: '^2.4.0' flutter_phoenix: '^1.1.1' flutter_tts: '^4.0.2' - fluttertoast: '^8.2.5' - intl: '^0.19.0' + fluttertoast: ^9.0.0 + intl: '^0.20.2' json_annotation: '^4.9.0' numberpicker: '^2.1.2' path: '^1.8.0' @@ -25,17 +27,18 @@ dependencies: prefs: '^4.1.0+3' provider: '^6.1.2' scrollable_positioned_list: '^0.3.8' - share_plus: '^9.0.0' - soundpool: '^2.4.1' + share_plus: ^12.0.1 + audioplayers: ^6.1.0 url_launcher: '^6.2.6' uuid: '^4.4.0' wakelock_plus: '^1.2.5' dev_dependencies: build_runner: '^2.4.11' - flutter_launcher_icons: '^0.13.1' - flutter_lints: '^4.0.0' + flutter_launcher_icons: ^0.14.4 + flutter_lints: ^5.0.0 flutter_oss_licenses: '^3.0.2' + dart_pubspec_licenses: '>=3.0.14' intl_utils: '^2.8.7' json_serializable: '^6.8.0' pubspec_dependency_sorter: '^1.0.5'