Skip to content

Commit aa5fdf4

Browse files
authored
Support force mode (#17)
* Added shared pref methods * Added required packages * Implemented force mode and split widgets * Enabled windows and linux * config native for window manager package * bump app version * Revert rule value * fix minor issue on nodejs * Used minimize instead of hide window when 20s over * try fix dmg build issue * Improved force mode checkbox and split rule text as widget * updated readme * Split countDownscreen on separate file * Updated notification message base on case & enable fullscreen for 20s * Renamed handleWindowState method * Show message when take break on app * minor fix * Minor improve on force mode checkbox
1 parent 22a809f commit aa5fdf4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1934
-193
lines changed

.github/workflows/desktop_build.yaml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,9 @@ jobs:
9494
working-directory: ${{ matrix.build_path }}
9595
- name: Setup Node.js environment
9696
uses: actions/setup-node@v2
97-
with:
98-
node-version: '14'
9997
- name: Convert build to dmg for macOS
10098
if: matrix.target == 'macOS'
101-
run: |
102-
brew install python@3.9
103-
npm config set python /usr/local/bin/python3.9
104-
npm install -g appdmg
99+
run: npm install -g appdmg
105100
- name: run appdmg
106101
if: matrix.target == 'macOS'
107102
run: appdmg installer/dmg_creator/config.json $GITHUB_WORKSPACE/EyesCare${{ matrix.target }}.dmg

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ We welcome contributions from the community to enhance the Eyes Care app. To con
5050

5151
[MIT License](https://opensource.org/licenses/MIT)
5252

53+
## Tools / Acknowledgements
54+
55+
- [rocket_timer](https://pub.dev/packages/rocket_timer)
56+
57+
Special thanks to these amazing projects from [LeanFlutter](https://github.com/leanflutter) which help power CareEyes:
58+
59+
- [local_notifier](https://pub.dev/packages/local_notifier)
60+
- [window_manager](https://pub.dev/packages/window_manager)
61+
5362
## Support
5463

5564
If you encounter any issues or have any questions or suggestions, please [open an issue](https://github.com/bixat/eyes_care/issues) on the GitHub repository.

lib/countdown_screen.dart

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import 'package:eyes_care/main.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:rocket_timer/rocket_timer.dart';
4+
import 'package:window_manager/window_manager.dart';
5+
import 'package:eyes_care/shared_pref.dart';
6+
import 'package:eyes_care/widgets/force_mode_check_box.dart';
7+
import 'package:eyes_care/widgets/rule_text.dart';
8+
import 'package:eyes_care/widgets/rule_timer.dart';
9+
import 'package:local_notifier/local_notifier.dart';
10+
11+
class CountdownScreen extends StatefulWidget {
12+
const CountdownScreen({super.key});
13+
14+
@override
15+
CountdownScreenState createState() => CountdownScreenState();
16+
}
17+
18+
const int rule = 20;
19+
const duration = Duration(minutes: rule);
20+
const size = Size(400, 400);
21+
22+
class CountdownScreenState extends State<CountdownScreen> with WindowListener {
23+
late RocketTimer _timer;
24+
bool inProgress = false;
25+
late ValueNotifier<bool> forceModeEnabled = ValueNotifier(false);
26+
int followed = 0;
27+
WindowOptions windowOptions = const WindowOptions(
28+
windowButtonVisibility: false,
29+
size: size,
30+
minimumSize: size,
31+
maximumSize: size,
32+
center: true,
33+
backgroundColor: Colors.transparent,
34+
skipTaskbar: false,
35+
titleBarStyle: TitleBarStyle.hidden,
36+
);
37+
38+
@override
39+
void initState() {
40+
setUpForceMode();
41+
windowManager.waitUntilReadyToShow(windowOptions, () async {
42+
await windowManager.show();
43+
await windowManager.focus();
44+
});
45+
windowManager.addListener(this);
46+
localNotifier.setup(
47+
appName: 'CareYourEyes',
48+
shortcutPolicy: ShortcutPolicy.requireCreate,
49+
);
50+
super.initState();
51+
initTimer();
52+
}
53+
54+
void initTimer() {
55+
_timer = RocketTimer(type: TimerType.countdown, duration: duration);
56+
_timer.addListener(() {
57+
if (_timer.kDuration == 0) {
58+
showNotification();
59+
_timer.kDuration = inProgress ? duration.inSeconds : rule;
60+
inProgress = !inProgress;
61+
setState(() {});
62+
}
63+
});
64+
_timer.start();
65+
}
66+
67+
setUpForceMode() {
68+
PreferenceService.getBool(PreferenceService.forceModeKey).then((value) {
69+
forceModeEnabled.value = value ?? false;
70+
});
71+
}
72+
73+
@override
74+
Future<void> onWindowMinimize() async {
75+
if (forceModeEnabled.value) {
76+
await handleWindowState();
77+
}
78+
super.onWindowMinimize();
79+
}
80+
81+
@override
82+
Future<void> onWindowBlur() async {
83+
if (forceModeEnabled.value) {
84+
await handleWindowState();
85+
}
86+
super.onWindowBlur();
87+
}
88+
89+
Future<void> handleWindowState() async {
90+
if (inProgress) {
91+
await windowManager.show();
92+
await windowManager.focus();
93+
await windowManager.setFullScreen(true);
94+
} else {
95+
windowManager.minimize();
96+
await windowManager.setFullScreen(false);
97+
}
98+
}
99+
100+
@override
101+
void dispose() {
102+
_timer.dispose();
103+
windowManager.removeListener(this);
104+
super.dispose();
105+
}
106+
107+
Future<void> showNotification() async {
108+
LocalNotification notification = LocalNotification(
109+
title: inProgress ? "Stay Focused 💪" : "Take a Moment 🌟",
110+
body: inProgress
111+
? "Keep your gaze on the screen. Remember, every 20 minutes, take a 20-second break looking at something 20 feet away."
112+
: "Step back from the screen and focus on something 20 feet away for 20 seconds. Your eyes will thank you!",
113+
);
114+
notification.onShow = _onShowNotification;
115+
notification.show();
116+
}
117+
118+
_onShowNotification() async {
119+
if (forceModeEnabled.value) {
120+
await handleWindowState();
121+
}
122+
}
123+
124+
@override
125+
Widget build(BuildContext context) {
126+
return Scaffold(
127+
appBar: AppBar(
128+
title: const Text('Eyes Care'),
129+
centerTitle: true,
130+
actions: [
131+
AnimatedBuilder(
132+
animation: _timer,
133+
builder: (context, _) {
134+
return IconButton(
135+
icon: Icon(_timer.status == TimerStatus.pause
136+
? Icons.play_arrow
137+
: Icons.pause),
138+
onPressed: () {
139+
if (_timer.status == TimerStatus.pause) {
140+
_timer.start();
141+
} else {
142+
_timer.pause();
143+
}
144+
},
145+
);
146+
}),
147+
IconButton(
148+
onPressed: _timer.restart, icon: const Icon(Icons.restart_alt)),
149+
IconButton(
150+
onPressed: windowManager.minimize,
151+
icon: const Icon(Icons.minimize_rounded)),
152+
],
153+
leading: ValueListenableBuilder(
154+
valueListenable: themeNotifier,
155+
builder: (context, _, __) {
156+
final isLight = themeNotifier.value.index == 1;
157+
return IconButton(
158+
onPressed: () {
159+
themeNotifier.value =
160+
isLight ? ThemeMode.dark : ThemeMode.light;
161+
},
162+
icon: Icon(isLight ? Icons.dark_mode : Icons.light_mode));
163+
})),
164+
body: Container(
165+
padding: const EdgeInsets.all(8.0),
166+
child: Flex(
167+
direction: inProgress ? Axis.vertical : Axis.horizontal,
168+
mainAxisAlignment: MainAxisAlignment.center,
169+
crossAxisAlignment: CrossAxisAlignment.center,
170+
children: [
171+
Expanded(
172+
child: Column(
173+
mainAxisAlignment: MainAxisAlignment.center,
174+
children: [
175+
if (inProgress)
176+
Text(
177+
"look away from your screen and focus on something 20 feet away for 20 seconds.",
178+
style: Theme.of(context).textTheme.headlineMedium)
179+
else
180+
const RuleText(),
181+
ForceModeCheckBox(forceModeEnabled: forceModeEnabled)
182+
],
183+
),
184+
),
185+
RuleTimer(timer: _timer, inProgress: inProgress),
186+
],
187+
)),
188+
);
189+
}
190+
}

0 commit comments

Comments
 (0)