mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-07-04 15:23:02 +02:00
fix: restore per-rep breaks with audio/vibration
Breaks belong after each rep (circle tap), not removed entirely. Restored break_banner, audioplayers, vibration, and break_end.mp3 asset. Break triggers on every circle tap except the last one; 3 min on success, 5 min on failure; sound + vibration fires when the countdown ends. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
da269c537a
commit
735f900bf8
BIN
stronglift_replacement/workout_app/assets/sounds/break_end.mp3
Normal file
BIN
stronglift_replacement/workout_app/assets/sounds/break_end.mp3
Normal file
Binary file not shown.
@ -1,18 +1,25 @@
|
|||||||
/// Active workout screen: warmup, back-button protection,
|
/// Active workout screen: per-rep breaks, warmup, back-button protection,
|
||||||
/// and crash-safe session persistence.
|
/// and crash-safe session persistence.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
|
import 'package:vibration/vibration.dart';
|
||||||
import 'package:workout_app/models/exercise.dart';
|
import 'package:workout_app/models/exercise.dart';
|
||||||
import 'package:workout_app/models/exercise_result.dart';
|
import 'package:workout_app/models/exercise_result.dart';
|
||||||
import 'package:workout_app/models/set_result.dart';
|
import 'package:workout_app/models/set_result.dart';
|
||||||
import 'package:workout_app/models/workout_session.dart';
|
import 'package:workout_app/models/workout_session.dart';
|
||||||
import 'package:workout_app/services/storage_service.dart';
|
import 'package:workout_app/services/storage_service.dart';
|
||||||
import 'package:workout_app/services/sync_service.dart';
|
import 'package:workout_app/services/sync_service.dart';
|
||||||
|
import 'package:workout_app/widgets/break_banner.dart';
|
||||||
import 'package:workout_app/widgets/exercise_tile.dart';
|
import 'package:workout_app/widgets/exercise_tile.dart';
|
||||||
import 'package:workout_app/widgets/workout_summary_dialog.dart';
|
import 'package:workout_app/widgets/workout_summary_dialog.dart';
|
||||||
|
|
||||||
|
const _successBreakSecs = 180; // 3 min after successful rep
|
||||||
|
const _failBreakSecs = 300; // 5 min after failed rep
|
||||||
|
const _warmupBreakSecs = 180; // 3 min after warmup
|
||||||
|
|
||||||
class WorkoutScreen extends StatefulWidget {
|
class WorkoutScreen extends StatefulWidget {
|
||||||
const WorkoutScreen({
|
const WorkoutScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@ -39,6 +46,18 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
|||||||
late Timer _elapsedTimer;
|
late Timer _elapsedTimer;
|
||||||
Duration _elapsed = Duration.zero;
|
Duration _elapsed = Duration.zero;
|
||||||
|
|
||||||
|
// Break state
|
||||||
|
int _breakRemaining = 0;
|
||||||
|
int _breakDurationSecs = 0;
|
||||||
|
DateTime? _breakStartTime;
|
||||||
|
Timer? _breakTimer;
|
||||||
|
String _breakLabel = '';
|
||||||
|
int _breakForExIdx = -1;
|
||||||
|
int _breakForRepIdx = -1; // -1 = warmup break
|
||||||
|
|
||||||
|
bool get _inBreak => _breakRemaining > 0;
|
||||||
|
|
||||||
|
final _audio = AudioPlayer();
|
||||||
final _sync = SyncService();
|
final _sync = SyncService();
|
||||||
bool _finished = false;
|
bool _finished = false;
|
||||||
|
|
||||||
@ -78,11 +97,29 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
|||||||
.map((row) => (row as List).cast<int>())
|
.map((row) => (row as List).cast<int>())
|
||||||
.toList();
|
.toList();
|
||||||
_warmupTapped = (s['warmupTapped'] as List).cast<bool>();
|
_warmupTapped = (s['warmupTapped'] as List).cast<bool>();
|
||||||
|
|
||||||
|
final breakEndMs = s['breakEndMs'] as int? ?? 0;
|
||||||
|
final breakDur = s['breakDurationSecs'] as int? ?? 0;
|
||||||
|
if (breakEndMs > 0 && breakDur > 0) {
|
||||||
|
final endTime = DateTime.fromMillisecondsSinceEpoch(breakEndMs);
|
||||||
|
final remaining = endTime.difference(DateTime.now()).inSeconds;
|
||||||
|
if (remaining > 0) {
|
||||||
|
_breakForExIdx = s['breakForExIdx'] as int? ?? -1;
|
||||||
|
_breakForRepIdx = s['breakForRepIdx'] as int? ?? -1;
|
||||||
|
_breakLabel = s['breakLabel'] as String? ?? 'Rest';
|
||||||
|
_breakDurationSecs = breakDur;
|
||||||
|
_breakStartTime = endTime.subtract(Duration(seconds: breakDur));
|
||||||
|
_breakRemaining = remaining;
|
||||||
|
_breakTimer = Timer.periodic(const Duration(seconds: 1), _tickBreak);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_elapsedTimer.cancel();
|
_elapsedTimer.cancel();
|
||||||
|
_breakTimer?.cancel();
|
||||||
|
_audio.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +132,15 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
|||||||
'tapped': _tapped,
|
'tapped': _tapped,
|
||||||
'doneReps': _doneReps,
|
'doneReps': _doneReps,
|
||||||
'warmupTapped': _warmupTapped,
|
'warmupTapped': _warmupTapped,
|
||||||
|
'breakForExIdx': _breakForExIdx,
|
||||||
|
'breakForRepIdx': _breakForRepIdx,
|
||||||
|
'breakLabel': _breakLabel,
|
||||||
|
'breakDurationSecs': _breakDurationSecs,
|
||||||
|
'breakEndMs': _breakStartTime != null
|
||||||
|
? _breakStartTime!
|
||||||
|
.add(Duration(seconds: _breakDurationSecs))
|
||||||
|
.millisecondsSinceEpoch
|
||||||
|
: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,33 +154,142 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
|||||||
|
|
||||||
bool get _allSetsCompleted => _tapped.every((row) => row.every((t) => t));
|
bool get _allSetsCompleted => _tapped.every((row) => row.every((t) => t));
|
||||||
|
|
||||||
|
bool _isLastUntappedCircle(int exIdx, int repIdx) {
|
||||||
|
int remaining = 0;
|
||||||
|
for (int i = 0; i < widget.exercises.length; i++) {
|
||||||
|
for (int s = 0; s < widget.exercises[i].sets; s++) {
|
||||||
|
if (!_tapped[i][s]) remaining++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remaining == 1;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Interaction ────────────────────────────────────────────────────────────
|
// ── Interaction ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
void _tapCircle(int exIdx, int setIdx) {
|
void _tapCircle(int exIdx, int repIdx) {
|
||||||
if (_finished) return;
|
if (_finished) return;
|
||||||
|
|
||||||
|
final wasNotTapped = !_tapped[exIdx][repIdx];
|
||||||
|
if (wasNotTapped && _inBreak) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
if (!_tapped[exIdx][setIdx]) {
|
if (wasNotTapped) {
|
||||||
_tapped[exIdx][setIdx] = true;
|
_tapped[exIdx][repIdx] = true;
|
||||||
} else {
|
} else {
|
||||||
// Subsequent taps decrement reps (records actual reps done).
|
// Subsequent taps decrement reps (records actual reps done).
|
||||||
_doneReps[exIdx][setIdx] =
|
_doneReps[exIdx][repIdx] =
|
||||||
(_doneReps[exIdx][setIdx] - 1).clamp(0, 999);
|
(_doneReps[exIdx][repIdx] - 1).clamp(0, 999);
|
||||||
|
_recomputeBreakIfNeeded(exIdx, repIdx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (wasNotTapped) {
|
||||||
|
final isLast = _isLastUntappedCircle(exIdx, repIdx);
|
||||||
|
if (!isLast) {
|
||||||
|
final succeeded =
|
||||||
|
_doneReps[exIdx][repIdx] >= widget.exercises[exIdx].reps;
|
||||||
|
_startBreak(
|
||||||
|
succeeded ? _successBreakSecs : _failBreakSecs,
|
||||||
|
succeeded
|
||||||
|
? 'Rest (3 min — well done!)'
|
||||||
|
: 'Rest (5 min — keep going!)',
|
||||||
|
exIdx,
|
||||||
|
repIdx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_saveActiveSession();
|
_saveActiveSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _tapWarmup(int exIdx) {
|
void _tapWarmup(int exIdx) {
|
||||||
if (_finished || _warmupTapped[exIdx]) return;
|
if (_finished || _warmupTapped[exIdx]) return;
|
||||||
setState(() => _warmupTapped[exIdx] = true);
|
setState(() => _warmupTapped[exIdx] = true);
|
||||||
|
if (!_inBreak) {
|
||||||
|
_startBreak(_warmupBreakSecs, 'Warmup rest (3 min)', exIdx, -1);
|
||||||
|
}
|
||||||
_saveActiveSession();
|
_saveActiveSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resetCircle(int exIdx, int setIdx) {
|
void _resetCircle(int exIdx, int repIdx) {
|
||||||
if (_finished) return;
|
if (_finished) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_tapped[exIdx][setIdx] = false;
|
_tapped[exIdx][repIdx] = false;
|
||||||
_doneReps[exIdx][setIdx] = widget.exercises[exIdx].reps;
|
_doneReps[exIdx][repIdx] = widget.exercises[exIdx].reps;
|
||||||
|
});
|
||||||
|
if (_breakForExIdx == exIdx && _breakForRepIdx == repIdx) {
|
||||||
|
_cancelBreak();
|
||||||
|
}
|
||||||
|
_saveActiveSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Break management ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void _startBreak(int secs, String label, int exIdx, int repIdx) {
|
||||||
|
_breakTimer?.cancel();
|
||||||
|
setState(() {
|
||||||
|
_breakDurationSecs = secs;
|
||||||
|
_breakRemaining = secs;
|
||||||
|
_breakLabel = label;
|
||||||
|
_breakForExIdx = exIdx;
|
||||||
|
_breakForRepIdx = repIdx;
|
||||||
|
_breakStartTime = DateTime.now();
|
||||||
|
});
|
||||||
|
_breakTimer = Timer.periodic(const Duration(seconds: 1), _tickBreak);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _tickBreak(Timer t) {
|
||||||
|
setState(() => _breakRemaining--);
|
||||||
|
if (_breakRemaining <= 0) {
|
||||||
|
t.cancel();
|
||||||
|
_onBreakFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cancelBreak() {
|
||||||
|
_breakTimer?.cancel();
|
||||||
|
setState(() {
|
||||||
|
_breakRemaining = 0;
|
||||||
|
_breakForExIdx = -1;
|
||||||
|
_breakForRepIdx = -1;
|
||||||
|
_breakStartTime = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _skipBreak() {
|
||||||
|
_cancelBreak();
|
||||||
|
_saveActiveSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the user reduces reps on the rep that triggered the current break,
|
||||||
|
/// switch from 3-min to 5-min (or vice versa).
|
||||||
|
void _recomputeBreakIfNeeded(int exIdx, int repIdx) {
|
||||||
|
if (!_inBreak) return;
|
||||||
|
if (_breakForExIdx != exIdx || _breakForRepIdx != repIdx) return;
|
||||||
|
if (_breakForRepIdx == -1) return; // warmup break, never recompute
|
||||||
|
|
||||||
|
final succeeded =
|
||||||
|
_doneReps[exIdx][repIdx] >= widget.exercises[exIdx].reps;
|
||||||
|
final newDuration = succeeded ? _successBreakSecs : _failBreakSecs;
|
||||||
|
if (newDuration == _breakDurationSecs) return;
|
||||||
|
|
||||||
|
final elapsed = DateTime.now().difference(_breakStartTime!).inSeconds;
|
||||||
|
final newRemaining = (newDuration - elapsed).clamp(0, newDuration);
|
||||||
|
|
||||||
|
_breakDurationSecs = newDuration;
|
||||||
|
_breakRemaining = newRemaining;
|
||||||
|
_breakLabel =
|
||||||
|
succeeded ? 'Rest (3 min — well done!)' : 'Rest (5 min — keep going!)';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onBreakFinished() async {
|
||||||
|
await _audio.play(AssetSource('sounds/break_end.mp3')).catchError((_) {});
|
||||||
|
final hasVibrator = await Vibration.hasVibrator() == true;
|
||||||
|
if (hasVibrator) Vibration.vibrate(duration: 800);
|
||||||
|
setState(() {
|
||||||
|
_breakForExIdx = -1;
|
||||||
|
_breakForRepIdx = -1;
|
||||||
|
_breakStartTime = null;
|
||||||
});
|
});
|
||||||
_saveActiveSession();
|
_saveActiveSession();
|
||||||
}
|
}
|
||||||
@ -153,12 +308,15 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, false),
|
onPressed: () => Navigator.pop(context, false),
|
||||||
child: const Text('Cancel', style: TextStyle(color: Colors.white70)),
|
child:
|
||||||
|
const Text('Cancel', style: TextStyle(color: Colors.white70)),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
child:
|
child: const Text(
|
||||||
const Text('Finish', style: TextStyle(color: Colors.greenAccent)),
|
'Finish',
|
||||||
|
style: TextStyle(color: Colors.greenAccent),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -201,6 +359,7 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
|||||||
|
|
||||||
Future<void> _finishWorkout() async {
|
Future<void> _finishWorkout() async {
|
||||||
_elapsedTimer.cancel();
|
_elapsedTimer.cancel();
|
||||||
|
_breakTimer?.cancel();
|
||||||
setState(() => _finished = true);
|
setState(() => _finished = true);
|
||||||
|
|
||||||
final endTime = DateTime.now();
|
final endTime = DateTime.now();
|
||||||
@ -301,19 +460,31 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView.separated(
|
body: Column(
|
||||||
padding: const EdgeInsets.all(12),
|
children: [
|
||||||
itemCount: widget.exercises.length,
|
if (_inBreak)
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
BreakBanner(
|
||||||
itemBuilder: (_, i) => ExerciseTile(
|
breakRemaining: _breakRemaining,
|
||||||
exercise: widget.exercises[i],
|
breakLabel: _breakLabel,
|
||||||
tapped: _tapped[i],
|
onSkip: _skipBreak,
|
||||||
doneReps: _doneReps[i],
|
),
|
||||||
warmupTapped: _warmupTapped[i],
|
Expanded(
|
||||||
onTapCircle: (s) => _tapCircle(i, s),
|
child: ListView.separated(
|
||||||
onLongPressCircle: (s) => _resetCircle(i, s),
|
padding: const EdgeInsets.all(12),
|
||||||
onTapWarmup: () => _tapWarmup(i),
|
itemCount: widget.exercises.length,
|
||||||
),
|
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||||
|
itemBuilder: (_, i) => ExerciseTile(
|
||||||
|
exercise: widget.exercises[i],
|
||||||
|
tapped: _tapped[i],
|
||||||
|
doneReps: _doneReps[i],
|
||||||
|
warmupTapped: _warmupTapped[i],
|
||||||
|
onTapCircle: (s) => _tapCircle(i, s),
|
||||||
|
onLongPressCircle: (s) => _resetCircle(i, s),
|
||||||
|
onTapWarmup: () => _tapWarmup(i),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
/// Countdown banner displayed at the top of the workout screen during a rest.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BreakBanner extends StatelessWidget {
|
||||||
|
const BreakBanner({
|
||||||
|
super.key,
|
||||||
|
required this.breakRemaining,
|
||||||
|
required this.breakLabel,
|
||||||
|
required this.onSkip,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int breakRemaining;
|
||||||
|
final String breakLabel;
|
||||||
|
final VoidCallback onSkip;
|
||||||
|
|
||||||
|
String _fmt(int secs) {
|
||||||
|
final m = (secs ~/ 60).toString().padLeft(2, '0');
|
||||||
|
final s = (secs % 60).toString().padLeft(2, '0');
|
||||||
|
return '$m:$s';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.indigo.shade900,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
breakLabel,
|
||||||
|
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_fmt(breakRemaining),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 26,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFeatures: [FontFeature.tabularFigures()],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: onSkip,
|
||||||
|
child: const Text(
|
||||||
|
'Skip',
|
||||||
|
style: TextStyle(color: Colors.white70),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,62 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.1"
|
version: "2.13.1"
|
||||||
|
audioplayers:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: audioplayers
|
||||||
|
sha256: "1d0c1b1f2095e59080e2d5046639096417a86687d89778da41b0c9a06d683dfd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.7.0"
|
||||||
|
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: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.4.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: "24a6f258062bd7da8cb2157e83fccb9816a08dd306cbaaa24f9813d071470545"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.1"
|
||||||
|
audioplayers_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audioplayers_windows
|
||||||
|
sha256: "95f875a96c88c3dbbcb608d4f8288e300b0113d256a81d0b3197fcc18f0dc91a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -65,6 +121,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7"
|
||||||
|
device_info_plus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus
|
||||||
|
sha256: "6a642e1daa10190af89ba6cb6386c0df7d071a3592080bfe1e44faa63ae1df65"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "13.1.0"
|
||||||
|
device_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus_platform_interface
|
||||||
|
sha256: "04b173a92e2d9161dfead145667037c8d834db725ce2e7b942bfe18fd2f45a46"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -81,6 +153,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
ffi_leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi_leak_tracker
|
||||||
|
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -89,6 +169,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -120,6 +208,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
jni:
|
jni:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -517,6 +621,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.3"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -525,6 +637,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
vibration:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: vibration
|
||||||
|
sha256: "9bb06614c69260f8bd11c80fe01ed7988905cf00e3417d656c2647e41f261d87"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.8"
|
||||||
|
vibration_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vibration_platform_interface
|
||||||
|
sha256: "258c273268f8aa40c88d29741137c536874a738779b92ddb8aa32ed093721ec5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -541,6 +669,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.0"
|
||||||
|
win32_registry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32_registry
|
||||||
|
sha256: "73b1d78920a9d6e03f8b4e43e612b87bf3152a0e5c5e5150267762b7c4116904"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -14,6 +14,8 @@ dependencies:
|
|||||||
sqflite: ^2.4.2
|
sqflite: ^2.4.2
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
shared_preferences: ^2.5.3
|
shared_preferences: ^2.5.3
|
||||||
|
audioplayers: ^6.4.0
|
||||||
|
vibration: ^3.1.0
|
||||||
permission_handler: ^12.0.0
|
permission_handler: ^12.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
@ -23,3 +25,5 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- assets/sounds/break_end.mp3
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user