Skip to content

Commit

Permalink
Adjust triggers UX
Browse files Browse the repository at this point in the history
  • Loading branch information
Codel1417 committed Jun 5, 2024
1 parent 1d332cf commit 48da787
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 68 deletions.
165 changes: 100 additions & 65 deletions lib/Frontend/pages/action_selector.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart';
import 'package:tail_app/Frontend/Widgets/tutorial_card.dart';
import 'package:tail_app/Frontend/utils.dart';
import 'package:tail_app/constants.dart';

Expand Down Expand Up @@ -40,73 +43,105 @@ class _ActionSelectorState extends ConsumerState<ActionSelector> {

@override
Widget build(BuildContext context) {
Set<DeviceType> knownDeviceTypes = ref
.watch(knownDevicesProvider)
.values
.map(
(e) => e.baseDeviceDefinition.deviceType,
)
.toSet();
return Scaffold(
primary: true,
appBar: AppBar(
title: Text(actionsSelectScreen()),
actions: [
IconButton(
onPressed: () {
if (selected.isEmpty) {
context.pop(true);
} else {
context.pop(selected);
}
},
icon: const Icon(Icons.save),
tooltip: triggersSelectSaveLabel(),
),
IconButton(
onPressed: () {
setState(() {
selected.clear();
});
},
icon: const Icon(Icons.clear),
tooltip: triggersSelectClearLabel(),
)
],
),
body: ListView.builder(
primary: true,
itemCount: catList.length,
itemBuilder: (BuildContext context, int categoryIndex) {
List<BaseAction> actionsForCat = actionsCatMap.values.toList()[categoryIndex].toList();
return Column(
children: [
Center(
child: Text(
catList[categoryIndex].friendly,
style: Theme.of(context).textTheme.titleLarge,
),
),
GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 125),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: actionsForCat.length,
itemBuilder: (BuildContext context, int actionIndex) {
BaseAction baseAction = actionsForCat[actionIndex];
bool isSelected = selected.contains(baseAction);
return TweenAnimationBuilder(
builder: (context, value, child) {
Color? color = Color.lerp(Theme.of(context).cardColor, Theme.of(context).colorScheme.primary, value);
return Card(
clipBehavior: Clip.antiAlias,
elevation: 2,
color: color,
child: cardChild(isSelected, baseAction, color!),
);
},
tween: isSelected ? Tween<double>(begin: 0, end: 1) : Tween<double>(begin: 1, end: 0),
duration: animationTransitionDuration,
);
})
],
);
},
),
);
appBar: AppBar(
title: Text(actionsSelectScreen()),
actions: [
IconButton(
onPressed: () {
setState(() {
selected = actionsCatMap.values.flattened.toList();
});
},
icon: const Icon(Icons.select_all),
tooltip: triggersSelectAllLabel(),
),
IconButton(
onPressed: () {
setState(() {
selected.clear();
});
},
icon: const Icon(Icons.deselect),
tooltip: triggersSelectClearLabel(),
)
],
),
extendBody: true,
bottomNavigationBar: ButtonBar(
alignment: MainAxisAlignment.center,
children: [
FilledButton(
onPressed: () {
setState(() {
if (selected.isEmpty) {
context.pop(true);
} else {
context.pop(selected);
}
});
},
child: Text(triggersSelectSaveLabel()),
)
],
),
body: ListView(
primary: true,
children: [
PageInfoCard(text: triggerActionSelectorTutorialLabel()),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: catList.length,
itemBuilder: (BuildContext context, int categoryIndex) {
List<BaseAction> actionsForCat = actionsCatMap.values.toList()[categoryIndex].toList();
bool hasConnectedDevice = actionsForCat.map((e) => e.deviceCategory).flattened.toSet().intersection(knownDeviceTypes).isNotEmpty;
return Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
initiallyExpanded: hasConnectedDevice,
title: Text(
catList[categoryIndex].friendly,
style: Theme.of(context).textTheme.titleLarge,
),
children: [
GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 125),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: actionsForCat.length,
itemBuilder: (BuildContext context, int actionIndex) {
BaseAction baseAction = actionsForCat[actionIndex];
bool isSelected = selected.contains(baseAction);
return TweenAnimationBuilder(
builder: (context, value, child) {
Color? color = Color.lerp(Theme.of(context).colorScheme.primary, Theme.of(context).cardColor, value);
return Card(
clipBehavior: Clip.antiAlias,
elevation: 2,
color: color,
child: cardChild(isSelected, baseAction, color!),
);
},
tween: isSelected ? Tween<double>(begin: 1, end: 0) : Tween<double>(begin: 0, end: 1),
duration: animationTransitionDuration,
);
})
],
),
);
},
),
],
));
}

InkWell cardChild(bool isSelected, BaseAction baseAction, Color color) {
Expand Down
20 changes: 18 additions & 2 deletions lib/Frontend/pages/triggers.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:choice/choice.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';
import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart';
import 'package:tail_app/Backend/Definitions/Action/base_action.dart';
import 'package:tail_app/Backend/Definitions/Device/device_definition.dart';
import 'package:tail_app/Backend/sensors.dart';
Expand Down Expand Up @@ -267,9 +269,16 @@ class _TriggerEditState extends ConsumerState<TriggerEdit> {
),
firstChild: Builder(builder: (context) {
String text = "";
Iterable<BaseStatefulDevice> knownDevices = ref.read(knownDevicesProvider).values;
for (String actionUUID in e.actions) {
BaseAction? baseAction = ref.watch(getActionFromUUIDProvider(actionUUID));
if (baseAction != null) {
if (baseAction != null &&
(knownDevices.isEmpty ||
knownDevices
.where(
(element) => baseAction.deviceCategory.contains(element.baseDeviceDefinition.deviceType),
)
.isNotEmpty)) {
if (text.isNotEmpty) {
text += ', ';
}
Expand All @@ -294,7 +303,14 @@ class _TriggerEditState extends ConsumerState<TriggerEdit> {
return Dialog.fullscreen(
backgroundColor: Theme.of(context).canvasColor,
child: ActionSelector(
actionSelectorInfo: ActionSelectorInfo(deviceType: widget.trigger.deviceType.toSet(), selectedActions: []),
actionSelectorInfo: ActionSelectorInfo(
deviceType: widget.trigger.deviceType.toSet(),
selectedActions: e.actions
.map(
(e) => ref.read(getActionFromUUIDProvider(e)),
)
.whereNotNull()
.toList()),
));
},
);
Expand Down
7 changes: 6 additions & 1 deletion lib/Frontend/translation_string_definitions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ String sequencesPageDescription() => Intl.message('Create custom Actions for you
// Triggers Page
String triggersSelectLabel() => Intl.message('Select a Trigger Type', name: 'triggersSelectLabel', desc: 'The title of the add trigger dialog');

String triggersSelectClearLabel() => Intl.message('Remove Actions', name: 'triggersSelectClearLabel', desc: 'The button label on the trigger select screen for clearing the selected actions');
String triggersSelectClearLabel() => Intl.message('Select None', name: 'triggersSelectClearLabel', desc: 'The button label on the trigger select screen for clearing the selected actions');

String triggersSelectAllLabel() => Intl.message('Select All', name: 'triggersSelectAllLabel', desc: 'The button label on the trigger select screen for clearing the selected actions');

String triggersSelectSaveLabel() => Intl.message('Save Actions', name: 'triggersSelectSaveLabel', desc: 'The button label on the trigger select screen for saving the selected actions');

Expand Down Expand Up @@ -331,3 +333,6 @@ String scanRemoveDemoGear() => Intl.message("Remove all fake gear", name: 'scanR

String scanDemoGearTip() => Intl.message("Want to try out the app but are waiting for your gear to arrive? Add a fake gear. This lets you experience the app as if you had your gear, or if you want to try out gear you currently do not own. This enables a new section on the 'Scan For New Gear' page.",
name: 'scanDemoGearTip', desc: 'Tip Card description for the demo gear on the scan for new devices page');

String triggerActionSelectorTutorialLabel() => Intl.message("Select as many actions as you want. An action will be randomly selected that is compatible with connected gear. GlowTip and Sound actions will trigger alongside Move actions.",
name: 'triggerActionSelectorTutorialLabel', desc: 'Label for the tutorial card on the Action selector for triggers');

0 comments on commit 48da787

Please sign in to comment.