Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
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();
|
||||
}
|
||||
}
|
@ -46,12 +46,9 @@ class _EntityViewPageState extends State<EntityViewPage> {
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: new Text(_title),
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: HomeAssistantModel(
|
||||
body: HomeAssistantModel(
|
||||
homeAssistant: widget.homeAssistant,
|
||||
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ class AutomationEntity extends Entity {
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
FlatServiceButton(
|
||||
serviceDomain: domain,
|
||||
entityId: entityId,
|
||||
text: "TRIGGER",
|
||||
serviceName: "trigger",
|
||||
)
|
||||
|
@ -6,6 +6,9 @@ class ButtonEntity extends Entity {
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return FlatServiceButton(
|
||||
entityId: entityId,
|
||||
serviceDomain: domain,
|
||||
serviceName: 'turn_on',
|
||||
text: "EXECUTE",
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class CameraEntity extends Entity {
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return CameraControlsWidget(
|
||||
url: 'https://citadel.vynn.co:8123/api/camera_proxy_stream/camera.demo_camera?token=${this.attributes['access_token']}',
|
||||
url: '$homeAssistantWebHost/api/camera_proxy_stream/camera.demo_camera?token=${this.attributes['access_token']}',
|
||||
);
|
||||
}
|
||||
}
|
@ -80,6 +80,7 @@ class ClimateEntity extends Entity {
|
||||
double get targetHumidity => _getDoubleAttributeValue('humidity');
|
||||
double get maxHumidity => _getDoubleAttributeValue('max_humidity');
|
||||
double get minHumidity => _getDoubleAttributeValue('min_humidity');
|
||||
double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5;
|
||||
String get operationMode => attributes['operation_mode'];
|
||||
String get fanMode => attributes['fan_mode'];
|
||||
String get swingMode => attributes['swing_mode'];
|
||||
|
@ -162,7 +162,10 @@ class Entity {
|
||||
return EntityModel(
|
||||
entityWrapper: EntityWrapper(entity: this),
|
||||
child: EntityPageContainer(children: <Widget>[
|
||||
DefaultEntityContainer(state: _buildStatePartForPage(context)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||
child: DefaultEntityContainer(state: _buildStatePartForPage(context)),
|
||||
),
|
||||
LastUpdatedWidget(),
|
||||
Divider(),
|
||||
_buildAdditionalControlsForPage(context),
|
||||
|
@ -37,17 +37,19 @@ class LightEntity extends Entity {
|
||||
int get colorTemp => _getIntAttributeValue("color_temp");
|
||||
double get maxMireds => _getDoubleAttributeValue("max_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));
|
||||
List<String> get effectList => getStringListAttributeValue("effect_list");
|
||||
|
||||
LightEntity(Map rawData) : super(rawData);
|
||||
|
||||
Color _getColor() {
|
||||
List rgb = attributes["rgb_color"];
|
||||
HSVColor _getColor() {
|
||||
List hs = attributes["hs_color"];
|
||||
try {
|
||||
if ((rgb != null) && (rgb.length > 0)) {
|
||||
return Color.fromARGB(255, rgb[0], rgb[1], rgb[2]);
|
||||
if ((hs != null) && (hs.length > 0)) {
|
||||
double sat = hs[1]/100;
|
||||
String ssat = sat.toStringAsFixed(2);
|
||||
return HSVColor.fromAHSV(1.0, hs[0], double.parse(ssat), 1.0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -63,7 +65,7 @@ class LightEntity extends Entity {
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
if (!isAdditionalControls) {
|
||||
if (!isAdditionalControls || state == EntityState.unavailable) {
|
||||
return Container(height: 0.0, width: 0.0);
|
||||
} else {
|
||||
return LightControlsWidget();
|
||||
|
@ -86,9 +86,9 @@ class EntityCollection {
|
||||
case "fan": {
|
||||
return FanEntity(rawEntityData);
|
||||
}
|
||||
/*case "camera": {
|
||||
case "camera": {
|
||||
return CameraEntity(rawEntityData);
|
||||
}*/
|
||||
}
|
||||
case "alarm_control_panel": {
|
||||
return AlarmControlPanelEntity(rawEntityData);
|
||||
}
|
||||
|
@ -23,7 +23,22 @@ class BadgeWidget extends StatelessWidget {
|
||||
);
|
||||
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;
|
||||
badgeIcon = Center(
|
||||
@ -37,18 +52,6 @@ class BadgeWidget extends StatelessWidget {
|
||||
);
|
||||
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;
|
||||
if (onBadgeTextValue == null || onBadgeTextValue.length == 0) {
|
||||
|
@ -21,10 +21,13 @@ class EntityAttributesList extends StatelessWidget {
|
||||
}
|
||||
});
|
||||
}
|
||||
return Column(
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||
child: Column(
|
||||
children: attrs,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,29 +4,30 @@ class FlatServiceButton extends StatelessWidget {
|
||||
|
||||
final String serviceDomain;
|
||||
final String serviceName;
|
||||
final String entityId;
|
||||
final String text;
|
||||
final double fontSize;
|
||||
|
||||
FlatServiceButton({
|
||||
Key key,
|
||||
this.serviceDomain,
|
||||
this.serviceName: "turn_on",
|
||||
@required this.serviceDomain,
|
||||
@required this.serviceName,
|
||||
@required this.entityId,
|
||||
@required this.text,
|
||||
this.fontSize: Sizes.stateFontSize
|
||||
}) : super(key: key);
|
||||
|
||||
void _setNewState(Entity entity) {
|
||||
eventBus.fire(new ServiceCallEvent(serviceDomain ?? entity.domain, serviceName, entity.entityId, null));
|
||||
void _setNewState() {
|
||||
eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return SizedBox(
|
||||
height: fontSize*2.5,
|
||||
child: FlatButton(
|
||||
onPressed: (() {
|
||||
_setNewState(entityModel.entityWrapper.entity);
|
||||
_setNewState();
|
||||
}),
|
||||
child: Text(
|
||||
text,
|
||||
|
@ -6,7 +6,7 @@ class LastUpdatedWidget extends StatelessWidget {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Sizes.leftWidgetPadding, 0.0, 0.0, 0.0),
|
||||
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0, 0.0),
|
||||
child: Text(
|
||||
'${entityModel.entityWrapper.entity.lastUpdated}',
|
||||
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 double captionFontSize;
|
||||
final double valueFontSize;
|
||||
final double bottomPadding;
|
||||
final onChange;
|
||||
final EdgeInsets padding;
|
||||
|
||||
ModeSelectorWidget({
|
||||
Key key,
|
||||
@ -18,12 +18,14 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
@required this.onChange,
|
||||
this.captionFontSize,
|
||||
this.valueFontSize,
|
||||
this.bottomPadding
|
||||
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
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 bool value;
|
||||
final bool expanded;
|
||||
final EdgeInsets padding;
|
||||
|
||||
ModeSwitchWidget({
|
||||
Key key,
|
||||
@ -14,12 +15,15 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
@required this.onChange,
|
||||
this.captionFontSize,
|
||||
this.value,
|
||||
this.expanded: true
|
||||
this.expanded: true,
|
||||
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
return Padding(
|
||||
padding: this.padding,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_buildCaption(),
|
||||
Switch(
|
||||
@ -27,6 +31,7 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
value: value ?? false,
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,9 @@ class UniversalSlider extends StatelessWidget {
|
||||
final double min;
|
||||
final double max;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
@ -33,7 +34,9 @@ class UniversalSlider extends StatelessWidget {
|
||||
if (closing != null) {
|
||||
row.add(closing);
|
||||
}
|
||||
return Column(
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(height: Sizes.rowPadding,),
|
||||
@ -48,6 +51,7 @@ class UniversalSlider extends StatelessWidget {
|
||||
),
|
||||
Container(height: Sizes.rowPadding,)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ class AlarmControlPanelControlsWidget extends StatefulWidget {
|
||||
final bool extended;
|
||||
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
|
||||
_AlarmControlPanelControlsWidgetWidgetState createState() => _AlarmControlPanelControlsWidgetWidgetState();
|
||||
@ -15,6 +15,14 @@ class AlarmControlPanelControlsWidget extends StatefulWidget {
|
||||
class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPanelControlsWidget> {
|
||||
|
||||
String code = "";
|
||||
List supportedStates;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
supportedStates = widget.states ?? ["arm_home", "arm_away"];
|
||||
}
|
||||
|
||||
|
||||
void _callService(AlarmControlPanelEntity entity, String service) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
@ -72,7 +80,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
||||
final AlarmControlPanelEntity entity = entityModel.entityWrapper.entity;
|
||||
List<Widget> buttons = [];
|
||||
if (entity.state == EntityState.alarm_disarmed) {
|
||||
if (widget.states.contains("arm_home")) {
|
||||
if (supportedStates.contains("arm_home")) {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
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(
|
||||
RaisedButton(
|
||||
onPressed: () => _callService(entity, "alarm_arm_away"),
|
||||
@ -89,7 +97,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
||||
);
|
||||
}
|
||||
if (widget.extended) {
|
||||
if (widget.states.contains("arm_night")) {
|
||||
if (supportedStates.contains("arm_night")) {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
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(
|
||||
RaisedButton(
|
||||
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),
|
||||
child: Column(
|
||||
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,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
@ -208,6 +225,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
Widget buttonsWrapper = Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
|
||||
child: Wrap(
|
||||
|
@ -15,120 +15,101 @@ 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 = [];
|
||||
String cameraState = "Connecting...";
|
||||
bool timeToStop = false;
|
||||
Completer streamCompleter;
|
||||
|
||||
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}");
|
||||
Logger.d("[Sending] ==> ${widget.url}");
|
||||
response = await client.send(request);
|
||||
setState(() {
|
||||
cameraState = "Starting...";
|
||||
});
|
||||
Logger.d("[Received] <== ${response.headers}");
|
||||
List<int> primaryBuffer=[];
|
||||
List<int> secondaryBuffer=[];
|
||||
final int imageSizeStart = 59;
|
||||
int imageSizeEnd = 0;
|
||||
int imageStart = 0;
|
||||
int imageEnd = 0;
|
||||
int imageSize = 0;
|
||||
String strBuffer = "";
|
||||
streamCompleter = Completer();
|
||||
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();
|
||||
if (primaryBuffer.length >= 66) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
imageSize = int.tryParse(utf8.decode(
|
||||
primaryBuffer.sublist(imageSizeStart, imageSizeEnd),
|
||||
allowMalformed: true));
|
||||
if (imageSize != null && primaryBuffer.length >= imageStart + imageSize + 2) {
|
||||
sink.add(
|
||||
primaryBuffer.sublist(imageStart, imageStart + imageSize));
|
||||
primaryBuffer.removeRange(0, imageStart + imageSize + 2);
|
||||
}
|
||||
}
|
||||
//byteCount += data.length;
|
||||
//Logger.d("$byteCount");
|
||||
|
||||
if (timeToStop) {
|
||||
sink?.close();
|
||||
streamCompleter.complete();
|
||||
}
|
||||
},
|
||||
handleError: (error, stack, sink) {
|
||||
Logger.e("Error parsing MJPEG stream: $error");
|
||||
},
|
||||
handleError: (error, stack, sink) {},
|
||||
handleDone: (sink) {
|
||||
sink.close();
|
||||
sink?.close();
|
||||
},
|
||||
)
|
||||
).listen((d) {
|
||||
setState(() {
|
||||
binaryImage = d;
|
||||
});
|
||||
//Logger.d("==binary imagesize=${d.length}");
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (binaryImage.isEmpty) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Image.memory(Uint8List.fromList(binaryImage)),
|
||||
FlatButton(
|
||||
child: Text("VIEW"),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
|
||||
});
|
||||
},
|
||||
)
|
||||
Text("$cameraState")
|
||||
],
|
||||
);
|
||||
return Image.network("${widget.url}");
|
||||
return FlatButton(
|
||||
child: Text("VIEW"),
|
||||
onPressed: () {
|
||||
HAUtils.launchURL(widget.url);
|
||||
},
|
||||
} else {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Image.memory(Uint8List.fromList(binaryImage), gaplessPlayback: true),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
client.close();
|
||||
super.dispose();
|
||||
timeToStop = true;
|
||||
if (streamCompleter != null && !streamCompleter.isCompleted) {
|
||||
streamCompleter.future.then((_) {
|
||||
client?.close();
|
||||
});
|
||||
} else {
|
||||
client?.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -42,33 +42,33 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
_changedHere = false;
|
||||
}
|
||||
|
||||
void _temperatureUp(ClimateEntity entity, double step) {
|
||||
_tmpTemperature = ((_tmpTemperature + step) <= entity.maxTemp) ? _tmpTemperature + step : entity.maxTemp;
|
||||
void _temperatureUp(ClimateEntity entity) {
|
||||
_tmpTemperature = ((_tmpTemperature + entity.temperatureStep) <= entity.maxTemp) ? _tmpTemperature + entity.temperatureStep : entity.maxTemp;
|
||||
_setTemperature(entity);
|
||||
}
|
||||
|
||||
void _temperatureDown(ClimateEntity entity, double step) {
|
||||
_tmpTemperature = ((_tmpTemperature - step) >= entity.minTemp) ? _tmpTemperature - step : entity.minTemp;
|
||||
void _temperatureDown(ClimateEntity entity) {
|
||||
_tmpTemperature = ((_tmpTemperature - entity.temperatureStep) >= entity.minTemp) ? _tmpTemperature - entity.temperatureStep : entity.minTemp;
|
||||
_setTemperature(entity);
|
||||
}
|
||||
|
||||
void _targetLowUp(ClimateEntity entity, double step) {
|
||||
_tmpTargetLow = ((_tmpTargetLow + step) <= entity.maxTemp) ? _tmpTargetLow + step : entity.maxTemp;
|
||||
void _targetLowUp(ClimateEntity entity) {
|
||||
_tmpTargetLow = ((_tmpTargetLow + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetLow + entity.temperatureStep : entity.maxTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
void _targetLowDown(ClimateEntity entity, double step) {
|
||||
_tmpTargetLow = ((_tmpTargetLow - step) >= entity.minTemp) ? _tmpTargetLow - step : entity.minTemp;
|
||||
void _targetLowDown(ClimateEntity entity) {
|
||||
_tmpTargetLow = ((_tmpTargetLow - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetLow - entity.temperatureStep : entity.minTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
void _targetHighUp(ClimateEntity entity, double step) {
|
||||
_tmpTargetHigh = ((_tmpTargetHigh + step) <= entity.maxTemp) ? _tmpTargetHigh + step : entity.maxTemp;
|
||||
void _targetHighUp(ClimateEntity entity) {
|
||||
_tmpTargetHigh = ((_tmpTargetHigh + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetHigh + entity.temperatureStep : entity.maxTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
void _targetHighDown(ClimateEntity entity, double step) {
|
||||
_tmpTargetHigh = ((_tmpTargetHigh - step) >= entity.minTemp) ? _tmpTargetHigh - step : entity.minTemp;
|
||||
void _targetHighDown(ClimateEntity entity) {
|
||||
_tmpTargetHigh = ((_tmpTargetHigh - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetHigh - entity.temperatureStep : entity.minTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
@ -296,8 +296,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTemperature,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _temperatureDown(entity, 0.5),
|
||||
onInc: () => _temperatureUp(entity, 0.5),
|
||||
onDec: () => _temperatureDown(entity),
|
||||
onInc: () => _temperatureUp(entity),
|
||||
)
|
||||
],
|
||||
);
|
||||
@ -313,8 +313,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetLow,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _targetLowDown(entity, 0.5),
|
||||
onInc: () => _targetLowUp(entity, 0.5),
|
||||
onDec: () => _targetLowDown(entity),
|
||||
onInc: () => _targetLowUp(entity),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(height: 10.0),
|
||||
@ -326,8 +326,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetHigh,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _targetHighDown(entity, 0.5),
|
||||
onInc: () => _targetHighUp(entity, 0.5),
|
||||
onDec: () => _targetHighDown(entity),
|
||||
onInc: () => _targetHighUp(entity),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
int _tmpBrightness;
|
||||
int _tmpColorTemp = 0;
|
||||
Color _tmpColor = Colors.white;
|
||||
HSVColor _tmpColor = HSVColor.fromAHSV(1.0, 30.0, 0.0, 1.0);
|
||||
bool _changedHere = false;
|
||||
String _tmpEffect;
|
||||
|
||||
@ -48,20 +48,14 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
});
|
||||
}
|
||||
|
||||
void _setColor(LightEntity entity, Color color) {
|
||||
void _setColor(LightEntity entity, HSVColor color) {
|
||||
setState(() {
|
||||
_tmpColor = color;
|
||||
_changedHere = true;
|
||||
Logger.d( "Color: [${color.red}, ${color.green}, ${color.blue}]");
|
||||
if ((color == Colors.black) || ((color.red == color.green) && (color.green == color.blue))) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_off", entity.entityId,
|
||||
null));
|
||||
} else {
|
||||
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
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) {
|
||||
if ((entity.supportBrightness) && (_tmpBrightness != null) && (entity.state != EntityState.unavailable)) {
|
||||
if ((entity.supportBrightness) && (_tmpBrightness != null)) {
|
||||
return UniversalSlider(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@ -109,7 +103,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
min: 0.0,
|
||||
max: 255.0,
|
||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||
value: _tmpBrightness.toDouble(),
|
||||
value: _tmpBrightness == null ? 0.0 : _tmpBrightness.toDouble(),
|
||||
leading: Icon(Icons.brightness_5),
|
||||
title: "Brightness",
|
||||
);
|
||||
@ -123,7 +117,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
return UniversalSlider(
|
||||
title: "Color temperature",
|
||||
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
||||
value: _tmpColorTemp.toDouble(),
|
||||
value: _tmpColorTemp == null ? entity.maxMireds : _tmpColorTemp.toDouble(),
|
||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||
max: entity.maxMireds,
|
||||
min: entity.minMireds,
|
||||
@ -142,25 +136,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
Widget _buildColorControl(LightEntity entity) {
|
||||
if (entity.supportColor) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
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,),
|
||||
],
|
||||
return LightColorPicker(
|
||||
color: _tmpColor,
|
||||
onColorSelected: (color) => _setColor(entity, color),
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
@ -174,15 +152,8 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
return AlertDialog(
|
||||
titlePadding: EdgeInsets.all(0.0),
|
||||
contentPadding: EdgeInsets.all(0.0),
|
||||
content: SingleChildScrollView(
|
||||
child: MaterialPicker(
|
||||
pickerColor: _tmpColor,
|
||||
onColorChanged: (color) {
|
||||
_setColor(entity, color);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
enableLabel: true,
|
||||
),
|
||||
content: LightColorPicker(
|
||||
color: _tmpColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -12,7 +12,6 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:date_format/date_format.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter_colorpicker/material_picker.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:progress_indicators/progress_indicators.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
@ -50,6 +49,7 @@ part 'entity_widgets/common/mode_swicth.dart';
|
||||
part 'entity_widgets/common/mode_selector.dart';
|
||||
part 'entity_widgets/common/universal_slider.dart';
|
||||
part 'entity_widgets/common/flat_service_button.dart';
|
||||
part 'entity_widgets/common/light_color_picker.dart';
|
||||
part 'entity_widgets/entity_colors.class.dart';
|
||||
part 'entity_widgets/entity_page_container.dart';
|
||||
part 'entity_widgets/history_chart/entity_history.dart';
|
||||
@ -75,6 +75,7 @@ part 'entity_widgets/controls/fan_controls.dart';
|
||||
part 'entity_widgets/controls/alarm_control_panel_controls.dart';
|
||||
part 'entity_widgets/controls/camera_controls.dart';
|
||||
part 'settings.page.dart';
|
||||
part 'configuration.page.dart';
|
||||
part 'home_assistant.class.dart';
|
||||
part 'log.page.dart';
|
||||
part 'entity.page.dart';
|
||||
@ -92,7 +93,8 @@ part 'ui_widgets/card_header_widget.dart';
|
||||
|
||||
EventBus eventBus = new EventBus();
|
||||
const String appName = "HA Client";
|
||||
const appVersion = "0.3.14";
|
||||
const appVersion = "0.4.0";
|
||||
const appBuild = "91";
|
||||
|
||||
String homeAssistantWebHost;
|
||||
|
||||
@ -128,6 +130,7 @@ class HAClientApp extends StatelessWidget {
|
||||
routes: {
|
||||
"/": (context) => MainPage(title: 'HA Client'),
|
||||
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
|
||||
"/configuration": (context) => ConfigurationPage(title: "Configuration"),
|
||||
"/log-view": (context) => LogViewPage(title: "Log")
|
||||
},
|
||||
);
|
||||
@ -154,7 +157,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
StreamSubscription _settingsSubscription;
|
||||
StreamSubscription _serviceCallSubscription;
|
||||
StreamSubscription _showEntityPageSubscription;
|
||||
StreamSubscription _refreshDataSubscription;
|
||||
StreamSubscription _showErrorSubscription;
|
||||
bool _settingsLoaded = 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) {
|
||||
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
|
||||
_showErrorBottomBar(message: event.text, errorCode: event.errorCode);
|
||||
@ -345,6 +341,14 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
]);
|
||||
} else {
|
||||
menuItems.addAll([
|
||||
new ListTile(
|
||||
leading: Icon(Icons.settings),
|
||||
title: Text("Configuration"),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed('/configuration');
|
||||
},
|
||||
),
|
||||
new ListTile(
|
||||
leading: Icon(Icons.insert_drive_file),
|
||||
title: Text("Log"),
|
||||
@ -367,10 +371,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
HAUtils.launchURL("http://www.vynn.co/");
|
||||
HAUtils.launchURL("http://ha-client.homemade.systems/");
|
||||
},
|
||||
child: Text(
|
||||
"www.vynn.co",
|
||||
"ha-client.homemade.systems",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline
|
||||
@ -380,7 +384,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
],
|
||||
applicationName: appName,
|
||||
applicationVersion: appVersion,
|
||||
applicationLegalese: "Keyboard Crumbs",
|
||||
applicationLegalese: "build $appBuild",
|
||||
)
|
||||
]);
|
||||
}
|
||||
@ -524,6 +528,26 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
pinned: true,
|
||||
primary: true,
|
||||
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(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
@ -639,7 +663,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
if (_settingsSubscription != null) _settingsSubscription.cancel();
|
||||
if (_serviceCallSubscription != null) _serviceCallSubscription.cancel();
|
||||
if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel();
|
||||
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
||||
if (_showErrorSubscription != null) _showErrorSubscription.cancel();
|
||||
_homeAssistant.disconnect();
|
||||
super.dispose();
|
||||
|
@ -1,8 +1,8 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class Sizes {
|
||||
static const rightWidgetPadding = 14.0;
|
||||
static const leftWidgetPadding = 8.0;
|
||||
static const rightWidgetPadding = 16.0;
|
||||
static const leftWidgetPadding = 16.0;
|
||||
static const buttonPadding = 4.0;
|
||||
static const extendedWidgetHeight = 50.0;
|
||||
static const iconSize = 28.0;
|
||||
@ -11,7 +11,6 @@ class Sizes {
|
||||
static const nameFontSize = 15.0;
|
||||
static const smallFontSize = 14.0;
|
||||
static const largeFontSize = 24.0;
|
||||
static const mediumFontSize = 21.0;
|
||||
static const inputWidth = 160.0;
|
||||
static const rowPadding = 10.0;
|
||||
}
|
@ -18,7 +18,7 @@ class CardHeaderWidget extends StatelessWidget {
|
||||
title: Text("$name",
|
||||
textAlign: TextAlign.left,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: new TextStyle(fontSize: Sizes.mediumFontSize)),
|
||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
||||
);
|
||||
} else {
|
||||
result = new Container(width: 0.0, height: 0.0);
|
||||
|
@ -17,29 +17,17 @@ class ViewWidget extends StatefulWidget {
|
||||
|
||||
class ViewWidgetState extends State<ViewWidget> {
|
||||
|
||||
StreamSubscription _refreshDataSubscription;
|
||||
Completer _refreshCompleter;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
||||
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
|
||||
_refreshCompleter.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
color: Colors.amber,
|
||||
child: ListView(
|
||||
return ListView(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
//physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: _buildChildren(context),
|
||||
),
|
||||
onRefresh: () => _refreshData(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -57,12 +45,22 @@ class ViewWidgetState extends State<ViewWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> cards = [];
|
||||
widget.view.cards.forEach((HACard card){
|
||||
result.add(
|
||||
card.build(context)
|
||||
cards.add(
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: card.build(context),
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
result.add(
|
||||
Column (
|
||||
children: cards,
|
||||
)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -76,19 +74,8 @@ class ViewWidgetState extends State<ViewWidget> {
|
||||
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
|
||||
void dispose() {
|
||||
_refreshDataSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -73,10 +73,6 @@ class SettingsChangedEvent {
|
||||
SettingsChangedEvent(this.reconnect);
|
||||
}
|
||||
|
||||
class RefreshDataEvent {
|
||||
RefreshDataEvent();
|
||||
}
|
||||
|
||||
class RefreshDataFinishedEvent {
|
||||
RefreshDataFinishedEvent();
|
||||
}
|
||||
|
15
pubspec.lock
15
pubspec.lock
@ -112,14 +112,7 @@ packages:
|
||||
name: flutter_cache_manager
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.0-alpha.2"
|
||||
flutter_colorpicker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_colorpicker
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
version: "0.3.0-beta.2+1"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -159,7 +152,7 @@ packages:
|
||||
name: image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
version: "2.0.7"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -236,7 +229,7 @@ packages:
|
||||
name: shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "0.5.1+1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -311,7 +304,7 @@ packages:
|
||||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.0.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: hass_client
|
||||
description: Home Assistant Android Client
|
||||
|
||||
version: 0.3.14+85
|
||||
version: 0.4.0+91
|
||||
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
@ -16,7 +16,6 @@ dependencies:
|
||||
cached_network_image: any
|
||||
url_launcher: any
|
||||
date_format: any
|
||||
flutter_colorpicker: any
|
||||
charts_flutter: any
|
||||
flutter_markdown: any
|
||||
|
||||
|
Reference in New Issue
Block a user