Skip to content

Commit 556f270

Browse files
authored
Feature: flags added to stopwatch (#653)
* flags feature added to stopwatch * shifted Flag to models
1 parent 40c2235 commit 556f270

File tree

3 files changed

+222
-104
lines changed

3 files changed

+222
-104
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//Flag model for timer
2+
class Flag {
3+
final int number;
4+
final Duration lapTime;
5+
final Duration totalTime;
6+
7+
Flag({
8+
required this.number,
9+
required this.lapTime,
10+
required this.totalTime,
11+
});
12+
}

lib/app/modules/stopwatch/controllers/stopwatch_controller.dart

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
11
import 'dart:async';
22
import 'package:flutter/material.dart';
33
import 'package:get/get.dart';
4+
import 'package:ultimate_alarm_clock/app/data/models/flag_model.dart';
45

56
class StopwatchController extends GetxController {
67
final RxBool isTimerPaused = true.obs;
78
final Stopwatch _stopwatch = Stopwatch();
89
final RxString _result = '00:00:00'.obs;
10+
final RxList<Flag> flags = <Flag>[].obs;
11+
Duration _lastFlagTime = Duration.zero;
12+
final RxBool hasFlags = false.obs;
13+
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
914
late Timer timer;
1015

1116
String get result => _result.value;
1217

18+
void addFlag() {
19+
if (_stopwatch.isRunning) {
20+
final currentTime = _stopwatch.elapsed;
21+
final lapTime = currentTime - _lastFlagTime;
22+
hasFlags.value = true;
23+
flags.add(Flag(
24+
number: flags.length + 1, lapTime: lapTime, totalTime: currentTime));
25+
listKey.currentState
26+
?.insertItem(0, duration: const Duration(milliseconds: 300));
27+
_lastFlagTime = currentTime;
28+
}
29+
}
30+
31+
void clearFlags() {
32+
final length = flags.length;
33+
flags.clear();
34+
_lastFlagTime = Duration.zero;
35+
hasFlags.value = false;
36+
for (var i = 0; i < length; i++) {
37+
listKey.currentState?.removeItem(
38+
0,
39+
(context, animation) => const SizedBox.shrink(),
40+
duration: const Duration(milliseconds: 0),
41+
);
42+
}
43+
}
44+
1345
void toggleTimer() {
1446
if (isTimerPaused.value) {
1547
startTimer();
@@ -36,11 +68,11 @@ class StopwatchController extends GetxController {
3668
stopTimer();
3769
_stopwatch.reset();
3870
_updateResult();
71+
clearFlags();
3972
}
4073

4174
void _updateResult() {
4275
_result.value =
4376
'${_stopwatch.elapsed.inMinutes.toString().padLeft(2, '0')}:${(_stopwatch.elapsed.inSeconds % 60).toString().padLeft(2, '0')}:${(_stopwatch.elapsed.inMilliseconds % 1000 ~/ 10).toString().padLeft(2, '0')}';
4477
}
45-
4678
}
Lines changed: 177 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:get/get.dart';
3+
import 'package:intl/intl.dart';
34
import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_controller.dart';
45
import 'package:ultimate_alarm_clock/app/modules/stopwatch/controllers/stopwatch_controller.dart';
56
import 'package:ultimate_alarm_clock/app/utils/end_drawer.dart';
@@ -15,127 +16,200 @@ class StopwatchView extends GetView<StopwatchController> {
1516
final double width = MediaQuery.of(context).size.width;
1617
final double height = MediaQuery.of(context).size.height;
1718
return Scaffold(
18-
appBar: PreferredSize(
19-
preferredSize: Size.fromHeight(height / 7.9),
20-
child: AppBar(
21-
toolbarHeight: height / 7.9,
22-
elevation: 0.0,
23-
centerTitle: true,
24-
actions: [
25-
LayoutBuilder(
26-
builder: (BuildContext context, BoxConstraints constraints) {
27-
return Obx(
28-
() => IconButton(
29-
onPressed: () {
30-
Utils.hapticFeedback();
31-
Scaffold.of(context).openEndDrawer();
32-
},
33-
icon: const Icon(
34-
Icons.menu,
19+
appBar: PreferredSize(
20+
preferredSize: Size.fromHeight(height / 7.9),
21+
child: AppBar(
22+
toolbarHeight: height / 7.9,
23+
elevation: 0.0,
24+
centerTitle: true,
25+
actions: [
26+
LayoutBuilder(
27+
builder: (BuildContext context, BoxConstraints constraints) {
28+
return Obx(
29+
() => IconButton(
30+
onPressed: () {
31+
Utils.hapticFeedback();
32+
Scaffold.of(context).openEndDrawer();
33+
},
34+
icon: const Icon(
35+
Icons.menu,
36+
),
37+
color: themeController.primaryTextColor.value
38+
.withOpacity(0.75),
39+
iconSize: 27,
40+
// splashRadius: 0.000001,
3541
),
36-
color: themeController.primaryTextColor.value
37-
.withOpacity(0.75),
38-
iconSize: 27,
39-
// splashRadius: 0.000001,
40-
),
41-
);
42-
},
43-
),
44-
],
45-
),
46-
),
47-
body: Column(
48-
children: [
49-
SizedBox(
50-
height: height * 0.3,
42+
);
43+
},
44+
),
45+
],
5146
),
52-
Obx(
53-
() => Row(
54-
mainAxisAlignment: MainAxisAlignment.center,
55-
crossAxisAlignment: CrossAxisAlignment.start,
56-
children: [
57-
Expanded(
58-
child: Center(
59-
child: Text(
60-
controller.result.split(':')[0],
61-
style: const TextStyle(
62-
fontSize: 50.0,
63-
fontWeight: FontWeight.bold,
47+
),
48+
body: Column(
49+
children: [
50+
Obx(
51+
() => AnimatedContainer(
52+
duration: const Duration(milliseconds: 300),
53+
curve: Curves.easeInOut,
54+
height: controller.hasFlags.value ? height * 0.1 : height * 0.3,
55+
),
56+
),
57+
Obx(
58+
() => Row(
59+
mainAxisAlignment: MainAxisAlignment.center,
60+
crossAxisAlignment: CrossAxisAlignment.start,
61+
children: [
62+
Expanded(
63+
child: Center(
64+
child: Text(
65+
controller.result.split(':')[0],
66+
style: const TextStyle(
67+
fontSize: 50.0,
68+
fontWeight: FontWeight.bold,
69+
),
6470
),
6571
),
6672
),
67-
),
68-
const Text(
69-
':',
70-
style: TextStyle(
71-
fontSize: 50.0,
72-
fontWeight: FontWeight.bold,
73+
const Text(
74+
':',
75+
style: TextStyle(
76+
fontSize: 50.0,
77+
fontWeight: FontWeight.bold,
78+
),
7379
),
74-
),
75-
Expanded(
76-
child: Center(
77-
child: Text(
78-
controller.result.split(':')[1],
79-
style: const TextStyle(
80-
fontSize: 50.0,
81-
fontWeight: FontWeight.bold,
80+
Expanded(
81+
child: Center(
82+
child: Text(
83+
controller.result.split(':')[1],
84+
style: const TextStyle(
85+
fontSize: 50.0,
86+
fontWeight: FontWeight.bold,
87+
),
8288
),
8389
),
8490
),
85-
),
86-
const Text(
87-
':',
88-
style: TextStyle(
89-
fontSize: 50.0,
90-
fontWeight: FontWeight.bold,
91+
const Text(
92+
':',
93+
style: TextStyle(
94+
fontSize: 50.0,
95+
fontWeight: FontWeight.bold,
96+
),
9197
),
92-
),
93-
Expanded(
94-
child: Center(
95-
child: Text(
96-
controller.result.split(':')[2],
97-
style: const TextStyle(
98-
fontSize: 50.0,
99-
fontWeight: FontWeight.bold,
98+
Expanded(
99+
child: Center(
100+
child: Text(
101+
controller.result.split(':')[2],
102+
style: const TextStyle(
103+
fontSize: 50.0,
104+
fontWeight: FontWeight.bold,
105+
),
100106
),
101107
),
102108
),
103-
),
104-
],
109+
],
110+
),
105111
),
106-
),
107-
const SizedBox(
108-
height: 10.0,
109-
),
110-
Row(
111-
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
112-
children: [
113-
FloatingActionButton(
114-
heroTag: "start",
115-
onPressed: controller.toggleTimer,
116-
child: Obx(
117-
() => Icon(
118-
controller.isTimerPaused.value
119-
? Icons.play_arrow_rounded
120-
: Icons.pause_rounded,
112+
const SizedBox(
113+
height: 10.0,
114+
),
115+
Row(
116+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
117+
children: [
118+
FloatingActionButton(
119+
heroTag: "flag",
120+
onPressed: controller.addFlag,
121+
child: Icon(
122+
Icons.flag,
121123
size: 33,
124+
color: themeController.secondaryTextColor.value,
122125
),
123126
),
124-
),
125-
// Reset button
126-
FloatingActionButton(
127-
heroTag: "stop",
128-
onPressed: controller.resetTime,
129-
child: Icon(
130-
Icons.stop_rounded,
131-
size: 33,
127+
FloatingActionButton(
128+
heroTag: "start",
129+
onPressed: controller.toggleTimer,
130+
child: Obx(
131+
() => Icon(
132+
controller.isTimerPaused.value
133+
? Icons.play_arrow_rounded
134+
: Icons.pause_rounded,
135+
size: 33,
136+
),
137+
),
138+
),
139+
// Reset button
140+
FloatingActionButton(
141+
heroTag: "stop",
142+
onPressed: controller.resetTime,
143+
child: Icon(
144+
Icons.stop_rounded,
145+
size: 33,
146+
),
147+
),
148+
],
149+
),
150+
const SizedBox(
151+
height: 10.0,
152+
),
153+
Expanded(
154+
child: Obx(
155+
() => AnimatedList(
156+
key: controller.listKey, // Add this key to controller
157+
initialItemCount: controller.flags.length,
158+
padding: const EdgeInsets.symmetric(horizontal: 12),
159+
itemBuilder: (context, index, animation) {
160+
final reversedIndex = controller.flags.length - 1 - index;
161+
return SlideTransition(
162+
position: animation.drive(
163+
Tween<Offset>(
164+
begin: const Offset(0, -0.3),
165+
end: Offset.zero,
166+
).chain(CurveTween(curve: Curves.easeInOut)),
167+
),
168+
child: ListTile(
169+
minVerticalPadding: 0,
170+
title: Text(
171+
'${controller.flags[reversedIndex].number}',
172+
style: const TextStyle(
173+
fontSize: 32.0,
174+
fontWeight: FontWeight.bold,
175+
height: 1,
176+
),
177+
),
178+
trailing: Text(
179+
"+${DateFormat('mm:ss:SS').format(
180+
DateTime(0)
181+
.add(controller.flags[reversedIndex].lapTime),
182+
)}",
183+
style: TextStyle(
184+
fontSize: 12.0,
185+
height: -0.5,
186+
color:
187+
themeController.primaryDisabledTextColor.value,
188+
),
189+
),
190+
subtitle: Text(
191+
DateFormat('mm:ss:SS').format(
192+
DateTime(0)
193+
.add(controller.flags[reversedIndex].totalTime),
194+
),
195+
style: TextStyle(
196+
fontSize: 14.0,
197+
color:
198+
themeController.primaryDisabledTextColor.value,
199+
fontWeight: FontWeight.bold,
200+
),
201+
),
202+
),
203+
);
204+
},
132205
),
133206
),
134-
],
135-
),
136-
],
137-
),
138-
endDrawer: buildEndDrawer(context)
139-
);
207+
),
208+
const SizedBox(
209+
height: 10.0,
210+
),
211+
],
212+
),
213+
endDrawer: buildEndDrawer(context));
140214
}
141215
}

0 commit comments

Comments
 (0)