Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
4b6dda5a9c | |||
4099fa0c83 | |||
76057e8797 | |||
538d3603dc | |||
bc0e72ca52 | |||
f25a47beb2 | |||
cc3c6b0087 | |||
6cf80c0bfd | |||
8ce9bdb7a5 | |||
31e50150b1 | |||
e359150d97 | |||
93680c981c | |||
e06b66c523 | |||
3dea844e1e | |||
62b1af30e0 | |||
e006c4e403 | |||
983573388e | |||
bdd1dc7e17 | |||
9c1970ee14 | |||
d0e0bf3571 | |||
b399357517 | |||
0290cd3a32 | |||
d8a1d03179 | |||
216fad3cb9 | |||
fead6ea348 | |||
8814687be6 | |||
71c0e2caa0 | |||
1531c41542 | |||
bc90d013e8 | |||
2adfaca0c4 | |||
6cc1a37d9d |
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal 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
105
lib/configuration.page.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
)
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
)
|
)
|
||||||
|
@ -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",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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']}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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'];
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
175
lib/entity_widgets/common/camera_stream_view.dart
Normal file
175
lib/entity_widgets/common/camera_stream_view.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
101
lib/entity_widgets/common/light_color_picker.dart
Normal file
101
lib/entity_widgets/common/light_color_picker.dart
Normal 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,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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,)
|
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,)
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
}
|
}
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +73,6 @@ class SettingsChangedEvent {
|
|||||||
SettingsChangedEvent(this.reconnect);
|
SettingsChangedEvent(this.reconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RefreshDataEvent {
|
|
||||||
RefreshDataEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
class RefreshDataFinishedEvent {
|
class RefreshDataFinishedEvent {
|
||||||
RefreshDataFinishedEvent();
|
RefreshDataFinishedEvent();
|
||||||
}
|
}
|
||||||
|
58
pubspec.lock
58
pubspec.lock
@ -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"
|
||||||
|
@ -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:
|
||||||
|
Reference in New Issue
Block a user