Compare commits

...

23 Commits

Author SHA1 Message Date
0219f7bfbb Version 0.3.10 2018-11-24 17:04:41 +02:00
5f3c77f4b9 Resolves #145 Fan support 2018-11-24 17:00:45 +02:00
a36c7a9ca3 Resolves #186 Switch for group with same domain antities 2018-11-24 11:33:59 +02:00
56ce6dfeeb Improved tap animation for glance card 2018-11-24 11:02:28 +02:00
67c214454f build number 2018-11-24 01:20:18 +02:00
73398378c4 Resolves #227 2018-11-24 00:37:55 +02:00
215871ce9e Resolves #226 2018-11-23 21:59:33 +02:00
fd8ea6befd Show auto groups that is not hidden 2018-11-23 19:30:16 +02:00
809a1a1c8c Resolves #146 Lock support 2018-11-23 19:18:17 +02:00
fc8f2f200f Small screens support 2018-11-23 18:16:38 +02:00
f41c9f9197 Resolves #202 Service call info 2018-11-23 16:38:26 +02:00
cdf55ce68b Resolves #201 New progress indicator in the bottom of the app 2018-11-23 16:30:42 +02:00
12088d9516 Resolves #223, Resolves #197 2018-11-23 16:03:38 +02:00
a0235ee385 Handle entity taps and holds in one place 2018-11-23 15:06:42 +02:00
67fbdb13c6 Proper autogenerateg groups detection 2018-11-23 14:33:03 +02:00
c5960de0be Resolves #193 Source selection support for media_player 2018-11-23 14:18:25 +02:00
da15e880ec Sound mode support for media player 2018-11-23 14:11:34 +02:00
efbe33f4e3 Fix font sizes, long entity states 2018-11-18 16:40:12 +02:00
af84c99a2d build 69 2018-11-18 13:25:00 +02:00
438449cad8 Handling taps on entity name and state for glance card 2018-11-18 13:24:05 +02:00
d9ca55c3b7 Resolves #131 2018-11-18 13:19:00 +02:00
f248268984 New bottom info bar 2018-11-18 12:46:54 +02:00
8ee096595c Resolves #192 Don't fetch data on every app resume 2018-11-18 09:47:22 +02:00
33 changed files with 744 additions and 296 deletions

View File

@ -1,9 +1,9 @@
part of 'main.dart'; part of 'main.dart';
class EntityViewPage extends StatefulWidget { 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; final HomeAssistant homeAssistant;
@override @override
@ -12,30 +12,26 @@ class EntityViewPage extends StatefulWidget {
class _EntityViewPageState extends State<EntityViewPage> { class _EntityViewPageState extends State<EntityViewPage> {
String _title; String _title;
StreamSubscription _refreshDataSubscription;
StreamSubscription _stateSubscription; StreamSubscription _stateSubscription;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { _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(() {}); setState(() {});
} }
}); });
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
setState(() {});
});
_prepareData(); _prepareData();
_getHistory();
} }
void _prepareData() async { void _prepareData() async {
_title = widget.entity.displayName; _title = widget.homeAssistant.entities.get(widget.entityId).displayName;
}
void _getHistory() {
/* widget.homeAssistant.getHistory(widget.entity.entityId).then((List history) {
if (history != null) {
}
});*/
} }
@ -54,7 +50,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
child: HomeAssistantModel( child: HomeAssistantModel(
homeAssistant: widget.homeAssistant, 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 @override
void dispose(){ void dispose(){
if (_stateSubscription != null) _stateSubscription.cancel(); if (_stateSubscription != null) _stateSubscription.cancel();
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
super.dispose(); super.dispose();
} }
} }

View File

@ -34,4 +34,24 @@ class EntityTapAction {
static const moreInfo = 'more-info'; static const moreInfo = 'more-info';
static const toggle = 'toggle'; static const toggle = 'toggle';
static const callService = 'call-service'; 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";
} }

View File

@ -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) { Widget buildDefaultWidget(BuildContext context) {
return DefaultEntityContainer( return DefaultEntityContainer(
state: _buildStatePart(context) state: _buildStatePart(context)

View File

@ -6,8 +6,10 @@ class EntityWrapper {
String icon; String icon;
String tapAction; String tapAction;
String holdAction; String holdAction;
String actionService; String tapActionService;
Map<String, dynamic> actionServiceData; Map<String, dynamic> tapActionServiceData;
String holdActionService;
Map<String, dynamic> holdActionServiceData;
Entity entity; Entity entity;
@ -16,12 +18,66 @@ class EntityWrapper {
String icon, String icon,
String displayName, String displayName,
this.tapAction: EntityTapAction.moreInfo, this.tapAction: EntityTapAction.moreInfo,
this.holdAction, this.holdAction: EntityTapAction.none,
this.actionService, this.tapActionService,
this.actionServiceData this.tapActionServiceData,
this.holdActionService,
this.holdActionServiceData
}) { }) {
this.icon = icon ?? entity.icon; this.icon = icon ?? entity.icon;
this.displayName = displayName ?? entity.displayName; 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;
}
}
}
} }

View 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();
}
}

View 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;
}
}

View File

@ -38,7 +38,7 @@ class LightEntity extends Entity {
double get minMireds => _getDoubleAttributeValue("min_mireds"); double get minMireds => _getDoubleAttributeValue("min_mireds");
Color get color => _getColor(); Color get color => _getColor();
bool get isAdditionalControls => ((attributes["supported_features"] != null) && (attributes["supported_features"] != 0)); 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); 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 @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {
return SwitchStateWidget(); return SwitchStateWidget();

View 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();
}
}

View File

@ -72,6 +72,9 @@ class MediaPlayerEntity extends Entity {
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) == MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
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 @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {
return MediaPlayerControls(); return MediaPlayerControls();

View File

@ -44,6 +44,9 @@ class EntityCollection {
case 'sensor': { case 'sensor': {
return SensorEntity(rawEntityData); return SensorEntity(rawEntityData);
} }
case 'lock': {
return LockEntity(rawEntityData);
}
case "automation": case "automation":
case "input_boolean": case "input_boolean":
case "switch": { case "switch": {
@ -52,6 +55,9 @@ class EntityCollection {
case "light": { case "light": {
return LightEntity(rawEntityData); return LightEntity(rawEntityData);
} }
case "group": {
return GroupEntity(rawEntityData);
}
case "script": case "script":
case "scene": { case "scene": {
return ButtonEntity(rawEntityData); return ButtonEntity(rawEntityData);
@ -74,6 +80,9 @@ class EntityCollection {
case "cover": { case "cover": {
return CoverEntity(rawEntityData); return CoverEntity(rawEntityData);
} }
case "fan": {
return FanEntity(rawEntityData);
}
default: { default: {
return Entity(rawEntityData); return Entity(rawEntityData);
} }
@ -126,7 +135,7 @@ class EntityCollection {
List<Entity> groups = []; List<Entity> groups = [];
List<Entity> nonGroupEntities = []; List<Entity> nonGroupEntities = [];
_allEntities.forEach((id, entity){ _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); groups.add(entity);
} }
if (!entity.isGroup) { if (!entity.isGroup) {

View File

@ -12,7 +12,7 @@ class ModeSelectorWidget extends StatelessWidget {
ModeSelectorWidget({ ModeSelectorWidget({
Key key, Key key,
this.caption, @required this.caption,
@required this.options, @required this.options,
this.value, this.value,
@required this.onChange, @required this.onChange,

View File

@ -6,27 +6,22 @@ class ModeSwitchWidget extends StatelessWidget {
final onChange; final onChange;
final double captionFontSize; final double captionFontSize;
final bool value; final bool value;
final bool expanded;
ModeSwitchWidget({ ModeSwitchWidget({
Key key, Key key,
@required this.caption, @required this.caption,
@required this.onChange, @required this.onChange,
this.captionFontSize, this.captionFontSize,
this.value this.value,
this.expanded: true
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
children: <Widget>[ children: <Widget>[
Expanded( _buildCaption(),
child: Text(
"$caption",
style: TextStyle(
fontSize: captionFontSize ?? Sizes.stateFontSize
),
),
),
Switch( Switch(
onChanged: (value) => onChange(value), onChanged: (value) => onChange(value),
value: value ?? false, 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;
}
} }

View 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);
}
}
}

View File

@ -79,11 +79,13 @@ class MediaPlayerWidget extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Image( Flexible(
image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"), child: Image(
height: 240.0, image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"),
width: 320.0, height: 240.0,
fit: BoxFit.contain, //width: 320.0,
fit: BoxFit.contain,
),
) )
], ],
), ),
@ -255,6 +257,8 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
double _newVolumeLevel; double _newVolumeLevel;
bool _changedHere = false; bool _changedHere = false;
String _newSoundMode;
String _newSource;
void _setVolume(double value, String entityId) { void _setVolume(double value, String entityId) {
setState(() { setState(() {
@ -276,6 +280,22 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null)); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity; 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( return Column(
children: children, children: children,

View File

@ -10,15 +10,31 @@ class DefaultEntityContainer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( final EntityModel entityModel = EntityModel.of(context);
mainAxisSize: MainAxisSize.max, return InkWell(
children: <Widget>[ onLongPress: () {
EntityIcon(), if (entityModel.handleTap) {
Expanded( entityModel.entityWrapper.handleHold();
child: EntityName(), }
), },
state onTap: () {
], if (entityModel.handleTap) {
entityModel.entityWrapper.handleTap();
}
},
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
EntityIcon(),
Flexible(
fit: FlexFit.tight,
flex: 3,
child: EntityName(),
),
state
],
),
); );
} }
} }

View File

@ -10,59 +10,14 @@ class EntityIcon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final entityModel = EntityModel.of(context); final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
return GestureDetector( return Padding(
child: Padding( padding: padding,
padding: padding, child: MaterialDesignIcons.createIconWidgetFromEntityData(
child: MaterialDesignIcons.createIconWidgetFromEntityData( entityWrapper,
entityModel.entityWrapper, iconSize,
iconSize, EntityColor.stateColor(entityWrapper.entity.state)
EntityColor.stateColor(entityModel.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;
}
}
}
}
); );
} }
} }

View File

@ -12,22 +12,16 @@ class EntityName extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final entityModel = EntityModel.of(context); final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
return GestureDetector( return Padding(
child: Padding( padding: padding,
padding: padding, child: Text(
child: Text( "${entityWrapper.displayName}",
"${entityModel.entityWrapper.displayName}", overflow: textOverflow,
overflow: textOverflow, softWrap: wordsWrap,
softWrap: wordsWrap, style: TextStyle(fontSize: fontSize),
style: TextStyle(fontSize: fontSize), textAlign: textAlign,
textAlign: textAlign,
),
), ),
onTap: () =>
entityModel.handleTap
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
: null,
); );
} }
} }

View File

@ -11,6 +11,7 @@ class GlanceEntityContainer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
List<Widget> result = []; List<Widget> result = [];
if (showName) { if (showName) {
result.add(EntityName( result.add(EntityName(
@ -21,10 +22,12 @@ class GlanceEntityContainer extends StatelessWidget {
fontSize: Sizes.smallFontSize, fontSize: Sizes.smallFontSize,
)); ));
} }
result.add(EntityIcon( result.add(
padding: EdgeInsets.all(0.0), EntityIcon(
iconSize: Sizes.largeIconSize, padding: EdgeInsets.all(0.0),
)); iconSize: Sizes.iconSize,
)
);
if (showState) { if (showState) {
result.add(SimpleEntityState( result.add(SimpleEntityState(
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -32,11 +35,20 @@ class GlanceEntityContainer extends StatelessWidget {
padding: EdgeInsets.only(top: Sizes.rowPadding), padding: EdgeInsets.only(top: Sizes.rowPadding),
)); ));
} }
return Column( return Center(
mainAxisSize: MainAxisSize.min, child: InkResponse(
mainAxisAlignment: MainAxisAlignment.start, child: ConstrainedBox(
crossAxisAlignment: CrossAxisAlignment.center, constraints: BoxConstraints(minWidth: Sizes.iconSize*2),
children: result, child: Column(
mainAxisSize: MainAxisSize.min,
//mainAxisAlignment: MainAxisAlignment.start,
//crossAxisAlignment: CrossAxisAlignment.center,
children: result,
),
),
onTap: () => entityWrapper.handleTap(),
onLongPress: () => entityWrapper.handleHold(),
),
); );
} }
} }

View File

@ -19,39 +19,34 @@ class ClimateStateWidget extends StatelessWidget {
return Padding( return Padding(
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
0.0, 0.0, Sizes.rightWidgetPadding, 0.0), 0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
child: GestureDetector( child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
children: <Widget>[ Row(
Row( children: <Widget>[
children: <Widget>[ Text("${entity.state}",
Text("${entity.state}", textAlign: TextAlign.right,
textAlign: TextAlign.right, style: new TextStyle(
style: new TextStyle( fontWeight: FontWeight.bold,
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(
fontSize: Sizes.stateFontSize, fontSize: Sizes.stateFontSize,
color: Colors.black45) )),
) : Text(" $targetTemp",
Container(height: 0.0,) textAlign: TextAlign.right,
], style: new TextStyle(
), fontSize: Sizes.stateFontSize,
onTap: () => entityModel.handleTap ))
? eventBus.fire(new ShowEntityPageEvent(entity)) ],
: null, ),
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,)
],
)); ));
} }
} }

View 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),
),
)
);
}
}

View File

@ -23,6 +23,7 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
if (entity.listOptions.isNotEmpty) { if (entity.listOptions.isNotEmpty) {
ctrl = DropdownButton<String>( ctrl = DropdownButton<String>(
value: entity.state, value: entity.state,
isExpanded: true,
items: entity.listOptions.map((String value) { items: entity.listOptions.map((String value) {
return new DropdownMenuItem<String>( return new DropdownMenuItem<String>(
value: value, value: value,
@ -36,7 +37,9 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
} else { } else {
ctrl = Text('---'); ctrl = Text('---');
} }
return Expanded( return Flexible(
flex: 2,
fit: FlexFit.tight,
//width: Entity.INPUT_WIDTH, //width: Entity.INPUT_WIDTH,
child: ctrl, child: ctrl,
); );

View File

@ -12,25 +12,22 @@ class SimpleEntityState extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final entityModel = EntityModel.of(context); final entityModel = EntityModel.of(context);
Widget result = Padding( Widget result = Padding(
padding: padding, padding: padding,
child: GestureDetector( child: Text(
child: Text( "${entityModel.entityWrapper.entity.state} ${entityModel.entityWrapper.entity.unitOfMeasurement}",
"${entityModel.entityWrapper.entity.state}${entityModel.entityWrapper.entity.unitOfMeasurement}", textAlign: textAlign,
textAlign: textAlign, maxLines: 10,
maxLines: 4, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, softWrap: true,
softWrap: true, style: new TextStyle(
style: new TextStyle( fontSize: Sizes.stateFontSize,
fontSize: Sizes.stateFontSize,
)),
onTap: () => entityModel.handleTap
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
: null,
) )
)
); );
if (expanded) { if (expanded) {
return SizedBox( return Flexible(
width: MediaQuery.of(context).size.width * 0.3, fit: FlexFit.tight,
flex: 2,
child: result, child: result,
); );
} else { } else {

View File

@ -1,6 +1,11 @@
part of '../../main.dart'; part of '../../main.dart';
class SwitchStateWidget extends StatefulWidget { class SwitchStateWidget extends StatefulWidget {
final String domainForService;
const SwitchStateWidget({Key key, this.domainForService}) : super(key: key);
@override @override
_SwitchStateWidgetState createState() => _SwitchStateWidgetState(); _SwitchStateWidgetState createState() => _SwitchStateWidgetState();
} }
@ -24,11 +29,17 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
setState(() { setState(() {
newState = entity.state; newState = entity.state;
updatedHere = true; 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( 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 @override

View File

@ -66,7 +66,9 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
_tmpValue = entity.state; _tmpValue = entity.state;
} }
if (entity.isTextField || entity.isPasswordField) { if (entity.isTextField || entity.isPasswordField) {
return Expanded( return Flexible(
fit: FlexFit.tight,
flex: 2,
//width: Entity.INPUT_WIDTH, //width: Entity.INPUT_WIDTH,
child: TextField( child: TextField(
focusNode: _focusNode, focusNode: _focusNode,

View File

@ -447,15 +447,21 @@ class HomeAssistant {
} else { } else {
if (entities.isExist(rawEntity["entity"])) { if (entities.isExist(rawEntity["entity"])) {
Entity e = entities.get(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( card.entities.add(
EntityWrapper( EntityWrapper(
entity: e, entity: e,
displayName: rawEntity["name"], displayName: rawEntity["name"],
icon: rawEntity["icon"], icon: rawEntity["icon"],
tapAction: rawEntity["tap_action"] ?? EntityTapAction.moreInfo, tapAction: tapAction,
holdAction: rawEntity["hold_action"] ?? EntityTapAction.moreInfo, holdAction: holdAction,
actionService: rawEntity["service"], tapActionService: rawEntity["service"],
actionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId} tapActionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId}
) )
); );
} }

View File

@ -4,7 +4,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/io.dart';
import 'package:progress_indicators/progress_indicators.dart';
import 'package:event_bus/event_bus.dart'; import 'package:event_bus/event_bus.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:cached_network_image/cached_network_image.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:http/http.dart' as http;
import 'package:flutter_colorpicker/material_picker.dart'; import 'package:flutter_colorpicker/material_picker.dart';
import 'package:charts_flutter/flutter.dart' as charts; import 'package:charts_flutter/flutter.dart' as charts;
import 'package:progress_indicators/progress_indicators.dart';
part 'entity_class/const.dart'; part 'entity_class/const.dart';
part 'entity_class/entity.class.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/other_entity.class.dart';
part 'entity_class/slider_entity.dart'; part 'entity_class/slider_entity.dart';
part 'entity_class/media_player_entity.class.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/common/badge.dart';
part 'entity_widgets/model_widgets.dart'; part 'entity_widgets/model_widgets.dart';
part 'entity_widgets/default_entity_container.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/cover_state.dart';
part 'entity_widgets/state/date_time_state.dart'; part 'entity_widgets/state/date_time_state.dart';
part 'entity_widgets/state/button_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/climate_controls.dart';
part 'entity_widgets/controls/cover_controls.dart'; part 'entity_widgets/controls/cover_controls.dart';
part 'entity_widgets/controls/light_controls.dart'; part 'entity_widgets/controls/light_controls.dart';
part 'entity_widgets/controls/media_player_widgets.dart'; part 'entity_widgets/controls/media_player_widgets.dart';
part 'entity_widgets/controls/fan_controls.dart';
part 'settings.page.dart'; part 'settings.page.dart';
part 'home_assistant.class.dart'; part 'home_assistant.class.dart';
part 'log.page.dart'; part 'log.page.dart';
@ -82,7 +87,7 @@ part 'ui_widgets/card_header_widget.dart';
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
const String appName = "HA Client"; const String appName = "HA Client";
const appVersion = "0.3.9"; const appVersion = "0.3.10-73";
String homeAssistantWebHost; String homeAssistantWebHost;
@ -147,7 +152,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
StreamSubscription _showEntityPageSubscription; StreamSubscription _showEntityPageSubscription;
StreamSubscription _refreshDataSubscription; StreamSubscription _refreshDataSubscription;
StreamSubscription _showErrorSubscription; StreamSubscription _showErrorSubscription;
int _isLoading = 1;
bool _settingsLoaded = false; bool _settingsLoaded = false;
bool _accountMenuExpanded = false; bool _accountMenuExpanded = false;
bool _useLovelaceUI; bool _useLovelaceUI;
@ -158,6 +162,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_settingsLoaded = false; _settingsLoaded = false;
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
TheLogger.debug("<!!!> Creating new HomeAssistant instance");
_homeAssistant = HomeAssistant(); _homeAssistant = HomeAssistant();
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) { _settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
@ -176,10 +181,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_subscribe(); _subscribe();
_refreshData(); _refreshData();
}, onError: (_) { }, onError: (_) {
setState(() { _showErrorBottomBar(message: _, errorCode: 5);
_isLoading = 2;
});
_showErrorSnackBar(message: _, errorCode: 5);
}); });
} }
@ -226,7 +228,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
if (_showEntityPageSubscription == null) { if (_showEntityPageSubscription == null) {
_showEntityPageSubscription = _showEntityPageSubscription =
eventBus.on<ShowEntityPageEvent>().listen((event) { 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) { if (_showErrorSubscription == null) {
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){ _showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
_showErrorSnackBar(message: event.text, errorCode: event.errorCode); _showErrorBottomBar(message: event.text, errorCode: event.errorCode);
}); });
} }
} }
_refreshData() async { _refreshData() async {
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI); _homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI);
setState(() { _hideBottomBar();
_hideErrorSnackBar(); _showInfoBottomBar(progress: true,);
_isLoading = 1;
});
await _homeAssistant.fetch().then((result) { await _homeAssistant.fetch().then((result) {
setState(() { _hideBottomBar();
_isLoading = 0;
});
}).catchError((e) { }).catchError((e) {
_setErrorState(e); _setErrorState(e);
}); });
@ -260,18 +258,15 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
_setErrorState(e) { _setErrorState(e) {
setState(() {
_isLoading = 2;
});
if (e is Error) { if (e is Error) {
TheLogger.error(e.toString()); TheLogger.error(e.toString());
TheLogger.error("${e.stackTrace}"); TheLogger.error("${e.stackTrace}");
_showErrorSnackBar( _showErrorBottomBar(
message: "There was some error", message: "There was some error",
errorCode: 13 errorCode: 13
); );
} else { } else {
_showErrorSnackBar( _showErrorBottomBar(
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error", message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
errorCode: e["errorCode"] != null ? e["errorCode"] : 99 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) { 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)); _homeAssistant.callService(domain, service, entityId, additionalParams).catchError((e) => _setErrorState(e));
} }
void _showEntityPage(Entity entity) { void _showEntityPage(String entityId) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( 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; 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() { Drawer _buildAppDrawer() {
List<Widget> menuItems = []; List<Widget> menuItems = [];
menuItems.add( menuItems.add(
@ -409,21 +383,51 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
); );
} }
void _hideErrorSnackBar() { void _hideBottomBar() {
_scaffoldKey?.currentState?.hideCurrentSnackBar(); //_scaffoldKey?.currentState?.hideCurrentSnackBar();
setState(() {
_showBottomBar = false;
});
} }
void _showErrorSnackBar({Key key, @required String message, @required int errorCode}) { Widget _bottomBarAction;
SnackBarAction action; 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) { switch (errorCode) {
case 9: case 9:
case 11: case 11:
case 7: case 7:
case 1: { case 1: {
action = SnackBarAction( _bottomBarAction = FlatButton(
label: "Retry", child: Text("Retry", style: textStyle),
onPressed: () { onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar(); //_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData(); _refreshData();
}, },
); );
@ -432,10 +436,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
case 5: { case 5: {
message = "Check connection settings"; message = "Check connection settings";
action = SnackBarAction( _bottomBarAction = FlatButton(
label: "Open", child: Text("Open", style: textStyle),
onPressed: () { onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar(); //_scaffoldKey?.currentState?.hideCurrentSnackBar();
Navigator.pushNamed(context, '/connection-settings'); Navigator.pushNamed(context, '/connection-settings');
}, },
); );
@ -443,10 +447,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
case 6: { case 6: {
action = SnackBarAction( _bottomBarAction = FlatButton(
label: "Settings", child: Text("Settings", style: textStyle),
onPressed: () { onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar(); //_scaffoldKey?.currentState?.hideCurrentSnackBar();
Navigator.pushNamed(context, '/connection-settings'); Navigator.pushNamed(context, '/connection-settings');
}, },
); );
@ -454,10 +458,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
case 10: { case 10: {
action = SnackBarAction( _bottomBarAction = FlatButton(
label: "Refresh", child: Text("Refresh", style: textStyle),
onPressed: () { onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar(); //_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData(); _refreshData();
}, },
); );
@ -465,10 +469,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
case 8: { case 8: {
action = SnackBarAction( _bottomBarAction = FlatButton(
label: "Reconnect", child: Text("Reconnect", style: textStyle),
onPressed: () { onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar(); //_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData(); _refreshData();
}, },
); );
@ -476,24 +480,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
default: { default: {
action = SnackBarAction( _bottomBarAction = FlatButton(
label: "Reload", child: Text("Reload", style: textStyle),
onPressed: () { onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar(); //_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData(); _refreshData();
}, },
); );
break; break;
} }
} }
_scaffoldKey.currentState.hideCurrentSnackBar(); setState(() {
_bottomBarProgress = false;
_bottomBarText = "$message (code: $errorCode)";
_showBottomBar = true;
});
/*_scaffoldKey.currentState.hideCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar( _scaffoldKey.currentState.showSnackBar(
SnackBar( SnackBar(
content: Text("$message (code: $errorCode)"), content: Text("$message (code: $errorCode)"),
action: action, action: action,
duration: Duration(hours: 1), duration: Duration(hours: 1),
) )
); );*/
} }
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@ -506,7 +515,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
floating: true, floating: true,
pinned: true, pinned: true,
primary: true, primary: true,
title: _buildAppTitle(), title: Text(_homeAssistant != null ? _homeAssistant.locationName : ""),
leading: IconButton( leading: IconButton(
icon: Icon(Icons.menu), icon: Icon(Icons.menu),
onPressed: () { onPressed: () {
@ -532,7 +541,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
Icon( Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"), MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
size: 100.0, 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 @override
Widget build(BuildContext context) { 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. // This method is rerun every time setState is called.
if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) { if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) {
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
primary: false, primary: false,
drawer: _buildAppDrawer(), drawer: _buildAppDrawer(),
bottomNavigationBar: bottomBar,
body: _buildScaffoldBody(true) body: _buildScaffoldBody(true)
); );
} else { } else {
@ -557,6 +615,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
key: _scaffoldKey, key: _scaffoldKey,
drawer: _buildAppDrawer(), drawer: _buildAppDrawer(),
primary: false, primary: false,
bottomNavigationBar: bottomBar,
body: DefaultTabController( body: DefaultTabController(
length: _homeAssistant.ui?.views?.length ?? 0, length: _homeAssistant.ui?.views?.length ?? 0,
child: _buildScaffoldBody(false), child: _buildScaffoldBody(false),

View File

@ -22,6 +22,9 @@ class MaterialDesignIcons {
"cover.closed": "mdi:window-closed", "cover.closed": "mdi:window-closed",
"cover.closing": "mdi:window-open", "cover.closing": "mdi:window-open",
"cover.opening": "mdi:window-open", "cover.opening": "mdi:window-open",
"lock.locked": "mdi:lock",
"lock.unlocked": "mdi:lock-open",
"fan": "mdi:fan"
}; };
static Map _defaultIconsByDeviceClass = { static Map _defaultIconsByDeviceClass = {

View File

@ -97,6 +97,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
], ],
), ),
body: ListView( body: ListView(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
children: <Widget>[ children: <Widget>[
Text( Text(
@ -150,8 +151,15 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
} }
), ),
new Row( new Row(
mainAxisSize: MainAxisSize.min,
children: [ 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( Switch(
value: (_newAuthType == "access_token"), value: (_newAuthType == "access_token"),
onChanged: (value) { onChanged: (value) {

View File

@ -23,37 +23,37 @@ class HACard {
Widget build(BuildContext context) { Widget build(BuildContext context) {
switch (type) { switch (type) {
case "entities": { case CardType.entities: {
return EntitiesCardWidget( return EntitiesCardWidget(
card: this, card: this,
); );
} }
case "glance": { case CardType.glance: {
return GlanceCardWidget( return GlanceCardWidget(
card: this, card: this,
); );
} }
case "media-control": { case CardType.mediaControl: {
return MediaControlCardWidget( return MediaControlCardWidget(
card: this, card: this,
); );
} }
case "weather-forecast": case CardType.weatherForecast:
case "thermostat": case CardType.thermostat:
case "sensor": case CardType.sensor:
case "plant-status": case CardType.plantStatus:
case "picture-entity": case CardType.pictureEntity:
case "picture-elements": case CardType.pictureElements:
case "picture": case CardType.picture:
case "map": case CardType.map:
case "iframe": case CardType.iframe:
case "gauge": case CardType.gauge:
case "entity-button": case CardType.entityButton:
case "conditional": case CardType.conditional:
case "alarm-panel": { case CardType.alarmPanel: {
return UnsupportedCardWidget( return UnsupportedCardWidget(
card: this, card: this,
); );

View File

@ -6,8 +6,8 @@ class Sizes {
static const extendedWidgetHeight = 50.0; static const extendedWidgetHeight = 50.0;
static const iconSize = 28.0; static const iconSize = 28.0;
static const largeIconSize = 34.0; static const largeIconSize = 34.0;
static const stateFontSize = 16.0; static const stateFontSize = 15.0;
static const nameFontSize = 16.0; static const nameFontSize = 15.0;
static const smallFontSize = 14.0; static const smallFontSize = 14.0;
static const largeFontSize = 24.0; static const largeFontSize = 24.0;
static const inputWidth = 160.0; static const inputWidth = 160.0;

View File

@ -14,7 +14,7 @@ class CardHeaderWidget extends StatelessWidget {
title: Text("$name", title: Text("$name",
textAlign: TextAlign.left, textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)), style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
); );
} else { } else {
result = new Container(width: 0.0, height: 0.0); result = new Container(width: 0.0, height: 0.0);

View File

@ -24,27 +24,25 @@ class GlanceCardWidget extends StatelessWidget {
Widget _buildRows(BuildContext context) { Widget _buildRows(BuildContext context) {
List<Widget> result = []; 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(); List<EntityWrapper> toShow = card.entities.where((entity) {return !entity.entity.isHidden;}).toList();
int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length; int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length;
card.entities.forEach((EntityWrapper entity) {
if (!entity.entity.isHidden) { toShow.forEach((EntityWrapper entity) {
result.add( result.add(
SizedBox( FractionallySizedBox(
width: width / columnsCount, widthFactor: 1/columnsCount,
child: EntityModel( child: EntityModel(
entityWrapper: entity, entityWrapper: entity,
child: entity.entity.buildGlanceWidget(context, card.showName, card.showState), child: entity.entity.buildGlanceWidget(context, card.showName, card.showState),
handleTap: true handleTap: true
), ),
) )
); );
}
}); });
return Padding( return Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding), padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
child: Wrap( child: Wrap(
alignment: WrapAlignment.spaceAround, //alignment: WrapAlignment.spaceAround,
runSpacing: Sizes.rowPadding*2, runSpacing: Sizes.rowPadding*2,
children: result, children: result,
), ),

View File

@ -1,7 +1,7 @@
name: hass_client name: hass_client
description: Home Assistant Android Client description: Home Assistant Android Client
version: 0.3.9+67 version: 0.3.10+73
environment: environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0" sdk: ">=2.0.0-dev.68.0 <3.0.0"