Compare commits

..

1 Commits

Author SHA1 Message Date
3234ffc20c build 713 2019-10-29 18:14:56 +00:00
39 changed files with 415 additions and 586 deletions

View File

@ -7,16 +7,35 @@ 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:** **Home Assistant version:** <!-- 0.94.1 for example -->
**Device name:** **Device name:** <!-- Pixel 2 for example -->
**Android version:** **Android version:** <!-- 8.1 for example -->
**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]
```

View File

@ -7,7 +7,7 @@ Visit [homemade.systems](http://ha-client.homemade.systems/) for more info.
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient) Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient)
Discuss it on [Spectrum.chat](https://spectrum.chat/ha-client) or at [Home Assistant community](https://community.home-assistant.io/c/mobile-apps/ha-client-android) Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912) or on [Discord server](https://discord.gg/AUzEvwn)
#### Pre-release CI build #### Pre-release CI build
[![Codemagic build status](https://api.codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/status_badge.svg)](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/latest_build) [![Codemagic build status](https://api.codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/status_badge.svg)](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/latest_build)

View File

@ -3,6 +3,11 @@
<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" />
@ -10,7 +15,6 @@
<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

View File

@ -285,9 +285,7 @@ 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
) )
); );

View File

@ -2,10 +2,8 @@ part of '../../main.dart';
class EntityButtonCardBody extends StatelessWidget { class EntityButtonCardBody extends StatelessWidget {
final bool showName;
EntityButtonCardBody({ EntityButtonCardBody({
Key key, this.showName: true, Key key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -41,16 +39,13 @@ 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, maxLines: 3,
maxLines: 3, wordsWrap: true,
wordsWrap: true, textAlign: TextAlign.center,
textAlign: TextAlign.center, fontSize: Sizes.nameFontSize,
fontSize: Sizes.nameFontSize, );
);
}
return Container(width: 0, height: 0);
} }
} }

View File

@ -25,12 +25,9 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
void _callService(AlarmControlPanelEntity entity, String service) { void _callService(AlarmControlPanelEntity entity, String service) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, service, entity.entityId,
service: service, {"code": "$code"}));
entityId: entity.entityId,
data: {"code": "$code"}
);
setState(() { setState(() {
code = ""; code = "";
}); });
@ -61,11 +58,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
FlatButton( FlatButton(
child: new Text("Yes"), child: new Text("Yes"),
onPressed: () { onPressed: () {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
domain: entity.domain,
service: "alarm_trigger",
entityId: entity.entityId
);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),

View File

@ -83,12 +83,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
_tempThrottleTimer = Timer(Duration(seconds: 2), () { _tempThrottleTimer = Timer(Duration(seconds: 2), () {
setState(() { setState(() {
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
domain: entity.domain,
service: "set_temperature",
entityId: entity.entityId,
data: {"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}
);
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
}); });
@ -106,12 +101,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () { _targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
setState(() { setState(() {
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
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);
}); });
}); });
@ -121,12 +111,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpTargetHumidity = value.roundToDouble(); _tmpTargetHumidity = value.roundToDouble();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_humidity", entity.entityId,{"humidity": "$_tmpTargetHumidity"}));
domain: entity.domain,
service: "set_humidity",
entityId: entity.entityId,
data: {"humidity": "$_tmpTargetHumidity"}
);
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
} }
@ -135,12 +120,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpHVACMode = value; _tmpHVACMode = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_hvac_mode", entity.entityId,{"hvac_mode": "$_tmpHVACMode"}));
domain: entity.domain,
service: "set_hvac_mode",
entityId: entity.entityId,
data: {"hvac_mode": "$_tmpHVACMode"}
);
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
} }
@ -149,12 +129,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpSwingMode = value; _tmpSwingMode = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_swing_mode", entity.entityId,{"swing_mode": "$_tmpSwingMode"}));
domain: entity.domain,
service: "set_swing_mode",
entityId: entity.entityId,
data: {"swing_mode": "$_tmpSwingMode"}
);
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
} }
@ -163,7 +138,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpFanMode = value; _tmpFanMode = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService(domain: entity.domain, service: "set_fan_mode", entityId: entity.entityId, data: {"fan_mode": "$_tmpFanMode"}); eventBus.fire(new ServiceCallEvent(entity.domain, "set_fan_mode", entity.entityId,{"fan_mode": "$_tmpFanMode"}));
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
} }
@ -172,7 +147,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpPresetMode = value; _tmpPresetMode = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService(domain: entity.domain, service: "set_preset_mode", entityId: entity.entityId, data: {"preset_mode": "$_tmpPresetMode"}); eventBus.fire(new ServiceCallEvent(entity.domain, "set_preset_mode", entity.entityId,{"preset_mode": "$_tmpPresetMode"}));
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
} }
@ -190,7 +165,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpAuxHeat = value; _tmpAuxHeat = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService(domain: entity.domain, service: "set_aux_heat", entityId: entity.entityId, data: {"aux_heat": "$_tmpAuxHeat"}); eventBus.fire(new ServiceCallEvent(entity.domain, "set_aux_heat", entity.entityId, {"aux_heat": "$_tmpAuxHeat"}));
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
} }

View File

@ -18,7 +18,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
setState(() { setState(() {
_tmpPosition = position.roundToDouble(); _tmpPosition = position.roundToDouble();
_changedHere = true; _changedHere = true;
ConnectionManager().callService(domain: entity.domain, service: "set_cover_position", entityId: entity.entityId, data: {"position": _tmpPosition.round()}); eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()}));
}); });
} }
@ -26,7 +26,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
setState(() { setState(() {
_tmpTiltPosition = position.roundToDouble(); _tmpTiltPosition = position.roundToDouble();
_changedHere = true; _changedHere = true;
ConnectionManager().callService(domain: entity.domain, service: "set_cover_tilt_position", entityId: entity.entityId, data: {"tilt_position": _tmpTiltPosition.round()}); eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_tilt_position", entity.entityId,{"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) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, service: "open_cover_tilt", entityId: entity.entityId); entity.domain, "open_cover_tilt", entity.entityId, null));
} }
void _close(CoverEntity entity) { void _close(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, service: "close_cover_tilt", entityId: entity.entityId); entity.domain, "close_cover_tilt", entity.entityId, null));
} }
void _stop(CoverEntity entity) { void _stop(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, service: "stop_cover_tilt", entityId: entity.entityId); entity.domain, "stop_cover_tilt", entity.entityId, null));
} }
@override @override

View File

@ -2,27 +2,18 @@ part of '../../../main.dart';
class CoverStateWidget extends StatelessWidget { class CoverStateWidget extends StatelessWidget {
void _open(CoverEntity entity) { void _open(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "open_cover", entity.entityId, null));
service: "open_cover",
entityId: entity.entityId
);
} }
void _close(CoverEntity entity) { void _close(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "close_cover", entity.entityId, null));
service: "close_cover",
entityId: entity.entityId
);
} }
void _stop(CoverEntity entity) { void _stop(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "stop_cover", entity.entityId, null));
service: "stop_cover",
entityId: entity.entityId
);
} }
@override @override

View File

@ -35,7 +35,8 @@ class DateTimeEntity extends Entity {
return formattedState; return formattedState;
} }
void setNewState(Map newValue) { void setNewState(newValue) {
ConnectionManager().callService(domain: domain, service: "set_datetime", entityId: entityId, data: newValue); eventBus
.fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue));
} }
} }

View File

@ -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"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["state"]; state = 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);

View File

@ -17,7 +17,7 @@ class EntityPageLayout extends StatelessWidget {
showClose ? showClose ?
Container( Container(
color: Colors.blue[300], color: Colors.blue[300],
height: 40, height: 36,
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: 36.0, iconSize: 30.0,
onPressed: () { onPressed: () {
eventBus.fire(ShowEntityPageEvent()); eventBus.fire(ShowEntityPageEvent());
}, },

View File

@ -32,17 +32,17 @@ class EntityWrapper {
void handleTap() { void handleTap() {
switch (uiAction.tapAction) { switch (uiAction.tapAction) {
case EntityUIAction.toggle: { case EntityUIAction.toggle: {
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId); eventBus.fire(
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
break; break;
} }
case EntityUIAction.callService: { case EntityUIAction.callService: {
if (uiAction.tapService != null) { if (uiAction.tapService != null) {
ConnectionManager().callService( eventBus.fire(
domain: uiAction.tapService.split(".")[0], ServiceCallEvent(uiAction.tapService.split(".")[0],
service: uiAction.tapService.split(".")[1], uiAction.tapService.split(".")[1], null,
data: uiAction.tapServiceData uiAction.tapServiceData));
);
} }
break; break;
} }
@ -58,7 +58,7 @@ class EntityWrapper {
} }
case EntityUIAction.navigate: { case EntityUIAction.navigate: {
if (uiAction.tapService != null && uiAction.tapService.startsWith("/")) { if (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: {
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId); eventBus.fire(
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
break; break;
} }
case EntityUIAction.callService: { case EntityUIAction.callService: {
if (uiAction.holdService != null) { if (uiAction.holdService != null) {
ConnectionManager().callService( eventBus.fire(
domain: uiAction.holdService.split(".")[0], ServiceCallEvent(uiAction.holdService.split(".")[0],
service: uiAction.holdService.split(".")[1], uiAction.holdService.split(".")[1], null,
data: uiAction.holdServiceData uiAction.holdServiceData));
);
} }
break; break;
} }
@ -98,7 +98,7 @@ class EntityWrapper {
} }
case EntityUIAction.navigate: { case EntityUIAction.navigate: {
if (uiAction.holdService != null && uiAction.holdService.startsWith("/")) { if (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 {

View File

@ -24,12 +24,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
setState(() { setState(() {
_tmpOscillate = oscillate; _tmpOscillate = oscillate;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: "fan", "fan", "oscillate", entity.entityId,
service: "oscillate", {"oscillating": oscillate}));
entityId: entity.entityId,
data: {"oscillating": oscillate}
);
}); });
} }
@ -37,12 +34,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
setState(() { setState(() {
_tmpDirectionForward = forward; _tmpDirectionForward = forward;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: "fan", "fan", "set_direction", entity.entityId,
service: "set_direction", {"direction": forward ? "forward" : "reverse"}));
entityId: entity.entityId,
data: {"direction": forward ? "forward" : "reverse"}
);
}); });
} }
@ -50,12 +44,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
setState(() { setState(() {
_tmpSpeed = value; _tmpSpeed = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: "fan", "fan", "set_speed", entity.entityId,
service: "set_speed", {"speed": value}));
entityId: entity.entityId,
data: {"speed": value}
);
}); });
} }

View File

@ -18,7 +18,7 @@ class FlatServiceButton extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
void _setNewState() { void _setNewState() {
ConnectionManager().callService(domain: serviceDomain, service: serviceName, entityId: entityId); eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
} }
@override @override

View File

@ -28,12 +28,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpBrightness = value.round(); _tmpBrightness = value.round();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "turn_on", entity.entityId,
service: "turn_on", {"brightness": _tmpBrightness}));
entityId: entity.entityId,
data: {"brightness": _tmpBrightness}
);
}); });
} }
@ -41,12 +38,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpWhiteValue = value.round(); _tmpWhiteValue = value.round();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "turn_on", entity.entityId,
service: "turn_on", {"white_value": _tmpWhiteValue}));
entityId: entity.entityId,
data: {"white_value": _tmpWhiteValue}
);
}); });
} }
@ -55,12 +49,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpColorTemp = value.round(); _tmpColorTemp = value.round();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "turn_on", entity.entityId,
service: "turn_on", {"color_temp": _tmpColorTemp}));
entityId: entity.entityId,
data: {"color_temp": _tmpColorTemp}
);
}); });
} }
@ -68,12 +59,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpColor = color; _tmpColor = color;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
domain: entity.domain, eventBus.fire(new ServiceCallEvent(
service: "turn_on", entity.domain, "turn_on", entity.entityId,
entityId: entity.entityId, {"hs_color": [color.hue, color.saturation*100]}));
data: {"hs_color": [color.hue, color.saturation*100]}
);
}); });
} }
@ -82,12 +71,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
_tmpEffect = value; _tmpEffect = value;
_changedHere = true; _changedHere = true;
if (_tmpEffect != null) { if (_tmpEffect != null) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "turn_on", entity.entityId,
service: "turn_on", {"effect": "$value"}));
entityId: entity.entityId,
data: {"effect": "$value"}
);
} }
}); });
} }
@ -241,6 +227,8 @@ 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);

View File

@ -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) {
ConnectionManager().callService(domain: "lock", service: "lock", entityId: entity.entityId); eventBus.fire(new ServiceCallEvent("lock", "lock", entity.entityId, null));
} }
void _unlock(Entity entity) { void _unlock(Entity entity) {
ConnectionManager().callService(domain: "lock", service: "unlock", entityId: entity.entityId); eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
} }
@override @override

View File

@ -84,22 +84,25 @@ 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;
Duration durationD; if (canCalculateActualPosition()) {
Duration positionD; Duration durationD;
durationD = Duration(seconds: durationSeconds); Duration positionD;
positionD = Duration( durationD = Duration(seconds: durationSeconds);
positionD = Duration(
seconds: positionSeconds); seconds: positionSeconds);
result = positionD.inSeconds.toDouble(); result = positionD.inSeconds.toDouble();
int differenceInSeconds = DateTime int differenceInSeconds = DateTime
.now() .now()
.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;
} }

View File

@ -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 = 0; double progress;
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,

View File

@ -56,12 +56,12 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
color: Colors.orange, color: Colors.orange,
focusColor: Colors.white, focusColor: Colors.white,
onPressed: () { onPressed: () {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent(
domain: "media_player", "media_player",
service: "media_seek", "media_seek",
entityId: entity.entityId, "${entity.entityId}",
data: {"seek_position": _savedPosition} {"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) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent(
domain: "media_player", "media_player",
service: "media_seek", "media_seek",
entityId: entity.entityId, "${entity.entityId}",
data: {"seek_position": val} {"seek_position": val}
); ));
setState(() { setState(() {
_changedHere = true; _changedHere = true;
_currentPosition = val; _currentPosition = val;

View File

@ -118,28 +118,26 @@ 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) {
ConnectionManager().callService( Logger.d("${entity.entityId} turn_on");
domain: entity.domain, eventBus.fire(new ServiceCallEvent(
service: "turn_on", entity.domain, "turn_on", entity.entityId,
entityId: entity.entityId null));
);
} else { } else {
ConnectionManager().callService( Logger.d("${entity.entityId} turn_off");
domain: entity.domain, eventBus.fire(new ServiceCallEvent(
service: "turn_off", entity.domain, "turn_off", entity.entityId,
entityId: entity.entityId null));
);
} }
}
} }
void _callAction(MediaPlayerEntity entity, String action) { void _callAction(MediaPlayerEntity entity, String action) {
Logger.d("${entity.entityId} $action"); Logger.d("${entity.entityId} $action");
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "$action", entity.entityId,
service: "$action", null));
entityId: entity.entityId
);
} }
@override @override
@ -266,50 +264,27 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
setState(() { setState(() {
_changedHere = true; _changedHere = true;
_newVolumeLevel = value; _newVolumeLevel = value;
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value}));
domain: "media_player",
service: "volume_set",
entityId: entityId,
data: {"volume_level": value}
);
}); });
} }
void _setVolumeMute(bool isMuted, String entityId) { void _setVolumeMute(bool isMuted, String entityId) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted}));
domain: "media_player",
service: "volume_mute",
entityId: entityId,
data: {"is_volume_muted": isMuted}
);
} }
void _setVolumeUp(String entityId) { void _setVolumeUp(String entityId) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
domain: "media_player",
service: "volume_up",
entityId: entityId
);
} }
void _setVolumeDown(String entityId) { void _setVolumeDown(String entityId) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
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;
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
domain: "media_player",
service: "select_sound_mode",
entityId: entityId,
data: {"sound_mode": "$value"}
);
}); });
} }
@ -317,12 +292,7 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
setState(() { setState(() {
_newSource = source; _newSource = source;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
domain: "media_player",
service: "select_source",
entityId: entityId,
data: {"source": "$source"}
);
}); });
} }
@ -460,15 +430,15 @@ 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) {

View File

@ -11,12 +11,8 @@ class SelectStateWidget extends StatefulWidget {
class _SelectStateWidgetState extends State<SelectStateWidget> { class _SelectStateWidgetState extends State<SelectStateWidget> {
void setNewState(domain, entityId, newValue) { void setNewState(domain, entityId, newValue) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId,
domain: domain, {"option": "$newValue"}));
service: "select_option",
entityId: entityId,
data: {"option": "$newValue"}
);
} }
@override @override

View File

@ -18,12 +18,8 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
_newValue = newValue; _newValue = newValue;
_changedHere = true; _changedHere = true;
}); });
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
domain: domain, {"value": "${newValue.toString()}"}));
service: "set_value",
entityId: entityId,
data: {"value": "${newValue.toString()}"}
);
} }
@override @override

View File

@ -38,11 +38,8 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
} else { } else {
domain = entity.domain; domain = entity.domain;
} }
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: domain, domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
service: (newValue as bool) ? "turn_on" : "turn_off",
entityId: entity.entityId
);
} }
@override @override

View File

@ -26,12 +26,8 @@ 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)) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
domain: domain, {"value": "$newValue"}));
service: "set_value",
entityId: entityId,
data: {"value": "$newValue"}
);
} else { } else {
setState(() { setState(() {
_tmpValue = _entityState; _tmpValue = _entityState;

View File

@ -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",
data: {"fan_speed": val} additionalServiceData: {"fan_speed": val}
) )
), ),
); );

View File

@ -63,6 +63,10 @@ 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();
@ -102,21 +106,10 @@ class HomeAssistant {
}); });
} }
Future _getLovelace() { Future _getLovelace() async {
Completer completer = Completer(); await ConnectionManager().sendSocketMessage(type: "lovelace/config").then((data) => _rawLovelaceData = data).catchError((e) {
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 {
@ -132,6 +125,7 @@ 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");
@ -160,7 +154,7 @@ class HomeAssistant {
void _handleEntityStateChange(Map eventData) { void _handleEntityStateChange(Map eventData) {
//TheLogger.debug( "New state for ${eventData['entity_id']}"); //TheLogger.debug( "New state for ${eventData['entity_id']}");
if (_fetchCompleter != null && _fetchCompleter.isCompleted) { if (_fetchCompleter.isCompleted) {
Map data = Map.from(eventData); Map data = Map.from(eventData);
eventBus.fire(new StateChangedEvent( eventBus.fire(new StateChangedEvent(
entityId: data["entity_id"], entityId: data["entity_id"],
@ -186,17 +180,9 @@ 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);
}
} }
}); });
} }
@ -220,10 +206,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'] ?? rawCard['show_name']) ?? true, showName: rawCardInfo['show_name'] ?? true,
showState: (rawCardInfo['show_state'] ?? rawCard['show_state']) ?? true, showState: rawCardInfo['show_state'] ?? true,
showEmpty: (rawCardInfo['show_empty'] ?? rawCard['show_empty']) ?? true, showEmpty: rawCardInfo['show_empty'] ?? true,
stateFilter: (rawCard['state_filter'] ?? rawCardInfo['state_filter']) ?? [], stateFilter: rawCardInfo['state_filter'] ?? [],
states: rawCardInfo['states'], states: rawCardInfo['states'],
conditions: rawCard['conditions'] ?? [], conditions: rawCard['conditions'] ?? [],
content: rawCardInfo['content'], content: rawCardInfo['content'],

View File

@ -30,7 +30,6 @@ 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';
@ -139,46 +138,34 @@ 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.7"; const appVersionNumber = "0.7.0";
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 = (FlutterErrorDetails details) { FlutterError.onError = (errorDetails) {
Logger.e(" Caut Flutter runtime error: ${details.exception}"); Logger.e( "${errorDetails.exception}");
if (Logger.isInDebugMode) { if (Logger.isInDebugMode) {
FlutterError.dumpErrorToConsole(details); FlutterError.dumpErrorToConsole(errorDetails);
} 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) {
_reportError(error, stack); Logger.e("$error");
Logger.e("$stack");
if (Logger.isInDebugMode) {
debugPrint("$stack");
}
}); });
} }

View File

@ -98,23 +98,16 @@ class ConnectionManager {
void _doConnect({Completer completer, bool forceReconnect}) { void _doConnect({Completer completer, bool forceReconnect}) {
if (forceReconnect || !isConnected) { if (forceReconnect || !isConnected) {
_disconnect().then((_){ _connect().timeout(connectTimeout, onTimeout: () {
_connect().timeout(connectTimeout).then((_) { _disconnect().then((_) {
completer?.complete(); if (completer != null && !completer.isCompleted) {
}).catchError((e) { completer.completeError(HAError("Connection timeout"));
_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);
} else {
completer?.completeError(HAError("${e.toString()}"));
}
});
}); });
}).then((_) {
completer?.complete();
}).catchError((e) {
completer?.completeError(e);
}); });
} else { } else {
completer?.complete(); completer?.complete();
@ -131,50 +124,40 @@ 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(
(message) { (message) {
isConnected = true; isConnected = true;
var data = json.decode(message); var data = json.decode(message);
if (data["type"] == "auth_required") { if (data["type"] == "auth_required") {
Logger.d("[Received] <== ${data.toString()}"); Logger.d("[Received] <== ${data.toString()}");
_authenticate().then((_) { _authenticate().then((_) {
Logger.d('Authentication complete'); Logger.d('Authentication complete');
connecting.complete(); connecting.complete();
}).catchError((e) { }).catchError((e) {
if (!connecting.isCompleted) connecting.completeError(e); if (!connecting.isCompleted) connecting.completeError(e);
}); });
} 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"); _messageResolver["auth"]?.complete();
sendSocketMessage( _messageResolver.remove("auth");
type: "subscribe_events", if (_token != null) {
additionalData: {"event_type": "state_changed"}, if (!connecting.isCompleted) connecting.complete();
).whenComplete((){
_messageResolver["auth"]?.complete();
_messageResolver.remove("auth");
if (_token != null) {
if (!connecting.isCompleted) connecting.complete();
}
});
} else if (data["type"] == "auth_invalid") {
Logger.d("[Received] <== ${data.toString()}");
_messageResolver["auth"]?.completeError(HAError("${data["message"]}", actions: [HAErrorAction.loginAgain()]));
_messageResolver.remove("auth");
if (!connecting.isCompleted) connecting.completeError(HAError("${data["message"]}", actions: [HAErrorAction.tryAgain(title: "Retry"), HAErrorAction.loginAgain(title: "Relogin")]));
} else {
_handleMessage(data);
} }
}, } else if (data["type"] == "auth_invalid") {
cancelOnError: true, Logger.d("[Received] <== ${data.toString()}");
onDone: () => _handleSocketClose(connecting), _messageResolver["auth"]?.completeError(HAError("${data["message"]}", actions: [HAErrorAction.loginAgain()]));
onError: (e) => _handleSocketError(e, connecting) _messageResolver.remove("auth");
); if (!connecting.isCompleted) connecting.completeError(HAError("${data["message"]}", actions: [HAErrorAction.tryAgain(title: "Retry"), HAErrorAction.loginAgain(title: "Relogin")]));
} catch(exeption) { } else {
connecting.completeError(HAError("${exeption.toString()}")); _handleMessage(data);
} }
},
cancelOnError: true,
onDone: () => _handleSocketClose(connecting),
onError: (e) => _handleSocketError(e, connecting)
);
}); });
return connecting.future; return connecting.future;
} }
@ -206,13 +189,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"]["code"]}"); _messageResolver["${data["id"]}"]?.completeError("${data['error']["message"]}");
} }
_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"]}");
@ -226,24 +209,38 @@ 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((_) {
eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant"))); Timer(Duration(seconds: 5), () {
}); Logger.d("Trying to reconnect...");
_connect().catchError((e) {
isConnected = false;
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((_) {
eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant"))); Timer(Duration(seconds: 5), () {
}); Logger.d("Trying to reconnect...");
_connect().catchError((e) {
isConnected = false;
eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant")));
});
});
});
}
} }
Future _authenticate() { Future _authenticate() {
@ -332,13 +329,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).then((_) { _connect().timeout(connectTimeout, onTimeout: (){
_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) {
if (!_completer.isCompleted) { _completer.completeError(e);
_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}");
@ -351,27 +348,25 @@ class ConnectionManager {
_currentMessageId += 1; _currentMessageId += 1;
} }
Future callService({@required String domain, @required String service, String entityId, Map data}) { Future callService({String domain, String service, String entityId, Map additionalServiceData}) {
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 (data != null && data.isNotEmpty) { if (additionalServiceData != null && additionalServiceData.isNotEmpty) {
serviceData.addAll(data); serviceData.addAll(additionalServiceData);
} }
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.toString()))); ).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError("${e["message"]}")));
//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.toString()))); ).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError("${e["message"]}")));;
//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;
} }
@ -412,12 +407,11 @@ 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 {
Logger.d("[Received] <== HTTP ${response.statusCode}: ${response.body}"); completer.completeError({"code": response.statusCode, "message": "${response.body}"});
completer.completeError(response);
} }
}).catchError((e) { }).catchError((e) {
completer.completeError(e); completer.completeError(e);

View File

@ -14,7 +14,7 @@ class LocationManager {
} }
final int defaultUpdateIntervalMinutes = 20; final int defaultUpdateIntervalMinutes = 20;
final String backgroundTaskId = "haclocationtask0"; final String backgroundTaskId = "haclocationtask4352";
final String backgroundTaskTag = "haclocation"; final String backgroundTaskTag = "haclocation";
Duration _updateInterval; Duration _updateInterval;
bool _isRunning; bool _isRunning;
@ -57,48 +57,27 @@ 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; await workManager.Workmanager.registerPeriodicTask(
int delayFactor; backgroundTaskId,
int taskCount; "haClientLocationTracking",
Logger.d("Starting location update for every ${_updateInterval tag: backgroundTaskTag,
.inMinutes} minutes..."); inputData: {
if (_updateInterval.inMinutes == 10) { "webhookId": webhookId,
interval = Duration(minutes: 20); "httpWebHost": httpWebHost
taskCount = 2; },
delayFactor = 10; frequency: _updateInterval,
} else if (_updateInterval.inMinutes == 5) { existingWorkPolicy: workManager.ExistingWorkPolicy.keep,
interval = Duration(minutes: 15); backoffPolicy: workManager.BackoffPolicy.linear,
taskCount = 3; backoffPolicyDelay: _updateInterval,
delayFactor = 5; constraints: workManager.Constraints(
} else { networkType: workManager.NetworkType.connected
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(
"$backgroundTaskId$i",
"haClientLocationTracking-0$i",
tag: backgroundTaskTag,
inputData: {
"webhookId": webhookId,
"httpWebHost": httpWebHost
},
frequency: interval,
initialDelay: Duration(minutes: delay),
existingWorkPolicy: workManager.ExistingWorkPolicy.keep,
backoffPolicy: workManager.BackoffPolicy.linear,
backoffPolicyDelay: interval,
constraints: workManager.Constraints(
networkType: workManager.NetworkType.connected
)
);
}
} }
} }
@ -108,40 +87,32 @@ class LocationManager {
} }
updateDeviceLocation() async { updateDeviceLocation() async {
Logger.d("[Foreground location] Started"); if (ConnectionManager().webhookId != null &&
Geolocator geolocator = Geolocator(); ConnectionManager().webhookId.isNotEmpty) {
var battery = Battery(); String url = "${ConnectionManager()
String webhookId = ConnectionManager().webhookId; .httpWebHost}/api/webhook/${ConnectionManager().webhookId}";
String httpWebHost = ConnectionManager().httpWebHost; Map<String, String> headers = {};
if (webhookId != null && webhookId.isNotEmpty) { Logger.d("[Location] Getting device location...");
Logger.d("[Foreground location] Getting battery level..."); Position location = await Geolocator().getCurrentPosition(
int batteryLevel = await battery.batteryLevel; desiredAccuracy: LocationAccuracy.medium);
Logger.d("[Foreground location] Getting device location..."); Logger.d("[Location] Got location: ${location.latitude} ${location
Position position = await geolocator.getCurrentPosition( .longitude}. Sending home...");
desiredAccuracy: LocationAccuracy.high, int battery = await Battery().batteryLevel;
locationPermissionLevel: GeolocationPermission.locationAlways var data = {
); "type": "update_location",
if (position != null) { "data": {
Logger.d("[Foreground location] Location: ${position.latitude} ${position.longitude}. Accuracy: ${position.accuracy}. (${position.timestamp})"); "gps": [location.latitude, location.longitude],
String url = "$httpWebHost/api/webhook/$webhookId"; "gps_accuracy": location.accuracy,
Map data = { "battery": battery
"type": "update_location", }
"data": { };
"gps": [position.latitude, position.longitude], headers["Content-Type"] = "application/json";
"gps_accuracy": position.accuracy, await http.post(
"battery": batteryLevel ?? 100
}
};
Logger.d("[Foreground location] Sending data home...");
var response = await http.post(
url, url,
headers: {"Content-Type": "application/json"}, headers: headers,
body: json.encode(data) body: json.encode(data)
); );
Logger.d("[Foreground location] Got HTTP ${response.statusCode}"); Logger.d("[Location] ...done.");
} else {
Logger.d("[Foreground location] No location. Aborting.");
}
} }
} }
@ -150,7 +121,6 @@ class LocationManager {
void updateDeviceLocationIsolate() { void updateDeviceLocationIsolate() {
workManager.Workmanager.executeTask((backgroundTask, data) { workManager.Workmanager.executeTask((backgroundTask, data) {
//print("[Background $backgroundTask] Started"); //print("[Background $backgroundTask] Started");
Geolocator geolocator = Geolocator();
var battery = Battery(); var battery = Battery();
int batteryLevel = 100; int batteryLevel = 100;
String webhookId = data["webhookId"]; String webhookId = data["webhookId"];
@ -171,22 +141,33 @@ void updateDeviceLocationIsolate() {
//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.high, locationPermissionLevel: GeolocationPermission.locationAlways).then((location) { Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.medium).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()}"); //print("[Background $backgroundTask] Error getting current location: ${e.toString()}. Trying last known...");
Geolocator().getLastKnownPosition(desiredAccuracy: LocationAccuracy.medium).then((location){
//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)
);
}
});
}); });
}); });
} }

View File

@ -45,7 +45,7 @@ class MobileAppIntegrationManager {
positiveText: "Restart now", positiveText: "Restart now",
negativeText: "Later", negativeText: "Later",
onPositive: () { onPositive: () {
ConnectionManager().callService(domain: "homeassistant", service: "restart"); ConnectionManager().callService(domain: "homeassistant", service: "restart", entityId: null);
}, },
)); ));
}); });
@ -81,7 +81,7 @@ class MobileAppIntegrationManager {
} }
completer.complete(); completer.complete();
}).catchError((e) { }).catchError((e) {
if (e is http.Response && e.statusCode == 410) { if (e['code'] != null && e['code'] == 410) {
Logger.e("MobileApp integration was removed"); Logger.e("MobileApp integration was removed");
_askToRegisterApp(); _askToRegisterApp();
} else { } else {

View File

@ -29,9 +29,6 @@ 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);
}
}); });
}); });
} }
@ -39,15 +36,15 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
void incLocationInterval() { void incLocationInterval() {
if (_locationInterval < 720) { if (_locationInterval < 720) {
setState(() { setState(() {
_locationInterval = _locationInterval + 5; _locationInterval = _locationInterval + 1;
}); });
} }
} }
void decLocationInterval() { void decLocationInterval() {
if (_locationInterval > 5) { if (_locationInterval > 1) {
setState(() { setState(() {
_locationInterval = _locationInterval - 5; _locationInterval = _locationInterval - 1;
}); });
} }
} }
@ -59,7 +56,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"); ConnectionManager().callService(domain: "homeassistant", service: "restart", entityId: null);
}, },
)); ));
} }
@ -71,7 +68,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"); ConnectionManager().callService(domain: "homeassistant", service: "stop", entityId: null);
}, },
)); ));
} }

View File

@ -26,20 +26,16 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
bool _showLoginButton = false; bool _showLoginButton = false;
bool _preventAppRefresh = false; bool _preventAppRefresh = false;
String _savedSharedText; String _savedSharedText;
Entity _entityToShow; String _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);
}); });
workManager.Workmanager.initialize( super.initState();
updateDeviceLocationIsolate,
isInDebugMode: false
);
enableShareReceiving(); enableShareReceiving();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
@ -126,6 +122,7 @@ 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);
}); });
@ -139,8 +136,11 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
} }
await HomeAssistant().fetchData().then((_) { await HomeAssistant().fetchData().then((_) {
_hideBottomBar(); _hideBottomBar();
if (_entityToShow != null) { int currentViewCount = HomeAssistant().ui?.views?.length ?? 0;
_entityToShow = HomeAssistant().entities.get(_entityToShow.entityId); if (_previousViewCount != currentViewCount) {
Logger.d("Views count changed ($_previousViewCount->$currentViewCount). Creating new tabs controller.");
_viewsTabController = TabController(vsync: this, length: currentViewCount);
_previousViewCount = currentViewCount;
} }
}).catchError((e) { }).catchError((e) {
if (e is HAError) { if (e is HAError) {
@ -218,8 +218,9 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
} }
if (_serviceCallSubscription == null) { if (_serviceCallSubscription == null) {
_serviceCallSubscription = _serviceCallSubscription =
eventBus.on<NotifyServiceCallEvent>().listen((event) { eventBus.on<ServiceCallEvent>().listen((event) {
_notifyServiceCalled(event.domain, event.service, event.entityId); _callService(event.domain, event.service, event.entityId,
event.additionalParams);
}); });
} }
@ -317,28 +318,27 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
); );
} }
void _notifyServiceCalled(String domain, String service, String entityId) { //TODO remove this shit.... maybe
void _callService(String domain, String service, String entityId, Map additionalParams) {
_showInfoBottomBar( _showInfoBottomBar(
message: "Calling $domain.$service", message: "Calling $domain.$service",
duration: Duration(seconds: 4) duration: Duration(seconds: 3)
); );
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 = HomeAssistant().entities?.get(entityId); _entityToShow = 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,7 +368,12 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
menuItems.add( menuItems.add(
UserAccountsDrawerHeader( UserAccountsDrawerHeader(
accountName: Text(HomeAssistant().userName), accountName: Text(HomeAssistant().userName),
accountEmail: Text(HomeAssistant().locationName ?? ""), accountEmail: Text(ConnectionManager().displayHostname ?? "Not configured"),
onDetailsPressed: () {
Launcher.launchURLInCustomTab(
url: "${ConnectionManager().httpWebHost}/profile?external_auth=1"
);
},
currentAccountPicture: CircleAvatar( currentAccountPicture: CircleAvatar(
child: Text( child: Text(
HomeAssistant().userAvatarText, HomeAssistant().userAvatarText,
@ -458,11 +463,11 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
}, },
), ),
new ListTile( new ListTile(
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:forum")), leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:discord")),
title: Text("Contacts/Discussion"), title: Text("Join Discord channel"),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
Launcher.launchURL("https://spectrum.chat/ha-client"); Launcher.launchURL("https://discord.gg/AUzEvwn");
}, },
), ),
new AboutListTile( new AboutListTile(
@ -631,19 +636,11 @@ 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 = [];
List<PopupMenuItem<String>> mediaMenuItems = []; List<PopupMenuItem<String>> mediaMenuItems = [];
int currentViewCount = HomeAssistant().ui?.views?.length ?? 0;
if (_previousViewCount != currentViewCount) {
Logger.d("Views count changed ($_previousViewCount->$currentViewCount). Creating new tabs controller.");
_viewsTabController = TabController(vsync: this, length: currentViewCount);
_previousViewCount = currentViewCount;
}
serviceMenuItems.add(PopupMenuItem<String>( serviceMenuItems.add(PopupMenuItem<String>(
child: new Text("Reload"), child: new Text("Reload"),
value: "reload", value: "reload",
@ -733,6 +730,7 @@ 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>[
@ -745,13 +743,12 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
), ),
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints.tightFor(width: Sizes.entityPageMaxWidth), constraints: BoxConstraints.tightFor(width: Sizes.entityPageMaxWidth),
child: EntityPageLayout(entity: _entityToShow, showClose: true,), child: EntityPageLayout(entity: entity, 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);
} }
} }
@ -807,7 +804,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
_scaffoldKey.currentState.openDrawer(); _scaffoldKey.currentState.openDrawer();
}, },
), ),
bottom: (empty || _entityToShow != null) ? null : TabBar( bottom: empty ? null : TabBar(
controller: _viewsTabController, controller: _viewsTabController,
tabs: buildUIViewTabs(), tabs: buildUIViewTabs(),
isScrollable: true, isScrollable: true,
@ -816,8 +813,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
]; ];
}, },
body: mainScrollBody, body: mainScrollBody
controller: _mainScrollController,
); );
} }
@ -883,22 +879,12 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
body: _buildScaffoldBody(true) body: _buildScaffoldBody(true)
); );
} else { } else {
return WillPopScope( return Scaffold(
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);
}
},
); );
} }
} }

View File

@ -90,20 +90,16 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
Navigator.pop(context); Navigator.pop(context);
ConnectionManager().callService( ConnectionManager().callService(
domain: serviceDomain, domain: serviceDomain,
service: "play_media",
entityId: entity.entityId, entityId: entity.entityId,
data: { service: "play_media",
"media_content_id": _mediaUrl, additionalServiceData: {
"media_content_type": _contentType "media_content_id": _mediaUrl,
} "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) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent(HomeAssistant().sendFromPlayerId.split(".")[0], "turn_off", HomeAssistant().sendFromPlayerId, null));
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));

View File

@ -75,16 +75,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
_saveSettings() async { _saveSettings() async {
_newHassioDomain = _newHassioDomain.trim(); _newHassioDomain = _newHassioDomain.trim();
if (_newHassioDomain.startsWith("http") && _newHassioDomain.indexOf("//") > 0) { if (_newHassioDomain.indexOf("http") == 0 && _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) {

View File

@ -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_0.7.0.md"); response = await http.get("http://ha-client.homemade.systems/service/whats_new_$appVersionNumber.md");
if (response.statusCode == 200) { if (response.statusCode == 200) {
setState(() { setState(() {
data = response.body; data = response.body;

View File

@ -33,12 +33,13 @@ class StartAuthEvent {
StartAuthEvent(this.oauthUrl, this.showButton); StartAuthEvent(this.oauthUrl, this.showButton);
} }
class NotifyServiceCallEvent { class ServiceCallEvent {
String domain; String domain;
String service; String service;
String entityId; String entityId;
Map<String, dynamic> additionalParams;
NotifyServiceCallEvent(this.domain, this.service, this.entityId); ServiceCallEvent(this.domain, this.service, this.entityId, this.additionalParams);
} }
class ShowPopupDialogEvent { class ShowPopupDialogEvent {

View File

@ -23,10 +23,6 @@ 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);
} }

View File

@ -1,8 +1,7 @@
name: hass_client name: hass_client
description: Home Assistant Android Client description: Home Assistant Android Client
version: 0.7.7+770 version: 0.7.1+713
environment: environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0" sdk: ">=2.0.0-dev.68.0 <3.0.0"
@ -18,7 +17,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: 0.3.0 flutter_markdown: any
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
@ -26,10 +25,9 @@ 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.5 geolocator: ^5.1.4+2
workmanager: ^0.1.5 workmanager: ^0.1.3
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