Compare commits
23 Commits
beta/0.7.5
...
beta/0.7.7
Author | SHA1 | Date | |
---|---|---|---|
9cc60a136b | |||
78eb1e779c | |||
8db2d8508e | |||
3f1ece26ec | |||
d1912a44c6 | |||
36a05eb390 | |||
4f39ea1ad8 | |||
a241cc1d61 | |||
8b4df98cb9 | |||
7d30c2f9d5 | |||
44acabadfe | |||
6f3a2bb78d | |||
dd5f8b155d | |||
cd81fc72fd | |||
890da650dc | |||
9897b6a44b | |||
7969f54d3b | |||
7c18454de3 | |||
dcf5efddd1 | |||
a6541134e0 | |||
90504047b4 | |||
ca1eec6602 | |||
edc01d14b7 |
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]
|
|
||||||
```
|
|
||||||
|
@ -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 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)
|
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)
|
||||||
|
|
||||||
#### Pre-release CI build
|
#### Pre-release CI build
|
||||||
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/latest_build)
|
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/latest_build)
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
@ -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 {
|
||||||
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -460,7 +460,11 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _duplicateTo(entity) {
|
void _duplicateTo(entity) {
|
||||||
|
if (entity.canCalculateActualPosition()) {
|
||||||
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
||||||
|
} 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"]
|
||||||
|
@ -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();
|
||||||
@ -108,6 +104,7 @@ class HomeAssistant {
|
|||||||
|
|
||||||
Future _getLovelace() {
|
Future _getLovelace() {
|
||||||
Completer completer = Completer();
|
Completer completer = Completer();
|
||||||
|
|
||||||
ConnectionManager().sendSocketMessage(type: "lovelace/config").then((data) {
|
ConnectionManager().sendSocketMessage(type: "lovelace/config").then((data) {
|
||||||
_rawLovelaceData = data;
|
_rawLovelaceData = data;
|
||||||
completer.complete();
|
completer.complete();
|
||||||
@ -163,7 +160,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.isCompleted) {
|
if (_fetchCompleter != null && _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"],
|
||||||
@ -223,9 +220,9 @@ 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: (rawCard['state_filter'] ?? rawCardInfo['state_filter']) ?? [],
|
stateFilter: (rawCard['state_filter'] ?? rawCardInfo['state_filter']) ?? [],
|
||||||
states: rawCardInfo['states'],
|
states: rawCardInfo['states'],
|
||||||
conditions: rawCard['conditions'] ?? [],
|
conditions: rawCard['conditions'] ?? [],
|
||||||
|
@ -143,7 +143,7 @@ final SentryClient _sentry = SentryClient(dsn: "https://03ef364745cc4c23a60ddbc8
|
|||||||
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.5";
|
const appVersionNumber = "0.7.7";
|
||||||
const appVersionAdd = "";
|
const appVersionAdd = "";
|
||||||
const appVersion = "$appVersionNumber$appVersionAdd";
|
const appVersion = "$appVersionNumber$appVersionAdd";
|
||||||
|
|
||||||
@ -176,12 +176,7 @@ void main() async {
|
|||||||
};
|
};
|
||||||
|
|
||||||
runZoned(() {
|
runZoned(() {
|
||||||
workManager.Workmanager.initialize(
|
|
||||||
updateDeviceLocationIsolate,
|
|
||||||
isInDebugMode: false
|
|
||||||
);
|
|
||||||
runApp(new HAClientApp());
|
runApp(new HAClientApp());
|
||||||
|
|
||||||
}, onError: (error, stack) {
|
}, onError: (error, stack) {
|
||||||
_reportError(error, stack);
|
_reportError(error, 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;
|
||||||
}
|
}
|
||||||
@ -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}");
|
||||||
@ -363,12 +366,12 @@ class ConnectionManager {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@ -414,7 +417,7 @@ class ConnectionManager {
|
|||||||
completer.complete(response.body);
|
completer.complete(response.body);
|
||||||
} else {
|
} else {
|
||||||
Logger.d("[Received] <== HTTP ${response.statusCode}: ${response.body}");
|
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);
|
||||||
|
@ -82,8 +82,8 @@ class LocationManager {
|
|||||||
int delay = i*delayFactor;
|
int delay = i*delayFactor;
|
||||||
Logger.d("Scheduling location update task #$i for every ${interval.inMinutes} minutes in $delay minutes...");
|
Logger.d("Scheduling location update task #$i for every ${interval.inMinutes} minutes in $delay minutes...");
|
||||||
await workManager.Workmanager.registerPeriodicTask(
|
await workManager.Workmanager.registerPeriodicTask(
|
||||||
"$backgroundTaskId$n",
|
"$backgroundTaskId$i",
|
||||||
"haClientLocationTracking-0$n",
|
"haClientLocationTracking-0$i",
|
||||||
tag: backgroundTaskTag,
|
tag: backgroundTaskTag,
|
||||||
inputData: {
|
inputData: {
|
||||||
"webhookId": webhookId,
|
"webhookId": webhookId,
|
||||||
@ -149,15 +149,14 @@ 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();
|
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";
|
||||||
@ -169,15 +168,15 @@ 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.high, locationPermissionLevel: GeolocationPermission.locationAlways).then((location) {
|
geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high, locationPermissionLevel: GeolocationPermission.locationAlways).then((location) {
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
print("[Background $backgroundTask] Got location: ${location.latitude} ${location.longitude}");
|
//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,
|
||||||
@ -187,10 +186,7 @@ void updateDeviceLocationIsolate() {
|
|||||||
throw "Can't get device location. Location is null";
|
throw "Can't get device location. Location is null";
|
||||||
}
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
sentryBackgroundClient.captureException(
|
//print("[Background $backgroundTask] Error getting current location: ${e.toString()}");
|
||||||
exception: "${e.toString()}"
|
|
||||||
);
|
|
||||||
print("[Background $backgroundTask] Error getting current location: ${e.toString()}");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -30,12 +30,16 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
|
|
||||||
@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);
|
||||||
|
|
||||||
@ -135,12 +139,6 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
}
|
}
|
||||||
await HomeAssistant().fetchData().then((_) {
|
await HomeAssistant().fetchData().then((_) {
|
||||||
_hideBottomBar();
|
_hideBottomBar();
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (_entityToShow != null) {
|
if (_entityToShow != null) {
|
||||||
_entityToShow = HomeAssistant().entities.get(_entityToShow.entityId);
|
_entityToShow = HomeAssistant().entities.get(_entityToShow.entityId);
|
||||||
}
|
}
|
||||||
@ -328,7 +326,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
|
|
||||||
void _showEntityPage(String entityId) {
|
void _showEntityPage(String entityId) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_entityToShow = HomeAssistant().entities.get(entityId);
|
_entityToShow = HomeAssistant().entities?.get(entityId);
|
||||||
if (_entityToShow != null) {
|
if (_entityToShow != null) {
|
||||||
_mainScrollController?.jumpTo(0);
|
_mainScrollController?.jumpTo(0);
|
||||||
}
|
}
|
||||||
@ -460,11 +458,11 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:discord")),
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:forum")),
|
||||||
title: Text("Join Discord channel"),
|
title: Text("Contacts/Discussion"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Launcher.launchURL("https://discord.gg/AUzEvwn");
|
Launcher.launchURL("https://spectrum.chat/ha-client");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new AboutListTile(
|
new AboutListTile(
|
||||||
@ -639,6 +637,13 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
|
|||||||
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",
|
||||||
|
@ -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) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
name: hass_client
|
name: hass_client
|
||||||
description: Home Assistant Android Client
|
description: Home Assistant Android Client
|
||||||
|
|
||||||
version: 0.7.5+750
|
version: 0.7.7+770
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -27,7 +27,7 @@ dependencies:
|
|||||||
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.5
|
||||||
workmanager: ^0.1.3
|
workmanager: ^0.1.5
|
||||||
battery: ^0.3.1+1
|
battery: ^0.3.1+1
|
||||||
sentry: ^2.3.1
|
sentry: ^2.3.1
|
||||||
share:
|
share:
|
||||||
|
Reference in New Issue
Block a user