Skip to content

Commit

Permalink
feat(ColorTemperature): ColorTemperature dynamic widget (DX-362) (#44)
Browse files Browse the repository at this point in the history
* ci: generate dartdocs and push new PR after merging to main

* ci(config.yml): fixes to dartdoc generation + pr creation steps

* feat(color_temperature_icon): icon to represent color temperature trait

* feat(color_temperature): introduce ColorTemperatureWidget

* style(misc): styling, trailing commas, etc.

* feat(color_temperature_widget): add min and max color temperature range
to provider and widget

* feat(color_temp_slim_widget): introduce color temperature slim widget

* feat(deviceWidgetFactory): have deviceWidgetFactory build colorTemperatureWidgets

* test(color_temp_widget): fix test name descriptions

* test(color_temp_widget_test): fix typo

* refactor(color_temp_widget): fix setting up min & max on slim widget

* build(pubspec.yaml): temporarily point to git PR reference

* refactor(color_temp_icon): create default icon field

* test(detail_screen_test): include test case for ColorTemperature

* test(color_temperature_provider): improve test coverage

* fix(providers): fix compilation issues to get State values with null-safe operator

* ci(config.yml): print flutter version

* build(pubspec.yaml): Update to use published version of dart sdk

Co-authored-by: Rigoberto L. Perez <rigoberto.perez@allegion.com>
  • Loading branch information
ses110 and rlperez committed Apr 26, 2022
1 parent 0dfebf3 commit ae07596
Show file tree
Hide file tree
Showing 20 changed files with 1,043 additions and 58 deletions.
28 changes: 20 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ version: 2.1
# Orb declarations
orbs:
codecov: codecov/codecov@1.2.5

gh: circleci/github-cli@2.1.0

anchors:
- &main_only
filters:
Expand Down Expand Up @@ -95,7 +96,9 @@ commands:
- v1.0-dependencies-{{ arch }}-
- run:
name: Download deps
command: pub get
command: |
flutter --version
flutter pub get
- run:
name: Get junitreporter
command: |
Expand Down Expand Up @@ -140,23 +143,32 @@ jobs:
steps:
- dependencies
- run-publish
generate_documentation:
generate-documentation:
executor: default-executor
steps:
- dependencies
- generate-dartdoc
- gh/setup
- setup-github
- run:
name: Check if changes made to documentation were not checked in
command: |
if [[ `git status doc/ --porcelain` ]];
then
echo "New documents detected. Checking in new docs."
BRANCH_NAME="$(git branch --show-current)"
git checkout -b dartdoc_$BRANCHNAME
git add doc/
git commit -m "docs: updated dartdocs"
REMOTE_URL="$( echo << pipeline.project.git_url >> | sed -e 's#^https://##; s#/score/$##' )"
git remote set-url origin "https://$REMOTE_URL.git"
git push -u origin HEAD
gh pr create -f <(echo "
title: Updated dartdocs
body: New generated dartdocs after merging `$BRANCH_NAME`
assignees:
- Yonomi/dx
")
else
echo "Nothing to check in. No new docs generated"
fi
Expand All @@ -170,15 +182,15 @@ workflows:
jobs:
- test:
<<: *feature-branch-filter
- generate_documentation:
context:
- org-global
requires:
- test
main:
jobs:
- test:
<<: *main_only
- generate-documentation:
context:
- org-global
requires:
- test
- semantic-versioning:
context:
- org-global
Expand Down
13 changes: 13 additions & 0 deletions lib/assets/traits/color_temperature_icon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/material.dart';
import 'package:yonomi_device_widgets/ui/widget_style_constants.dart';

class ColorTemperatureIcon extends Icon {
static const DEFAULT_ICON = BootstrapIcons.lightbulb;

ColorTemperatureIcon(int colorTemperature,
{size = WidgetStyleConstants.defaultDeviceIconSize,
color = WidgetStyleConstants.deviceIconColor,
Key? key})
: super(DEFAULT_ICON, key: key, size: size, color: color);
}
7 changes: 6 additions & 1 deletion lib/assets/traits/device_item_icon.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:yonomi_device_widgets/assets/traits/battery_level_icon.dart';
import 'package:yonomi_device_widgets/assets/traits/color_temperature_icon.dart';
import 'package:yonomi_device_widgets/assets/traits/lock_item_icon.dart';
import 'package:yonomi_device_widgets/assets/traits/power_item_icon.dart';
import 'package:yonomi_device_widgets/assets/traits/battery_level_icon.dart';
import 'package:yonomi_device_widgets/assets/traits/thermostat_icon.dart';
import 'package:yonomi_device_widgets/assets/traits/unknown_item_icon.dart';
import 'package:yonomi_device_widgets/ui/widget_style_constants.dart';
Expand All @@ -26,6 +27,10 @@ class DeviceItemIcon {
case sdk.BatteryLevelTrait:
return BatteryLevelIcon(findIconStateValue<sdk.BatteryLevel, int>(
determiningTrait.states));
case sdk.ColorTemperatureTrait:
return ColorTemperatureIcon(
findIconStateValue<sdk.ColorTemperature, int>(
determiningTrait.states));
default:
return UnknownItemIcon();
}
Expand Down
52 changes: 52 additions & 0 deletions lib/providers/color_temperature_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'package:yonomi_device_widgets/providers/device_provider.dart';
import 'package:yonomi_platform_sdk/yonomi-sdk.dart';

typedef SetColorTemperatureFunction = Future<void> Function(
Request request, String id, int colorTemperature);

class ColorTemperatureProvider extends DeviceProvider {
static const _DEFAULT_DISPLAY_NAME = 'COLOR TEMPERATURE';

late final String _deviceId;
late final Request _request;

ColorTemperatureProvider(Request request, String deviceId,
{GetDeviceDetailsMethod getDetails = DevicesRepository.getDeviceDetails})
: super(request, deviceId, getDetails: getDetails) {
this._deviceId = deviceId;
this._request = request;
}

ColorTemperatureTrait? getColorTemperatureTrait() {
return trait<ColorTemperatureTrait>() as ColorTemperatureTrait?;
}

int? get getColorTemperatureState =>
getColorTemperatureTrait()?.stateWhereType<ColorTemperature>()?.value
as int?;

int? get getMinColorTemperature => getColorTemperatureTrait()
?.propertyWhereType<SupportedColorTemperatureRange>()
.value
.min as int?;

int? get getMaxColorTemperature => getColorTemperatureTrait()
?.propertyWhereType<SupportedColorTemperatureRange>()
.value
.max as int?;

Future<void> setColorTemperatureAction(int colorTemperature,
{GetDeviceDetailsMethod getDetails = DevicesRepository.getDeviceDetails,
SetColorTemperatureFunction setColorTemperatureFunction =
ColorTemperatureRepository.setColorTemperatureAction}) {
return performAction<int>(
colorTemperature,
() => getColorTemperatureState,
() => setColorTemperatureFunction(_request, _deviceId, colorTemperature),
getDetails: getDetails,
);
}

@override
String get displayName => deviceDetail?.displayName ?? _DEFAULT_DISPLAY_NAME;
}
10 changes: 5 additions & 5 deletions lib/providers/device_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,18 @@ abstract class DeviceProvider extends ChangeNotifier {

String get displayName;

/// To know if this ChangeNotifier is fetching device data
/// ChangeNotifier is fetching device data
bool get isLoading => _state == WidgetState.loading;

/// To know if this ChangeNotifier is performing an action
/// ChangeNotifier is performing an action
bool get isPerformingAction => _state == WidgetState.performingAction;

/// To know if this ChangeNotifier is busy from fetching data or running an action
/// ChangeNotifier is busy from fetching data or running an action
bool get isBusy =>
(_state == WidgetState.loading || _state == WidgetState.performingAction);

/// To know if this ChangeNotifier had an error
/// see [getErrorMessage] to get the accomponying error message
/// ChangeNotifier had an error
/// see [getErrorMessage] to get the accompanying error message
bool get isInErrorState => _state == WidgetState.error;

/// Get the error message whenever this ChangeNotifier had an error
Expand Down
4 changes: 2 additions & 2 deletions lib/providers/thermostat_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ class ThermostatProvider extends DeviceProvider {
getThermostatTrait()?.stateWhereType<AmbientTemperature>()?.value;

TemperatureRange? get getCoolTemperatureRange =>
getThermostatTrait()?.propertyWhereType<CoolSetPointRange>().value;
getThermostatTrait()?.propertyWhereType<CoolSetPointRange>()?.value;

TemperatureRange? get getHeatTemperatureRange =>
getThermostatTrait()?.propertyWhereType<HeatSetPointRange>().value;
getThermostatTrait()?.propertyWhereType<HeatSetPointRange>()?.value;

AvailableFanMode get getFanModeState {
return getThermostatTrait()?.stateWhereType<FanMode>()?.value;
Expand Down
135 changes: 135 additions & 0 deletions lib/traits/color_temperature_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/material.dart';
import 'package:yonomi_device_widgets/mixins/toast_notifications.dart';
import 'package:yonomi_device_widgets/providers/color_temperature_provider.dart';
import 'package:yonomi_device_widgets/ui/widget_style_constants.dart';

class ColorTemperatureWidget extends StatefulWidget {
final ColorTemperatureProvider _colorTemperatureProvider;
late final Color _iconColor;
late final Color _textColor;
late final double _iconSize;

static const String label = 'Color Temp.';

ColorTemperatureWidget(this._colorTemperatureProvider,
{Color iconColor = WidgetStyleConstants.deviceDetailIconColorActive,
Color textColor = WidgetStyleConstants.darkTextColor,
double iconSize = 100,
Key? key})
: super(key: key) {
this._iconColor = iconColor;
this._textColor = textColor;
this._iconSize = iconSize;
}

@override
State<StatefulWidget> createState() => _ColorTemperatureWidgetState();
}

class _ColorTemperatureWidgetState extends State<ColorTemperatureWidget>
with ToastNotifications {
int? _value;

static const double DEFAULT_COLOR_TEMP = 2500;
static const double DEFAULT_MIN_COLOR_TEMP = 1000;
static const double DEFAULT_MAX_COLOR_TEMP = 7000;

@override
void initState() {
super.initState();
_value = widget._colorTemperatureProvider.getColorTemperatureState;
}

@override
Widget build(BuildContext context) {
if (widget._colorTemperatureProvider.isLoading) {
return CircularProgressIndicator();
} else if (widget._colorTemperatureProvider.isInErrorState) {
showToast(context, widget._colorTemperatureProvider.getErrorMessage);
return Icon(
Icons.error,
color: WidgetStyleConstants.globalWarningColor,
);
} else {
final colorTemperature = _colorTemperatureValue();
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Text(
ColorTemperatureWidget.label.toUpperCase(),
style: Theme.of(context)
.textTheme
.headline6
?.copyWith(color: widget._textColor),
),
],
),
SizedBox(height: 10),
Container(
child: Center(
child: SizedBox(
width: widget._iconSize,
height: widget._iconSize,
child: widget._colorTemperatureProvider.isPerformingAction
? CircularProgressIndicator()
: Icon(
BootstrapIcons.sun,
color: widget._iconColor,
size: widget._iconSize,
),
)),
),
SizedBox(height: 10),
Row(children: [
Icon(BootstrapIcons.sun, color: widget._iconColor),
Expanded(
child: Slider(
label: ColorTemperatureWidget.label,
value: colorTemperature?.toDouble() ?? DEFAULT_COLOR_TEMP,
min: widget._colorTemperatureProvider.getMinColorTemperature
?.toDouble() ??
DEFAULT_MIN_COLOR_TEMP,
max: widget._colorTemperatureProvider.getMaxColorTemperature
?.toDouble() ??
DEFAULT_MAX_COLOR_TEMP,
divisions: 100,
activeColor: WidgetStyleConstants.globalSuccessColor,
// When onChanged is null it makes the slider disabled
onChanged: (colorTemperature == null)
? null
: (double value) {
setState(() => _value = value.toInt());
}, // Required
onChangeEnd: (double value) {
// Only send the update when user releases slider
setState(() => _value = value.toInt());
widget._colorTemperatureProvider
.setColorTemperatureAction(value.round());
},
),
),
Text(
'${colorTemperature ?? "--"}',
style: Theme.of(context)
.textTheme
.headline6
?.copyWith(color: widget._textColor),
)
])
]);
}
}

int? _colorTemperatureValue() {
if (_value == null) {
setState(() {
_value = widget._colorTemperatureProvider.getColorTemperatureState;
super.reassemble();
});
}
return _value;
}
}
10 changes: 8 additions & 2 deletions lib/traits/detail_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:yonomi_device_widgets/providers/battery_level_provider.dart';
import 'package:yonomi_device_widgets/providers/brightness_provider.dart';
import 'package:yonomi_device_widgets/providers/color_provider.dart';
import 'package:yonomi_device_widgets/providers/color_temperature_provider.dart';
import 'package:yonomi_device_widgets/providers/lock_provider.dart';
import 'package:yonomi_device_widgets/providers/power_trait_provider.dart';
import 'package:yonomi_device_widgets/providers/thermostat_provider.dart';
Expand Down Expand Up @@ -35,6 +36,8 @@ class DetailScreen extends StatelessWidget {
create: (context) => BrightnessProvider(request, deviceId)),
ChangeNotifierProvider<ColorProvider>(
create: (context) => ColorProvider(request, deviceId)),
ChangeNotifierProvider<ColorTemperatureProvider>(
create: (context) => ColorTemperatureProvider(request, deviceId)),
],
child: DetailScreenWidget(request, deviceId),
);
Expand Down Expand Up @@ -70,7 +73,10 @@ class DetailScreenWidget extends StatelessWidget {
.build();

return SingleChildScrollView(
child: Container(
alignment: Alignment.center, child: Center(child: deviceWidget)));
child: Container(
alignment: Alignment.center,
child: Center(child: deviceWidget),
),
);
}
}
Loading

0 comments on commit ae07596

Please sign in to comment.