Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
4492fb9f0c | |||
36410752e4 | |||
0219f7bfbb | |||
5f3c77f4b9 | |||
a36c7a9ca3 | |||
56ce6dfeeb | |||
67c214454f | |||
73398378c4 | |||
215871ce9e | |||
fd8ea6befd | |||
809a1a1c8c | |||
fc8f2f200f | |||
f41c9f9197 | |||
cdf55ce68b | |||
12088d9516 | |||
a0235ee385 | |||
67fbdb13c6 | |||
c5960de0be | |||
da15e880ec | |||
efbe33f4e3 | |||
af84c99a2d | |||
438449cad8 | |||
d9ca55c3b7 | |||
f248268984 | |||
8ee096595c |
@ -1,9 +1,9 @@
|
||||
part of 'main.dart';
|
||||
|
||||
class EntityViewPage extends StatefulWidget {
|
||||
EntityViewPage({Key key, @required this.entity, @required this.homeAssistant }) : super(key: key);
|
||||
EntityViewPage({Key key, @required this.entityId, @required this.homeAssistant }) : super(key: key);
|
||||
|
||||
final Entity entity;
|
||||
final String entityId;
|
||||
final HomeAssistant homeAssistant;
|
||||
|
||||
@override
|
||||
@ -12,30 +12,26 @@ class EntityViewPage extends StatefulWidget {
|
||||
|
||||
class _EntityViewPageState extends State<EntityViewPage> {
|
||||
String _title;
|
||||
StreamSubscription _refreshDataSubscription;
|
||||
StreamSubscription _stateSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||
if (event.entityId == widget.entity.entityId) {
|
||||
TheLogger.debug("State change event handled by entity page: ${event.entityId}");
|
||||
if (event.entityId == widget.entityId) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
||||
setState(() {});
|
||||
});
|
||||
_prepareData();
|
||||
_getHistory();
|
||||
}
|
||||
|
||||
void _prepareData() async {
|
||||
_title = widget.entity.displayName;
|
||||
}
|
||||
|
||||
void _getHistory() {
|
||||
/* widget.homeAssistant.getHistory(widget.entity.entityId).then((List history) {
|
||||
if (history != null) {
|
||||
|
||||
}
|
||||
});*/
|
||||
_title = widget.homeAssistant.entities.get(widget.entityId).displayName;
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +50,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: HomeAssistantModel(
|
||||
homeAssistant: widget.homeAssistant,
|
||||
child: widget.entity.buildEntityPageWidget(context)
|
||||
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
|
||||
)
|
||||
),
|
||||
);
|
||||
@ -63,6 +59,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
|
||||
@override
|
||||
void dispose(){
|
||||
if (_stateSubscription != null) _stateSubscription.cancel();
|
||||
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -34,4 +34,24 @@ class EntityTapAction {
|
||||
static const moreInfo = 'more-info';
|
||||
static const toggle = 'toggle';
|
||||
static const callService = 'call-service';
|
||||
static const none = 'none';
|
||||
}
|
||||
|
||||
class CardType {
|
||||
static const entities = "entities";
|
||||
static const glance = "glance";
|
||||
static const mediaControl = "media-control";
|
||||
static const weatherForecast = "weather-forecast";
|
||||
static const thermostat = "thermostat";
|
||||
static const sensor = "sensor";
|
||||
static const plantStatus = "plant-status";
|
||||
static const pictureEntity = "picture-entity";
|
||||
static const pictureElements = "picture-elements";
|
||||
static const picture = "picture";
|
||||
static const map = "map";
|
||||
static const iframe = "iframe";
|
||||
static const gauge = "gauge";
|
||||
static const entityButton = "entity-button";
|
||||
static const conditional = "conditional";
|
||||
static const alarmPanel = "alarm-panel";
|
||||
}
|
@ -76,6 +76,15 @@ class Entity {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getStringListAttributeValue(String attribute) {
|
||||
if (attributes["$attribute"] != null) {
|
||||
List<String> result = (attributes["$attribute"] as List).cast<String>();
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildDefaultWidget(BuildContext context) {
|
||||
return DefaultEntityContainer(
|
||||
state: _buildStatePart(context)
|
||||
|
@ -6,8 +6,10 @@ class EntityWrapper {
|
||||
String icon;
|
||||
String tapAction;
|
||||
String holdAction;
|
||||
String actionService;
|
||||
Map<String, dynamic> actionServiceData;
|
||||
String tapActionService;
|
||||
Map<String, dynamic> tapActionServiceData;
|
||||
String holdActionService;
|
||||
Map<String, dynamic> holdActionServiceData;
|
||||
Entity entity;
|
||||
|
||||
|
||||
@ -16,12 +18,66 @@ class EntityWrapper {
|
||||
String icon,
|
||||
String displayName,
|
||||
this.tapAction: EntityTapAction.moreInfo,
|
||||
this.holdAction,
|
||||
this.actionService,
|
||||
this.actionServiceData
|
||||
this.holdAction: EntityTapAction.none,
|
||||
this.tapActionService,
|
||||
this.tapActionServiceData,
|
||||
this.holdActionService,
|
||||
this.holdActionServiceData
|
||||
}) {
|
||||
this.icon = icon ?? entity.icon;
|
||||
this.displayName = displayName ?? entity.displayName;
|
||||
}
|
||||
|
||||
void handleTap() {
|
||||
switch (tapAction) {
|
||||
case EntityTapAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.callService: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(tapActionService.split(".")[0], tapActionService.split(".")[1], null, tapActionServiceData));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.none: {
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleHold() {
|
||||
switch (holdAction) {
|
||||
case EntityTapAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.callService: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(tapActionService.split(".")[0], tapActionService.split(".")[1], null, tapActionServiceData));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.moreInfo: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entity));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
lib/entity_class/fan_entity.class.dart
Normal file
32
lib/entity_class/fan_entity.class.dart
Normal file
@ -0,0 +1,32 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class FanEntity extends Entity {
|
||||
|
||||
static const SUPPORT_SET_SPEED = 1;
|
||||
static const SUPPORT_OSCILLATE = 2;
|
||||
static const SUPPORT_DIRECTION = 4;
|
||||
|
||||
FanEntity(Map rawData) : super(rawData);
|
||||
|
||||
bool get supportSetSpeed => ((attributes["supported_features"] &
|
||||
FanEntity.SUPPORT_SET_SPEED) ==
|
||||
FanEntity.SUPPORT_SET_SPEED);
|
||||
bool get supportOscillate => ((attributes["supported_features"] &
|
||||
FanEntity.SUPPORT_OSCILLATE) ==
|
||||
FanEntity.SUPPORT_OSCILLATE);
|
||||
bool get supportDirection => ((attributes["supported_features"] &
|
||||
FanEntity.SUPPORT_DIRECTION) ==
|
||||
FanEntity.SUPPORT_DIRECTION);
|
||||
|
||||
List<String> get speedList => getStringListAttributeValue("speed_list");
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return SwitchStateWidget();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return FanControlsWidget();
|
||||
}
|
||||
}
|
43
lib/entity_class/group_entity.class.dart
Normal file
43
lib/entity_class/group_entity.class.dart
Normal file
@ -0,0 +1,43 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class GroupEntity extends Entity {
|
||||
GroupEntity(Map rawData) : super(rawData);
|
||||
|
||||
final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"];
|
||||
String mutualDomain;
|
||||
bool switchable = false;
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
if (switchable) {
|
||||
return SwitchStateWidget(
|
||||
domainForService: "homeassistant",
|
||||
);
|
||||
} else {
|
||||
return super._buildStatePart(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void update(Map rawData) {
|
||||
super.update(rawData);
|
||||
if (_isOneDomain()) {
|
||||
mutualDomain = attributes['entity_id'][0].split(".")[0];
|
||||
switchable = _domainsForSwitchableGroup.contains(mutualDomain);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isOneDomain() {
|
||||
bool result = false;
|
||||
if (attributes['entity_id'] != null && attributes['entity_id'] is List && attributes['entity_id'].isNotEmpty) {
|
||||
String firstChildDomain = attributes['entity_id'][0].split(".")[0];
|
||||
result = true;
|
||||
attributes['entity_id'].forEach((childEntityId){
|
||||
if (childEntityId.split(".")[0] != firstChildDomain) {
|
||||
result = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ class LightEntity extends Entity {
|
||||
double get minMireds => _getDoubleAttributeValue("min_mireds");
|
||||
Color get color => _getColor();
|
||||
bool get isAdditionalControls => ((attributes["supported_features"] != null) && (attributes["supported_features"] != 0));
|
||||
List<String> get effectList => _getEffectList();
|
||||
List<String> get effectList => getStringListAttributeValue("effect_list");
|
||||
|
||||
LightEntity(Map rawData) : super(rawData);
|
||||
|
||||
@ -55,15 +55,6 @@ class LightEntity extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _getEffectList() {
|
||||
if (attributes["effect_list"] != null) {
|
||||
List<String> result = (attributes["effect_list"] as List).cast<String>();
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return SwitchStateWidget();
|
||||
|
12
lib/entity_class/lock_entity.class.dart
Normal file
12
lib/entity_class/lock_entity.class.dart
Normal file
@ -0,0 +1,12 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class LockEntity extends Entity {
|
||||
LockEntity(Map rawData) : super(rawData);
|
||||
|
||||
bool get isLocked => state == "locked";
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return LockStateWidget();
|
||||
}
|
||||
}
|
@ -72,6 +72,9 @@ class MediaPlayerEntity extends Entity {
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE);
|
||||
|
||||
List<String> get soundModeList => getStringListAttributeValue("sound_mode_list");
|
||||
List<String> get sourceList => getStringListAttributeValue("source_list");
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return MediaPlayerControls();
|
||||
|
@ -44,6 +44,9 @@ class EntityCollection {
|
||||
case 'sensor': {
|
||||
return SensorEntity(rawEntityData);
|
||||
}
|
||||
case 'lock': {
|
||||
return LockEntity(rawEntityData);
|
||||
}
|
||||
case "automation":
|
||||
case "input_boolean":
|
||||
case "switch": {
|
||||
@ -52,6 +55,9 @@ class EntityCollection {
|
||||
case "light": {
|
||||
return LightEntity(rawEntityData);
|
||||
}
|
||||
case "group": {
|
||||
return GroupEntity(rawEntityData);
|
||||
}
|
||||
case "script":
|
||||
case "scene": {
|
||||
return ButtonEntity(rawEntityData);
|
||||
@ -74,6 +80,9 @@ class EntityCollection {
|
||||
case "cover": {
|
||||
return CoverEntity(rawEntityData);
|
||||
}
|
||||
case "fan": {
|
||||
return FanEntity(rawEntityData);
|
||||
}
|
||||
default: {
|
||||
return Entity(rawEntityData);
|
||||
}
|
||||
@ -126,7 +135,7 @@ class EntityCollection {
|
||||
List<Entity> groups = [];
|
||||
List<Entity> nonGroupEntities = [];
|
||||
_allEntities.forEach((id, entity){
|
||||
if ((id.indexOf("group.") == 0) && (id.indexOf(".all_") == -1) && (!entity.isView)) {
|
||||
if (entity.isGroup && (entity.attributes['auto'] == null || (entity.attributes['auto'] && !entity.isHidden)) && (!entity.isView)) {
|
||||
groups.add(entity);
|
||||
}
|
||||
if (!entity.isGroup) {
|
||||
|
@ -12,7 +12,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
|
||||
ModeSelectorWidget({
|
||||
Key key,
|
||||
this.caption,
|
||||
@required this.caption,
|
||||
@required this.options,
|
||||
this.value,
|
||||
@required this.onChange,
|
||||
|
@ -6,27 +6,22 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
final onChange;
|
||||
final double captionFontSize;
|
||||
final bool value;
|
||||
final bool expanded;
|
||||
|
||||
ModeSwitchWidget({
|
||||
Key key,
|
||||
@required this.caption,
|
||||
@required this.onChange,
|
||||
this.captionFontSize,
|
||||
this.value
|
||||
this.value,
|
||||
this.expanded: true
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
"$caption",
|
||||
style: TextStyle(
|
||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildCaption(),
|
||||
Switch(
|
||||
onChanged: (value) => onChange(value),
|
||||
value: value ?? false,
|
||||
@ -35,4 +30,19 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCaption() {
|
||||
Widget captionWidget = Text(
|
||||
"$caption",
|
||||
style: TextStyle(
|
||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||
),
|
||||
);
|
||||
if (expanded) {
|
||||
return Expanded(
|
||||
child: captionWidget,
|
||||
);
|
||||
}
|
||||
return captionWidget;
|
||||
}
|
||||
|
||||
}
|
123
lib/entity_widgets/controls/fan_controls.dart
Normal file
123
lib/entity_widgets/controls/fan_controls.dart
Normal file
@ -0,0 +1,123 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class FanControlsWidget extends StatefulWidget {
|
||||
|
||||
@override
|
||||
_FanControlsWidgetState createState() => _FanControlsWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _FanControlsWidgetState extends State<FanControlsWidget> {
|
||||
|
||||
bool _tmpOscillate;
|
||||
bool _tmpDirectionForward;
|
||||
bool _changedHere = false;
|
||||
String _tmpSpeed;
|
||||
|
||||
void _resetState(FanEntity entity) {
|
||||
_tmpOscillate = entity.attributes["oscillating"] ?? false;
|
||||
_tmpDirectionForward = entity.attributes["direction"] == "forward";
|
||||
_tmpSpeed = entity.attributes["speed"];
|
||||
}
|
||||
|
||||
void _setOscillate(FanEntity entity, bool oscillate) {
|
||||
setState(() {
|
||||
_tmpOscillate = oscillate;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "oscillate", entity.entityId,
|
||||
{"oscillating": oscillate}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setDirection(FanEntity entity, bool forward) {
|
||||
setState(() {
|
||||
_tmpDirectionForward = forward;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "set_direction", entity.entityId,
|
||||
{"direction": forward ? "forward" : "reverse"}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setSpeed(FanEntity entity, String value) {
|
||||
setState(() {
|
||||
_tmpSpeed = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "set_speed", entity.entityId,
|
||||
{"speed": value}));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final FanEntity entity = entityModel.entityWrapper.entity;
|
||||
if (!_changedHere) {
|
||||
_resetState(entity);
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
_buildSpeedControl(entity),
|
||||
_buildOscillateControl(entity),
|
||||
_buildDirectionControl(entity)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSpeedControl(FanEntity entity) {
|
||||
if (entity.supportSetSpeed && entity.speedList != null && entity.speedList.isNotEmpty) {
|
||||
return ModeSelectorWidget(
|
||||
onChange: (effect) => _setSpeed(entity, effect),
|
||||
caption: "Speed",
|
||||
options: entity.speedList,
|
||||
value: _tmpSpeed
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildOscillateControl(FanEntity entity) {
|
||||
if (entity.supportOscillate) {
|
||||
return ModeSwitchWidget(
|
||||
onChange: (value) => _setOscillate(entity, value),
|
||||
caption: "Oscillate",
|
||||
value: _tmpOscillate,
|
||||
expanded: false,
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDirectionControl(FanEntity entity) {
|
||||
if (entity.supportDirection) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: _tmpDirectionForward ?
|
||||
() => _setDirection(entity, false) :
|
||||
null,
|
||||
icon: Icon(Icons.rotate_left),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: !_tmpDirectionForward ?
|
||||
() => _setDirection(entity, true) :
|
||||
null,
|
||||
icon: Icon(Icons.rotate_right),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -79,11 +79,13 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Image(
|
||||
image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"),
|
||||
height: 240.0,
|
||||
width: 320.0,
|
||||
fit: BoxFit.contain,
|
||||
Flexible(
|
||||
child: Image(
|
||||
image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"),
|
||||
height: 240.0,
|
||||
//width: 320.0,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -255,6 +257,8 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
|
||||
double _newVolumeLevel;
|
||||
bool _changedHere = false;
|
||||
String _newSoundMode;
|
||||
String _newSource;
|
||||
|
||||
void _setVolume(double value, String entityId) {
|
||||
setState(() {
|
||||
@ -276,6 +280,22 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
|
||||
}
|
||||
|
||||
void _setSoundMode(String value, String entityId) {
|
||||
setState(() {
|
||||
_newSoundMode = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setSource(String source, String entityId) {
|
||||
setState(() {
|
||||
_newSource = source;
|
||||
_changedHere = true;
|
||||
eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||
@ -347,6 +367,38 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
));
|
||||
}
|
||||
|
||||
if (entity.supportSelectSoundMode && entity.soundModeList != null) {
|
||||
if (!_changedHere) {
|
||||
_newSoundMode = entity.attributes["sound_mode"];
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
children.add(
|
||||
ModeSelectorWidget(
|
||||
options: entity.soundModeList,
|
||||
caption: "Sound mode",
|
||||
value: _newSoundMode,
|
||||
onChange: (value) => _setSoundMode(value, entity.entityId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (entity.supportSelectSource && entity.sourceList != null) {
|
||||
if (!_changedHere) {
|
||||
_newSource = entity.attributes["source"];
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
children.add(
|
||||
ModeSelectorWidget(
|
||||
options: entity.sourceList,
|
||||
caption: "Source",
|
||||
value: _newSource,
|
||||
onChange: (value) => _setSource(value, entity.entityId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
return Column(
|
||||
children: children,
|
||||
|
@ -10,15 +10,31 @@ class DefaultEntityContainer extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
EntityIcon(),
|
||||
Expanded(
|
||||
child: EntityName(),
|
||||
),
|
||||
state
|
||||
],
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
return InkWell(
|
||||
onLongPress: () {
|
||||
if (entityModel.handleTap) {
|
||||
entityModel.entityWrapper.handleHold();
|
||||
}
|
||||
},
|
||||
onTap: () {
|
||||
if (entityModel.handleTap) {
|
||||
entityModel.entityWrapper.handleTap();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
EntityIcon(),
|
||||
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 3,
|
||||
child: EntityName(),
|
||||
),
|
||||
state
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -10,59 +10,14 @@ class EntityIcon extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return GestureDetector(
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
entityModel.entityWrapper,
|
||||
iconSize,
|
||||
EntityColor.stateColor(entityModel.entityWrapper.entity.state)
|
||||
),
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
entityWrapper,
|
||||
iconSize,
|
||||
EntityColor.stateColor(entityWrapper.entity.state)
|
||||
),
|
||||
onLongPress: () {
|
||||
if (entityModel.handleTap) {
|
||||
switch (entityModel.entityWrapper.holdAction) {
|
||||
case EntityTapAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entityModel.entityWrapper.entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entityModel.entityWrapper.entity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
onTap: () {
|
||||
if (entityModel.handleTap) {
|
||||
switch (entityModel.entityWrapper.tapAction) {
|
||||
case EntityTapAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entityModel.entityWrapper.entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.callService: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(entityModel.entityWrapper.actionService.split(".")[0], entityModel.entityWrapper.actionService.split(".")[1], null, entityModel.entityWrapper.actionServiceData));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entityModel.entityWrapper.entity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
}
|
@ -12,22 +12,16 @@ class EntityName extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return GestureDetector(
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: Text(
|
||||
"${entityModel.entityWrapper.displayName}",
|
||||
overflow: textOverflow,
|
||||
softWrap: wordsWrap,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
textAlign: textAlign,
|
||||
),
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Text(
|
||||
"${entityWrapper.displayName}",
|
||||
overflow: textOverflow,
|
||||
softWrap: wordsWrap,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
textAlign: textAlign,
|
||||
),
|
||||
onTap: () =>
|
||||
entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ class GlanceEntityContainer extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
List<Widget> result = [];
|
||||
if (showName) {
|
||||
result.add(EntityName(
|
||||
@ -21,10 +22,12 @@ class GlanceEntityContainer extends StatelessWidget {
|
||||
fontSize: Sizes.smallFontSize,
|
||||
));
|
||||
}
|
||||
result.add(EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
iconSize: Sizes.largeIconSize,
|
||||
));
|
||||
result.add(
|
||||
EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
if (showState) {
|
||||
result.add(SimpleEntityState(
|
||||
textAlign: TextAlign.center,
|
||||
@ -32,11 +35,20 @@ class GlanceEntityContainer extends StatelessWidget {
|
||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||
));
|
||||
}
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: result,
|
||||
return Center(
|
||||
child: InkResponse(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: Sizes.iconSize*2),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
//mainAxisAlignment: MainAxisAlignment.start,
|
||||
//crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: result,
|
||||
),
|
||||
),
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -19,39 +19,34 @@ class ClimateStateWidget extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||
child: GestureDetector(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text("${entity.state}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)),
|
||||
Text(" $targetTemp",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
))
|
||||
],
|
||||
),
|
||||
entity.attributes["current_temperature"] != null ?
|
||||
Text("Currently: ${entity.attributes["current_temperature"]}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text("${entity.state}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: Sizes.stateFontSize,
|
||||
color: Colors.black45)
|
||||
) :
|
||||
Container(height: 0.0,)
|
||||
],
|
||||
),
|
||||
onTap: () => entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entity))
|
||||
: null,
|
||||
)),
|
||||
Text(" $targetTemp",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
))
|
||||
],
|
||||
),
|
||||
entity.attributes["current_temperature"] != null ?
|
||||
Text("Currently: ${entity.attributes["current_temperature"]}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
color: Colors.black45)
|
||||
) :
|
||||
Container(height: 0.0,)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
32
lib/entity_widgets/state/lock_state.dart
Normal file
32
lib/entity_widgets/state/lock_state.dart
Normal file
@ -0,0 +1,32 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class LockStateWidget extends StatelessWidget {
|
||||
|
||||
void _lock(Entity entity) {
|
||||
eventBus.fire(new ServiceCallEvent("lock", "lock", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _unlock(Entity entity) {
|
||||
eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final LockEntity entity = entityModel.entityWrapper.entity;
|
||||
return SizedBox(
|
||||
height: 34.0,
|
||||
child: FlatButton(
|
||||
onPressed: (() {
|
||||
entity.isLocked ? _unlock(entity) : _lock(entity);
|
||||
}),
|
||||
child: Text(
|
||||
entity.isLocked ? "UNLOCK" : "LOCK",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
|
||||
if (entity.listOptions.isNotEmpty) {
|
||||
ctrl = DropdownButton<String>(
|
||||
value: entity.state,
|
||||
isExpanded: true,
|
||||
items: entity.listOptions.map((String value) {
|
||||
return new DropdownMenuItem<String>(
|
||||
value: value,
|
||||
@ -36,7 +37,9 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
|
||||
} else {
|
||||
ctrl = Text('---');
|
||||
}
|
||||
return Expanded(
|
||||
return Flexible(
|
||||
flex: 2,
|
||||
fit: FlexFit.tight,
|
||||
//width: Entity.INPUT_WIDTH,
|
||||
child: ctrl,
|
||||
);
|
||||
|
@ -12,25 +12,22 @@ class SimpleEntityState extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
Widget result = Padding(
|
||||
padding: padding,
|
||||
child: GestureDetector(
|
||||
child: Text(
|
||||
"${entityModel.entityWrapper.entity.state}${entityModel.entityWrapper.entity.unitOfMeasurement}",
|
||||
textAlign: textAlign,
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)),
|
||||
onTap: () => entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
|
||||
: null,
|
||||
padding: padding,
|
||||
child: Text(
|
||||
"${entityModel.entityWrapper.entity.state} ${entityModel.entityWrapper.entity.unitOfMeasurement}",
|
||||
textAlign: textAlign,
|
||||
maxLines: 10,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)
|
||||
)
|
||||
);
|
||||
if (expanded) {
|
||||
return SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.3,
|
||||
return Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 2,
|
||||
child: result,
|
||||
);
|
||||
} else {
|
||||
|
@ -1,6 +1,11 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SwitchStateWidget extends StatefulWidget {
|
||||
|
||||
final String domainForService;
|
||||
|
||||
const SwitchStateWidget({Key key, this.domainForService}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SwitchStateWidgetState createState() => _SwitchStateWidgetState();
|
||||
}
|
||||
@ -24,11 +29,17 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
||||
setState(() {
|
||||
newState = entity.state;
|
||||
updatedHere = true;
|
||||
TheLogger.debug("Timer@!!");
|
||||
//TheLogger.debug("Timer@!!");
|
||||
});
|
||||
});
|
||||
String domain;
|
||||
if (widget.domainForService != null) {
|
||||
domain = widget.domainForService;
|
||||
} else {
|
||||
domain = entity.domain;
|
||||
}
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
|
||||
domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -66,7 +66,9 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
||||
_tmpValue = entity.state;
|
||||
}
|
||||
if (entity.isTextField || entity.isPasswordField) {
|
||||
return Expanded(
|
||||
return Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 2,
|
||||
//width: Entity.INPUT_WIDTH,
|
||||
child: TextField(
|
||||
focusNode: _focusNode,
|
||||
|
@ -447,15 +447,21 @@ class HomeAssistant {
|
||||
} else {
|
||||
if (entities.isExist(rawEntity["entity"])) {
|
||||
Entity e = entities.get(rawEntity["entity"]);
|
||||
String tapAction = EntityTapAction.moreInfo;
|
||||
String holdAction = EntityTapAction.none;
|
||||
if (card.type == CardType.glance) {
|
||||
tapAction = rawEntity["tap_action"] ?? EntityTapAction.moreInfo;
|
||||
holdAction = rawEntity["hold_action"] ?? EntityTapAction.none;
|
||||
}
|
||||
card.entities.add(
|
||||
EntityWrapper(
|
||||
entity: e,
|
||||
displayName: rawEntity["name"],
|
||||
icon: rawEntity["icon"],
|
||||
tapAction: rawEntity["tap_action"] ?? EntityTapAction.moreInfo,
|
||||
holdAction: rawEntity["hold_action"] ?? EntityTapAction.moreInfo,
|
||||
actionService: rawEntity["service"],
|
||||
actionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId}
|
||||
tapAction: tapAction,
|
||||
holdAction: holdAction,
|
||||
tapActionService: rawEntity["service"],
|
||||
tapActionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
207
lib/main.dart
207
lib/main.dart
@ -4,7 +4,6 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
import 'package:progress_indicators/progress_indicators.dart';
|
||||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
@ -14,6 +13,7 @@ import 'package:date_format/date_format.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter_colorpicker/material_picker.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:progress_indicators/progress_indicators.dart';
|
||||
|
||||
part 'entity_class/const.dart';
|
||||
part 'entity_class/entity.class.dart';
|
||||
@ -29,6 +29,9 @@ part 'entity_class/select_entity.class.dart';
|
||||
part 'entity_class/other_entity.class.dart';
|
||||
part 'entity_class/slider_entity.dart';
|
||||
part 'entity_class/media_player_entity.class.dart';
|
||||
part 'entity_class/lock_entity.class.dart';
|
||||
part 'entity_class/group_entity.class.dart';
|
||||
part 'entity_class/fan_entity.class.dart';
|
||||
part 'entity_widgets/common/badge.dart';
|
||||
part 'entity_widgets/model_widgets.dart';
|
||||
part 'entity_widgets/default_entity_container.dart';
|
||||
@ -57,10 +60,12 @@ part 'entity_widgets/state/climate_state.dart';
|
||||
part 'entity_widgets/state/cover_state.dart';
|
||||
part 'entity_widgets/state/date_time_state.dart';
|
||||
part 'entity_widgets/state/button_state.dart';
|
||||
part 'entity_widgets/state/lock_state.dart';
|
||||
part 'entity_widgets/controls/climate_controls.dart';
|
||||
part 'entity_widgets/controls/cover_controls.dart';
|
||||
part 'entity_widgets/controls/light_controls.dart';
|
||||
part 'entity_widgets/controls/media_player_widgets.dart';
|
||||
part 'entity_widgets/controls/fan_controls.dart';
|
||||
part 'settings.page.dart';
|
||||
part 'home_assistant.class.dart';
|
||||
part 'log.page.dart';
|
||||
@ -82,7 +87,7 @@ part 'ui_widgets/card_header_widget.dart';
|
||||
|
||||
EventBus eventBus = new EventBus();
|
||||
const String appName = "HA Client";
|
||||
const appVersion = "0.3.9";
|
||||
const appVersion = "0.3.10";
|
||||
|
||||
String homeAssistantWebHost;
|
||||
|
||||
@ -147,7 +152,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
StreamSubscription _showEntityPageSubscription;
|
||||
StreamSubscription _refreshDataSubscription;
|
||||
StreamSubscription _showErrorSubscription;
|
||||
int _isLoading = 1;
|
||||
bool _settingsLoaded = false;
|
||||
bool _accountMenuExpanded = false;
|
||||
bool _useLovelaceUI;
|
||||
@ -158,6 +162,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
_settingsLoaded = false;
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
TheLogger.debug("<!!!> Creating new HomeAssistant instance");
|
||||
_homeAssistant = HomeAssistant();
|
||||
|
||||
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
|
||||
@ -176,10 +181,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
_subscribe();
|
||||
_refreshData();
|
||||
}, onError: (_) {
|
||||
setState(() {
|
||||
_isLoading = 2;
|
||||
});
|
||||
_showErrorSnackBar(message: _, errorCode: 5);
|
||||
_showErrorBottomBar(message: _, errorCode: 5);
|
||||
});
|
||||
}
|
||||
|
||||
@ -226,7 +228,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
if (_showEntityPageSubscription == null) {
|
||||
_showEntityPageSubscription =
|
||||
eventBus.on<ShowEntityPageEvent>().listen((event) {
|
||||
_showEntityPage(event.entity);
|
||||
_showEntityPage(event.entity.entityId);
|
||||
});
|
||||
}
|
||||
|
||||
@ -238,21 +240,17 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
|
||||
if (_showErrorSubscription == null) {
|
||||
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
|
||||
_showErrorSnackBar(message: event.text, errorCode: event.errorCode);
|
||||
_showErrorBottomBar(message: event.text, errorCode: event.errorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_refreshData() async {
|
||||
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI);
|
||||
setState(() {
|
||||
_hideErrorSnackBar();
|
||||
_isLoading = 1;
|
||||
});
|
||||
_hideBottomBar();
|
||||
_showInfoBottomBar(progress: true,);
|
||||
await _homeAssistant.fetch().then((result) {
|
||||
setState(() {
|
||||
_isLoading = 0;
|
||||
});
|
||||
_hideBottomBar();
|
||||
}).catchError((e) {
|
||||
_setErrorState(e);
|
||||
});
|
||||
@ -260,18 +258,15 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
_setErrorState(e) {
|
||||
setState(() {
|
||||
_isLoading = 2;
|
||||
});
|
||||
if (e is Error) {
|
||||
TheLogger.error(e.toString());
|
||||
TheLogger.error("${e.stackTrace}");
|
||||
_showErrorSnackBar(
|
||||
_showErrorBottomBar(
|
||||
message: "There was some error",
|
||||
errorCode: 13
|
||||
);
|
||||
} else {
|
||||
_showErrorSnackBar(
|
||||
_showErrorBottomBar(
|
||||
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
|
||||
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
|
||||
);
|
||||
@ -279,14 +274,18 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
void _callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
|
||||
_showInfoBottomBar(
|
||||
message: "Calling $domain.$service",
|
||||
duration: Duration(seconds: 3)
|
||||
);
|
||||
_homeAssistant.callService(domain, service, entityId, additionalParams).catchError((e) => _setErrorState(e));
|
||||
}
|
||||
|
||||
void _showEntityPage(Entity entity) {
|
||||
void _showEntityPage(String entityId) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EntityViewPage(entity: entity, homeAssistant: _homeAssistant),
|
||||
builder: (context) => EntityViewPage(entityId: entityId, homeAssistant: _homeAssistant),
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -303,31 +302,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildAppTitle() {
|
||||
Row titleRow = Row(
|
||||
children: [Text(_homeAssistant != null ? _homeAssistant.locationName : "")],
|
||||
);
|
||||
if (_isLoading == 1) {
|
||||
titleRow.children.add(Padding(
|
||||
child: JumpingDotsProgressIndicator(
|
||||
fontSize: 26.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(5.0, 0.0, 0.0, 30.0),
|
||||
));
|
||||
} else if (_isLoading == 2) {
|
||||
titleRow.children.add(Padding(
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 20.0,
|
||||
color: Colors.red,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(5.0, 0.0, 0.0, 0.0),
|
||||
));
|
||||
}
|
||||
return titleRow;
|
||||
}
|
||||
|
||||
Drawer _buildAppDrawer() {
|
||||
List<Widget> menuItems = [];
|
||||
menuItems.add(
|
||||
@ -409,21 +383,51 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
);
|
||||
}
|
||||
|
||||
void _hideErrorSnackBar() {
|
||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
void _hideBottomBar() {
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
setState(() {
|
||||
_showBottomBar = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _showErrorSnackBar({Key key, @required String message, @required int errorCode}) {
|
||||
SnackBarAction action;
|
||||
Widget _bottomBarAction;
|
||||
bool _showBottomBar = false;
|
||||
String _bottomBarText;
|
||||
bool _bottomBarProgress;
|
||||
Color _bottomBarColor;
|
||||
Timer _bottomBarTimer;
|
||||
|
||||
void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
|
||||
_bottomBarTimer?.cancel();
|
||||
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
||||
_bottomBarColor = Colors.grey.shade50;
|
||||
setState(() {
|
||||
_bottomBarText = message;
|
||||
_bottomBarProgress = progress;
|
||||
_showBottomBar = true;
|
||||
});
|
||||
if (duration != null) {
|
||||
_bottomBarTimer = Timer(duration, () {
|
||||
_hideBottomBar();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _showErrorBottomBar({Key key, @required String message, @required int errorCode}) {
|
||||
TextStyle textStyle = TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: Sizes.nameFontSize
|
||||
);
|
||||
_bottomBarColor = Colors.red.shade100;
|
||||
switch (errorCode) {
|
||||
case 9:
|
||||
case 11:
|
||||
case 7:
|
||||
case 1: {
|
||||
action = SnackBarAction(
|
||||
label: "Retry",
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Retry", style: textStyle),
|
||||
onPressed: () {
|
||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
_refreshData();
|
||||
},
|
||||
);
|
||||
@ -432,10 +436,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
|
||||
case 5: {
|
||||
message = "Check connection settings";
|
||||
action = SnackBarAction(
|
||||
label: "Open",
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Open", style: textStyle),
|
||||
onPressed: () {
|
||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
Navigator.pushNamed(context, '/connection-settings');
|
||||
},
|
||||
);
|
||||
@ -443,10 +447,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
case 6: {
|
||||
action = SnackBarAction(
|
||||
label: "Settings",
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Settings", style: textStyle),
|
||||
onPressed: () {
|
||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
Navigator.pushNamed(context, '/connection-settings');
|
||||
},
|
||||
);
|
||||
@ -454,10 +458,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
case 10: {
|
||||
action = SnackBarAction(
|
||||
label: "Refresh",
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Refresh", style: textStyle),
|
||||
onPressed: () {
|
||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
_refreshData();
|
||||
},
|
||||
);
|
||||
@ -465,10 +469,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
case 8: {
|
||||
action = SnackBarAction(
|
||||
label: "Reconnect",
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Reconnect", style: textStyle),
|
||||
onPressed: () {
|
||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
_refreshData();
|
||||
},
|
||||
);
|
||||
@ -476,24 +480,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
default: {
|
||||
action = SnackBarAction(
|
||||
label: "Reload",
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Reload", style: textStyle),
|
||||
onPressed: () {
|
||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
_refreshData();
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_scaffoldKey.currentState.hideCurrentSnackBar();
|
||||
setState(() {
|
||||
_bottomBarProgress = false;
|
||||
_bottomBarText = "$message (code: $errorCode)";
|
||||
_showBottomBar = true;
|
||||
});
|
||||
/*_scaffoldKey.currentState.hideCurrentSnackBar();
|
||||
_scaffoldKey.currentState.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text("$message (code: $errorCode)"),
|
||||
action: action,
|
||||
duration: Duration(hours: 1),
|
||||
)
|
||||
);
|
||||
);*/
|
||||
}
|
||||
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
@ -506,7 +515,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
floating: true,
|
||||
pinned: true,
|
||||
primary: true,
|
||||
title: _buildAppTitle(),
|
||||
title: Text(_homeAssistant != null ? _homeAssistant.locationName : ""),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
@ -532,7 +541,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
|
||||
size: 100.0,
|
||||
color: _isLoading == 2 ? Colors.redAccent : Colors.blue,
|
||||
color: Colors.blue,
|
||||
),
|
||||
]
|
||||
),
|
||||
@ -544,12 +553,61 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget bottomBar;
|
||||
if (_showBottomBar) {
|
||||
List<Widget> bottomBarChildren = [];
|
||||
if (_bottomBarText != null) {
|
||||
bottomBarChildren.add(
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0,
|
||||
Sizes.rowPadding),
|
||||
child: Text(
|
||||
"$_bottomBarText",
|
||||
textAlign: TextAlign.left,
|
||||
softWrap: true,
|
||||
),
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
if (_bottomBarProgress) {
|
||||
bottomBarChildren.add(
|
||||
CollectionScaleTransition(
|
||||
children: <Widget>[
|
||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.on),),
|
||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.unavailable),),
|
||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.off),),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
if (bottomBarChildren.isNotEmpty) {
|
||||
bottomBar = Container(
|
||||
color: _bottomBarColor,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: _bottomBarProgress ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: bottomBarChildren,
|
||||
),
|
||||
),
|
||||
_bottomBarAction
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// This method is rerun every time setState is called.
|
||||
if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
primary: false,
|
||||
drawer: _buildAppDrawer(),
|
||||
bottomNavigationBar: bottomBar,
|
||||
body: _buildScaffoldBody(true)
|
||||
);
|
||||
} else {
|
||||
@ -557,6 +615,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
key: _scaffoldKey,
|
||||
drawer: _buildAppDrawer(),
|
||||
primary: false,
|
||||
bottomNavigationBar: bottomBar,
|
||||
body: DefaultTabController(
|
||||
length: _homeAssistant.ui?.views?.length ?? 0,
|
||||
child: _buildScaffoldBody(false),
|
||||
|
@ -22,6 +22,9 @@ class MaterialDesignIcons {
|
||||
"cover.closed": "mdi:window-closed",
|
||||
"cover.closing": "mdi:window-open",
|
||||
"cover.opening": "mdi:window-open",
|
||||
"lock.locked": "mdi:lock",
|
||||
"lock.unlocked": "mdi:lock-open",
|
||||
"fan": "mdi:fan"
|
||||
};
|
||||
|
||||
static Map _defaultIconsByDeviceClass = {
|
||||
|
@ -97,6 +97,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
children: <Widget>[
|
||||
Text(
|
||||
@ -150,8 +151,15 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
}
|
||||
),
|
||||
new Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Login with access token (HA >= 0.78.0)"),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Login with access token (HA >= 0.78.0)",
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: (_newAuthType == "access_token"),
|
||||
onChanged: (value) {
|
||||
|
@ -23,37 +23,37 @@ class HACard {
|
||||
Widget build(BuildContext context) {
|
||||
switch (type) {
|
||||
|
||||
case "entities": {
|
||||
case CardType.entities: {
|
||||
return EntitiesCardWidget(
|
||||
card: this,
|
||||
);
|
||||
}
|
||||
|
||||
case "glance": {
|
||||
case CardType.glance: {
|
||||
return GlanceCardWidget(
|
||||
card: this,
|
||||
);
|
||||
}
|
||||
|
||||
case "media-control": {
|
||||
case CardType.mediaControl: {
|
||||
return MediaControlCardWidget(
|
||||
card: this,
|
||||
);
|
||||
}
|
||||
|
||||
case "weather-forecast":
|
||||
case "thermostat":
|
||||
case "sensor":
|
||||
case "plant-status":
|
||||
case "picture-entity":
|
||||
case "picture-elements":
|
||||
case "picture":
|
||||
case "map":
|
||||
case "iframe":
|
||||
case "gauge":
|
||||
case "entity-button":
|
||||
case "conditional":
|
||||
case "alarm-panel": {
|
||||
case CardType.weatherForecast:
|
||||
case CardType.thermostat:
|
||||
case CardType.sensor:
|
||||
case CardType.plantStatus:
|
||||
case CardType.pictureEntity:
|
||||
case CardType.pictureElements:
|
||||
case CardType.picture:
|
||||
case CardType.map:
|
||||
case CardType.iframe:
|
||||
case CardType.gauge:
|
||||
case CardType.entityButton:
|
||||
case CardType.conditional:
|
||||
case CardType.alarmPanel: {
|
||||
return UnsupportedCardWidget(
|
||||
card: this,
|
||||
);
|
||||
|
@ -6,8 +6,8 @@ class Sizes {
|
||||
static const extendedWidgetHeight = 50.0;
|
||||
static const iconSize = 28.0;
|
||||
static const largeIconSize = 34.0;
|
||||
static const stateFontSize = 16.0;
|
||||
static const nameFontSize = 16.0;
|
||||
static const stateFontSize = 15.0;
|
||||
static const nameFontSize = 15.0;
|
||||
static const smallFontSize = 14.0;
|
||||
static const largeFontSize = 24.0;
|
||||
static const inputWidth = 160.0;
|
||||
|
@ -14,7 +14,7 @@ class CardHeaderWidget extends StatelessWidget {
|
||||
title: Text("$name",
|
||||
textAlign: TextAlign.left,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)),
|
||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
||||
);
|
||||
} else {
|
||||
result = new Container(width: 0.0, height: 0.0);
|
||||
|
@ -24,27 +24,25 @@ class GlanceCardWidget extends StatelessWidget {
|
||||
|
||||
Widget _buildRows(BuildContext context) {
|
||||
List<Widget> result = [];
|
||||
double width = MediaQuery.of(context).size.width - Sizes.leftWidgetPadding - (2*Sizes.rightWidgetPadding);
|
||||
List<EntityWrapper> toShow = card.entities.where((entity) {return !entity.entity.isHidden;}).toList();
|
||||
int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length;
|
||||
card.entities.forEach((EntityWrapper entity) {
|
||||
if (!entity.entity.isHidden) {
|
||||
result.add(
|
||||
SizedBox(
|
||||
width: width / columnsCount,
|
||||
child: EntityModel(
|
||||
entityWrapper: entity,
|
||||
child: entity.entity.buildGlanceWidget(context, card.showName, card.showState),
|
||||
handleTap: true
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
toShow.forEach((EntityWrapper entity) {
|
||||
result.add(
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1/columnsCount,
|
||||
child: EntityModel(
|
||||
entityWrapper: entity,
|
||||
child: entity.entity.buildGlanceWidget(context, card.showName, card.showState),
|
||||
handleTap: true
|
||||
),
|
||||
)
|
||||
);
|
||||
});
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceAround,
|
||||
//alignment: WrapAlignment.spaceAround,
|
||||
runSpacing: Sizes.rowPadding*2,
|
||||
children: result,
|
||||
),
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: hass_client
|
||||
description: Home Assistant Android Client
|
||||
|
||||
version: 0.3.9+67
|
||||
version: 0.3.10+74
|
||||
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
|
Reference in New Issue
Block a user