Compare commits

..

31 Commits

Author SHA1 Message Date
4b6dda5a9c version 0.4.4 2019-02-20 18:54:54 +02:00
4099fa0c83 WIP #302 fix SVG size 2019-02-20 18:50:58 +02:00
76057e8797 WIP #302 simple SVG support 2019-02-20 17:55:56 +02:00
538d3603dc Resolves #306 Improve camera connection 2019-02-20 16:39:57 +02:00
bc0e72ca52 version 0.4.3 2019-02-20 13:58:30 +02:00
f25a47beb2 Add camera stream reconnect on closing 2019-02-20 13:57:25 +02:00
cc3c6b0087 Resolves #307 Support different frame bounderies for MJPEG stream 2019-02-20 12:06:03 +02:00
6cf80c0bfd version 0.4.2 2019-02-19 19:22:40 +02:00
8ce9bdb7a5 Resolves #303, Resolves #304 Fix wrong camera stream url 2019-02-19 19:21:52 +02:00
31e50150b1 Resolves #263 Fix error when supported_features is null 2019-02-17 13:52:24 +02:00
e359150d97 Version 0.4.0 2019-02-16 22:05:43 +02:00
93680c981c Resolves #283 Add possibility to restrat HA 2019-02-16 22:04:49 +02:00
e06b66c523 Resolves #259 target_temp_step support for climate 2019-02-16 20:44:41 +02:00
3dea844e1e Resolves #290 Hide pin inputs if code_format is null 2019-02-16 20:33:56 +02:00
62b1af30e0 Resolves #291 some padding issues 2019-02-16 19:59:39 +02:00
e006c4e403 build number 2019-02-10 22:36:30 +02:00
983573388e Remove pull-to-refresh. Add new menu in header. 2019-02-10 22:33:46 +02:00
bdd1dc7e17 Hide light additional controls if state=unavailable 2019-02-10 19:06:07 +02:00
9c1970ee14 build number 2019-02-10 18:26:48 +02:00
d0e0bf3571 Current color for color picker fix 2019-02-10 18:24:54 +02:00
b399357517 Back to old version format 2019-02-10 17:23:57 +02:00
0290cd3a32 Resolves #273 New color picker 2019-02-10 17:15:52 +02:00
d8a1d03179 v.3.14.87 2019-02-09 02:21:20 +02:00
216fad3cb9 Fix entity page padding 2019-02-09 02:21:20 +02:00
fead6ea348 Resolves #143 Camera support 2019-02-09 02:21:20 +02:00
8814687be6 WIP #143 Initial not optimized MJPEG streaming 2019-02-09 02:21:20 +02:00
71c0e2caa0 Change version numeration 2019-02-09 02:21:20 +02:00
1531c41542 Create CODE_OF_CONDUCT.md 2019-02-01 12:55:25 +02:00
bc90d013e8 Build 86 2019-02-01 11:52:09 +02:00
2adfaca0c4 Resolves #286 2019-02-01 11:51:35 +02:00
6cc1a37d9d Resolves #285 2019-02-01 11:49:27 +02:00
33 changed files with 875 additions and 520 deletions

76
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at vyalov.egor@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

105
lib/configuration.page.dart Normal file
View File

@ -0,0 +1,105 @@
part of 'main.dart';
class ConfigurationPage extends StatefulWidget {
ConfigurationPage({Key key, this.title}) : super(key: key);
final String title;
@override
_ConfigurationPageState createState() => new _ConfigurationPageState();
}
class ConfigurationItem {
ConfigurationItem({ this.isExpanded: false, this.header, this.body });
bool isExpanded;
final String header;
final Widget body;
}
class _ConfigurationPageState extends State<ConfigurationPage> {
List<ConfigurationItem> _items;
@override
void initState() {
super.initState();
_items = <ConfigurationItem>[
ConfigurationItem(
header: 'General',
body: Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, 0.0, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Server management", style: TextStyle(fontSize: Sizes.largeFontSize)),
Container(height: Sizes.rowPadding,),
Text("Control your Home Assistant server from HA Client."),
Divider(),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlatServiceButton(
text: "Restart",
serviceName: "restart",
serviceDomain: "homeassistant",
entityId: null,
),
FlatServiceButton(
text: "Stop",
serviceName: "stop",
serviceDomain: "homeassistant",
entityId: null,
),
],
)
],
),
)
)
];
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
Navigator.pop(context);
}),
title: new Text(widget.title),
),
body: ListView(
children: [
new ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_items[index].isExpanded = !_items[index].isExpanded;
});
},
children: _items.map((ConfigurationItem item) {
return new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return CardHeaderWidget(
name: item.header,
);
},
isExpanded: item.isExpanded,
body: new Container(
child: item.body,
),
);
}).toList(),
),
],
),
);
}
@override
void dispose() {
super.dispose();
}
}

View File

@ -19,8 +19,8 @@ class _EntityViewPageState extends State<EntityViewPage> {
void initState() { void initState() {
super.initState(); super.initState();
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
Logger.d("State change event handled by entity page: ${event.entityId}");
if (event.entityId == widget.entityId) { if (event.entityId == widget.entityId) {
Logger.d("State change event handled by entity page: ${event.entityId}");
setState(() {}); setState(() {});
} }
}); });
@ -46,12 +46,9 @@ class _EntityViewPageState extends State<EntityViewPage> {
// the App.build method, and use it to set our appbar title. // the App.build method, and use it to set our appbar title.
title: new Text(_title), title: new Text(_title),
), ),
body: Padding( body: HomeAssistantModel(
padding: EdgeInsets.all(10.0),
child: HomeAssistantModel(
homeAssistant: widget.homeAssistant, homeAssistant: widget.homeAssistant,
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context) child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
)
), ),
); );
} }

View File

@ -15,6 +15,8 @@ class AutomationEntity extends Entity {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
FlatServiceButton( FlatServiceButton(
serviceDomain: domain,
entityId: entityId,
text: "TRIGGER", text: "TRIGGER",
serviceName: "trigger", serviceName: "trigger",
) )

View File

@ -6,6 +6,9 @@ class ButtonEntity extends Entity {
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {
return FlatServiceButton( return FlatServiceButton(
entityId: entityId,
serviceDomain: domain,
serviceName: 'turn_on',
text: "EXECUTE", text: "EXECUTE",
); );
} }

View File

@ -6,14 +6,12 @@ class CameraEntity extends Entity {
CameraEntity(Map rawData) : super(rawData); CameraEntity(Map rawData) : super(rawData);
bool get supportOnOff => ((attributes["supported_features"] & bool get supportOnOff => ((supportedFeatures &
CameraEntity.SUPPORT_ON_OFF) == CameraEntity.SUPPORT_ON_OFF) ==
CameraEntity.SUPPORT_ON_OFF); CameraEntity.SUPPORT_ON_OFF);
@override @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {
return CameraControlsWidget( return CameraStreamView();
url: 'https://citadel.vynn.co:8123/api/camera_proxy_stream/camera.demo_camera?token=${this.attributes['access_token']}',
);
} }
} }

View File

@ -23,44 +23,44 @@ class ClimateEntity extends Entity {
static const SUPPORT_AUX_HEAT = 2048; static const SUPPORT_AUX_HEAT = 2048;
static const SUPPORT_ON_OFF = 4096; static const SUPPORT_ON_OFF = 4096;
bool get supportTargetTemperature => ((attributes["supported_features"] & bool get supportTargetTemperature => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE); ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
bool get supportTargetTemperatureHigh => ((attributes["supported_features"] & bool get supportTargetTemperatureHigh => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH); ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH);
bool get supportTargetTemperatureLow => ((attributes["supported_features"] & bool get supportTargetTemperatureLow => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW); ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW);
bool get supportTargetHumidity => ((attributes["supported_features"] & bool get supportTargetHumidity => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY) == ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY); ClimateEntity.SUPPORT_TARGET_HUMIDITY);
bool get supportTargetHumidityHigh => ((attributes["supported_features"] & bool get supportTargetHumidityHigh => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) == ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH); ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH);
bool get supportTargetHumidityLow => ((attributes["supported_features"] & bool get supportTargetHumidityLow => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) == ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW); ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW);
bool get supportFanMode => bool get supportFanMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_FAN_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) ==
ClimateEntity.SUPPORT_FAN_MODE); ClimateEntity.SUPPORT_FAN_MODE);
bool get supportOperationMode => ((attributes["supported_features"] & bool get supportOperationMode => ((supportedFeatures &
ClimateEntity.SUPPORT_OPERATION_MODE) == ClimateEntity.SUPPORT_OPERATION_MODE) ==
ClimateEntity.SUPPORT_OPERATION_MODE); ClimateEntity.SUPPORT_OPERATION_MODE);
bool get supportHoldMode => bool get supportHoldMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_HOLD_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_HOLD_MODE) ==
ClimateEntity.SUPPORT_HOLD_MODE); ClimateEntity.SUPPORT_HOLD_MODE);
bool get supportSwingMode => bool get supportSwingMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_SWING_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
ClimateEntity.SUPPORT_SWING_MODE); ClimateEntity.SUPPORT_SWING_MODE);
bool get supportAwayMode => bool get supportAwayMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_AWAY_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_AWAY_MODE) ==
ClimateEntity.SUPPORT_AWAY_MODE); ClimateEntity.SUPPORT_AWAY_MODE);
bool get supportAuxHeat => bool get supportAuxHeat =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_AUX_HEAT) == ((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
ClimateEntity.SUPPORT_AUX_HEAT); ClimateEntity.SUPPORT_AUX_HEAT);
bool get supportOnOff => bool get supportOnOff =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_ON_OFF) == ((supportedFeatures & ClimateEntity.SUPPORT_ON_OFF) ==
ClimateEntity.SUPPORT_ON_OFF); ClimateEntity.SUPPORT_ON_OFF);
List<String> get operationList => attributes["operation_list"] != null List<String> get operationList => attributes["operation_list"] != null
@ -80,6 +80,7 @@ class ClimateEntity extends Entity {
double get targetHumidity => _getDoubleAttributeValue('humidity'); double get targetHumidity => _getDoubleAttributeValue('humidity');
double get maxHumidity => _getDoubleAttributeValue('max_humidity'); double get maxHumidity => _getDoubleAttributeValue('max_humidity');
double get minHumidity => _getDoubleAttributeValue('min_humidity'); double get minHumidity => _getDoubleAttributeValue('min_humidity');
double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5;
String get operationMode => attributes['operation_mode']; String get operationMode => attributes['operation_mode'];
String get fanMode => attributes['fan_mode']; String get fanMode => attributes['fan_mode'];
String get swingMode => attributes['swing_mode']; String get swingMode => attributes['swing_mode'];

View File

@ -11,29 +11,29 @@ class CoverEntity extends Entity {
static const SUPPORT_STOP_TILT = 64; static const SUPPORT_STOP_TILT = 64;
static const SUPPORT_SET_TILT_POSITION = 128; static const SUPPORT_SET_TILT_POSITION = 128;
bool get supportOpen => ((attributes["supported_features"] & bool get supportOpen => ((supportedFeatures &
CoverEntity.SUPPORT_OPEN) == CoverEntity.SUPPORT_OPEN) ==
CoverEntity.SUPPORT_OPEN); CoverEntity.SUPPORT_OPEN);
bool get supportClose => ((attributes["supported_features"] & bool get supportClose => ((supportedFeatures &
CoverEntity.SUPPORT_CLOSE) == CoverEntity.SUPPORT_CLOSE) ==
CoverEntity.SUPPORT_CLOSE); CoverEntity.SUPPORT_CLOSE);
bool get supportSetPosition => ((attributes["supported_features"] & bool get supportSetPosition => ((supportedFeatures &
CoverEntity.SUPPORT_SET_POSITION) == CoverEntity.SUPPORT_SET_POSITION) ==
CoverEntity.SUPPORT_SET_POSITION); CoverEntity.SUPPORT_SET_POSITION);
bool get supportStop => ((attributes["supported_features"] & bool get supportStop => ((supportedFeatures &
CoverEntity.SUPPORT_STOP) == CoverEntity.SUPPORT_STOP) ==
CoverEntity.SUPPORT_STOP); CoverEntity.SUPPORT_STOP);
bool get supportOpenTilt => ((attributes["supported_features"] & bool get supportOpenTilt => ((supportedFeatures &
CoverEntity.SUPPORT_OPEN_TILT) == CoverEntity.SUPPORT_OPEN_TILT) ==
CoverEntity.SUPPORT_OPEN_TILT); CoverEntity.SUPPORT_OPEN_TILT);
bool get supportCloseTilt => ((attributes["supported_features"] & bool get supportCloseTilt => ((supportedFeatures &
CoverEntity.SUPPORT_CLOSE_TILT) == CoverEntity.SUPPORT_CLOSE_TILT) ==
CoverEntity.SUPPORT_CLOSE_TILT); CoverEntity.SUPPORT_CLOSE_TILT);
bool get supportStopTilt => ((attributes["supported_features"] & bool get supportStopTilt => ((supportedFeatures &
CoverEntity.SUPPORT_STOP_TILT) == CoverEntity.SUPPORT_STOP_TILT) ==
CoverEntity.SUPPORT_STOP_TILT); CoverEntity.SUPPORT_STOP_TILT);
bool get supportSetTiltPosition => ((attributes["supported_features"] & bool get supportSetTiltPosition => ((supportedFeatures &
CoverEntity.SUPPORT_SET_TILT_POSITION) == CoverEntity.SUPPORT_SET_TILT_POSITION) ==
CoverEntity.SUPPORT_SET_TILT_POSITION); CoverEntity.SUPPORT_SET_TILT_POSITION);

View File

@ -91,6 +91,7 @@ class Entity {
String get lastUpdated => _getLastUpdatedFormatted(); String get lastUpdated => _getLastUpdatedFormatted();
bool get isHidden => attributes["hidden"] ?? false; bool get isHidden => attributes["hidden"] ?? false;
double get doubleState => double.tryParse(state) ?? 0.0; double get doubleState => double.tryParse(state) ?? 0.0;
int get supportedFeatures => attributes["supported_features"] ?? 0;
Entity(Map rawData) { Entity(Map rawData) {
update(rawData); update(rawData);
@ -162,7 +163,10 @@ class Entity {
return EntityModel( return EntityModel(
entityWrapper: EntityWrapper(entity: this), entityWrapper: EntityWrapper(entity: this),
child: EntityPageContainer(children: <Widget>[ child: EntityPageContainer(children: <Widget>[
DefaultEntityContainer(state: _buildStatePartForPage(context)), Padding(
padding: EdgeInsets.only(top: Sizes.rowPadding),
child: DefaultEntityContainer(state: _buildStatePartForPage(context)),
),
LastUpdatedWidget(), LastUpdatedWidget(),
Divider(), Divider(),
_buildAdditionalControlsForPage(context), _buildAdditionalControlsForPage(context),

View File

@ -8,13 +8,13 @@ class FanEntity extends Entity {
FanEntity(Map rawData) : super(rawData); FanEntity(Map rawData) : super(rawData);
bool get supportSetSpeed => ((attributes["supported_features"] & bool get supportSetSpeed => ((supportedFeatures &
FanEntity.SUPPORT_SET_SPEED) == FanEntity.SUPPORT_SET_SPEED) ==
FanEntity.SUPPORT_SET_SPEED); FanEntity.SUPPORT_SET_SPEED);
bool get supportOscillate => ((attributes["supported_features"] & bool get supportOscillate => ((supportedFeatures &
FanEntity.SUPPORT_OSCILLATE) == FanEntity.SUPPORT_OSCILLATE) ==
FanEntity.SUPPORT_OSCILLATE); FanEntity.SUPPORT_OSCILLATE);
bool get supportDirection => ((attributes["supported_features"] & bool get supportDirection => ((supportedFeatures &
FanEntity.SUPPORT_DIRECTION) == FanEntity.SUPPORT_DIRECTION) ==
FanEntity.SUPPORT_DIRECTION); FanEntity.SUPPORT_DIRECTION);

View File

@ -10,25 +10,25 @@ class LightEntity extends Entity {
static const SUPPORT_TRANSITION = 32; static const SUPPORT_TRANSITION = 32;
static const SUPPORT_WHITE_VALUE = 128; static const SUPPORT_WHITE_VALUE = 128;
bool get supportBrightness => ((attributes["supported_features"] & bool get supportBrightness => ((supportedFeatures &
LightEntity.SUPPORT_BRIGHTNESS) == LightEntity.SUPPORT_BRIGHTNESS) ==
LightEntity.SUPPORT_BRIGHTNESS); LightEntity.SUPPORT_BRIGHTNESS);
bool get supportColorTemp => ((attributes["supported_features"] & bool get supportColorTemp => ((supportedFeatures &
LightEntity.SUPPORT_COLOR_TEMP) == LightEntity.SUPPORT_COLOR_TEMP) ==
LightEntity.SUPPORT_COLOR_TEMP); LightEntity.SUPPORT_COLOR_TEMP);
bool get supportEffect => ((attributes["supported_features"] & bool get supportEffect => ((supportedFeatures &
LightEntity.SUPPORT_EFFECT) == LightEntity.SUPPORT_EFFECT) ==
LightEntity.SUPPORT_EFFECT); LightEntity.SUPPORT_EFFECT);
bool get supportFlash => ((attributes["supported_features"] & bool get supportFlash => ((supportedFeatures &
LightEntity.SUPPORT_FLASH) == LightEntity.SUPPORT_FLASH) ==
LightEntity.SUPPORT_FLASH); LightEntity.SUPPORT_FLASH);
bool get supportColor => ((attributes["supported_features"] & bool get supportColor => ((supportedFeatures &
LightEntity.SUPPORT_COLOR) == LightEntity.SUPPORT_COLOR) ==
LightEntity.SUPPORT_COLOR); LightEntity.SUPPORT_COLOR);
bool get supportTransition => ((attributes["supported_features"] & bool get supportTransition => ((supportedFeatures &
LightEntity.SUPPORT_TRANSITION) == LightEntity.SUPPORT_TRANSITION) ==
LightEntity.SUPPORT_TRANSITION); LightEntity.SUPPORT_TRANSITION);
bool get supportWhiteValue => ((attributes["supported_features"] & bool get supportWhiteValue => ((supportedFeatures &
LightEntity.SUPPORT_WHITE_VALUE) == LightEntity.SUPPORT_WHITE_VALUE) ==
LightEntity.SUPPORT_WHITE_VALUE); LightEntity.SUPPORT_WHITE_VALUE);
@ -37,17 +37,19 @@ class LightEntity extends Entity {
int get colorTemp => _getIntAttributeValue("color_temp"); int get colorTemp => _getIntAttributeValue("color_temp");
double get maxMireds => _getDoubleAttributeValue("max_mireds"); double get maxMireds => _getDoubleAttributeValue("max_mireds");
double get minMireds => _getDoubleAttributeValue("min_mireds"); double get minMireds => _getDoubleAttributeValue("min_mireds");
Color get color => _getColor(); HSVColor get color => _getColor();
bool get isAdditionalControls => ((attributes["supported_features"] != null) && (attributes["supported_features"] != 0)); bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
List<String> get effectList => getStringListAttributeValue("effect_list"); List<String> get effectList => getStringListAttributeValue("effect_list");
LightEntity(Map rawData) : super(rawData); LightEntity(Map rawData) : super(rawData);
Color _getColor() { HSVColor _getColor() {
List rgb = attributes["rgb_color"]; List hs = attributes["hs_color"];
try { try {
if ((rgb != null) && (rgb.length > 0)) { if ((hs != null) && (hs.length > 0)) {
return Color.fromARGB(255, rgb[0], rgb[1], rgb[2]); double sat = hs[1]/100;
String ssat = sat.toStringAsFixed(2);
return HSVColor.fromAHSV(1.0, hs[0], double.parse(ssat), 1.0);
} else { } else {
return null; return null;
} }
@ -63,7 +65,7 @@ class LightEntity extends Entity {
@override @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {
if (!isAdditionalControls) { if (!isAdditionalControls || state == EntityState.unavailable) {
return Container(height: 0.0, width: 0.0); return Container(height: 0.0, width: 0.0);
} else { } else {
return LightControlsWidget(); return LightControlsWidget();

View File

@ -22,53 +22,53 @@ class MediaPlayerEntity extends Entity {
MediaPlayerEntity(Map rawData) : super(rawData); MediaPlayerEntity(Map rawData) : super(rawData);
bool get supportPause => ((attributes["supported_features"] & bool get supportPause => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PAUSE) == MediaPlayerEntity.SUPPORT_PAUSE) ==
MediaPlayerEntity.SUPPORT_PAUSE); MediaPlayerEntity.SUPPORT_PAUSE);
bool get supportSeek => ((attributes["supported_features"] & bool get supportSeek => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SEEK) == MediaPlayerEntity.SUPPORT_SEEK) ==
MediaPlayerEntity.SUPPORT_SEEK); MediaPlayerEntity.SUPPORT_SEEK);
bool get supportVolumeSet => ((attributes["supported_features"] & bool get supportVolumeSet => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_VOLUME_SET) == MediaPlayerEntity.SUPPORT_VOLUME_SET) ==
MediaPlayerEntity.SUPPORT_VOLUME_SET); MediaPlayerEntity.SUPPORT_VOLUME_SET);
bool get supportVolumeMute => ((attributes["supported_features"] & bool get supportVolumeMute => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_VOLUME_MUTE) == MediaPlayerEntity.SUPPORT_VOLUME_MUTE) ==
MediaPlayerEntity.SUPPORT_VOLUME_MUTE); MediaPlayerEntity.SUPPORT_VOLUME_MUTE);
bool get supportPreviousTrack => ((attributes["supported_features"] & bool get supportPreviousTrack => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) == MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) ==
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK); MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK);
bool get supportNextTrack => ((attributes["supported_features"] & bool get supportNextTrack => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_NEXT_TRACK) == MediaPlayerEntity.SUPPORT_NEXT_TRACK) ==
MediaPlayerEntity.SUPPORT_NEXT_TRACK); MediaPlayerEntity.SUPPORT_NEXT_TRACK);
bool get supportTurnOn => ((attributes["supported_features"] & bool get supportTurnOn => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_TURN_ON) == MediaPlayerEntity.SUPPORT_TURN_ON) ==
MediaPlayerEntity.SUPPORT_TURN_ON); MediaPlayerEntity.SUPPORT_TURN_ON);
bool get supportTurnOff => ((attributes["supported_features"] & bool get supportTurnOff => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_TURN_OFF) == MediaPlayerEntity.SUPPORT_TURN_OFF) ==
MediaPlayerEntity.SUPPORT_TURN_OFF); MediaPlayerEntity.SUPPORT_TURN_OFF);
bool get supportPlayMedia => ((attributes["supported_features"] & bool get supportPlayMedia => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PLAY_MEDIA) == MediaPlayerEntity.SUPPORT_PLAY_MEDIA) ==
MediaPlayerEntity.SUPPORT_PLAY_MEDIA); MediaPlayerEntity.SUPPORT_PLAY_MEDIA);
bool get supportVolumeStep => ((attributes["supported_features"] & bool get supportVolumeStep => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_VOLUME_STEP) == MediaPlayerEntity.SUPPORT_VOLUME_STEP) ==
MediaPlayerEntity.SUPPORT_VOLUME_STEP); MediaPlayerEntity.SUPPORT_VOLUME_STEP);
bool get supportSelectSource => ((attributes["supported_features"] & bool get supportSelectSource => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SELECT_SOURCE) == MediaPlayerEntity.SUPPORT_SELECT_SOURCE) ==
MediaPlayerEntity.SUPPORT_SELECT_SOURCE); MediaPlayerEntity.SUPPORT_SELECT_SOURCE);
bool get supportStop => ((attributes["supported_features"] & bool get supportStop => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_STOP) == MediaPlayerEntity.SUPPORT_STOP) ==
MediaPlayerEntity.SUPPORT_STOP); MediaPlayerEntity.SUPPORT_STOP);
bool get supportClearPlaylist => ((attributes["supported_features"] & bool get supportClearPlaylist => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) == MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) ==
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST); MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST);
bool get supportPlay => ((attributes["supported_features"] & bool get supportPlay => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PLAY) == MediaPlayerEntity.SUPPORT_PLAY) ==
MediaPlayerEntity.SUPPORT_PLAY); MediaPlayerEntity.SUPPORT_PLAY);
bool get supportShuffleSet => ((attributes["supported_features"] & bool get supportShuffleSet => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SHUFFLE_SET) == MediaPlayerEntity.SUPPORT_SHUFFLE_SET) ==
MediaPlayerEntity.SUPPORT_SHUFFLE_SET); MediaPlayerEntity.SUPPORT_SHUFFLE_SET);
bool get supportSelectSoundMode => ((attributes["supported_features"] & bool get supportSelectSoundMode => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) == MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE); MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE);

View File

@ -86,9 +86,9 @@ class EntityCollection {
case "fan": { case "fan": {
return FanEntity(rawEntityData); return FanEntity(rawEntityData);
} }
/*case "camera": { case "camera": {
return CameraEntity(rawEntityData); return CameraEntity(rawEntityData);
}*/ }
case "alarm_control_panel": { case "alarm_control_panel": {
return AlarmControlPanelEntity(rawEntityData); return AlarmControlPanelEntity(rawEntityData);
} }

View File

@ -23,7 +23,22 @@ class BadgeWidget extends StatelessWidget {
); );
break; break;
} }
case "sensor": case "camera":
case "media_player":
case "binary_sensor":
{
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
entityModel.entityWrapper, iconSize, Colors.black);
break;
}
case "device_tracker":
{
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
entityModel.entityWrapper, iconSize, Colors.black);
onBadgeTextValue = entityModel.entityWrapper.entity.state;
break;
}
default:
{ {
onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement; onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement;
badgeIcon = Center( badgeIcon = Center(
@ -37,18 +52,6 @@ class BadgeWidget extends StatelessWidget {
); );
break; break;
} }
case "device_tracker":
{
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
entityModel.entityWrapper, iconSize, Colors.black);
onBadgeTextValue = entityModel.entityWrapper.entity.state;
break;
}
default:
{
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
entityModel.entityWrapper, iconSize, Colors.black);
}
} }
Widget onBadgeText; Widget onBadgeText;
if (onBadgeTextValue == null || onBadgeTextValue.length == 0) { if (onBadgeTextValue == null || onBadgeTextValue.length == 0) {

View File

@ -0,0 +1,175 @@
part of '../../main.dart';
class CameraStreamView extends StatefulWidget {
CameraStreamView({Key key}) : super(key: key);
@override
_CameraStreamViewState createState() => _CameraStreamViewState();
}
class _CameraStreamViewState extends State<CameraStreamView> {
@override
void initState() {
super.initState();
}
CameraEntity _entity;
http.Client client;
http.StreamedResponse response;
List<int> binaryImage = [];
bool timeToStop = false;
Completer streamCompleter;
bool started = false;
bool useSVG = false;
void _connect() async {
started = true;
timeToStop = false;
String streamUrl = '$homeAssistantWebHost/api/camera_proxy_stream/${_entity.entityId}?token=${_entity.attributes['access_token']}';
client = new http.Client(); // create a client to make api calls
http.Request request = new http.Request("GET", Uri.parse(streamUrl)); // create get request
Logger.d("[Sending] ==> $streamUrl");
response = await client.send(request);
Logger.d("[Received] <== ${response.headers}");
String frameBoundary = response.headers['content-type'].split('boundary=')[1];
final int frameBoundarySize = frameBoundary.length;
List<int> primaryBuffer=[];
int imageSizeStart = 59;
int imageSizeEnd = 0;
int imageStart = 0;
int imageSize = 0;
String strBuffer = "";
String contentType = "";
streamCompleter = Completer();
response.stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
primaryBuffer.addAll(data);
imageStart = 0;
imageSizeEnd = 0;
if (primaryBuffer.length >= imageSizeStart + 10) {
contentType = utf8.decode(
primaryBuffer.sublist(frameBoundarySize+16, imageSizeStart + 10), allowMalformed: true).split("\r\n")[0];
useSVG = contentType == "image/svg+xml";
imageSizeStart = frameBoundarySize + 16 + contentType.length + 18;
for (int i = imageSizeStart; i < primaryBuffer.length - 4; i++) {
strBuffer = utf8.decode(
primaryBuffer.sublist(i, i + 4), allowMalformed: true);
if (strBuffer == "\r\n\r\n") {
imageSizeEnd = i;
imageStart = i + 4;
break;
}
}
if (imageSizeEnd > 0) {
imageSize = int.tryParse(utf8.decode(
primaryBuffer.sublist(imageSizeStart, imageSizeEnd),
allowMalformed: true));
//Logger.d("content-length: $imageSize");
if (imageSize != null &&
primaryBuffer.length >= imageStart + imageSize + 2) {
sink.add(
primaryBuffer.sublist(
imageStart, imageStart + imageSize));
primaryBuffer.removeRange(0, imageStart + imageSize + 2);
}
}
}
if (timeToStop) {
sink?.close();
streamCompleter.complete();
}
},
handleError: (error, stack, sink) {
Logger.e("Error parsing MJPEG stream: $error");
},
handleDone: (sink) {
Logger.d("Camera stream finished. Reconnecting...");
sink?.close();
streamCompleter?.complete();
_reconnect();
},
)
).listen((d) {
if (!timeToStop) {
setState(() {
binaryImage = d;
});
}
});
}
void _reconnect() {
disconnect().then((_){
_connect();
});
}
Future disconnect() {
Completer disconF = Completer();
timeToStop = true;
if (streamCompleter != null && !streamCompleter.isCompleted) {
streamCompleter.future.then((_) {
client?.close();
disconF.complete();
});
} else {
client?.close();
disconF.complete();
}
return disconF.future;
}
@override
Widget build(BuildContext context) {
if (!started) {
_entity = EntityModel
.of(context)
.entityWrapper
.entity;
_connect();
}
if (binaryImage.isEmpty) {
return Column(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20.0),
child: const CircularProgressIndicator()
)
],
);
} else {
if (useSVG) {
return Column(
children: <Widget>[
SvgPicture.memory(
Uint8List.fromList(binaryImage),
placeholderBuilder: (BuildContext context) =>
new Container(
padding: const EdgeInsets.all(20.0),
child: const CircularProgressIndicator()
),
)
],
);
} else {
return Column(
children: <Widget>[
Image.memory(
Uint8List.fromList(binaryImage), gaplessPlayback: true),
],
);
}
}
}
@override
void dispose() {
disconnect();
super.dispose();
}
}

View File

@ -21,10 +21,13 @@ class EntityAttributesList extends StatelessWidget {
} }
}); });
} }
return Column( return Padding(
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
child: Column(
children: attrs, children: attrs,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
)
); );
} }

View File

@ -4,29 +4,30 @@ class FlatServiceButton extends StatelessWidget {
final String serviceDomain; final String serviceDomain;
final String serviceName; final String serviceName;
final String entityId;
final String text; final String text;
final double fontSize; final double fontSize;
FlatServiceButton({ FlatServiceButton({
Key key, Key key,
this.serviceDomain, @required this.serviceDomain,
this.serviceName: "turn_on", @required this.serviceName,
@required this.entityId,
@required this.text, @required this.text,
this.fontSize: Sizes.stateFontSize this.fontSize: Sizes.stateFontSize
}) : super(key: key); }) : super(key: key);
void _setNewState(Entity entity) { void _setNewState() {
eventBus.fire(new ServiceCallEvent(serviceDomain ?? entity.domain, serviceName, entity.entityId, null)); eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
return SizedBox( return SizedBox(
height: fontSize*2.5, height: fontSize*2.5,
child: FlatButton( child: FlatButton(
onPressed: (() { onPressed: (() {
_setNewState(entityModel.entityWrapper.entity); _setNewState();
}), }),
child: Text( child: Text(
text, text,

View File

@ -6,7 +6,7 @@ class LastUpdatedWidget extends StatelessWidget {
final entityModel = EntityModel.of(context); final entityModel = EntityModel.of(context);
return Padding( return Padding(
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
Sizes.leftWidgetPadding, 0.0, 0.0, 0.0), Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0, 0.0),
child: Text( child: Text(
'${entityModel.entityWrapper.entity.lastUpdated}', '${entityModel.entityWrapper.entity.lastUpdated}',
textAlign: TextAlign.left, textAlign: TextAlign.left,

View File

@ -0,0 +1,101 @@
part of '../../main.dart';
class LightColorPicker extends StatefulWidget {
final HSVColor color;
final onColorSelected;
final double hueStep;
final double saturationStep;
final EdgeInsets padding;
LightColorPicker({this.color, this.onColorSelected, this.hueStep: 15.0, this.saturationStep: 0.2, this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0)});
@override
LightColorPickerState createState() => new LightColorPickerState();
}
class LightColorPickerState extends State<LightColorPicker> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
List<Widget> colorRows = [];
Border border;
bool isSomethingSelected = false;
Logger.d("Current colotfor picker: [${widget.color.hue}, ${widget.color.saturation}]");
for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) {
List<Widget> rowChildren = [];
//Logger.d("$saturation");
double roundedSaturation = double.parse(widget.color.saturation.toStringAsFixed(1));
//Logger.d("Rounded saturation=$roundedSaturation");
for (double hue = 0; hue <= (365 - widget.hueStep);
hue += widget.hueStep) {
bool isExactHue = widget.color.hue.round() == hue;
bool isHueInRange = widget.color.hue.round() > hue && widget.color.hue.round() < (hue+widget.hueStep);
bool isExactSaturation = roundedSaturation == saturation;
bool isSaturationInRange = roundedSaturation > saturation && roundedSaturation < double.parse((saturation+widget.saturationStep).toStringAsFixed(1));
if ((isExactHue || isHueInRange) && (isExactSaturation || isSaturationInRange)) {
//Logger.d("$isExactHue $isHueInRange $isExactSaturation $isSaturationInRange (${saturation+widget.saturationStep})");
border = Border.all(
width: 2.0,
color: Colors.white,
);
isSomethingSelected = true;
} else {
border = null;
}
HSVColor currentColor = HSVColor.fromAHSV(1.0, hue, double.parse(saturation.toStringAsFixed(2)), 1.0);
rowChildren.add(
Flexible(
child: GestureDetector(
child: Container(
height: 40.0,
decoration: BoxDecoration(
color: currentColor.toColor(),
border: border,
),
),
onTap: () => widget.onColorSelected(currentColor),
)
)
);
}
colorRows.add(
Row(
children: rowChildren,
)
);
}
colorRows.add(
Flexible(
child: GestureDetector(
child: Container(
height: 40.0,
decoration: BoxDecoration(
color: Colors.white,
border: isSomethingSelected ? null : Border.all(
width: 2.0,
color: Colors.amber[200],
)
),
),
onTap: () => widget.onColorSelected(HSVColor.fromAHSV(1.0, 30.0, 0.0, 1.0)),
)
)
);
return Padding(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: colorRows,
),
padding: widget.padding,
);
}
}

View File

@ -7,8 +7,8 @@ class ModeSelectorWidget extends StatelessWidget {
final String value; final String value;
final double captionFontSize; final double captionFontSize;
final double valueFontSize; final double valueFontSize;
final double bottomPadding;
final onChange; final onChange;
final EdgeInsets padding;
ModeSelectorWidget({ ModeSelectorWidget({
Key key, Key key,
@ -18,12 +18,14 @@ class ModeSelectorWidget extends StatelessWidget {
@required this.onChange, @required this.onChange,
this.captionFontSize, this.captionFontSize,
this.valueFontSize, this.valueFontSize,
this.bottomPadding this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Padding(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text("$caption", style: TextStyle( Text("$caption", style: TextStyle(
@ -54,9 +56,9 @@ class ModeSelectorWidget extends StatelessWidget {
), ),
) )
], ],
), )
Container(height: bottomPadding ?? Sizes.rowPadding,)
], ],
),
); );
} }
} }

View File

@ -7,6 +7,7 @@ class ModeSwitchWidget extends StatelessWidget {
final double captionFontSize; final double captionFontSize;
final bool value; final bool value;
final bool expanded; final bool expanded;
final EdgeInsets padding;
ModeSwitchWidget({ ModeSwitchWidget({
Key key, Key key,
@ -14,12 +15,15 @@ class ModeSwitchWidget extends StatelessWidget {
@required this.onChange, @required this.onChange,
this.captionFontSize, this.captionFontSize,
this.value, this.value,
this.expanded: true this.expanded: true,
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Padding(
padding: this.padding,
child: Row(
children: <Widget>[ children: <Widget>[
_buildCaption(), _buildCaption(),
Switch( Switch(
@ -27,6 +31,7 @@ class ModeSwitchWidget extends StatelessWidget {
value: value ?? false, value: value ?? false,
) )
], ],
)
); );
} }

View File

@ -10,8 +10,9 @@ class UniversalSlider extends StatelessWidget {
final double min; final double min;
final double max; final double max;
final double value; final double value;
final EdgeInsets padding;
const UniversalSlider({Key key, this.onChanged, this.onChangeEnd, this.leading, this.closing, this.title, this.min, this.max, this.value}) : super(key: key); const UniversalSlider({Key key, this.onChanged, this.onChangeEnd, this.leading, this.closing, this.title, this.min, this.max, this.value, this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0)}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -33,7 +34,9 @@ class UniversalSlider extends StatelessWidget {
if (closing != null) { if (closing != null) {
row.add(closing); row.add(closing);
} }
return Column( return Padding(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container(height: Sizes.rowPadding,), Container(height: Sizes.rowPadding,),
@ -48,6 +51,7 @@ class UniversalSlider extends StatelessWidget {
), ),
Container(height: Sizes.rowPadding,) Container(height: Sizes.rowPadding,)
], ],
),
); );
} }

View File

@ -5,7 +5,7 @@ class AlarmControlPanelControlsWidget extends StatefulWidget {
final bool extended; final bool extended;
final List states; final List states;
const AlarmControlPanelControlsWidget({Key key, @required this.extended, this.states: const ["arm_home", "arm_away"]}) : super(key: key); const AlarmControlPanelControlsWidget({Key key, @required this.extended, this.states}) : super(key: key);
@override @override
_AlarmControlPanelControlsWidgetWidgetState createState() => _AlarmControlPanelControlsWidgetWidgetState(); _AlarmControlPanelControlsWidgetWidgetState createState() => _AlarmControlPanelControlsWidgetWidgetState();
@ -15,6 +15,14 @@ class AlarmControlPanelControlsWidget extends StatefulWidget {
class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPanelControlsWidget> { class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPanelControlsWidget> {
String code = ""; String code = "";
List supportedStates;
@override
void initState() {
super.initState();
supportedStates = widget.states ?? ["arm_home", "arm_away"];
}
void _callService(AlarmControlPanelEntity entity, String service) { void _callService(AlarmControlPanelEntity entity, String service) {
eventBus.fire(new ServiceCallEvent( eventBus.fire(new ServiceCallEvent(
@ -72,7 +80,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
final AlarmControlPanelEntity entity = entityModel.entityWrapper.entity; final AlarmControlPanelEntity entity = entityModel.entityWrapper.entity;
List<Widget> buttons = []; List<Widget> buttons = [];
if (entity.state == EntityState.alarm_disarmed) { if (entity.state == EntityState.alarm_disarmed) {
if (widget.states.contains("arm_home")) { if (supportedStates.contains("arm_home")) {
buttons.add( buttons.add(
RaisedButton( RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_home"), onPressed: () => _callService(entity, "alarm_arm_home"),
@ -80,7 +88,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
) )
); );
} }
if (widget.states.contains("arm_away")) { if (supportedStates.contains("arm_away")) {
buttons.add( buttons.add(
RaisedButton( RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_away"), onPressed: () => _callService(entity, "alarm_arm_away"),
@ -89,7 +97,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
); );
} }
if (widget.extended) { if (widget.extended) {
if (widget.states.contains("arm_night")) { if (supportedStates.contains("arm_night")) {
buttons.add( buttons.add(
RaisedButton( RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_night"), onPressed: () => _callService(entity, "alarm_arm_night"),
@ -97,7 +105,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
) )
); );
} }
if (widget.states.contains("arm_custom_bypass")) { if (supportedStates.contains("arm_custom_bypass")) {
buttons.add( buttons.add(
RaisedButton( RaisedButton(
onPressed: () => onPressed: () =>
@ -115,7 +123,11 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
) )
); );
} }
Widget pinPad = Padding( Widget pinPad;
if (entity.attributes["code_format"] == null) {
pinPad = Container(width: 0.0, height: 0.0,);
} else {
pinPad = Padding(
padding: EdgeInsets.only(bottom: Sizes.rowPadding), padding: EdgeInsets.only(bottom: Sizes.rowPadding),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -188,7 +200,12 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
], ],
) )
); );
Widget inputWrapper = Container( }
Widget inputWrapper;
if (entity.attributes["code_format"] == null) {
inputWrapper = Container(width: 0.0, height: 0.0,);
} else {
inputWrapper = Container(
width: 150.0, width: 150.0,
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
@ -208,6 +225,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
} }
) )
); );
}
Widget buttonsWrapper = Padding( Widget buttonsWrapper = Padding(
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding), padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
child: Wrap( child: Wrap(

View File

@ -1,134 +0,0 @@
part of '../../main.dart';
class CameraControlsWidget extends StatefulWidget {
final String url;
CameraControlsWidget({Key key, @required this.url}) : super(key: key);
@override
_CameraControlsWidgetState createState() => _CameraControlsWidgetState();
}
class _CameraControlsWidgetState extends State<CameraControlsWidget> {
@override
void initState() {
super.initState();
Logger.d("Camera source: ${widget.url}");
_getData();
}
http.Client client;
http.StreamedResponse response;
List<int> binaryImage = [];
void _getData() async {
client = new http.Client(); // create a client to make api calls
http.Request request = new http.Request("GET", Uri.parse(widget.url)); // create get request
//Log.d
Logger.d("==Sending");
response = await client.send(request); // sends request and waits for response stream
Logger.d("==Reading");
int byteCount = 0;
Logger.d("==${response.headers}");
List<int> primaryBuffer=[];
List<int> secondaryBuffer=[];
int imageStart = 0;
int imageEnd = 0;
response.stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
primaryBuffer.addAll(data);
Logger.d("== data recived: ${data.length}");
Logger.d("== primary buffer size: ${primaryBuffer.length}");
//Logger.d("${data.toString()}");
for (int i = 0; i < primaryBuffer.length - 15; i++) {
String startBoundary = utf8.decode(primaryBuffer.sublist(i, i+15),allowMalformed: true);
if (startBoundary == "--frameboundary") {
Logger.d("== START found at $i");
imageStart = i;
//secondaryBuffer.addAll(primaryBuffer.sublist(i));
//Logger.d("== secondary.length=${secondaryBuffer.length}. clearinig primary");
//primaryBuffer.clear();
break;
}
/*String startBoundary = utf8.decode(primaryBuffer.sublist(i, i+4),allowMalformed: true);
if (startBoundary == "\r\n\r\n") {
Logger.d("==Binary image start found ($i). primary.length=${primaryBuffer.length}");
secondaryBuffer.addAll(primaryBuffer.sublist(i+5));
Logger.d("==secondary.length=${secondaryBuffer.length}. clearinig primary");
primaryBuffer.clear();
Logger.d("==secondary.length=${secondaryBuffer.length}");
for (int j = 0; j < secondaryBuffer.length - 15; j++) {
String endBoundary = utf8.decode(secondaryBuffer.sublist(j, j+15),allowMalformed: true);
if (endBoundary == "--frameboundary") {
Logger.d("==Binary image end found");
sink.add(secondaryBuffer.sublist(0, j-1));
primaryBuffer.addAll(secondaryBuffer.sublist(j));
secondaryBuffer.clear();
break;
}
}
break;
}*/
}
for (int i = imageStart+15; i < primaryBuffer.length - 15; i++) {
String endBoundary = utf8.decode(primaryBuffer.sublist(i, i+15),allowMalformed: true);
if (endBoundary == "--frameboundary") {
Logger.d("==END found");
imageEnd = i;
sink.add(primaryBuffer.sublist(imageStart, imageEnd - 1));
primaryBuffer = primaryBuffer.sublist(imageEnd);
break;
}
}
//byteCount += data.length;
//Logger.d("$byteCount");
},
handleError: (error, stack, sink) {},
handleDone: (sink) {
sink.close();
},
)
).listen((d) {
setState(() {
binaryImage = d;
});
//Logger.d("==binary imagesize=${d.length}");
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Image.memory(Uint8List.fromList(binaryImage)),
FlatButton(
child: Text("VIEW"),
onPressed: () {
setState(() {
});
},
)
],
);
return Image.network("${widget.url}");
return FlatButton(
child: Text("VIEW"),
onPressed: () {
HAUtils.launchURL(widget.url);
},
);
}
@override
void dispose() {
client.close();
super.dispose();
}
}

View File

@ -42,33 +42,33 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
_changedHere = false; _changedHere = false;
} }
void _temperatureUp(ClimateEntity entity, double step) { void _temperatureUp(ClimateEntity entity) {
_tmpTemperature = ((_tmpTemperature + step) <= entity.maxTemp) ? _tmpTemperature + step : entity.maxTemp; _tmpTemperature = ((_tmpTemperature + entity.temperatureStep) <= entity.maxTemp) ? _tmpTemperature + entity.temperatureStep : entity.maxTemp;
_setTemperature(entity); _setTemperature(entity);
} }
void _temperatureDown(ClimateEntity entity, double step) { void _temperatureDown(ClimateEntity entity) {
_tmpTemperature = ((_tmpTemperature - step) >= entity.minTemp) ? _tmpTemperature - step : entity.minTemp; _tmpTemperature = ((_tmpTemperature - entity.temperatureStep) >= entity.minTemp) ? _tmpTemperature - entity.temperatureStep : entity.minTemp;
_setTemperature(entity); _setTemperature(entity);
} }
void _targetLowUp(ClimateEntity entity, double step) { void _targetLowUp(ClimateEntity entity) {
_tmpTargetLow = ((_tmpTargetLow + step) <= entity.maxTemp) ? _tmpTargetLow + step : entity.maxTemp; _tmpTargetLow = ((_tmpTargetLow + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetLow + entity.temperatureStep : entity.maxTemp;
_setTargetTemp(entity); _setTargetTemp(entity);
} }
void _targetLowDown(ClimateEntity entity, double step) { void _targetLowDown(ClimateEntity entity) {
_tmpTargetLow = ((_tmpTargetLow - step) >= entity.minTemp) ? _tmpTargetLow - step : entity.minTemp; _tmpTargetLow = ((_tmpTargetLow - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetLow - entity.temperatureStep : entity.minTemp;
_setTargetTemp(entity); _setTargetTemp(entity);
} }
void _targetHighUp(ClimateEntity entity, double step) { void _targetHighUp(ClimateEntity entity) {
_tmpTargetHigh = ((_tmpTargetHigh + step) <= entity.maxTemp) ? _tmpTargetHigh + step : entity.maxTemp; _tmpTargetHigh = ((_tmpTargetHigh + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetHigh + entity.temperatureStep : entity.maxTemp;
_setTargetTemp(entity); _setTargetTemp(entity);
} }
void _targetHighDown(ClimateEntity entity, double step) { void _targetHighDown(ClimateEntity entity) {
_tmpTargetHigh = ((_tmpTargetHigh - step) >= entity.minTemp) ? _tmpTargetHigh - step : entity.minTemp; _tmpTargetHigh = ((_tmpTargetHigh - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetHigh - entity.temperatureStep : entity.minTemp;
_setTargetTemp(entity); _setTargetTemp(entity);
} }
@ -296,8 +296,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTemperature, value: _tmpTemperature,
fontColor: _showPending ? Colors.red : Colors.black, fontColor: _showPending ? Colors.red : Colors.black,
onDec: () => _temperatureDown(entity, 0.5), onDec: () => _temperatureDown(entity),
onInc: () => _temperatureUp(entity, 0.5), onInc: () => _temperatureUp(entity),
) )
], ],
); );
@ -313,8 +313,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTargetLow, value: _tmpTargetLow,
fontColor: _showPending ? Colors.red : Colors.black, fontColor: _showPending ? Colors.red : Colors.black,
onDec: () => _targetLowDown(entity, 0.5), onDec: () => _targetLowDown(entity),
onInc: () => _targetLowUp(entity, 0.5), onInc: () => _targetLowUp(entity),
), ),
Expanded( Expanded(
child: Container(height: 10.0), child: Container(height: 10.0),
@ -326,8 +326,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTargetHigh, value: _tmpTargetHigh,
fontColor: _showPending ? Colors.red : Colors.black, fontColor: _showPending ? Colors.red : Colors.black,
onDec: () => _targetHighDown(entity, 0.5), onDec: () => _targetHighDown(entity),
onInc: () => _targetHighUp(entity, 0.5), onInc: () => _targetHighUp(entity),
) )
); );
} }

View File

@ -11,7 +11,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
int _tmpBrightness; int _tmpBrightness;
int _tmpColorTemp = 0; int _tmpColorTemp = 0;
Color _tmpColor = Colors.white; HSVColor _tmpColor = HSVColor.fromAHSV(1.0, 30.0, 0.0, 1.0);
bool _changedHere = false; bool _changedHere = false;
String _tmpEffect; String _tmpEffect;
@ -48,20 +48,14 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
}); });
} }
void _setColor(LightEntity entity, Color color) { void _setColor(LightEntity entity, HSVColor color) {
setState(() { setState(() {
_tmpColor = color; _tmpColor = color;
_changedHere = true; _changedHere = true;
Logger.d( "Color: [${color.red}, ${color.green}, ${color.blue}]"); Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
if ((color == Colors.black) || ((color.red == color.green) && (color.green == color.blue))) {
eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_off", entity.entityId,
null));
} else {
eventBus.fire(new ServiceCallEvent( eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_on", entity.entityId, entity.domain, "turn_on", entity.entityId,
{"rgb_color": [color.red, color.green, color.blue]})); {"hs_color": [color.hue, color.saturation*100]}));
}
}); });
} }
@ -98,7 +92,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
} }
Widget _buildBrightnessControl(LightEntity entity) { Widget _buildBrightnessControl(LightEntity entity) {
if ((entity.supportBrightness) && (_tmpBrightness != null) && (entity.state != EntityState.unavailable)) { if ((entity.supportBrightness) && (_tmpBrightness != null)) {
return UniversalSlider( return UniversalSlider(
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -109,7 +103,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
min: 0.0, min: 0.0,
max: 255.0, max: 255.0,
onChangeEnd: (value) => _setBrightness(entity, value), onChangeEnd: (value) => _setBrightness(entity, value),
value: _tmpBrightness.toDouble(), value: _tmpBrightness == null ? 0.0 : _tmpBrightness.toDouble(),
leading: Icon(Icons.brightness_5), leading: Icon(Icons.brightness_5),
title: "Brightness", title: "Brightness",
); );
@ -123,7 +117,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
return UniversalSlider( return UniversalSlider(
title: "Color temperature", title: "Color temperature",
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),), leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
value: _tmpColorTemp.toDouble(), value: _tmpColorTemp == null ? entity.maxMireds : _tmpColorTemp.toDouble(),
onChangeEnd: (value) => _setColorTemp(entity, value), onChangeEnd: (value) => _setColorTemp(entity, value),
max: entity.maxMireds, max: entity.maxMireds,
min: entity.minMireds, min: entity.minMireds,
@ -142,25 +136,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
Widget _buildColorControl(LightEntity entity) { Widget _buildColorControl(LightEntity entity) {
if (entity.supportColor) { if (entity.supportColor) {
return Column( return LightColorPicker(
crossAxisAlignment: CrossAxisAlignment.center, color: _tmpColor,
children: <Widget>[ onColorSelected: (color) => _setColor(entity, color),
Container(height: Sizes.rowPadding,),
RaisedButton(
onPressed: () => _showColorPicker(entity),
color: _tmpColor ?? Colors.black45,
child: Text(
"COLOR",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 50.0,
fontWeight: FontWeight.bold,
color: Colors.black12,
),
),
),
Container(height: 2*Sizes.rowPadding,),
],
); );
} else { } else {
return Container(width: 0.0, height: 0.0); return Container(width: 0.0, height: 0.0);
@ -174,15 +152,8 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
return AlertDialog( return AlertDialog(
titlePadding: EdgeInsets.all(0.0), titlePadding: EdgeInsets.all(0.0),
contentPadding: EdgeInsets.all(0.0), contentPadding: EdgeInsets.all(0.0),
content: SingleChildScrollView( content: LightColorPicker(
child: MaterialPicker( color: _tmpColor,
pickerColor: _tmpColor,
onColorChanged: (color) {
_setColor(entity, color);
Navigator.of(context).pop();
},
enableLabel: true,
),
), ),
); );
}, },

View File

@ -12,10 +12,10 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:date_format/date_format.dart'; 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:charts_flutter/flutter.dart' as charts; import 'package:charts_flutter/flutter.dart' as charts;
import 'package:progress_indicators/progress_indicators.dart'; import 'package:progress_indicators/progress_indicators.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_svg/flutter_svg.dart';
part 'entity_class/const.dart'; part 'entity_class/const.dart';
part 'entity_class/entity.class.dart'; part 'entity_class/entity.class.dart';
@ -50,6 +50,8 @@ part 'entity_widgets/common/mode_swicth.dart';
part 'entity_widgets/common/mode_selector.dart'; part 'entity_widgets/common/mode_selector.dart';
part 'entity_widgets/common/universal_slider.dart'; part 'entity_widgets/common/universal_slider.dart';
part 'entity_widgets/common/flat_service_button.dart'; part 'entity_widgets/common/flat_service_button.dart';
part 'entity_widgets/common/light_color_picker.dart';
part 'entity_widgets/common/camera_stream_view.dart';
part 'entity_widgets/entity_colors.class.dart'; part 'entity_widgets/entity_colors.class.dart';
part 'entity_widgets/entity_page_container.dart'; part 'entity_widgets/entity_page_container.dart';
part 'entity_widgets/history_chart/entity_history.dart'; part 'entity_widgets/history_chart/entity_history.dart';
@ -73,8 +75,8 @@ 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 'entity_widgets/controls/fan_controls.dart';
part 'entity_widgets/controls/alarm_control_panel_controls.dart'; part 'entity_widgets/controls/alarm_control_panel_controls.dart';
part 'entity_widgets/controls/camera_controls.dart';
part 'settings.page.dart'; part 'settings.page.dart';
part 'configuration.page.dart';
part 'home_assistant.class.dart'; part 'home_assistant.class.dart';
part 'log.page.dart'; part 'log.page.dart';
part 'entity.page.dart'; part 'entity.page.dart';
@ -92,7 +94,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.14"; const appVersion = "0.4.4";
String homeAssistantWebHost; String homeAssistantWebHost;
@ -128,6 +130,7 @@ class HAClientApp extends StatelessWidget {
routes: { routes: {
"/": (context) => MainPage(title: 'HA Client'), "/": (context) => MainPage(title: 'HA Client'),
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"), "/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
"/configuration": (context) => ConfigurationPage(title: "Configuration"),
"/log-view": (context) => LogViewPage(title: "Log") "/log-view": (context) => LogViewPage(title: "Log")
}, },
); );
@ -154,7 +157,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
StreamSubscription _settingsSubscription; StreamSubscription _settingsSubscription;
StreamSubscription _serviceCallSubscription; StreamSubscription _serviceCallSubscription;
StreamSubscription _showEntityPageSubscription; StreamSubscription _showEntityPageSubscription;
StreamSubscription _refreshDataSubscription;
StreamSubscription _showErrorSubscription; StreamSubscription _showErrorSubscription;
bool _settingsLoaded = false; bool _settingsLoaded = false;
bool _accountMenuExpanded = false; bool _accountMenuExpanded = false;
@ -240,12 +242,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}); });
} }
if (_refreshDataSubscription == null) {
_refreshDataSubscription = eventBus.on<RefreshDataEvent>().listen((event){
_refreshData();
});
}
if (_showErrorSubscription == null) { if (_showErrorSubscription == null) {
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){ _showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
_showErrorBottomBar(message: event.text, errorCode: event.errorCode); _showErrorBottomBar(message: event.text, errorCode: event.errorCode);
@ -345,6 +341,14 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
]); ]);
} else { } else {
menuItems.addAll([ menuItems.addAll([
new ListTile(
leading: Icon(Icons.settings),
title: Text("Configuration"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/configuration');
},
),
new ListTile( new ListTile(
leading: Icon(Icons.insert_drive_file), leading: Icon(Icons.insert_drive_file),
title: Text("Log"), title: Text("Log"),
@ -367,10 +371,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
HAUtils.launchURL("http://www.vynn.co/"); HAUtils.launchURL("http://ha-client.homemade.systems/");
}, },
child: Text( child: Text(
"www.vynn.co", "ha-client.homemade.systems",
style: TextStyle( style: TextStyle(
color: Colors.blue, color: Colors.blue,
decoration: TextDecoration.underline decoration: TextDecoration.underline
@ -379,8 +383,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
) )
], ],
applicationName: appName, applicationName: appName,
applicationVersion: appVersion, applicationVersion: appVersion
applicationLegalese: "Keyboard Crumbs",
) )
]); ]);
} }
@ -524,6 +527,26 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
pinned: true, pinned: true,
primary: true, primary: true,
title: Text(_homeAssistant != null ? _homeAssistant.locationName : ""), title: Text(_homeAssistant != null ? _homeAssistant.locationName : ""),
actions: <Widget>[
IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
"mdi:dots-vertical"), color: Colors.white,),
onPressed: () {
showMenu(
position: RelativeRect.fromLTRB(MediaQuery.of(context).size.width, 70.0, 0.0, 0.0),
context: context,
items: [PopupMenuItem<String>(
child: new Text("Reload"),
value: "reload",
)]
).then((String val) {
if (val == "reload") {
_refreshData();
}
});
}
)
],
leading: IconButton( leading: IconButton(
icon: Icon(Icons.menu), icon: Icon(Icons.menu),
onPressed: () { onPressed: () {
@ -639,7 +662,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
if (_settingsSubscription != null) _settingsSubscription.cancel(); if (_settingsSubscription != null) _settingsSubscription.cancel();
if (_serviceCallSubscription != null) _serviceCallSubscription.cancel(); if (_serviceCallSubscription != null) _serviceCallSubscription.cancel();
if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel(); if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel();
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
if (_showErrorSubscription != null) _showErrorSubscription.cancel(); if (_showErrorSubscription != null) _showErrorSubscription.cancel();
_homeAssistant.disconnect(); _homeAssistant.disconnect();
super.dispose(); super.dispose();

View File

@ -1,8 +1,8 @@
part of '../main.dart'; part of '../main.dart';
class Sizes { class Sizes {
static const rightWidgetPadding = 14.0; static const rightWidgetPadding = 16.0;
static const leftWidgetPadding = 8.0; static const leftWidgetPadding = 16.0;
static const buttonPadding = 4.0; static const buttonPadding = 4.0;
static const extendedWidgetHeight = 50.0; static const extendedWidgetHeight = 50.0;
static const iconSize = 28.0; static const iconSize = 28.0;
@ -11,7 +11,6 @@ class Sizes {
static const nameFontSize = 15.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 mediumFontSize = 21.0;
static const inputWidth = 160.0; static const inputWidth = 160.0;
static const rowPadding = 10.0; static const rowPadding = 10.0;
} }

View File

@ -18,7 +18,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(fontSize: Sizes.mediumFontSize)), 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

@ -17,29 +17,17 @@ class ViewWidget extends StatefulWidget {
class ViewWidgetState extends State<ViewWidget> { class ViewWidgetState extends State<ViewWidget> {
StreamSubscription _refreshDataSubscription;
Completer _refreshCompleter;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
_refreshCompleter.complete();
}
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RefreshIndicator( return ListView(
color: Colors.amber,
child: ListView(
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
physics: const AlwaysScrollableScrollPhysics(), //physics: const AlwaysScrollableScrollPhysics(),
children: _buildChildren(context), children: _buildChildren(context),
),
onRefresh: () => _refreshData(),
); );
} }
@ -57,12 +45,22 @@ class ViewWidgetState extends State<ViewWidget> {
); );
} }
List<Widget> cards = [];
widget.view.cards.forEach((HACard card){ widget.view.cards.forEach((HACard card){
result.add( cards.add(
card.build(context) ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: card.build(context),
)
); );
}); });
result.add(
Column (
children: cards,
)
);
return result; return result;
} }
@ -76,19 +74,8 @@ class ViewWidgetState extends State<ViewWidget> {
return result; return result;
} }
Future _refreshData() {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
Logger.d("Previous data refresh is still in progress");
} else {
_refreshCompleter = Completer();
eventBus.fire(RefreshDataEvent());
}
return _refreshCompleter.future;
}
@override @override
void dispose() { void dispose() {
_refreshDataSubscription.cancel();
super.dispose(); super.dispose();
} }

View File

@ -73,10 +73,6 @@ class SettingsChangedEvent {
SettingsChangedEvent(this.reconnect); SettingsChangedEvent(this.reconnect);
} }
class RefreshDataEvent {
RefreshDataEvent();
}
class RefreshDataFinishedEvent { class RefreshDataFinishedEvent {
RefreshDataFinishedEvent(); RefreshDataFinishedEvent();
} }

View File

@ -35,7 +35,7 @@ packages:
name: cached_network_image name: cached_network_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.0-alpha.2" version: "0.6.0+1"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -49,14 +49,14 @@ packages:
name: charts_common name: charts_common
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0" version: "0.6.0"
charts_flutter: charts_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: charts_flutter name: charts_flutter
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0" version: "0.6.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -100,7 +100,7 @@ packages:
name: event_bus name: event_bus
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.3"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -112,14 +112,7 @@ packages:
name: flutter_cache_manager name: flutter_cache_manager
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0-alpha.2" version: "0.3.0"
flutter_colorpicker:
dependency: "direct main"
description:
name: flutter_colorpicker
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -134,6 +127,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.3"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -159,7 +159,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.6" version: "2.0.7"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@ -202,13 +202,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.2" version: "1.6.2"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.1" version: "0.5.0+1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -236,7 +250,7 @@ packages:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0" version: "0.5.1+1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -255,7 +269,7 @@ packages:
name: sqflite name: sqflite
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.2+1" version: "1.1.0+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -283,7 +297,7 @@ packages:
name: synchronized name: synchronized
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.3+2" version: "2.0.2+1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -311,14 +325,14 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.0.0" version: "5.0.1"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
name: uuid name: uuid
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -339,7 +353,7 @@ packages:
name: xml name: xml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.3.1" version: "3.2.5"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -348,5 +362,5 @@ packages:
source: hosted source: hosted
version: "2.1.15" version: "2.1.15"
sdks: sdks:
dart: ">=2.0.0 <3.0.0" dart: ">=2.1.0 <3.0.0"
flutter: ">=0.5.6 <2.0.0" flutter: ">=0.7.3 <2.0.0"

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.14+85 version: 0.4.4+95
environment: environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0" sdk: ">=2.0.0-dev.68.0 <3.0.0"
@ -16,9 +16,9 @@ dependencies:
cached_network_image: any cached_network_image: any
url_launcher: any url_launcher: any
date_format: any date_format: any
flutter_colorpicker: any
charts_flutter: any charts_flutter: any
flutter_markdown: any flutter_markdown: any
flutter_svg: ^0.10.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: