Skip to content

Commit

Permalink
feat: Add cursory display of target temperature
Browse files Browse the repository at this point in the history
  • Loading branch information
rlperez committed Jan 20, 2022
1 parent 5f6acf2 commit 89ddaf2
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 6 deletions.
2 changes: 1 addition & 1 deletion lib/components/thermostat_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ThermostatWidget extends StatelessWidget {
Center(
child: Arc(
centerWidget: Text(
thermostatProvider.thermostatTargetTemperature.toInt().toString(),
thermostatProvider.targetTemperature.toInt().toString(),
style: Theme.of(context).textTheme.headline2,
),
initialValue: 20.0,
Expand Down
3 changes: 3 additions & 0 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/lock_provider.dart';
import 'package:yonomi_device_widgets/providers/power_trait_provider.dart';
import 'package:yonomi_device_widgets/providers/thermostat_provider.dart';
import 'package:yonomi_device_widgets/providers/trait_detail_provider.dart';
import 'package:yonomi_device_widgets/traits/device_widget_builder.dart';
import 'package:yonomi_platform_sdk/yonomi-sdk.dart';
Expand All @@ -26,6 +27,8 @@ class DetailScreen extends StatelessWidget {
create: (context) => PowerTraitProvider(request, deviceId)),
ChangeNotifierProvider<BatteryLevelProvider>(
create: (context) => BatteryLevelProvider(request, deviceId)),
ChangeNotifierProvider<ThermostatProvider>(
create: (context) => ThermostatProvider(request, deviceId)),
],
child: DetailScreenWidget(request, deviceId),
);
Expand Down
7 changes: 7 additions & 0 deletions lib/traits/device_widget_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:yonomi_device_widgets/providers/battery_level_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';
import 'package:yonomi_device_widgets/traits/device_widget_factory.dart';
import 'package:yonomi_device_widgets/ui/widget_style_constants.dart';
import 'package:yonomi_platform_sdk/yonomi-sdk.dart';
Expand Down Expand Up @@ -66,6 +67,9 @@ class DeviceWidgetBuilder {
case BatteryLevelTrait:
return DeviceWidgetFactory.produceWidget<BatteryLevelProvider>(
iconColor: iconColor, textColor: textColor);
case ThermostatTrait:
return DeviceWidgetFactory.produceWidget<ThermostatProvider>(
iconColor: iconColor, textColor: textColor);
default:
return DeviceWidgetFactory.produceWidget(iconColor: iconColor);
}
Expand All @@ -82,6 +86,9 @@ class DeviceWidgetBuilder {
case BatteryLevelTrait:
return DeviceWidgetFactory.produceSlimWidget<BatteryLevelProvider>(
backgroundColor: backgroundColor);
case ThermostatTrait:
return DeviceWidgetFactory.produceSlimWidget<ThermostatProvider>(
backgroundColor: backgroundColor);
default:
return DeviceWidgetFactory.produceSlimWidget(
name: trait.name, backgroundColor: backgroundColor);
Expand Down
18 changes: 18 additions & 0 deletions lib/traits/device_widget_factory.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:yonomi_device_widgets/providers/battery_level_provider.dart';
import 'package:yonomi_device_widgets/providers/device_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';
import 'package:yonomi_device_widgets/traits/battery_widget.dart';
import 'package:yonomi_device_widgets/traits/lock_widget.dart';
import 'package:yonomi_device_widgets/traits/power_widget.dart';
import 'package:yonomi_device_widgets/traits/slim/base_slim_widget.dart';
import 'package:yonomi_device_widgets/traits/slim/battery_slim_widget.dart';
import 'package:yonomi_device_widgets/traits/slim/lock_slim_widget.dart';
import 'package:yonomi_device_widgets/traits/slim/power_slim_widget.dart';
Expand Down Expand Up @@ -36,6 +39,12 @@ class DeviceWidgetFactory<T> {
return BatteryWidget(batteryLevelProvider,
iconColor: iconColor, textColor: textColor);
});
case ThermostatProvider:
return Consumer<ThermostatProvider>(
builder: (_, thermostatProvider, child) {
return Text('${thermostatProvider.targetTemperature.toInt()}');
},
);
default:
return UnknownWidget(name: name, iconColor: iconColor);
}
Expand Down Expand Up @@ -65,6 +74,15 @@ class DeviceWidgetFactory<T> {
iconColor: WidgetStyleConstants.deviceDetailIconColorActive,
));
});
case ThermostatProvider:
return Consumer<ThermostatProvider>(
builder: (_, thermostatProvider, child) {
return BaseSlimWidget(
leftIcon: Icon(BootstrapIcons.thermometer),
headerText: Text(
'Target Temperature: ${thermostatProvider.targetTemperature.toInt()}'));
},
);
default:
return UnknownSlimWidget(name ?? '', backgroundColor: backgroundColor);
}
Expand Down
38 changes: 33 additions & 5 deletions test/traits/detail_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:yonomi_device_widgets/assets/traits/unknown_item_icon.dart';
import 'package:yonomi_device_widgets/providers/battery_level_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';
import 'package:yonomi_device_widgets/providers/trait_detail_provider.dart';
import 'package:yonomi_device_widgets/traits/battery_widget.dart';
import 'package:yonomi_device_widgets/traits/detail_screen.dart';
Expand All @@ -18,18 +19,21 @@ import 'package:yonomi_device_widgets/traits/slim/power_slim_widget.dart';
import 'package:yonomi_device_widgets/ui/widget_style_constants.dart';
import 'package:yonomi_platform_sdk/yonomi-sdk.dart';

import '../components/modes_toolbar_test.mocks.dart';
import 'detail_screen_test.mocks.dart';
import 'mixins/battery_widget_testing.dart';
import 'mixins/device_testing.dart';
import 'mixins/lock_widget_testing.dart';
import 'mixins/power_widget_testing.dart';
import 'mixins/thermostat_widget_testing.dart';

class DetailScreenTest
with
DeviceTesting,
PowerWidgetTesting,
LockWidgetTesting,
BatteryWidgetTesting {
BatteryWidgetTesting,
ThermostatWidgetTesting {
Widget createDetailScreenWhenLoading(
Request req,
String deviceId,
Expand All @@ -43,7 +47,8 @@ class DetailScreenTest
mockTraitDetailProvider,
MockLockProvider(),
MockPowerTraitProvider(),
MockBatteryLevelProvider());
MockBatteryLevelProvider(),
MockThermostatProvider());
}

Widget createDetailScreenWidgetForTraits(
Expand Down Expand Up @@ -77,8 +82,18 @@ class DetailScreenTest
.mockBatteryLevelProvider(batteryDevice,
batteryLevel: batteryLevelTrait.state.value as int);

return createMaterialApp(req, deviceId, mockTraitDetailProvider,
mockLockProvider, mockPowerTraitProvider, mockBatteryTraitProvider);
final thermostatTraits = traits.whereType<ThermostatTrait>().toList();
final thermostatDevice = device(thermostatTraits, name: 'THERMOSTAT');
ThermostatProvider mockThermostatProvider =
this.mockThermostatProvider(thermostatDevice);
return createMaterialApp(
req,
deviceId,
mockTraitDetailProvider,
mockLockProvider,
mockPowerTraitProvider,
mockBatteryTraitProvider,
mockThermostatProvider);
}

MaterialApp createMaterialApp(
Expand All @@ -87,7 +102,8 @@ class DetailScreenTest
TraitDetailProvider mockTraitBasedNotifier,
LockProvider mockLockProvider,
PowerTraitProvider mockPowerTraitProvider,
BatteryLevelProvider mockBatteryLevelProvider) {
BatteryLevelProvider mockBatteryLevelProvider,
ThermostatProvider mockThermostatProvider) {
return MaterialApp(
home: Column(children: [
MultiProvider(
Expand All @@ -99,6 +115,8 @@ class DetailScreenTest
value: mockPowerTraitProvider),
ChangeNotifierProvider<BatteryLevelProvider>.value(
value: mockBatteryLevelProvider),
ChangeNotifierProvider<ThermostatProvider>.value(
value: mockThermostatProvider),
],
child: DetailScreenWidget(req, deviceId),
),
Expand Down Expand Up @@ -175,6 +193,16 @@ void main() {
expect(find.byType(BatteryWidget), findsOneWidget);
});

testWidgets(
'For the Thermostat Trait, Detail screen should show the target temperature',
(WidgetTester tester) async {
final request = Request('', {});
await tester.pumpWidget(test.createDetailScreenWidgetForTraits(
[ThermostatTrait(TargetTemperature(100.0))], request, testedDeviceId));

expect(find.text('100'), findsOneWidget);
});

testWidgets(
'For any Unknown/Unsupported Trait, Detail screen should show the UnknownItemIcon widget',
(WidgetTester tester) async {
Expand Down
25 changes: 25 additions & 0 deletions test/traits/mixins/thermostat_widget_testing.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:yonomi_device_widgets/providers/thermostat_provider.dart';
import 'package:yonomi_platform_sdk/yonomi-sdk.dart';

import 'thermostat_widget_testing.mocks.dart';

@GenerateMocks([ThermostatProvider])
mixin ThermostatWidgetTesting {
MockThermostatProvider mockThermostatProvider(Device device) {
final mockThermostatProvider = MockThermostatProvider();
when(mockThermostatProvider.isLoading).thenReturn(false);
when(mockThermostatProvider.isInErrorState).thenReturn(false);
when(mockThermostatProvider.deviceDetail).thenReturn(device);
when(mockThermostatProvider.displayName).thenReturn('THERMOSTAT');
when(mockThermostatProvider.isPerformingAction).thenReturn(false);
when(mockThermostatProvider.targetTemperature).thenReturn(device.traits
.firstWhere((trait) => trait.state is TargetTemperature,
orElse: () => ThermostatTrait(TargetTemperature(70.0)))
.state
.value);

return mockThermostatProvider;
}
}
135 changes: 135 additions & 0 deletions test/traits/mixins/thermostat_widget_testing.mocks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Mocks generated by Mockito 5.0.15 from annotations
// in yonomi_device_widgets/test/traits/mixins/thermostat_widget_testing.dart.
// Do not manually edit this file.

import 'dart:async' as _i5;
import 'dart:ui' as _i10;

import 'package:mockito/mockito.dart' as _i1;
import 'package:yonomi_device_widgets/providers/device_provider.dart' as _i8;
import 'package:yonomi_device_widgets/providers/thermostat_provider.dart'
as _i2;
import 'package:yonomi_device_widgets/providers/widget_state.dart' as _i3;
import 'package:yonomi_platform_sdk/src/repository/devices/devices_repository.dart'
as _i9;
import 'package:yonomi_platform_sdk/src/repository/devices/thermostat_repository.dart'
as _i6;
import 'package:yonomi_platform_sdk/third_party/yonomi_graphql_schema/schema.docs.schema.gql.dart'
as _i7;
import 'package:yonomi_platform_sdk/yonomi-sdk.dart' as _i4;

// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis

/// A class which mocks [ThermostatProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockThermostatProvider extends _i1.Mock
implements _i2.ThermostatProvider {
MockThermostatProvider() {
_i1.throwOnMissingStub(this);
}

@override
double get targetTemperature => (super
.noSuchMethod(Invocation.getter(#targetTemperature), returnValue: 0.0)
as double);
@override
String get displayName =>
(super.noSuchMethod(Invocation.getter(#displayName), returnValue: '')
as String);
@override
set setState(_i3.WidgetState? newState) =>
super.noSuchMethod(Invocation.setter(#setState, newState),
returnValueForMissingStub: null);
@override
bool get isLoading =>
(super.noSuchMethod(Invocation.getter(#isLoading), returnValue: false)
as bool);
@override
bool get isPerformingAction =>
(super.noSuchMethod(Invocation.getter(#isPerformingAction),
returnValue: false) as bool);
@override
bool get isBusy =>
(super.noSuchMethod(Invocation.getter(#isBusy), returnValue: false)
as bool);
@override
bool get isInErrorState => (super
.noSuchMethod(Invocation.getter(#isInErrorState), returnValue: false)
as bool);
@override
String get getErrorMessage =>
(super.noSuchMethod(Invocation.getter(#getErrorMessage), returnValue: '')
as String);
@override
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
@override
List<_i4.ThermostatTrait> getThermostatTraits() =>
(super.noSuchMethod(Invocation.method(#getThermostatTraits, []),
returnValue: <_i4.ThermostatTrait>[]) as List<_i4.ThermostatTrait>);
@override
_i5.Future<void> setPointAction(String? deviceId, double? temperature,
{_i2.SetPointActionFunction? setPoint =
_i6.ThermostatRepository.setPointThermostat}) =>
(super.noSuchMethod(
Invocation.method(
#setPointAction, [deviceId, temperature], {#setPoint: setPoint}),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
@override
_i5.Future<void> setThermostatMode(
String? deviceId, _i7.GThermostatMode? mode,
{_i2.SetModeFunction? setMode = _i6.ThermostatRepository.setMode}) =>
(super.noSuchMethod(
Invocation.method(
#setThermostatMode, [deviceId, mode], {#setMode: setMode}),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
@override
_i5.Future<void> fetchData(
{_i8.GetDeviceDetailsMethod? getDetails =
_i9.DevicesRepository.getDeviceDetails}) =>
(super.noSuchMethod(
Invocation.method(#fetchData, [], {#getDetails: getDetails}),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
@override
void setErrorState(String? errorMsg) =>
super.noSuchMethod(Invocation.method(#setErrorState, [errorMsg]),
returnValueForMissingStub: null);
@override
_i5.Future<void> performAction<T>(
T? desiredState, Function? getState, Function? action,
{_i8.GetDeviceDetailsMethod? getDetails =
_i9.DevicesRepository.getDeviceDetails}) =>
(super.noSuchMethod(
Invocation.method(#performAction, [desiredState, getState, action],
{#getDetails: getDetails}),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
@override
void addListener(_i10.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#addListener, [listener]),
returnValueForMissingStub: null);
@override
void removeListener(_i10.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#removeListener, [listener]),
returnValueForMissingStub: null);
@override
void dispose() => super.noSuchMethod(Invocation.method(#dispose, []),
returnValueForMissingStub: null);
@override
void notifyListeners() =>
super.noSuchMethod(Invocation.method(#notifyListeners, []),
returnValueForMissingStub: null);
@override
String toString() => super.toString();
}

0 comments on commit 89ddaf2

Please sign in to comment.