Compare commits
56 Commits
beta/0.7.1
...
beta/0.7.6
Author | SHA1 | Date | |
---|---|---|---|
a241cc1d61 | |||
8b4df98cb9 | |||
7d30c2f9d5 | |||
44acabadfe | |||
6f3a2bb78d | |||
dd5f8b155d | |||
cd81fc72fd | |||
890da650dc | |||
9897b6a44b | |||
7969f54d3b | |||
7c18454de3 | |||
dcf5efddd1 | |||
a6541134e0 | |||
90504047b4 | |||
ca1eec6602 | |||
edc01d14b7 | |||
6cb5463b13 | |||
63a789ebfb | |||
a0994e9a60 | |||
8d1b728194 | |||
1a9fec8b98 | |||
e634253282 | |||
64b23ec7cc | |||
afe207a878 | |||
4bac0c092f | |||
74c8ae35a1 | |||
7856637456 | |||
965f80a6ca | |||
198c2ba49a | |||
4b9ec5ca6e | |||
5792652619 | |||
2c900333a5 | |||
1f782d7cd3 | |||
89cc1833de | |||
1262d8c9aa | |||
85b0c4f814 | |||
551a8dfa31 | |||
139533d2ca | |||
889682f771 | |||
f16c98057f | |||
26ec807c25 | |||
45af6cbe3c | |||
5dd9cde12d | |||
472fb1d367 | |||
8b372fbc0b | |||
d1f652282a | |||
f656528d5b | |||
bcdb2a648c | |||
8a78745aa7 | |||
2a3eaabbe4 | |||
bcd175fbfb | |||
f9f013636d | |||
b34cc97080 | |||
327f623ef7 | |||
4d0877e5ae | |||
0eac217399 |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,35 +7,16 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
**HA Client version:** [Main menu -> About HA Client]
|
||||||
Please provide as much information as possible.
|
|
||||||
-->
|
|
||||||
**HA Client version:** <!-- Main app menu => About HA Client -->
|
|
||||||
|
|
||||||
**Home Assistant version:** <!-- 0.94.1 for example -->
|
**Home Assistant version:**
|
||||||
|
|
||||||
**Device name:** <!-- Pixel 2 for example -->
|
**Device name:**
|
||||||
|
|
||||||
**Android version:** <!-- 8.1 for example -->
|
**Android version:**
|
||||||
|
|
||||||
**Connection type:** <!-- For example "Local IP" or "Remote UI" or "Own domain"-->
|
|
||||||
|
|
||||||
**Login type:** <!-- For example "HA Login" or "Manual token"-->
|
|
||||||
|
|
||||||
**Description**
|
**Description**
|
||||||
<!--
|
[Replace with description]
|
||||||
Describe your issue here
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
<!--
|
[Replace with screenshots]
|
||||||
Please provide screenshots if it is a UI issue. Also you can attach screenshot from Home Assistant web UI as an expected result
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Logs**
|
|
||||||
<!--
|
|
||||||
Right after issue reproduced go to app menu and tap "Log". Copy log with a "Copy" button in the upper-right corner and post it below
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
[Replace this text with your logs]
|
|
||||||
```
|
|
||||||
|
@ -3,11 +3,6 @@
|
|||||||
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen"
|
<uses-feature android:name="android.hardware.touchscreen"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
|
||||||
flutter needs it to communicate with the running application
|
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
|
||||||
-->
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
@ -15,6 +10,7 @@
|
|||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
|
||||||
|
|
||||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||||
In most cases you can leave this as-is, but you if you want to provide
|
In most cases you can leave this as-is, but you if you want to provide
|
||||||
|
@ -285,7 +285,9 @@ class CardWidget extends StatelessWidget {
|
|||||||
return Card(
|
return Card(
|
||||||
child: EntityModel(
|
child: EntityModel(
|
||||||
entityWrapper: card.linkedEntityWrapper,
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
child: EntityButtonCardBody(),
|
child: EntityButtonCardBody(
|
||||||
|
showName: card.showName,
|
||||||
|
),
|
||||||
handleTap: true
|
handleTap: true
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -2,8 +2,10 @@ part of '../../main.dart';
|
|||||||
|
|
||||||
class EntityButtonCardBody extends StatelessWidget {
|
class EntityButtonCardBody extends StatelessWidget {
|
||||||
|
|
||||||
|
final bool showName;
|
||||||
|
|
||||||
EntityButtonCardBody({
|
EntityButtonCardBody({
|
||||||
Key key,
|
Key key, this.showName: true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -39,6 +41,7 @@ class EntityButtonCardBody extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildName() {
|
Widget _buildName() {
|
||||||
|
if (showName) {
|
||||||
return EntityName(
|
return EntityName(
|
||||||
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
||||||
textOverflow: TextOverflow.ellipsis,
|
textOverflow: TextOverflow.ellipsis,
|
||||||
@ -48,4 +51,6 @@ class EntityButtonCardBody extends StatelessWidget {
|
|||||||
fontSize: Sizes.nameFontSize,
|
fontSize: Sizes.nameFontSize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return Container(width: 0, height: 0);
|
||||||
|
}
|
||||||
}
|
}
|
@ -25,9 +25,12 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
|||||||
|
|
||||||
|
|
||||||
void _callService(AlarmControlPanelEntity entity, String service) {
|
void _callService(AlarmControlPanelEntity entity, String service) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, service, entity.entityId,
|
domain: entity.domain,
|
||||||
{"code": "$code"}));
|
service: service,
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"code": "$code"}
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
code = "";
|
code = "";
|
||||||
});
|
});
|
||||||
@ -58,7 +61,11 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
|||||||
FlatButton(
|
FlatButton(
|
||||||
child: new Text("Yes"),
|
child: new Text("Yes"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "alarm_trigger",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -83,7 +83,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
|
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "set_temperature",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}
|
||||||
|
);
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -101,7 +106,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
|
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "set_temperature",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}
|
||||||
|
);
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -111,7 +121,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpTargetHumidity = value.roundToDouble();
|
_tmpTargetHumidity = value.roundToDouble();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_humidity", entity.entityId,{"humidity": "$_tmpTargetHumidity"}));
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "set_humidity",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"humidity": "$_tmpTargetHumidity"}
|
||||||
|
);
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -120,7 +135,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpHVACMode = value;
|
_tmpHVACMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_hvac_mode", entity.entityId,{"hvac_mode": "$_tmpHVACMode"}));
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "set_hvac_mode",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"hvac_mode": "$_tmpHVACMode"}
|
||||||
|
);
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -129,7 +149,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpSwingMode = value;
|
_tmpSwingMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_swing_mode", entity.entityId,{"swing_mode": "$_tmpSwingMode"}));
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "set_swing_mode",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"swing_mode": "$_tmpSwingMode"}
|
||||||
|
);
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -138,7 +163,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpFanMode = value;
|
_tmpFanMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_fan_mode", entity.entityId,{"fan_mode": "$_tmpFanMode"}));
|
ConnectionManager().callService(domain: entity.domain, service: "set_fan_mode", entityId: entity.entityId, data: {"fan_mode": "$_tmpFanMode"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -147,7 +172,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpPresetMode = value;
|
_tmpPresetMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_preset_mode", entity.entityId,{"preset_mode": "$_tmpPresetMode"}));
|
ConnectionManager().callService(domain: entity.domain, service: "set_preset_mode", entityId: entity.entityId, data: {"preset_mode": "$_tmpPresetMode"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -165,7 +190,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpAuxHeat = value;
|
_tmpAuxHeat = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_aux_heat", entity.entityId, {"aux_heat": "$_tmpAuxHeat"}));
|
ConnectionManager().callService(domain: entity.domain, service: "set_aux_heat", entityId: entity.entityId, data: {"aux_heat": "$_tmpAuxHeat"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpPosition = position.roundToDouble();
|
_tmpPosition = position.roundToDouble();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()}));
|
ConnectionManager().callService(domain: entity.domain, service: "set_cover_position", entityId: entity.entityId, data: {"position": _tmpPosition.round()});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpTiltPosition = position.roundToDouble();
|
_tmpTiltPosition = position.roundToDouble();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_tilt_position", entity.entityId,{"tilt_position": _tmpTiltPosition.round()}));
|
ConnectionManager().callService(domain: entity.domain, service: "set_cover_tilt_position", entityId: entity.entityId, data: {"tilt_position": _tmpTiltPosition.round()});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,18 +135,18 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
|
|
||||||
class CoverTiltControlsWidget extends StatelessWidget {
|
class CoverTiltControlsWidget extends StatelessWidget {
|
||||||
void _open(CoverEntity entity) {
|
void _open(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "open_cover_tilt", entity.entityId, null));
|
domain: entity.domain, service: "open_cover_tilt", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close(CoverEntity entity) {
|
void _close(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "close_cover_tilt", entity.entityId, null));
|
domain: entity.domain, service: "close_cover_tilt", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stop(CoverEntity entity) {
|
void _stop(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "stop_cover_tilt", entity.entityId, null));
|
domain: entity.domain, service: "stop_cover_tilt", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2,18 +2,27 @@ part of '../../../main.dart';
|
|||||||
|
|
||||||
class CoverStateWidget extends StatelessWidget {
|
class CoverStateWidget extends StatelessWidget {
|
||||||
void _open(CoverEntity entity) {
|
void _open(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "open_cover", entity.entityId, null));
|
domain: entity.domain,
|
||||||
|
service: "open_cover",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close(CoverEntity entity) {
|
void _close(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "close_cover", entity.entityId, null));
|
domain: entity.domain,
|
||||||
|
service: "close_cover",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stop(CoverEntity entity) {
|
void _stop(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "stop_cover", entity.entityId, null));
|
domain: entity.domain,
|
||||||
|
service: "stop_cover",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -35,8 +35,7 @@ class DateTimeEntity extends Entity {
|
|||||||
return formattedState;
|
return formattedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setNewState(newValue) {
|
void setNewState(Map newValue) {
|
||||||
eventBus
|
ConnectionManager().callService(domain: domain, service: "set_datetime", entityId: entityId, data: newValue);
|
||||||
.fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -153,7 +153,7 @@ class Entity {
|
|||||||
domain = rawData["entity_id"].split(".")[0];
|
domain = rawData["entity_id"].split(".")[0];
|
||||||
entityId = rawData["entity_id"];
|
entityId = rawData["entity_id"];
|
||||||
deviceClass = attributes["device_class"];
|
deviceClass = attributes["device_class"];
|
||||||
state = rawData["state"];
|
state = rawData["state"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["state"];
|
||||||
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state);
|
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state);
|
||||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||||
entityPicture = _getEntityPictureUrl(webHost);
|
entityPicture = _getEntityPictureUrl(webHost);
|
||||||
|
@ -17,7 +17,7 @@ class EntityPageLayout extends StatelessWidget {
|
|||||||
showClose ?
|
showClose ?
|
||||||
Container(
|
Container(
|
||||||
color: Colors.blue[300],
|
color: Colors.blue[300],
|
||||||
height: 36,
|
height: 40,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -37,7 +37,7 @@ class EntityPageLayout extends StatelessWidget {
|
|||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
icon: Icon(Icons.close),
|
icon: Icon(Icons.close),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
iconSize: 30.0,
|
iconSize: 36.0,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
eventBus.fire(ShowEntityPageEvent());
|
eventBus.fire(ShowEntityPageEvent());
|
||||||
},
|
},
|
||||||
|
@ -32,17 +32,17 @@ class EntityWrapper {
|
|||||||
void handleTap() {
|
void handleTap() {
|
||||||
switch (uiAction.tapAction) {
|
switch (uiAction.tapAction) {
|
||||||
case EntityUIAction.toggle: {
|
case EntityUIAction.toggle: {
|
||||||
eventBus.fire(
|
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId);
|
||||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.callService: {
|
case EntityUIAction.callService: {
|
||||||
if (uiAction.tapService != null) {
|
if (uiAction.tapService != null) {
|
||||||
eventBus.fire(
|
ConnectionManager().callService(
|
||||||
ServiceCallEvent(uiAction.tapService.split(".")[0],
|
domain: uiAction.tapService.split(".")[0],
|
||||||
uiAction.tapService.split(".")[1], null,
|
service: uiAction.tapService.split(".")[1],
|
||||||
uiAction.tapServiceData));
|
data: uiAction.tapServiceData
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ class EntityWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.navigate: {
|
case EntityUIAction.navigate: {
|
||||||
if (uiAction.tapService.startsWith("/")) {
|
if (uiAction.tapService != null && uiAction.tapService.startsWith("/")) {
|
||||||
//TODO handle local urls
|
//TODO handle local urls
|
||||||
Logger.w("Local urls is not supported yet");
|
Logger.w("Local urls is not supported yet");
|
||||||
} else {
|
} else {
|
||||||
@ -76,17 +76,17 @@ class EntityWrapper {
|
|||||||
void handleHold() {
|
void handleHold() {
|
||||||
switch (uiAction.holdAction) {
|
switch (uiAction.holdAction) {
|
||||||
case EntityUIAction.toggle: {
|
case EntityUIAction.toggle: {
|
||||||
eventBus.fire(
|
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId);
|
||||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.callService: {
|
case EntityUIAction.callService: {
|
||||||
if (uiAction.holdService != null) {
|
if (uiAction.holdService != null) {
|
||||||
eventBus.fire(
|
ConnectionManager().callService(
|
||||||
ServiceCallEvent(uiAction.holdService.split(".")[0],
|
domain: uiAction.holdService.split(".")[0],
|
||||||
uiAction.holdService.split(".")[1], null,
|
service: uiAction.holdService.split(".")[1],
|
||||||
uiAction.holdServiceData));
|
data: uiAction.holdServiceData
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ class EntityWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.navigate: {
|
case EntityUIAction.navigate: {
|
||||||
if (uiAction.holdService.startsWith("/")) {
|
if (uiAction.holdService != null && uiAction.holdService.startsWith("/")) {
|
||||||
//TODO handle local urls
|
//TODO handle local urls
|
||||||
Logger.w("Local urls is not supported yet");
|
Logger.w("Local urls is not supported yet");
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,9 +24,12 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpOscillate = oscillate;
|
_tmpOscillate = oscillate;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"fan", "oscillate", entity.entityId,
|
domain: "fan",
|
||||||
{"oscillating": oscillate}));
|
service: "oscillate",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"oscillating": oscillate}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,9 +37,12 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpDirectionForward = forward;
|
_tmpDirectionForward = forward;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"fan", "set_direction", entity.entityId,
|
domain: "fan",
|
||||||
{"direction": forward ? "forward" : "reverse"}));
|
service: "set_direction",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"direction": forward ? "forward" : "reverse"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +50,12 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpSpeed = value;
|
_tmpSpeed = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"fan", "set_speed", entity.entityId,
|
domain: "fan",
|
||||||
{"speed": value}));
|
service: "set_speed",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"speed": value}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
void _setNewState() {
|
void _setNewState() {
|
||||||
eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
|
ConnectionManager().callService(domain: serviceDomain, service: serviceName, entityId: entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,9 +28,12 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpBrightness = value.round();
|
_tmpBrightness = value.round();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
domain: entity.domain,
|
||||||
{"brightness": _tmpBrightness}));
|
service: "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"brightness": _tmpBrightness}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +41,12 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpWhiteValue = value.round();
|
_tmpWhiteValue = value.round();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
domain: entity.domain,
|
||||||
{"white_value": _tmpWhiteValue}));
|
service: "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"white_value": _tmpWhiteValue}
|
||||||
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -49,9 +55,12 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpColorTemp = value.round();
|
_tmpColorTemp = value.round();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
domain: entity.domain,
|
||||||
{"color_temp": _tmpColorTemp}));
|
service: "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"color_temp": _tmpColorTemp}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,10 +68,12 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpColor = color;
|
_tmpColor = color;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
ConnectionManager().callService(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
domain: entity.domain,
|
||||||
entity.domain, "turn_on", entity.entityId,
|
service: "turn_on",
|
||||||
{"hs_color": [color.hue, color.saturation*100]}));
|
entityId: entity.entityId,
|
||||||
|
data: {"hs_color": [color.hue, color.saturation*100]}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,9 +82,12 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpEffect = value;
|
_tmpEffect = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
if (_tmpEffect != null) {
|
if (_tmpEffect != null) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
domain: entity.domain,
|
||||||
{"effect": "$value"}));
|
service: "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"effect": "$value"}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -227,8 +241,6 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
|
|
||||||
Widget _buildEffectControl(LightEntity entity) {
|
Widget _buildEffectControl(LightEntity entity) {
|
||||||
if ((entity.supportEffect) && (entity.effectList != null)) {
|
if ((entity.supportEffect) && (entity.effectList != null)) {
|
||||||
Logger.d("[LIGHT] entity effects: ${entity.effectList}");
|
|
||||||
Logger.d("[LIGHT] current effect: $_tmpEffect");
|
|
||||||
List<String> list = List.from(entity.effectList);
|
List<String> list = List.from(entity.effectList);
|
||||||
if (_tmpEffect!= null && !list.contains(_tmpEffect)) {
|
if (_tmpEffect!= null && !list.contains(_tmpEffect)) {
|
||||||
list.insert(0, _tmpEffect);
|
list.insert(0, _tmpEffect);
|
||||||
|
@ -7,11 +7,11 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
||||||
|
|
||||||
void _lock(Entity entity) {
|
void _lock(Entity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent("lock", "lock", entity.entityId, null));
|
ConnectionManager().callService(domain: "lock", service: "lock", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unlock(Entity entity) {
|
void _unlock(Entity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
|
ConnectionManager().callService(domain: "lock", service: "unlock", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -84,12 +84,11 @@ class MediaPlayerEntity extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool canCalculateActualPosition() {
|
bool canCalculateActualPosition() {
|
||||||
return positionLastUpdated != null && durationSeconds != null && positionSeconds != null && durationSeconds >= 0;
|
return positionLastUpdated != null && durationSeconds != null && positionSeconds != null && durationSeconds > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
double getActualPosition() {
|
double getActualPosition() {
|
||||||
double result = 0;
|
double result = 0;
|
||||||
if (canCalculateActualPosition()) {
|
|
||||||
Duration durationD;
|
Duration durationD;
|
||||||
Duration positionD;
|
Duration positionD;
|
||||||
durationD = Duration(seconds: durationSeconds);
|
durationD = Duration(seconds: durationSeconds);
|
||||||
@ -101,8 +100,6 @@ class MediaPlayerEntity extends Entity {
|
|||||||
.difference(positionLastUpdated)
|
.difference(positionLastUpdated)
|
||||||
.inSeconds;
|
.inSeconds;
|
||||||
result = ((result + differenceInSeconds) <= durationD.inSeconds) ? (result + differenceInSeconds) : durationD.inSeconds.toDouble();
|
result = ((result + differenceInSeconds) <= durationD.inSeconds) ? (result + differenceInSeconds) : durationD.inSeconds.toDouble();
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,13 +22,13 @@ class _MediaPlayerProgressBarState extends State<MediaPlayerProgressBar> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final EntityModel entityModel = EntityModel.of(context);
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||||
double progress;
|
double progress = 0;
|
||||||
int currentPosition;
|
int currentPosition;
|
||||||
if (entity.canCalculateActualPosition()) {
|
if (entity.canCalculateActualPosition()) {
|
||||||
currentPosition = entity.getActualPosition().toInt();
|
currentPosition = entity.getActualPosition().toInt();
|
||||||
|
if (currentPosition > 0) {
|
||||||
progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
|
progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
|
||||||
} else {
|
}
|
||||||
progress = 0;
|
|
||||||
}
|
}
|
||||||
return LinearProgressIndicator(
|
return LinearProgressIndicator(
|
||||||
value: progress,
|
value: progress,
|
||||||
|
@ -56,12 +56,12 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
color: Colors.orange,
|
color: Colors.orange,
|
||||||
focusColor: Colors.white,
|
focusColor: Colors.white,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
eventBus.fire(ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"media_player",
|
domain: "media_player",
|
||||||
"media_seek",
|
service: "media_seek",
|
||||||
"${entity.entityId}",
|
entityId: entity.entityId,
|
||||||
{"seek_position": _savedPosition}
|
data: {"seek_position": _savedPosition}
|
||||||
));
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
_savedPosition = 0;
|
_savedPosition = 0;
|
||||||
});
|
});
|
||||||
@ -103,12 +103,12 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
_seekStarted = false;
|
_seekStarted = false;
|
||||||
Timer(Duration(milliseconds: 500), () {
|
Timer(Duration(milliseconds: 500), () {
|
||||||
if (!_seekStarted) {
|
if (!_seekStarted) {
|
||||||
eventBus.fire(ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"media_player",
|
domain: "media_player",
|
||||||
"media_seek",
|
service: "media_seek",
|
||||||
"${entity.entityId}",
|
entityId: entity.entityId,
|
||||||
{"seek_position": val}
|
data: {"seek_position": val}
|
||||||
));
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
_currentPosition = val;
|
_currentPosition = val;
|
||||||
|
@ -118,26 +118,28 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
|||||||
|
|
||||||
|
|
||||||
void _setPower(MediaPlayerEntity entity) {
|
void _setPower(MediaPlayerEntity entity) {
|
||||||
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
|
|
||||||
if (entity.state == EntityState.off) {
|
if (entity.state == EntityState.off) {
|
||||||
Logger.d("${entity.entityId} turn_on");
|
ConnectionManager().callService(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
domain: entity.domain,
|
||||||
entity.domain, "turn_on", entity.entityId,
|
service: "turn_on",
|
||||||
null));
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Logger.d("${entity.entityId} turn_off");
|
ConnectionManager().callService(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
domain: entity.domain,
|
||||||
entity.domain, "turn_off", entity.entityId,
|
service: "turn_off",
|
||||||
null));
|
entityId: entity.entityId
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _callAction(MediaPlayerEntity entity, String action) {
|
void _callAction(MediaPlayerEntity entity, String action) {
|
||||||
Logger.d("${entity.entityId} $action");
|
Logger.d("${entity.entityId} $action");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "$action", entity.entityId,
|
domain: entity.domain,
|
||||||
null));
|
service: "$action",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -264,27 +266,50 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
_newVolumeLevel = value;
|
_newVolumeLevel = value;
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value}));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_set",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"volume_level": value}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeMute(bool isMuted, String entityId) {
|
void _setVolumeMute(bool isMuted, String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted}));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_mute",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"is_volume_muted": isMuted}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeUp(String entityId) {
|
void _setVolumeUp(String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_up",
|
||||||
|
entityId: entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeDown(String entityId) {
|
void _setVolumeDown(String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_down",
|
||||||
|
entityId: entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setSoundMode(String value, String entityId) {
|
void _setSoundMode(String value, String entityId) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_newSoundMode = value;
|
_newSoundMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "select_sound_mode",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"sound_mode": "$value"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +317,12 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_newSource = source;
|
_newSource = source;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "select_source",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"source": "$source"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,16 +460,16 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _duplicateTo(entity) {
|
void _duplicateTo(entity) {
|
||||||
|
if (entity.canCalculateActualPosition()) {
|
||||||
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
||||||
if (MediaQuery.of(context).size.width < Sizes.tabletMinWidth) {
|
|
||||||
Navigator.of(context).popAndPushNamed("/play-media", arguments: {"url": entity.attributes["media_content_id"], "type": entity.attributes["media_content_type"]});
|
|
||||||
} else {
|
} else {
|
||||||
|
HomeAssistant().savedPlayerPosition = 0;
|
||||||
|
}
|
||||||
Navigator.of(context).pushNamed("/play-media", arguments: {
|
Navigator.of(context).pushNamed("/play-media", arguments: {
|
||||||
"url": entity.attributes["media_content_id"],
|
"url": entity.attributes["media_content_id"],
|
||||||
"type": entity.attributes["media_content_type"]
|
"type": entity.attributes["media_content_type"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _switchTo(entity) {
|
void _switchTo(entity) {
|
||||||
HomeAssistant().sendFromPlayerId = entity.entityId;
|
HomeAssistant().sendFromPlayerId = entity.entityId;
|
||||||
|
@ -11,8 +11,12 @@ class SelectStateWidget extends StatefulWidget {
|
|||||||
class _SelectStateWidgetState extends State<SelectStateWidget> {
|
class _SelectStateWidgetState extends State<SelectStateWidget> {
|
||||||
|
|
||||||
void setNewState(domain, entityId, newValue) {
|
void setNewState(domain, entityId, newValue) {
|
||||||
eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId,
|
ConnectionManager().callService(
|
||||||
{"option": "$newValue"}));
|
domain: domain,
|
||||||
|
service: "select_option",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"option": "$newValue"}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -18,8 +18,12 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
|||||||
_newValue = newValue;
|
_newValue = newValue;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
});
|
});
|
||||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
ConnectionManager().callService(
|
||||||
{"value": "${newValue.toString()}"}));
|
domain: domain,
|
||||||
|
service: "set_value",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"value": "${newValue.toString()}"}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -38,8 +38,11 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
|||||||
} else {
|
} else {
|
||||||
domain = entity.domain;
|
domain = entity.domain;
|
||||||
}
|
}
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
|
domain: domain,
|
||||||
|
service: (newValue as bool) ? "turn_on" : "turn_off",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -26,8 +26,12 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
|||||||
|
|
||||||
void setNewState(newValue, domain, entityId) {
|
void setNewState(newValue, domain, entityId) {
|
||||||
if (validate(newValue, _minLength, _maxLength)) {
|
if (validate(newValue, _minLength, _maxLength)) {
|
||||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
ConnectionManager().callService(
|
||||||
{"value": "$newValue"}));
|
domain: domain,
|
||||||
|
service: "set_value",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"value": "$newValue"}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpValue = _entityState;
|
_tmpValue = _entityState;
|
||||||
|
@ -197,7 +197,7 @@ class VacuumControls extends StatelessWidget {
|
|||||||
domain: "vacuum",
|
domain: "vacuum",
|
||||||
entityId: entity.entityId,
|
entityId: entity.entityId,
|
||||||
service: "set_fan_speed",
|
service: "set_fan_speed",
|
||||||
additionalServiceData: {"fan_speed": val}
|
data: {"fan_speed": val}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -63,10 +63,6 @@ class HomeAssistant {
|
|||||||
futures.add(_getServices());
|
futures.add(_getServices());
|
||||||
futures.add(_getUserInfo());
|
futures.add(_getUserInfo());
|
||||||
futures.add(_getPanels());
|
futures.add(_getPanels());
|
||||||
futures.add(ConnectionManager().sendSocketMessage(
|
|
||||||
type: "subscribe_events",
|
|
||||||
additionalData: {"event_type": "state_changed"},
|
|
||||||
));
|
|
||||||
Future.wait(futures).then((_) {
|
Future.wait(futures).then((_) {
|
||||||
if (isMobileAppEnabled) {
|
if (isMobileAppEnabled) {
|
||||||
if (!childMode) _createUI();
|
if (!childMode) _createUI();
|
||||||
@ -106,10 +102,20 @@ class HomeAssistant {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getLovelace() async {
|
Future _getLovelace() {
|
||||||
await ConnectionManager().sendSocketMessage(type: "lovelace/config").then((data) => _rawLovelaceData = data).catchError((e) {
|
Completer completer = Completer();
|
||||||
throw HAError("Error getting lovelace config: $e");
|
ConnectionManager().sendSocketMessage(type: "lovelace/config").then((data) {
|
||||||
|
_rawLovelaceData = data;
|
||||||
|
completer.complete();
|
||||||
|
}).catchError((e) {
|
||||||
|
if ("$e" == "config_not_found") {
|
||||||
|
ConnectionManager().useLovelace = false;
|
||||||
|
completer.complete();
|
||||||
|
} else {
|
||||||
|
completer.completeError(HAError("Error getting lovelace config: $e"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getUserInfo() async {
|
Future _getUserInfo() async {
|
||||||
@ -125,7 +131,6 @@ class HomeAssistant {
|
|||||||
Future _getServices() async {
|
Future _getServices() async {
|
||||||
await ConnectionManager().sendSocketMessage(type: "get_services").then((data) {
|
await ConnectionManager().sendSocketMessage(type: "get_services").then((data) {
|
||||||
Logger.d("Got ${data.length} services");
|
Logger.d("Got ${data.length} services");
|
||||||
Logger.d("Media extractor: ${data["media_extractor"]}");
|
|
||||||
services = data;
|
services = data;
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
Logger.w("Can't get services: $e");
|
Logger.w("Can't get services: $e");
|
||||||
@ -180,10 +185,18 @@ class HomeAssistant {
|
|||||||
|
|
||||||
if (rawView['badges'] != null && rawView['badges'] is List) {
|
if (rawView['badges'] != null && rawView['badges'] is List) {
|
||||||
rawView['badges'].forEach((entity) {
|
rawView['badges'].forEach((entity) {
|
||||||
|
if (entity is String) {
|
||||||
if (entities.isExist(entity)) {
|
if (entities.isExist(entity)) {
|
||||||
Entity e = entities.get(entity);
|
Entity e = entities.get(entity);
|
||||||
view.badges.add(e);
|
view.badges.add(e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
String eId = '${entity['entity']}';
|
||||||
|
if (entities.isExist(eId)) {
|
||||||
|
Entity e = entities.get(eId);
|
||||||
|
view.badges.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,10 +219,10 @@ class HomeAssistant {
|
|||||||
name: rawCardInfo["title"] ?? rawCardInfo["name"],
|
name: rawCardInfo["title"] ?? rawCardInfo["name"],
|
||||||
type: rawCardInfo['type'] ?? CardType.ENTITIES,
|
type: rawCardInfo['type'] ?? CardType.ENTITIES,
|
||||||
columnsCount: rawCardInfo['columns'] ?? 4,
|
columnsCount: rawCardInfo['columns'] ?? 4,
|
||||||
showName: rawCardInfo['show_name'] ?? true,
|
showName: (rawCardInfo['show_name'] ?? rawCard['show_name']) ?? true,
|
||||||
showState: rawCardInfo['show_state'] ?? true,
|
showState: (rawCardInfo['show_state'] ?? rawCard['show_state']) ?? true,
|
||||||
showEmpty: rawCardInfo['show_empty'] ?? true,
|
showEmpty: (rawCardInfo['show_empty'] ?? rawCard['show_empty']) ?? true,
|
||||||
stateFilter: rawCardInfo['state_filter'] ?? [],
|
stateFilter: (rawCard['state_filter'] ?? rawCardInfo['state_filter']) ?? [],
|
||||||
states: rawCardInfo['states'],
|
states: rawCardInfo['states'],
|
||||||
conditions: rawCard['conditions'] ?? [],
|
conditions: rawCard['conditions'] ?? [],
|
||||||
content: rawCardInfo['content'],
|
content: rawCardInfo['content'],
|
||||||
|
@ -30,6 +30,7 @@ import 'package:uni_links/uni_links.dart';
|
|||||||
import 'package:workmanager/workmanager.dart' as workManager;
|
import 'package:workmanager/workmanager.dart' as workManager;
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:battery/battery.dart';
|
import 'package:battery/battery.dart';
|
||||||
|
import 'package:sentry/sentry.dart';
|
||||||
|
|
||||||
import 'utils/logger.dart';
|
import 'utils/logger.dart';
|
||||||
|
|
||||||
@ -138,34 +139,46 @@ part 'entities/media_player/widgets/media_player_progress_bar.widget.dart';
|
|||||||
part 'pages/whats_new.page.dart';
|
part 'pages/whats_new.page.dart';
|
||||||
|
|
||||||
EventBus eventBus = new EventBus();
|
EventBus eventBus = new EventBus();
|
||||||
|
final SentryClient _sentry = SentryClient(dsn: "https://03ef364745cc4c23a60ddbc874c69925@sentry.io/1836118");
|
||||||
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
||||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
||||||
const String appName = "HA Client";
|
const String appName = "HA Client";
|
||||||
const appVersionNumber = "0.7.0";
|
const appVersionNumber = "0.7.6";
|
||||||
const appVersionAdd = "";
|
const appVersionAdd = "";
|
||||||
const appVersion = "$appVersionNumber-$appVersionAdd";
|
const appVersion = "$appVersionNumber$appVersionAdd";
|
||||||
|
|
||||||
|
Future<void> _reportError(dynamic error, dynamic stackTrace) async {
|
||||||
|
// Print the exception to the console.
|
||||||
|
if (Logger.isInDebugMode) {
|
||||||
|
Logger.e('Caught error: $error');
|
||||||
|
Logger.p(stackTrace);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Logger.e('Caught error: $error. Reporting to Senrty.');
|
||||||
|
// Send the Exception and Stacktrace to Sentry in Production mode.
|
||||||
|
_sentry.captureException(
|
||||||
|
exception: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
FlutterError.onError = (errorDetails) {
|
FlutterError.onError = (FlutterErrorDetails details) {
|
||||||
Logger.e( "${errorDetails.exception}");
|
Logger.e(" Caut Flutter runtime error: ${details.exception}");
|
||||||
if (Logger.isInDebugMode) {
|
if (Logger.isInDebugMode) {
|
||||||
FlutterError.dumpErrorToConsole(errorDetails);
|
FlutterError.dumpErrorToConsole(details);
|
||||||
|
} else {
|
||||||
|
// In production mode, report to the application zone to report to
|
||||||
|
// Sentry.
|
||||||
|
Zone.current.handleUncaughtError(details.exception, details.stack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
runZoned(() {
|
runZoned(() {
|
||||||
workManager.Workmanager.initialize(
|
|
||||||
updateDeviceLocationIsolate,
|
|
||||||
isInDebugMode: false
|
|
||||||
);
|
|
||||||
runApp(new HAClientApp());
|
runApp(new HAClientApp());
|
||||||
|
|
||||||
}, onError: (error, stack) {
|
}, onError: (error, stack) {
|
||||||
Logger.e("$error");
|
_reportError(error, stack);
|
||||||
Logger.e("$stack");
|
|
||||||
if (Logger.isInDebugMode) {
|
|
||||||
debugPrint("$stack");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,16 +98,23 @@ class ConnectionManager {
|
|||||||
|
|
||||||
void _doConnect({Completer completer, bool forceReconnect}) {
|
void _doConnect({Completer completer, bool forceReconnect}) {
|
||||||
if (forceReconnect || !isConnected) {
|
if (forceReconnect || !isConnected) {
|
||||||
_connect().timeout(connectTimeout, onTimeout: () {
|
|
||||||
_disconnect().then((_){
|
_disconnect().then((_){
|
||||||
if (completer != null && !completer.isCompleted) {
|
_connect().timeout(connectTimeout).then((_) {
|
||||||
completer.completeError(HAError("Connection timeout"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).then((_) {
|
|
||||||
completer?.complete();
|
completer?.complete();
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
|
_disconnect().then((_) {
|
||||||
|
if (e is TimeoutException) {
|
||||||
|
if (connecting != null && !connecting.isCompleted) {
|
||||||
|
connecting.completeError(HAError("Connection timeout"));
|
||||||
|
}
|
||||||
|
completer?.completeError(HAError("Connection timeout"));
|
||||||
|
} else if (e is HAError) {
|
||||||
completer?.completeError(e);
|
completer?.completeError(e);
|
||||||
|
} else {
|
||||||
|
completer?.completeError(HAError("${e.toString()}"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
completer?.complete();
|
completer?.complete();
|
||||||
@ -124,6 +131,7 @@ class ConnectionManager {
|
|||||||
connecting = Completer();
|
connecting = Completer();
|
||||||
_disconnect().then((_) {
|
_disconnect().then((_) {
|
||||||
Logger.d("Socket connecting...");
|
Logger.d("Socket connecting...");
|
||||||
|
try {
|
||||||
_socket = IOWebSocketChannel.connect(
|
_socket = IOWebSocketChannel.connect(
|
||||||
_webSocketAPIEndpoint, pingInterval: Duration(seconds: 15));
|
_webSocketAPIEndpoint, pingInterval: Duration(seconds: 15));
|
||||||
_socketSubscription = _socket.stream.listen(
|
_socketSubscription = _socket.stream.listen(
|
||||||
@ -140,11 +148,17 @@ class ConnectionManager {
|
|||||||
});
|
});
|
||||||
} else if (data["type"] == "auth_ok") {
|
} else if (data["type"] == "auth_ok") {
|
||||||
Logger.d("[Received] <== ${data.toString()}");
|
Logger.d("[Received] <== ${data.toString()}");
|
||||||
|
Logger.d("[Connection] Subscribing to events");
|
||||||
|
sendSocketMessage(
|
||||||
|
type: "subscribe_events",
|
||||||
|
additionalData: {"event_type": "state_changed"},
|
||||||
|
).whenComplete((){
|
||||||
_messageResolver["auth"]?.complete();
|
_messageResolver["auth"]?.complete();
|
||||||
_messageResolver.remove("auth");
|
_messageResolver.remove("auth");
|
||||||
if (_token != null) {
|
if (_token != null) {
|
||||||
if (!connecting.isCompleted) connecting.complete();
|
if (!connecting.isCompleted) connecting.complete();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
} else if (data["type"] == "auth_invalid") {
|
} else if (data["type"] == "auth_invalid") {
|
||||||
Logger.d("[Received] <== ${data.toString()}");
|
Logger.d("[Received] <== ${data.toString()}");
|
||||||
_messageResolver["auth"]?.completeError(HAError("${data["message"]}", actions: [HAErrorAction.loginAgain()]));
|
_messageResolver["auth"]?.completeError(HAError("${data["message"]}", actions: [HAErrorAction.loginAgain()]));
|
||||||
@ -158,6 +172,9 @@ class ConnectionManager {
|
|||||||
onDone: () => _handleSocketClose(connecting),
|
onDone: () => _handleSocketClose(connecting),
|
||||||
onError: (e) => _handleSocketError(e, connecting)
|
onError: (e) => _handleSocketError(e, connecting)
|
||||||
);
|
);
|
||||||
|
} catch(exeption) {
|
||||||
|
connecting.completeError(HAError("${exeption.toString()}"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return connecting.future;
|
return connecting.future;
|
||||||
}
|
}
|
||||||
@ -189,13 +206,13 @@ class ConnectionManager {
|
|||||||
//Logger.d("[Received] <== Request id ${data['id']} was successful");
|
//Logger.d("[Received] <== Request id ${data['id']} was successful");
|
||||||
_messageResolver["${data["id"]}"]?.complete(data["result"]);
|
_messageResolver["${data["id"]}"]?.complete(data["result"]);
|
||||||
} else if (data["id"] != null) {
|
} else if (data["id"] != null) {
|
||||||
//Logger.e("[Received] <== Error received on request id ${data['id']}: ${data['error']}");
|
Logger.e("[Received] <== Error received on request id ${data['id']}: ${data['error']}");
|
||||||
_messageResolver["${data["id"]}"]?.completeError("${data['error']["message"]}");
|
_messageResolver["${data["id"]}"]?.completeError("${data["error"]["code"]}");
|
||||||
}
|
}
|
||||||
_messageResolver.remove("${data["id"]}");
|
_messageResolver.remove("${data["id"]}");
|
||||||
} else if (data["type"] == "event") {
|
} else if (data["type"] == "event") {
|
||||||
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
|
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
|
||||||
//Logger.d("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
|
Logger.d("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
|
||||||
onStateChangeCallback(data["event"]["data"]);
|
onStateChangeCallback(data["event"]["data"]);
|
||||||
} else if (data["event"] != null) {
|
} else if (data["event"] != null) {
|
||||||
Logger.w("Unhandled event type: ${data["event"]["event_type"]}");
|
Logger.w("Unhandled event type: ${data["event"]["event_type"]}");
|
||||||
@ -209,38 +226,24 @@ class ConnectionManager {
|
|||||||
|
|
||||||
void _handleSocketClose(Completer connectionCompleter) {
|
void _handleSocketClose(Completer connectionCompleter) {
|
||||||
Logger.d("Socket disconnected.");
|
Logger.d("Socket disconnected.");
|
||||||
|
_disconnect().then((_) {
|
||||||
if (!connectionCompleter.isCompleted) {
|
if (!connectionCompleter.isCompleted) {
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
connectionCompleter.completeError(HAError("Disconnected", actions: [HAErrorAction.reconnect()]));
|
connectionCompleter.completeError(HAError("Disconnected", actions: [HAErrorAction.reconnect()]));
|
||||||
} else {
|
}
|
||||||
_disconnect().then((_) {
|
|
||||||
Timer(Duration(seconds: 5), () {
|
|
||||||
Logger.d("Trying to reconnect...");
|
|
||||||
_connect().catchError((e) {
|
|
||||||
isConnected = false;
|
|
||||||
eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant")));
|
eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant")));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSocketError(e, Completer connectionCompleter) {
|
void _handleSocketError(e, Completer connectionCompleter) {
|
||||||
Logger.e("Socket stream Error: $e");
|
Logger.e("Socket stream Error: $e");
|
||||||
|
_disconnect().then((_) {
|
||||||
if (!connectionCompleter.isCompleted) {
|
if (!connectionCompleter.isCompleted) {
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
connectionCompleter.completeError(HAError("Unable to connect to Home Assistant"));
|
connectionCompleter.completeError(HAError("Disconnected", actions: [HAErrorAction.reconnect()]));
|
||||||
} else {
|
}
|
||||||
_disconnect().then((_) {
|
|
||||||
Timer(Duration(seconds: 5), () {
|
|
||||||
Logger.d("Trying to reconnect...");
|
|
||||||
_connect().catchError((e) {
|
|
||||||
isConnected = false;
|
|
||||||
eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant")));
|
eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant")));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _authenticate() {
|
Future _authenticate() {
|
||||||
@ -329,13 +332,13 @@ class ConnectionManager {
|
|||||||
_messageResolver[callbackName] = _completer;
|
_messageResolver[callbackName] = _completer;
|
||||||
String rawMessage = json.encode(dataObject);
|
String rawMessage = json.encode(dataObject);
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
_connect().timeout(connectTimeout, onTimeout: (){
|
_connect().timeout(connectTimeout).then((_) {
|
||||||
_completer.completeError(HAError("No connection to Home Assistant", actions: [HAErrorAction.reconnect()]));
|
|
||||||
}).then((_) {
|
|
||||||
Logger.d("[Sending] ==> ${auth ? "type="+dataObject['type'] : rawMessage}");
|
Logger.d("[Sending] ==> ${auth ? "type="+dataObject['type'] : rawMessage}");
|
||||||
_socket.sink.add(rawMessage);
|
_socket.sink.add(rawMessage);
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
_completer.completeError(e);
|
if (!_completer.isCompleted) {
|
||||||
|
_completer.completeError(HAError("No connection to Home Assistant", actions: [HAErrorAction.reconnect()]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Logger.d("[Sending] ==> ${auth ? "type="+dataObject['type'] : rawMessage}");
|
Logger.d("[Sending] ==> ${auth ? "type="+dataObject['type'] : rawMessage}");
|
||||||
@ -348,25 +351,27 @@ class ConnectionManager {
|
|||||||
_currentMessageId += 1;
|
_currentMessageId += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future callService({String domain, String service, String entityId, Map additionalServiceData}) {
|
Future callService({@required String domain, @required String service, String entityId, Map data}) {
|
||||||
|
eventBus.fire(NotifyServiceCallEvent(domain, service, entityId));
|
||||||
|
Logger.d("Service call: $domain.$service, $entityId, $data");
|
||||||
Completer completer = Completer();
|
Completer completer = Completer();
|
||||||
Map serviceData = {};
|
Map serviceData = {};
|
||||||
if (entityId != null) {
|
if (entityId != null) {
|
||||||
serviceData["entity_id"] = entityId;
|
serviceData["entity_id"] = entityId;
|
||||||
}
|
}
|
||||||
if (additionalServiceData != null && additionalServiceData.isNotEmpty) {
|
if (data != null && data.isNotEmpty) {
|
||||||
serviceData.addAll(additionalServiceData);
|
serviceData.addAll(data);
|
||||||
}
|
}
|
||||||
if (serviceData.isNotEmpty)
|
if (serviceData.isNotEmpty)
|
||||||
sendHTTPPost(
|
sendHTTPPost(
|
||||||
endPoint: "/api/services/$domain/$service",
|
endPoint: "/api/services/$domain/$service",
|
||||||
data: json.encode(serviceData)
|
data: json.encode(serviceData)
|
||||||
).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError("${e["message"]}")));
|
).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError(e.toString())));
|
||||||
//return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service, "service_data": serviceData});
|
//return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service, "service_data": serviceData});
|
||||||
else
|
else
|
||||||
sendHTTPPost(
|
sendHTTPPost(
|
||||||
endPoint: "/api/services/$domain/$service"
|
endPoint: "/api/services/$domain/$service"
|
||||||
).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError("${e["message"]}")));;
|
).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError(e.toString())));
|
||||||
//return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service});
|
//return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service});
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
@ -407,11 +412,12 @@ class ConnectionManager {
|
|||||||
headers: headers,
|
headers: headers,
|
||||||
body: data
|
body: data
|
||||||
).then((response) {
|
).then((response) {
|
||||||
Logger.d("[Received] <== HTTP ${response.statusCode}");
|
|
||||||
if (response.statusCode >= 200 && response.statusCode < 300 ) {
|
if (response.statusCode >= 200 && response.statusCode < 300 ) {
|
||||||
|
Logger.d("[Received] <== HTTP ${response.statusCode}");
|
||||||
completer.complete(response.body);
|
completer.complete(response.body);
|
||||||
} else {
|
} else {
|
||||||
completer.completeError({"code": response.statusCode, "message": "${response.body}"});
|
Logger.d("[Received] <== HTTP ${response.statusCode}: ${response.body}");
|
||||||
|
completer.completeError(response);
|
||||||
}
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
completer.completeError(e);
|
completer.completeError(e);
|
||||||
|
@ -14,7 +14,7 @@ class LocationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int defaultUpdateIntervalMinutes = 20;
|
final int defaultUpdateIntervalMinutes = 20;
|
||||||
final String backgroundTaskId = "haclocationtask4352";
|
final String backgroundTaskId = "haclocationtask0";
|
||||||
final String backgroundTaskTag = "haclocation";
|
final String backgroundTaskTag = "haclocation";
|
||||||
Duration _updateInterval;
|
Duration _updateInterval;
|
||||||
bool _isRunning;
|
bool _isRunning;
|
||||||
@ -57,29 +57,50 @@ class LocationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_startLocationService() async {
|
_startLocationService() async {
|
||||||
Logger.d("Scheduling location update for every ${_updateInterval
|
|
||||||
.inMinutes} minutes...");
|
|
||||||
String webhookId = ConnectionManager().webhookId;
|
String webhookId = ConnectionManager().webhookId;
|
||||||
String httpWebHost = ConnectionManager().httpWebHost;
|
String httpWebHost = ConnectionManager().httpWebHost;
|
||||||
if (webhookId != null && webhookId.isNotEmpty) {
|
if (webhookId != null && webhookId.isNotEmpty) {
|
||||||
|
Duration interval;
|
||||||
|
int delayFactor;
|
||||||
|
int taskCount;
|
||||||
|
Logger.d("Starting location update for every ${_updateInterval
|
||||||
|
.inMinutes} minutes...");
|
||||||
|
if (_updateInterval.inMinutes == 10) {
|
||||||
|
interval = Duration(minutes: 20);
|
||||||
|
taskCount = 2;
|
||||||
|
delayFactor = 10;
|
||||||
|
} else if (_updateInterval.inMinutes == 5) {
|
||||||
|
interval = Duration(minutes: 15);
|
||||||
|
taskCount = 3;
|
||||||
|
delayFactor = 5;
|
||||||
|
} else {
|
||||||
|
interval = _updateInterval;
|
||||||
|
taskCount = 1;
|
||||||
|
delayFactor = 0;
|
||||||
|
}
|
||||||
|
for (int i = 1; i <= taskCount; i++) {
|
||||||
|
int delay = i*delayFactor;
|
||||||
|
Logger.d("Scheduling location update task #$i for every ${interval.inMinutes} minutes in $delay minutes...");
|
||||||
await workManager.Workmanager.registerPeriodicTask(
|
await workManager.Workmanager.registerPeriodicTask(
|
||||||
backgroundTaskId,
|
"$backgroundTaskId$i",
|
||||||
"haClientLocationTracking",
|
"haClientLocationTracking-0$i",
|
||||||
tag: backgroundTaskTag,
|
tag: backgroundTaskTag,
|
||||||
inputData: {
|
inputData: {
|
||||||
"webhookId": webhookId,
|
"webhookId": webhookId,
|
||||||
"httpWebHost": httpWebHost
|
"httpWebHost": httpWebHost
|
||||||
},
|
},
|
||||||
frequency: _updateInterval,
|
frequency: interval,
|
||||||
|
initialDelay: Duration(minutes: delay),
|
||||||
existingWorkPolicy: workManager.ExistingWorkPolicy.keep,
|
existingWorkPolicy: workManager.ExistingWorkPolicy.keep,
|
||||||
backoffPolicy: workManager.BackoffPolicy.linear,
|
backoffPolicy: workManager.BackoffPolicy.linear,
|
||||||
backoffPolicyDelay: _updateInterval,
|
backoffPolicyDelay: interval,
|
||||||
constraints: workManager.Constraints(
|
constraints: workManager.Constraints(
|
||||||
networkType: workManager.NetworkType.connected
|
networkType: workManager.NetworkType.connected
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_stopLocationService() async {
|
_stopLocationService() async {
|
||||||
Logger.d("Canceling previous schedule if any...");
|
Logger.d("Canceling previous schedule if any...");
|
||||||
@ -87,32 +108,40 @@ class LocationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateDeviceLocation() async {
|
updateDeviceLocation() async {
|
||||||
if (ConnectionManager().webhookId != null &&
|
Logger.d("[Foreground location] Started");
|
||||||
ConnectionManager().webhookId.isNotEmpty) {
|
Geolocator geolocator = Geolocator();
|
||||||
String url = "${ConnectionManager()
|
var battery = Battery();
|
||||||
.httpWebHost}/api/webhook/${ConnectionManager().webhookId}";
|
String webhookId = ConnectionManager().webhookId;
|
||||||
Map<String, String> headers = {};
|
String httpWebHost = ConnectionManager().httpWebHost;
|
||||||
Logger.d("[Location] Getting device location...");
|
if (webhookId != null && webhookId.isNotEmpty) {
|
||||||
Position location = await Geolocator().getCurrentPosition(
|
Logger.d("[Foreground location] Getting battery level...");
|
||||||
desiredAccuracy: LocationAccuracy.medium);
|
int batteryLevel = await battery.batteryLevel;
|
||||||
Logger.d("[Location] Got location: ${location.latitude} ${location
|
Logger.d("[Foreground location] Getting device location...");
|
||||||
.longitude}. Sending home...");
|
Position position = await geolocator.getCurrentPosition(
|
||||||
int battery = await Battery().batteryLevel;
|
desiredAccuracy: LocationAccuracy.high,
|
||||||
var data = {
|
locationPermissionLevel: GeolocationPermission.locationAlways
|
||||||
|
);
|
||||||
|
if (position != null) {
|
||||||
|
Logger.d("[Foreground location] Location: ${position.latitude} ${position.longitude}. Accuracy: ${position.accuracy}. (${position.timestamp})");
|
||||||
|
String url = "$httpWebHost/api/webhook/$webhookId";
|
||||||
|
Map data = {
|
||||||
"type": "update_location",
|
"type": "update_location",
|
||||||
"data": {
|
"data": {
|
||||||
"gps": [location.latitude, location.longitude],
|
"gps": [position.latitude, position.longitude],
|
||||||
"gps_accuracy": location.accuracy,
|
"gps_accuracy": position.accuracy,
|
||||||
"battery": battery
|
"battery": batteryLevel ?? 100
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
headers["Content-Type"] = "application/json";
|
Logger.d("[Foreground location] Sending data home...");
|
||||||
await http.post(
|
var response = await http.post(
|
||||||
url,
|
url,
|
||||||
headers: headers,
|
headers: {"Content-Type": "application/json"},
|
||||||
body: json.encode(data)
|
body: json.encode(data)
|
||||||
);
|
);
|
||||||
Logger.d("[Location] ...done.");
|
Logger.d("[Foreground location] Got HTTP ${response.statusCode}");
|
||||||
|
} else {
|
||||||
|
Logger.d("[Foreground location] No location. Aborting.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,13 +149,15 @@ class LocationManager {
|
|||||||
|
|
||||||
void updateDeviceLocationIsolate() {
|
void updateDeviceLocationIsolate() {
|
||||||
workManager.Workmanager.executeTask((backgroundTask, data) {
|
workManager.Workmanager.executeTask((backgroundTask, data) {
|
||||||
//print("[Background $backgroundTask] Started");
|
print("[Background $backgroundTask] Started");
|
||||||
|
final SentryClient sentryBackgroundClient = SentryClient(dsn: "https://5c868e5ef26947e2b61b189e391ec31b@sentry.io/1836366");
|
||||||
|
Geolocator geolocator = Geolocator();
|
||||||
var battery = Battery();
|
var battery = Battery();
|
||||||
int batteryLevel = 100;
|
int batteryLevel = 100;
|
||||||
String webhookId = data["webhookId"];
|
String webhookId = data["webhookId"];
|
||||||
String httpWebHost = data["httpWebHost"];
|
String httpWebHost = data["httpWebHost"];
|
||||||
if (webhookId != null && webhookId.isNotEmpty) {
|
if (webhookId != null && webhookId.isNotEmpty) {
|
||||||
//print("[Background $backgroundTask] hour=$battery");
|
print("[Background $backgroundTask] hour=$battery");
|
||||||
String url = "$httpWebHost/api/webhook/$webhookId";
|
String url = "$httpWebHost/api/webhook/$webhookId";
|
||||||
Map<String, String> headers = {};
|
Map<String, String> headers = {};
|
||||||
headers["Content-Type"] = "application/json";
|
headers["Content-Type"] = "application/json";
|
||||||
@ -138,36 +169,28 @@ void updateDeviceLocationIsolate() {
|
|||||||
"battery": batteryLevel
|
"battery": batteryLevel
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//print("[Background $backgroundTask] Getting battery level...");
|
print("[Background $backgroundTask] Getting battery level...");
|
||||||
battery.batteryLevel.then((val) => data["data"]["battery"] = val).whenComplete((){
|
battery.batteryLevel.then((val) => data["data"]["battery"] = val).whenComplete((){
|
||||||
//print("[Background $backgroundTask] Getting device location...");
|
print("[Background $backgroundTask] Getting device location...");
|
||||||
Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.medium).then((location) {
|
geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high, locationPermissionLevel: GeolocationPermission.locationAlways).then((location) {
|
||||||
//print("[Background $backgroundTask] Got location: ${location.latitude} ${location.longitude}");
|
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
|
print("[Background $backgroundTask] Got location: ${location.latitude} ${location.longitude}");
|
||||||
data["data"]["gps"] = [location.latitude, location.longitude];
|
data["data"]["gps"] = [location.latitude, location.longitude];
|
||||||
data["data"]["gps_accuracy"] = location.accuracy;
|
data["data"]["gps_accuracy"] = location.accuracy;
|
||||||
//print("[Background $backgroundTask] Sending data home...");
|
print("[Background $backgroundTask] Sending data home.");
|
||||||
http.post(
|
http.post(
|
||||||
url,
|
url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: json.encode(data)
|
body: json.encode(data)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
throw "Can't get device location. Location is null";
|
||||||
}
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
//print("[Background $backgroundTask] Error getting current location: ${e.toString()}. Trying last known...");
|
sentryBackgroundClient.captureException(
|
||||||
Geolocator().getLastKnownPosition(desiredAccuracy: LocationAccuracy.medium).then((location){
|
exception: "${e.toString()}"
|
||||||
//print("[Background $backgroundTask] Got last known location: ${location.latitude} ${location.longitude}");
|
|
||||||
if (location != null) {
|
|
||||||
data["data"]["gps"] = [location.latitude, location.longitude];
|
|
||||||
data["data"]["gps_accuracy"] = location.accuracy;
|
|
||||||
//print("[Background $backgroundTask] Sending data home...");
|
|
||||||
http.post(
|
|
||||||
url,
|
|
||||||
headers: headers,
|
|
||||||
body: json.encode(data)
|
|
||||||
);
|
);
|
||||||
}
|
print("[Background $backgroundTask] Error getting current location: ${e.toString()}");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ class MobileAppIntegrationManager {
|
|||||||
positiveText: "Restart now",
|
positiveText: "Restart now",
|
||||||
negativeText: "Later",
|
negativeText: "Later",
|
||||||
onPositive: () {
|
onPositive: () {
|
||||||
ConnectionManager().callService(domain: "homeassistant", service: "restart", entityId: null);
|
ConnectionManager().callService(domain: "homeassistant", service: "restart");
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
@ -81,7 +81,7 @@ class MobileAppIntegrationManager {
|
|||||||
}
|
}
|
||||||
completer.complete();
|
completer.complete();
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
if (e['code'] != null && e['code'] == 410) {
|
if (e is http.Response && e.statusCode == 410) {
|
||||||
Logger.e("MobileApp integration was removed");
|
Logger.e("MobileApp integration was removed");
|
||||||
_askToRegisterApp();
|
_askToRegisterApp();
|
||||||
} else {
|
} else {
|
||||||
|
@ -29,6 +29,9 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_locationTrackingEnabled = prefs.getBool("location-enabled") ?? false;
|
_locationTrackingEnabled = prefs.getBool("location-enabled") ?? false;
|
||||||
_locationInterval = prefs.getInt("location-interval") ?? LocationManager().defaultUpdateIntervalMinutes;
|
_locationInterval = prefs.getInt("location-interval") ?? LocationManager().defaultUpdateIntervalMinutes;
|
||||||
|
if (_locationInterval % 5 != 0) {
|
||||||
|
_locationInterval = 5 * (_locationInterval ~/ 5);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -36,15 +39,15 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
|||||||
void incLocationInterval() {
|
void incLocationInterval() {
|
||||||
if (_locationInterval < 720) {
|
if (_locationInterval < 720) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_locationInterval = _locationInterval + 1;
|
_locationInterval = _locationInterval + 5;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void decLocationInterval() {
|
void decLocationInterval() {
|
||||||
if (_locationInterval > 1) {
|
if (_locationInterval > 5) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_locationInterval = _locationInterval - 1;
|
_locationInterval = _locationInterval - 5;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +59,7 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
|||||||
positiveText: "Sure. Make it so",
|
positiveText: "Sure. Make it so",
|
||||||
negativeText: "What?? No!",
|
negativeText: "What?? No!",
|
||||||
onPositive: () {
|
onPositive: () {
|
||||||
ConnectionManager().callService(domain: "homeassistant", service: "restart", entityId: null);
|
ConnectionManager().callService(domain: "homeassistant", service: "restart");
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -68,7 +71,7 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
|||||||
positiveText: "Sure. Make it so",
|
positiveText: "Sure. Make it so",
|
||||||
negativeText: "What?? No!",
|
negativeText: "What?? No!",
|
||||||
onPositive: () {
|
onPositive: () {
|
||||||
ConnectionManager().callService(domain: "homeassistant", service: "stop", entityId: null);
|
ConnectionManager().callService(domain: "homeassistant", service: "stop");
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -26,16 +26,20 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
bool _showLoginButton = false;
|
bool _showLoginButton = false;
|
||||||
bool _preventAppRefresh = false;
|
bool _preventAppRefresh = false;
|
||||||
String _savedSharedText;
|
String _savedSharedText;
|
||||||
String _entityToShow;
|
Entity _entityToShow;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
final Stream purchaseUpdates =
|
final Stream purchaseUpdates =
|
||||||
InAppPurchaseConnection.instance.purchaseUpdatedStream;
|
InAppPurchaseConnection.instance.purchaseUpdatedStream;
|
||||||
_subscription = purchaseUpdates.listen((purchases) {
|
_subscription = purchaseUpdates.listen((purchases) {
|
||||||
_handlePurchaseUpdates(purchases);
|
_handlePurchaseUpdates(purchases);
|
||||||
});
|
});
|
||||||
super.initState();
|
workManager.Workmanager.initialize(
|
||||||
|
updateDeviceLocationIsolate,
|
||||||
|
isInDebugMode: false
|
||||||
|
);
|
||||||
enableShareReceiving();
|
enableShareReceiving();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
@ -122,7 +126,6 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
_showInfoBottomBar(progress: true,);
|
_showInfoBottomBar(progress: true,);
|
||||||
ConnectionManager().init(loadSettings: false, forceReconnect: false).then((_){
|
ConnectionManager().init(loadSettings: false, forceReconnect: false).then((_){
|
||||||
_fetchData();
|
_fetchData();
|
||||||
//StartupUserMessagesManager().checkMessagesToShow();
|
|
||||||
}, onError: (e) {
|
}, onError: (e) {
|
||||||
_setErrorState(e);
|
_setErrorState(e);
|
||||||
});
|
});
|
||||||
@ -142,6 +145,9 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
_viewsTabController = TabController(vsync: this, length: currentViewCount);
|
_viewsTabController = TabController(vsync: this, length: currentViewCount);
|
||||||
_previousViewCount = currentViewCount;
|
_previousViewCount = currentViewCount;
|
||||||
}
|
}
|
||||||
|
if (_entityToShow != null) {
|
||||||
|
_entityToShow = HomeAssistant().entities.get(_entityToShow.entityId);
|
||||||
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
if (e is HAError) {
|
if (e is HAError) {
|
||||||
_setErrorState(e);
|
_setErrorState(e);
|
||||||
@ -218,9 +224,8 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
}
|
}
|
||||||
if (_serviceCallSubscription == null) {
|
if (_serviceCallSubscription == null) {
|
||||||
_serviceCallSubscription =
|
_serviceCallSubscription =
|
||||||
eventBus.on<ServiceCallEvent>().listen((event) {
|
eventBus.on<NotifyServiceCallEvent>().listen((event) {
|
||||||
_callService(event.domain, event.service, event.entityId,
|
_notifyServiceCalled(event.domain, event.service, event.entityId);
|
||||||
event.additionalParams);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,27 +323,28 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO remove this shit.... maybe
|
void _notifyServiceCalled(String domain, String service, String entityId) {
|
||||||
void _callService(String domain, String service, String entityId, Map additionalParams) {
|
|
||||||
_showInfoBottomBar(
|
_showInfoBottomBar(
|
||||||
message: "Calling $domain.$service",
|
message: "Calling $domain.$service",
|
||||||
duration: Duration(seconds: 3)
|
duration: Duration(seconds: 4)
|
||||||
);
|
);
|
||||||
ConnectionManager().callService(domain: domain, service: service, entityId: entityId, additionalServiceData: additionalParams).catchError((e) => _setErrorState(e));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showEntityPage(String entityId) {
|
void _showEntityPage(String entityId) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_entityToShow = entityId;
|
_entityToShow = HomeAssistant().entities?.get(entityId);
|
||||||
|
if (_entityToShow != null) {
|
||||||
|
_mainScrollController?.jumpTo(0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (_entityToShow!= null && MediaQuery.of(context).size.width < Sizes.tabletMinWidth) {
|
/*if (_entityToShow!= null && MediaQuery.of(context).size.width < Sizes.tabletMinWidth) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => EntityViewPage(entityId: entityId),
|
builder: (context) => EntityViewPage(entityId: entityId),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showPage(String path, bool goBackFirst) {
|
void _showPage(String path, bool goBackFirst) {
|
||||||
@ -368,12 +374,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
menuItems.add(
|
menuItems.add(
|
||||||
UserAccountsDrawerHeader(
|
UserAccountsDrawerHeader(
|
||||||
accountName: Text(HomeAssistant().userName),
|
accountName: Text(HomeAssistant().userName),
|
||||||
accountEmail: Text(ConnectionManager().displayHostname ?? "Not configured"),
|
accountEmail: Text(HomeAssistant().locationName ?? ""),
|
||||||
onDetailsPressed: () {
|
|
||||||
Launcher.launchURLInCustomTab(
|
|
||||||
url: "${ConnectionManager().httpWebHost}/profile?external_auth=1"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
currentAccountPicture: CircleAvatar(
|
currentAccountPicture: CircleAvatar(
|
||||||
child: Text(
|
child: Text(
|
||||||
HomeAssistant().userAvatarText,
|
HomeAssistant().userAvatarText,
|
||||||
@ -636,6 +637,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
}
|
}
|
||||||
|
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
final ScrollController _mainScrollController = ScrollController();
|
||||||
|
|
||||||
Widget _buildScaffoldBody(bool empty) {
|
Widget _buildScaffoldBody(bool empty) {
|
||||||
List<PopupMenuItem<String>> serviceMenuItems = [];
|
List<PopupMenuItem<String>> serviceMenuItems = [];
|
||||||
@ -730,7 +732,6 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_entityToShow != null && MediaQuery.of(context).size.width >= Sizes.tabletMinWidth) {
|
if (_entityToShow != null && MediaQuery.of(context).size.width >= Sizes.tabletMinWidth) {
|
||||||
Entity entity = HomeAssistant().entities.get(_entityToShow);
|
|
||||||
mainScrollBody = Flex(
|
mainScrollBody = Flex(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -743,12 +744,13 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
),
|
),
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints.tightFor(width: Sizes.entityPageMaxWidth),
|
constraints: BoxConstraints.tightFor(width: Sizes.entityPageMaxWidth),
|
||||||
child: EntityPageLayout(entity: entity, showClose: true,),
|
child: EntityPageLayout(entity: _entityToShow, showClose: true,),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
} else if (_entityToShow != null) {
|
||||||
|
mainScrollBody = EntityPageLayout(entity: _entityToShow, showClose: true,);
|
||||||
} else {
|
} else {
|
||||||
_entityToShow = null;
|
|
||||||
mainScrollBody = HomeAssistant().buildViews(context, _viewsTabController);
|
mainScrollBody = HomeAssistant().buildViews(context, _viewsTabController);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -804,7 +806,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
_scaffoldKey.currentState.openDrawer();
|
_scaffoldKey.currentState.openDrawer();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
bottom: empty ? null : TabBar(
|
bottom: (empty || _entityToShow != null) ? null : TabBar(
|
||||||
controller: _viewsTabController,
|
controller: _viewsTabController,
|
||||||
tabs: buildUIViewTabs(),
|
tabs: buildUIViewTabs(),
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
@ -813,7 +815,8 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
|
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
body: mainScrollBody
|
body: mainScrollBody,
|
||||||
|
controller: _mainScrollController,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,12 +882,22 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
body: _buildScaffoldBody(true)
|
body: _buildScaffoldBody(true)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Scaffold(
|
return WillPopScope(
|
||||||
|
child: Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
drawer: _buildAppDrawer(),
|
drawer: _buildAppDrawer(),
|
||||||
primary: false,
|
primary: false,
|
||||||
bottomNavigationBar: bottomBar,
|
bottomNavigationBar: bottomBar,
|
||||||
body: _buildScaffoldBody(false)
|
body: _buildScaffoldBody(false)
|
||||||
|
),
|
||||||
|
onWillPop: () {
|
||||||
|
if (_entityToShow != null) {
|
||||||
|
eventBus.fire(ShowEntityPageEvent());
|
||||||
|
return Future.value(false);
|
||||||
|
} else {
|
||||||
|
return Future.value(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,16 +90,20 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
ConnectionManager().callService(
|
ConnectionManager().callService(
|
||||||
domain: serviceDomain,
|
domain: serviceDomain,
|
||||||
entityId: entity.entityId,
|
|
||||||
service: "play_media",
|
service: "play_media",
|
||||||
additionalServiceData: {
|
entityId: entity.entityId,
|
||||||
|
data: {
|
||||||
"media_content_id": _mediaUrl,
|
"media_content_id": _mediaUrl,
|
||||||
"media_content_type": _contentType
|
"media_content_type": _contentType
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
HomeAssistant().sendToPlayerId = entity.entityId;
|
HomeAssistant().sendToPlayerId = entity.entityId;
|
||||||
if (HomeAssistant().sendFromPlayerId != null && HomeAssistant().sendFromPlayerId != HomeAssistant().sendToPlayerId) {
|
if (HomeAssistant().sendFromPlayerId != null && HomeAssistant().sendFromPlayerId != HomeAssistant().sendToPlayerId) {
|
||||||
eventBus.fire(ServiceCallEvent(HomeAssistant().sendFromPlayerId.split(".")[0], "turn_off", HomeAssistant().sendFromPlayerId, null));
|
ConnectionManager().callService(
|
||||||
|
domain: HomeAssistant().sendFromPlayerId.split(".")[0],
|
||||||
|
service: "turn_off",
|
||||||
|
entityId: HomeAssistant().sendFromPlayerId
|
||||||
|
);
|
||||||
HomeAssistant().sendFromPlayerId = null;
|
HomeAssistant().sendFromPlayerId = null;
|
||||||
}
|
}
|
||||||
eventBus.fire(ShowEntityPageEvent(entity: entity));
|
eventBus.fire(ShowEntityPageEvent(entity: entity));
|
||||||
|
@ -75,10 +75,16 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
|
|
||||||
_saveSettings() async {
|
_saveSettings() async {
|
||||||
_newHassioDomain = _newHassioDomain.trim();
|
_newHassioDomain = _newHassioDomain.trim();
|
||||||
if (_newHassioDomain.indexOf("http") == 0 && _newHassioDomain.indexOf("//") > 0) {
|
if (_newHassioDomain.startsWith("http") && _newHassioDomain.indexOf("//") > 0) {
|
||||||
|
_newHassioDomain.startsWith("https") ? _newSocketProtocol = "wss" : _newSocketProtocol = "ws";
|
||||||
_newHassioDomain = _newHassioDomain.split("//")[1];
|
_newHassioDomain = _newHassioDomain.split("//")[1];
|
||||||
}
|
}
|
||||||
_newHassioDomain = _newHassioDomain.split("/")[0];
|
_newHassioDomain = _newHassioDomain.split("/")[0];
|
||||||
|
if (_newHassioDomain.contains(":")) {
|
||||||
|
List<String> domainAndPort = _newHassioDomain.split(":");
|
||||||
|
_newHassioDomain = domainAndPort[0];
|
||||||
|
_newHassioPort = domainAndPort[1];
|
||||||
|
}
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
final storage = new FlutterSecureStorage();
|
final storage = new FlutterSecureStorage();
|
||||||
if (_newLongLivedToken.isNotEmpty) {
|
if (_newLongLivedToken.isNotEmpty) {
|
||||||
|
@ -24,7 +24,7 @@ class _WhatsNewPageState extends State<WhatsNewPage> {
|
|||||||
error = "";
|
error = "";
|
||||||
});
|
});
|
||||||
http.Response response;
|
http.Response response;
|
||||||
response = await http.get("http://ha-client.homemade.systems/service/whats_new_$appVersionNumber.md");
|
response = await http.get("http://ha-client.homemade.systems/service/whats_new_0.7.0.md");
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
setState(() {
|
setState(() {
|
||||||
data = response.body;
|
data = response.body;
|
||||||
|
@ -33,13 +33,12 @@ class StartAuthEvent {
|
|||||||
StartAuthEvent(this.oauthUrl, this.showButton);
|
StartAuthEvent(this.oauthUrl, this.showButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServiceCallEvent {
|
class NotifyServiceCallEvent {
|
||||||
String domain;
|
String domain;
|
||||||
String service;
|
String service;
|
||||||
String entityId;
|
String entityId;
|
||||||
Map<String, dynamic> additionalParams;
|
|
||||||
|
|
||||||
ServiceCallEvent(this.domain, this.service, this.entityId, this.additionalParams);
|
NotifyServiceCallEvent(this.domain, this.service, this.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShowPopupDialogEvent {
|
class ShowPopupDialogEvent {
|
||||||
|
@ -23,6 +23,10 @@ class Logger {
|
|||||||
return inDebugMode;
|
return inDebugMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void p(data) {
|
||||||
|
print(data);
|
||||||
|
}
|
||||||
|
|
||||||
static void e(String message) {
|
static void e(String message) {
|
||||||
_writeToLog("Error", message);
|
_writeToLog("Error", message);
|
||||||
}
|
}
|
||||||
|
10
pubspec.yaml
10
pubspec.yaml
@ -1,7 +1,8 @@
|
|||||||
name: hass_client
|
name: hass_client
|
||||||
description: Home Assistant Android Client
|
description: Home Assistant Android Client
|
||||||
|
|
||||||
version: 0.7.1+712
|
version: 0.7.6+760
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||||
@ -17,7 +18,7 @@ dependencies:
|
|||||||
url_launcher: any
|
url_launcher: any
|
||||||
date_format: any
|
date_format: any
|
||||||
charts_flutter: ^0.8.1
|
charts_flutter: ^0.8.1
|
||||||
flutter_markdown: any
|
flutter_markdown: 0.3.0
|
||||||
in_app_purchase: ^0.2.1+4
|
in_app_purchase: ^0.2.1+4
|
||||||
flutter_custom_tabs: ^0.6.0
|
flutter_custom_tabs: ^0.6.0
|
||||||
firebase_messaging: ^5.1.6
|
firebase_messaging: ^5.1.6
|
||||||
@ -25,9 +26,10 @@ dependencies:
|
|||||||
flutter_secure_storage: ^3.3.1+1
|
flutter_secure_storage: ^3.3.1+1
|
||||||
device_info: ^0.4.0+3
|
device_info: ^0.4.0+3
|
||||||
flutter_local_notifications: ^0.8.4
|
flutter_local_notifications: ^0.8.4
|
||||||
geolocator: ^5.1.4+2
|
geolocator: ^5.1.5
|
||||||
workmanager: ^0.1.3
|
workmanager: ^0.1.5
|
||||||
battery: ^0.3.1+1
|
battery: ^0.3.1+1
|
||||||
|
sentry: ^2.3.1
|
||||||
share:
|
share:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/d-silveira/flutter-share.git
|
url: https://github.com/d-silveira/flutter-share.git
|
||||||
|
Reference in New Issue
Block a user