Compare commits

..

21 Commits

Author SHA1 Message Date
e359150d97 Version 0.4.0 2019-02-16 22:05:43 +02:00
93680c981c Resolves #283 Add possibility to restrat HA 2019-02-16 22:04:49 +02:00
e06b66c523 Resolves #259 target_temp_step support for climate 2019-02-16 20:44:41 +02:00
3dea844e1e Resolves #290 Hide pin inputs if code_format is null 2019-02-16 20:33:56 +02:00
62b1af30e0 Resolves #291 some padding issues 2019-02-16 19:59:39 +02:00
e006c4e403 build number 2019-02-10 22:36:30 +02:00
983573388e Remove pull-to-refresh. Add new menu in header. 2019-02-10 22:33:46 +02:00
bdd1dc7e17 Hide light additional controls if state=unavailable 2019-02-10 19:06:07 +02:00
9c1970ee14 build number 2019-02-10 18:26:48 +02:00
d0e0bf3571 Current color for color picker fix 2019-02-10 18:24:54 +02:00
b399357517 Back to old version format 2019-02-10 17:23:57 +02:00
0290cd3a32 Resolves #273 New color picker 2019-02-10 17:15:52 +02:00
d8a1d03179 v.3.14.87 2019-02-09 02:21:20 +02:00
216fad3cb9 Fix entity page padding 2019-02-09 02:21:20 +02:00
fead6ea348 Resolves #143 Camera support 2019-02-09 02:21:20 +02:00
8814687be6 WIP #143 Initial not optimized MJPEG streaming 2019-02-09 02:21:20 +02:00
71c0e2caa0 Change version numeration 2019-02-09 02:21:20 +02:00
1531c41542 Create CODE_OF_CONDUCT.md 2019-02-01 12:55:25 +02:00
bc90d013e8 Build 86 2019-02-01 11:52:09 +02:00
2adfaca0c4 Resolves #286 2019-02-01 11:51:35 +02:00
6cc1a37d9d Resolves #285 2019-02-01 11:49:27 +02:00
29 changed files with 674 additions and 399 deletions

76
CODE_OF_CONDUCT.md Normal file
View File

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

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

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

View File

@ -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(
homeAssistant: widget.homeAssistant,
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
)
body: HomeAssistantModel(
homeAssistant: widget.homeAssistant,
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
),
);
}

View File

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

View File

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

View File

@ -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']}',
);
}
}

View File

@ -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'];

View File

@ -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),

View File

@ -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();

View File

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

View File

@ -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) {

View File

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

View File

@ -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,

View File

@ -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,

View File

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

View File

@ -7,8 +7,8 @@ class ModeSelectorWidget extends StatelessWidget {
final String value;
final double captionFontSize;
final double valueFontSize;
final double bottomPadding;
final onChange;
final EdgeInsets padding;
ModeSelectorWidget({
Key key,
@ -18,45 +18,47 @@ 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(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("$caption", style: TextStyle(
fontSize: captionFontSize ?? Sizes.stateFontSize
)),
Row(
children: <Widget>[
Expanded(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: value,
iconSize: 30.0,
isExpanded: true,
style: TextStyle(
fontSize: valueFontSize ?? Sizes.largeFontSize,
color: Colors.black,
return Padding(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("$caption", style: TextStyle(
fontSize: captionFontSize ?? Sizes.stateFontSize
)),
Row(
children: <Widget>[
Expanded(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: value,
iconSize: 30.0,
isExpanded: true,
style: TextStyle(
fontSize: valueFontSize ?? Sizes.largeFontSize,
color: Colors.black,
),
hint: Text("Select ${caption.toLowerCase()}"),
items: options.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (mode) => onChange(mode),
),
hint: Text("Select ${caption.toLowerCase()}"),
items: options.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (mode) => onChange(mode),
),
),
)
],
),
Container(height: bottomPadding ?? Sizes.rowPadding,)
],
)
],
)
],
),
);
}
}

View File

@ -7,6 +7,7 @@ class ModeSwitchWidget extends StatelessWidget {
final double captionFontSize;
final bool value;
final bool expanded;
final EdgeInsets padding;
ModeSwitchWidget({
Key key,
@ -14,19 +15,23 @@ 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(
children: <Widget>[
_buildCaption(),
Switch(
onChanged: (value) => onChange(value),
value: value ?? false,
)
],
return Padding(
padding: this.padding,
child: Row(
children: <Widget>[
_buildCaption(),
Switch(
onChanged: (value) => onChange(value),
value: value ?? false,
)
],
)
);
}

View File

@ -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,21 +34,24 @@ class UniversalSlider extends StatelessWidget {
if (closing != null) {
row.add(closing);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(height: Sizes.rowPadding,),
Text(
"$title",
style: TextStyle(fontSize: Sizes.stateFontSize),
),
Container(height: Sizes.rowPadding,),
Row(
mainAxisSize: MainAxisSize.min,
children: row,
),
Container(height: Sizes.rowPadding,)
],
return Padding(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(height: Sizes.rowPadding,),
Text(
"$title",
style: TextStyle(fontSize: Sizes.stateFontSize),
),
Container(height: Sizes.rowPadding,),
Row(
mainAxisSize: MainAxisSize.min,
children: row,
),
Container(height: Sizes.rowPadding,)
],
),
);
}

View File

@ -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,99 +123,109 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
)
);
}
Widget pinPad = Padding(
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("1"),
child: Text("1"),
),
RaisedButton(
onPressed: () => _pinPadHandler("2"),
child: Text("2"),
),
RaisedButton(
onPressed: () => _pinPadHandler("3"),
child: Text("3"),
)
],
),
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("4"),
child: Text("4"),
),
RaisedButton(
onPressed: () => _pinPadHandler("5"),
child: Text("5"),
),
RaisedButton(
onPressed: () => _pinPadHandler("6"),
child: Text("6"),
)
],
),
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("7"),
child: Text("7"),
),
RaisedButton(
onPressed: () => _pinPadHandler("8"),
child: Text("8"),
),
RaisedButton(
onPressed: () => _pinPadHandler("9"),
child: Text("9"),
)
],
),
Wrap(
spacing: 5.0,
alignment: WrapAlignment.end,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("0"),
child: Text("0"),
),
RaisedButton(
onPressed: () => _pinPadClear(),
child: Text("CLEAR"),
)
],
)
],
)
);
Widget inputWrapper = Container(
width: 150.0,
child: TextField(
decoration: InputDecoration(
labelText: "Alarm Code"
),
//focusNode: _focusNode,
obscureText: true,
controller: new TextEditingController.fromValue(
new TextEditingValue(
text: code,
selection:
new TextSelection.collapsed(offset: code.length)
)
),
onChanged: (value) {
code = value;
}
)
);
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,
children: <Widget>[
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("1"),
child: Text("1"),
),
RaisedButton(
onPressed: () => _pinPadHandler("2"),
child: Text("2"),
),
RaisedButton(
onPressed: () => _pinPadHandler("3"),
child: Text("3"),
)
],
),
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("4"),
child: Text("4"),
),
RaisedButton(
onPressed: () => _pinPadHandler("5"),
child: Text("5"),
),
RaisedButton(
onPressed: () => _pinPadHandler("6"),
child: Text("6"),
)
],
),
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("7"),
child: Text("7"),
),
RaisedButton(
onPressed: () => _pinPadHandler("8"),
child: Text("8"),
),
RaisedButton(
onPressed: () => _pinPadHandler("9"),
child: Text("9"),
)
],
),
Wrap(
spacing: 5.0,
alignment: WrapAlignment.end,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("0"),
child: Text("0"),
),
RaisedButton(
onPressed: () => _pinPadClear(),
child: Text("CLEAR"),
)
],
)
],
)
);
}
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(
labelText: "Alarm Code"
),
//focusNode: _focusNode,
obscureText: true,
controller: new TextEditingController.fromValue(
new TextEditingValue(
text: code,
selection:
new TextSelection.collapsed(offset: code.length)
)
),
onChanged: (value) {
code = value;
}
)
);
}
Widget buttonsWrapper = Padding(
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
child: Wrap(

View File

@ -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();
break;
}
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) {
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);
},
);
if (binaryImage.isEmpty) {
return Column(
children: <Widget>[
Text("$cameraState")
],
);
} 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();
}
}
}

View File

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

View File

@ -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 {
eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_on", entity.entityId,
{"rgb_color": [color.red, color.green, color.blue]}));
}
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_on", entity.entityId,
{"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,
),
);
},

View File

@ -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();

View File

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

View File

@ -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);

View File

@ -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(
padding: EdgeInsets.all(0.0),
physics: const AlwaysScrollableScrollPhysics(),
children: _buildChildren(context),
),
onRefresh: () => _refreshData(),
return ListView(
padding: EdgeInsets.all(0.0),
//physics: const AlwaysScrollableScrollPhysics(),
children: _buildChildren(context),
);
}
@ -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();
}

View File

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

View File

@ -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:

View File

@ -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