Skip to content

New tasks: Squat and Light #450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/alarm/data/alarm_task_schemas.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:clock_app/alarm/types/alarm_task.dart';
import 'package:clock_app/alarm/widgets/tasks/light_task.dart';
import 'package:clock_app/alarm/widgets/tasks/math_task.dart';
import 'package:clock_app/alarm/widgets/tasks/memory_task.dart';
import 'package:clock_app/alarm/widgets/tasks/retype_task.dart';
import 'package:clock_app/alarm/widgets/tasks/sequence_task.dart';
import 'package:clock_app/alarm/widgets/tasks/squat_task.dart';
import 'package:clock_app/settings/types/setting.dart';
import 'package:clock_app/settings/types/setting_group.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Expand Down Expand Up @@ -115,4 +117,24 @@ Map<AlarmTaskType, AlarmTaskSchema> alarmTaskSchemasMap = {
return MemoryTask(onSolve: onSolve, settings: settings);
},
),
AlarmTaskType.squat: AlarmTaskSchema(
(context) => AppLocalizations.of(context)!.squatTask,
SettingGroup("squatSettings",
(context) => AppLocalizations.of(context)!.squatTask, [
NumberSetting("numberOfSquats", (context) => AppLocalizations.of(context)!.numberOfSquatsSetting, 10),
]),
(onSolve, settings) {
return SquatTask(onSolve: onSolve, settings: settings);
},
),
AlarmTaskType.lightSensor: AlarmTaskSchema(
(context) => AppLocalizations.of(context)!.lightTask,
SettingGroup("lightSensorSettings",
(context) => AppLocalizations.of(context)!.requiredLightLevelSetting, [
SliderSetting("targetLux", (context) => AppLocalizations.of(context)!.requiredLightLevelSetting, 0, 200, 100),
]),
(onSolve, settings) {
return LightTask(onSolve: onSolve, settings: settings);
},
),
};
2 changes: 2 additions & 0 deletions lib/alarm/types/alarm_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ enum AlarmTaskType {
sequence,
shake,
memory,
squat,
lightSensor
}

typedef AlarmTaskBuilder = Widget Function(
Expand Down
174 changes: 174 additions & 0 deletions lib/alarm/widgets/tasks/light_task.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import 'dart:async';

import 'package:clock_app/common/widgets/linear_progress_bar.dart';
import 'package:light_sensor/light_sensor.dart';
import 'package:clock_app/settings/types/setting_group.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LightTask extends StatefulWidget {
const LightTask({
super.key,
required this.onSolve,
required this.settings,
});

final VoidCallback onSolve;
final SettingGroup settings;

@override
State<LightTask> createState() => _LightTaskState();
}

class LightSample {
const LightSample({required this.timestamp, required this.value});

final DateTime timestamp;
final int value;
}

class _LightTaskState extends State<LightTask> with TickerProviderStateMixin {
late final double targetLux = widget.settings.getSetting("targetLux").value;
late final StreamSubscription<int> _luxStream;
final List<LightSample> lightSensorSamples = [];

double lightValue = 0.0;

@override
void initState() {
super.initState();

//Start a check for whether the sensor exists;
// if it doesn't, then immediately pass.
LightSensor.hasSensor().then((hasSensor) {
if(!hasSensor) {
widget.onSolve();
}
});

_luxStream = LightSensor.luxStream().listen((int lux) {
lightSensorSamples
.add(LightSample(timestamp: DateTime.now(), value: lux));
_dropOldSensorEvents();
_updateLightSensorData();
});
}

void _updateLightSensorData() {
double lightSecond = _lowPassLight();

if (lightSecond > targetLux) {
widget.onSolve();
} else {
setState(() {
lightValue = lightSecond;
});
}
}

double _lowPassLight() {
//use an average to put a low pass filter on the last second's worth of data
double avg = 0.0;
double count = 0.0;

DateTime oldest =
DateTime.now().subtract(const Duration(milliseconds: 500));

for (LightSample samp in lightSensorSamples.reversed) {
count++;
avg += (samp.value.toDouble() - avg) / count;

if (samp.timestamp.isBefore(oldest)) break;
}

return avg;
}

void _dropOldSensorEvents() {
DateTime oldestNonDropped =
DateTime.now().subtract(const Duration(milliseconds: 4000));

int accelerometerFirstValidIndex = lightSensorSamples
.indexWhere((samp) => samp.timestamp.isAfter(oldestNonDropped));
if (accelerometerFirstValidIndex == -1) {
accelerometerFirstValidIndex = lightSensorSamples.length;
}
lightSensorSamples.removeRange(0, accelerometerFirstValidIndex);
}

@override
void dispose() {
super.dispose();
_luxStream.cancel();
}

static String _roundLux(double lux) {
final rounded = (lux * 100).roundToDouble() / 100;

if(rounded.truncateToDouble() == rounded) {
return rounded.toInt().toString();
} else {
return rounded.toString();
}
}

@override
Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
ColorScheme colorScheme = theme.colorScheme;
TextTheme textTheme = theme.textTheme;

double percentageThere = lightValue / targetLux;

return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Text(
_roundLux(lightValue),
style: textTheme.displayLarge,
),
Text(
AppLocalizations.of(context)!.luxUnitSuffix,
style: textTheme.displaySmall,
),
]),
LinearProgressBar(
backgroundColor: colorScheme.onSurface.withOpacity(0.25),
value: percentageThere,
color: colorScheme.secondary,
minHeight: 18,
semanticsLabel: AppLocalizations.of(context)!.lightProgressSemanticsLabel,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_roundLux(0.0) + AppLocalizations.of(context)!.luxUnitSuffix,
style: textTheme.displaySmall,
),
Text(
_roundLux(targetLux) + AppLocalizations.of(context)!.luxUnitSuffix,
style: textTheme.displaySmall,
),
]),
const SizedBox(height: 16.0),
Text(
textAlign: TextAlign.center,
lightValue < 10
? AppLocalizations.of(context)!.lightNoticeTurnOnLights
: lightValue < 50
? AppLocalizations.of(context)!.lightNoticeFaceScreen
: AppLocalizations.of(context)!.lightNoticeMoveCloser,
style: textTheme.headlineLarge,
),
],
),
);
}
}
Loading