Skip to content

Commit

Permalink
Add progress indicator to trigger cooldown & trigger all gear
Browse files Browse the repository at this point in the history
  • Loading branch information
Codel1417 committed May 25, 2024
1 parent 79be1d8 commit 276beac
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 91 deletions.
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ linter:
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
analyzer:
exclude: [ build/** ]
exclude: [ build/**, pub_cache/** ]
language:
#strict-casts: true
strict-raw-types: true
87 changes: 76 additions & 11 deletions lib/Backend/sensors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,26 +179,66 @@ abstract class TriggerDefinition extends ChangeNotifier implements Comparable<Tr
actions.values.flattened.where((e) => actionTypes.firstWhere((element) => element.name == name).uuid == e.uuid).forEach(
(TriggerAction triggerAction) async {
if (triggerAction.isActive.value) {
// 15 second cooldown between moves
// 15 second cool-down between moves
return;
}
if (triggerAction.actions.isNotEmpty) {
String action = triggerAction.actions[_random.nextInt(triggerAction.actions.length)];
BaseAction? baseAction = ref.read(getActionFromUUIDProvider(action));
// This shouldn't be the case but it can happen with custom actions
if (baseAction == null) {
return;
}
triggerAction.isActive.value = true;
List<BaseAction> actionsToRun = [baseAction];
Set<DeviceType> flattenedDeviceTypes = deviceTypes.values.flattened.toSet();
Map<String, BaseStatefulDevice> knownDevices = ref.read(knownDevicesProvider);
List<BaseStatefulDevice> devices = knownDevices.values.where((BaseStatefulDevice element) => deviceTypes.values.flattened.toSet().contains(element.baseDeviceDefinition.deviceType)).where((element) => element.deviceState.value == DeviceState.standby).toList();
for (BaseStatefulDevice baseStatefulDevice in List.of(devices)..shuffle()) {
if (SentryHive.box(settings).get(kitsuneModeToggle, defaultValue: kitsuneModeDefault)) {
await Future.delayed(Duration(milliseconds: Random().nextInt(kitsuneDelayRange)));

if (!baseAction.deviceCategory.toSet().containsAll(flattenedDeviceTypes)) {
// find the missing device type
// The goal here is if a user selects multiple moves, send a move to all gear
Set<DeviceType> missingGearAction = baseAction.deviceCategory.toSet().difference(baseAction.deviceCategory.toSet());
List<BaseAction> remainingActions = triggerAction.actions
.map((element) => ref.read(getActionFromUUIDProvider(action)))
.where(
// filter out missing actions
(element) => element != null,
)
.map(
// mark remaining not null
(e) => e!,
)
.where(
// Check if any actions contain the device type of the gear the first action is missing
(element) => element.deviceCategory.toSet().intersection(missingGearAction).isNotEmpty,
)
.toList();
if (remainingActions.isNotEmpty) {
BaseAction otherAction = remainingActions[_random.nextInt(remainingActions.length)];
actionsToRun.add(otherAction);
}
}
triggerAction.isActive.value = true;
List<BaseStatefulDevice> devices = knownDevices.values.where((BaseStatefulDevice element) => flattenedDeviceTypes.contains(element.baseDeviceDefinition.deviceType)).where((element) => element.deviceState.value == DeviceState.standby).toList();

Set<DeviceType> sentDeviceTypes = {};
for (BaseAction baseAction in actionsToRun) {
for (BaseStatefulDevice baseStatefulDevice in List.of(devices)
.where(
(element) => baseAction.deviceCategory.contains(element.baseDeviceDefinition.deviceType),
)
.where(
// support sending to next device type if 2 actions+ actions are set
(element) => !sentDeviceTypes.contains(element.baseDeviceDefinition.deviceType),
)
.toList()
..shuffle()) {
if (SentryHive.box(settings).get(kitsuneModeToggle, defaultValue: kitsuneModeDefault)) {
await Future.delayed(Duration(milliseconds: Random().nextInt(kitsuneDelayRange)));
}
runAction(baseAction, baseStatefulDevice);
sentDeviceTypes.add(baseStatefulDevice.baseDeviceDefinition.deviceType);
}
runAction(baseAction, baseStatefulDevice);
}
await Future.delayed(const Duration(seconds: 15));
triggerAction.isActive.value = false;
}
},
);
Expand Down Expand Up @@ -598,13 +638,38 @@ class TriggerActionDef {

@HiveType(typeId: 8)
class TriggerAction {
Timer? _timer;
Timer? _periodicTimer;
@HiveField(1)
String uuid; //uuid matches triggerActionDef
@HiveField(2)
List<String> actions = [];
ValueNotifier<bool> isActive = ValueNotifier(false);

TriggerAction(this.uuid);
ValueNotifier<double> isActiveProgress = ValueNotifier(0);

TriggerAction(this.uuid) {
isActive.addListener(
() {
if (isActive.value) {
_timer = Timer(
const Duration(seconds: 15),
() {
isActive.value = false;
_periodicTimer?.cancel();
},
);
_periodicTimer = Timer.periodic(
const Duration(milliseconds: 500),
(Timer timer) {
timer.tick;
double change = timer.tick / 30;
isActiveProgress.value = change;
},
);
}
},
);
}

@override
bool operator ==(Object other) => identical(this, other) || other is TriggerAction && runtimeType == other.runtimeType && uuid == other.uuid;
Expand Down
152 changes: 73 additions & 79 deletions lib/Frontend/pages/triggers.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:multi_value_listenable_builder/multi_value_listenable_builder.dart';
Expand Down Expand Up @@ -38,11 +39,11 @@ class _TriggersState extends ConsumerState<Triggers> {
return const TriggerSelect();
},
).then(
(TriggerDefinition? value) {
(TriggerDefinition? value) {
if (value != null) {
// The user selected a Trigger Definition
setState(
() {
() {
Trigger trigger = Trigger.trigDef(value, const Uuid().v4());
ref.watch(triggerListProvider.notifier).add(trigger);
plausible.event(name: "Add Trigger", props: {"Trigger Type": value.runtimeType.toString()});
Expand Down Expand Up @@ -91,7 +92,14 @@ class _TriggersState extends ConsumerState<Triggers> {
builder: (BuildContext context, List<dynamic> values, Widget? child) {
return AnimatedCrossFade(
firstChild: Text(triggersList[index].triggerDefinition!.description),
secondChild: const LinearProgressIndicator(),
secondChild: MultiValueListenableBuilder(
valueListenables: triggersList[index].actions.map((e) => e.isActiveProgress).toList(),
builder: (context, values, child) {
return LinearProgressIndicator(
value: values.firstWhereOrNull((element) => element < 1 && element > 0),
);
},
),
crossFadeState: !values.any((element) => element == true) ? CrossFadeState.showFirst : CrossFadeState.showSecond,
duration: animationTransitionDuration,
);
Expand All @@ -104,7 +112,7 @@ class _TriggersState extends ConsumerState<Triggers> {
value: triggersList[index].enabled,
onChanged: (bool value) {
setState(
() {
() {
triggersList[index].enabled = value;
ref.watch(triggerListProvider.notifier).store();
},
Expand All @@ -123,7 +131,6 @@ class _TriggersState extends ConsumerState<Triggers> {
}
}


class TriggerEdit extends ConsumerStatefulWidget {
final ScrollController scrollController;
final Trigger trigger;
Expand Down Expand Up @@ -168,13 +175,9 @@ class _TriggerEditState extends ConsumerState<TriggerEdit> {
value: widget.trigger.enabled,
onChanged: (bool value) {
setState(
() {
() {
widget.trigger.enabled = value;
plausible.event(name: "Enable Trigger", props: {"Trigger Type": ref
.watch(triggerDefinitionListProvider)
.where((element) => element.uuid == widget.trigger.triggerDefUUID)
.first
.toString()});
plausible.event(name: "Enable Trigger", props: {"Trigger Type": ref.watch(triggerDefinitionListProvider).where((element) => element.uuid == widget.trigger.triggerDefUUID).first.toString()});
},
);
},
Expand All @@ -186,7 +189,7 @@ class _TriggerEditState extends ConsumerState<TriggerEdit> {
selected: widget.trigger.deviceType,
onSelectionChanged: (Set<DeviceType> value) {
setState(
() {
() {
widget.trigger.deviceType = value.toList();
ref.watch(triggerListProvider.notifier).store();
},
Expand All @@ -197,83 +200,74 @@ class _TriggerEditState extends ConsumerState<TriggerEdit> {
text: triggerInfoEditActionDescription(),
),
...widget.trigger.actions.map(
(TriggerAction e) =>
ListTile(
title: Text(widget.trigger.triggerDefinition!
.actionTypes
.where((element) => e.uuid == element.uuid)
.first
.translated),
subtitle: ValueListenableBuilder(
valueListenable: e.isActive,
builder: (BuildContext context, value, Widget? child) {
return AnimatedCrossFade(
duration: animationTransitionDuration,
secondChild: const LinearProgressIndicator(),
firstChild: Builder(builder: (context) {
String text = "";
for (String actionUUID in e.actions) {
BaseAction? baseAction = ref.watch(getActionFromUUIDProvider(actionUUID));
if (baseAction != null) {
if (text.isNotEmpty) {
text += ', ';
}
text += baseAction.name;
}
(TriggerAction e) => ListTile(
title: Text(widget.trigger.triggerDefinition!.actionTypes.where((element) => e.uuid == element.uuid).first.translated),
subtitle: ValueListenableBuilder(
valueListenable: e.isActive,
builder: (BuildContext context, value, Widget? child) {
return AnimatedCrossFade(
duration: animationTransitionDuration,
secondChild: const LinearProgressIndicator(),
firstChild: Builder(builder: (context) {
String text = "";
for (String actionUUID in e.actions) {
BaseAction? baseAction = ref.watch(getActionFromUUIDProvider(actionUUID));
if (baseAction != null) {
if (text.isNotEmpty) {
text += ', ';
}
return Text(text.isNotEmpty ? text : triggerActionNotSet());
}),
crossFadeState: !value ? CrossFadeState.showFirst : CrossFadeState.showSecond,
);
},
),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () async {
Object? result = await showDialog(
useRootNavigator: true,
barrierDismissible: true,
barrierColor: Theme
.of(context)
.canvasColor,
context: context,
builder: (BuildContext context) {
return Dialog.fullscreen(
backgroundColor: Theme
.of(context)
.canvasColor,
child: ActionSelector(
actionSelectorInfo: ActionSelectorInfo(deviceType: widget.trigger.deviceType.toSet(), selectedActions: []),
));
},
);
if (result is List<BaseAction>) {
setState(
() {
e.actions = result.map((element) => element.uuid).toList();
ref.watch(triggerListProvider.notifier).store();
},
);
} else if (result is bool) {
if (!result) {
setState(
() {
e.actions = [];
ref.watch(triggerListProvider.notifier).store();
},
);
text += baseAction.name;
}
}
return Text(text.isNotEmpty ? text : triggerActionNotSet());
}),
crossFadeState: !value ? CrossFadeState.showFirst : CrossFadeState.showSecond,
);
},
),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () async {
Object? result = await showDialog(
useRootNavigator: true,
barrierDismissible: true,
barrierColor: Theme.of(context).canvasColor,
context: context,
builder: (BuildContext context) {
return Dialog.fullscreen(
backgroundColor: Theme.of(context).canvasColor,
child: ActionSelector(
actionSelectorInfo: ActionSelectorInfo(deviceType: widget.trigger.deviceType.toSet(), selectedActions: []),
));
},
),
),
);
if (result is List<BaseAction>) {
setState(
() {
e.actions = result.map((element) => element.uuid).toList();
ref.watch(triggerListProvider.notifier).store();
},
);
} else if (result is bool) {
if (!result) {
setState(
() {
e.actions = [];
ref.watch(triggerListProvider.notifier).store();
},
);
}
}
},
),
),
),
ButtonBar(
children: [
TextButton(
onPressed: () {
setState(
() {
() {
ref.watch(triggerListProvider).remove(widget.trigger);
ref.watch(triggerListProvider.notifier).store();
Navigator.of(context).pop();
Expand Down

0 comments on commit 276beac

Please sign in to comment.