Compare commits

...

29 Commits

Author SHA1 Message Date
a241cc1d61 Resolves #494 2019-11-29 13:40:51 +00:00
8b4df98cb9 0.7.6 2019-11-29 13:02:22 +00:00
7d30c2f9d5 Wrap empty navigate action 2019-11-29 12:45:59 +00:00
44acabadfe Fix location tracking backfroud task id 2019-11-29 11:27:59 +00:00
6f3a2bb78d Fix timeout handling on socket message send 2019-11-29 11:24:29 +00:00
dd5f8b155d Handle some socket exceptions 2019-11-29 11:21:45 +00:00
cd81fc72fd Fix connection timeout handling 2019-11-29 10:58:24 +00:00
890da650dc Resolves #508 show_name for enriry button card 2019-11-29 10:12:41 +00:00
9897b6a44b Fix show_empty for entity-filter 2019-11-29 10:05:09 +00:00
7969f54d3b Fix MissedPluginException for workmanager 2019-11-28 19:54:22 +00:00
7c18454de3 Fix issue with handling service call exceptions 2019-11-28 19:14:50 +00:00
dcf5efddd1 Parse port and protocol from HA url 2019-11-28 18:57:41 +00:00
a6541134e0 Fix compliting alrady completed future 2019-11-28 18:33:27 +00:00
90504047b4 Resolves #492 Infinity media player progress error 2019-11-28 17:42:48 +00:00
ca1eec6602 Update bug_report.md 2019-11-27 18:44:45 +02:00
edc01d14b7 Resolves #511 2019-11-27 17:21:23 +02:00
6cb5463b13 minor background reporting fix 2019-11-27 14:50:53 +00:00
63a789ebfb 0.7.5 name fix 2019-11-27 12:48:03 +00:00
a0994e9a60 0.7.5 2019-11-27 12:42:09 +00:00
8d1b728194 Background location tracking crash reporting 2019-11-27 12:41:38 +00:00
1a9fec8b98 Senty reporting. Fix background location tracking crash 2019-11-27 12:26:55 +00:00
e634253282 0.7.4 2019-11-26 21:13:59 +00:00
64b23ec7cc Revert flutter_markdown to 0.3.0 2019-11-26 21:13:07 +00:00
afe207a878 Removes foreground location and Resolves #510 2019-11-26 20:56:24 +00:00
4bac0c092f Removes foreground location and Resolves #510 2019-11-26 20:54:36 +00:00
74c8ae35a1 Remove network security config 2019-11-26 20:48:08 +00:00
7856637456 Fix app version display 2019-11-26 16:50:19 +00:00
965f80a6ca 0.7.3 2019-11-14 12:58:56 +02:00
198c2ba49a build 730 2019-11-14 12:58:32 +02:00
19 changed files with 197 additions and 208 deletions

View File

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

View File

@ -6,6 +6,7 @@
<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" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<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"/>
@ -19,8 +20,7 @@
android:name=".Application" android:name=".Application"
android:label="HA Client" android:label="HA Client"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true">
android:networkSecurityConfig="@xml/network_security_config">
<meta-data <meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id" android:name="com.google.firebase.messaging.default_notification_channel_id"

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</base-config>
</network-security-config>

View File

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

View File

@ -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,13 +41,16 @@ class EntityButtonCardBody extends StatelessWidget {
} }
Widget _buildName() { Widget _buildName() {
return EntityName( if (showName) {
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding), return EntityName(
textOverflow: TextOverflow.ellipsis, padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
maxLines: 3, textOverflow: TextOverflow.ellipsis,
wordsWrap: true, maxLines: 3,
textAlign: TextAlign.center, wordsWrap: true,
fontSize: Sizes.nameFontSize, textAlign: TextAlign.center,
); fontSize: Sizes.nameFontSize,
);
}
return Container(width: 0, height: 0);
} }
} }

View File

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

View File

@ -84,25 +84,22 @@ 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); positionD = Duration(
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; double progress = 0;
int currentPosition; int currentPosition;
if (entity.canCalculateActualPosition()) { if (entity.canCalculateActualPosition()) {
currentPosition = entity.getActualPosition().toInt(); currentPosition = entity.getActualPosition().toInt();
progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100; if (currentPosition > 0) {
} else { progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
progress = 0; }
} }
return LinearProgressIndicator( return LinearProgressIndicator(
value: progress, value: progress,

View File

@ -460,7 +460,11 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
} }
void _duplicateTo(entity) { void _duplicateTo(entity) {
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt(); if (entity.canCalculateActualPosition()) {
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"]

View File

@ -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();
@ -218,17 +214,14 @@ class HomeAssistant {
try { try {
//bool isThereCardOptionsInside = rawCard["card"] != null; //bool isThereCardOptionsInside = rawCard["card"] != null;
var rawCardInfo = rawCard["card"] ?? rawCard; var rawCardInfo = rawCard["card"] ?? rawCard;
if (rawCardInfo['state_filter'] != null) {
Logger.d("Hey!!!!!! We found a card with state filter: ${rawCardInfo['state_filter']}");
}
HACard card = HACard( HACard card = HACard(
id: "card", id: "card",
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'] ?? [],

View File

@ -30,6 +30,7 @@ import 'package:uni_links/uni_links.dart';
import 'package:workmanager/workmanager.dart' as workManager; import 'package:workmanager/workmanager.dart' as workManager;
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:battery/battery.dart'; import 'package:battery/battery.dart';
import 'package:sentry/sentry.dart';
import 'utils/logger.dart'; import 'utils/logger.dart';
@ -138,34 +139,46 @@ part 'entities/media_player/widgets/media_player_progress_bar.widget.dart';
part 'pages/whats_new.page.dart'; part 'pages/whats_new.page.dart';
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
final SentryClient _sentry = SentryClient(dsn: "https://03ef364745cc4c23a60ddbc874c69925@sentry.io/1836118");
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
const String appName = "HA Client"; const String appName = "HA Client";
const appVersionNumber = "0.7.2"; const appVersionNumber = "0.7.6";
const appVersionAdd = ""; const appVersionAdd = "";
const appVersion = "$appVersionNumber-$appVersionAdd"; const appVersion = "$appVersionNumber$appVersionAdd";
Future<void> _reportError(dynamic error, dynamic stackTrace) async {
// Print the exception to the console.
if (Logger.isInDebugMode) {
Logger.e('Caught error: $error');
Logger.p(stackTrace);
return;
} else {
Logger.e('Caught error: $error. Reporting to Senrty.');
// Send the Exception and Stacktrace to Sentry in Production mode.
_sentry.captureException(
exception: error,
stackTrace: stackTrace,
);
}
}
void main() async { void main() async {
FlutterError.onError = (errorDetails) { FlutterError.onError = (FlutterErrorDetails details) {
Logger.e( "${errorDetails.exception}"); Logger.e(" Caut Flutter runtime error: ${details.exception}");
if (Logger.isInDebugMode) { if (Logger.isInDebugMode) {
FlutterError.dumpErrorToConsole(errorDetails); FlutterError.dumpErrorToConsole(details);
} else {
// In production mode, report to the application zone to report to
// Sentry.
Zone.current.handleUncaughtError(details.exception, details.stack);
} }
}; };
runZoned(() { runZoned(() {
workManager.Workmanager.initialize(
updateDeviceLocationIsolate,
isInDebugMode: false
);
runApp(new HAClientApp()); runApp(new HAClientApp());
}, onError: (error, stack) { }, onError: (error, stack) {
Logger.e("$error"); _reportError(error, stack);
Logger.e("$stack");
if (Logger.isInDebugMode) {
debugPrint("$stack");
}
}); });
} }

View File

@ -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((_) { _connect().timeout(connectTimeout).then((_) {
if (completer != null && !completer.isCompleted) { completer?.complete();
completer.completeError(HAError("Connection timeout")); }).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);
} else {
completer?.completeError(HAError("${e.toString()}"));
}
});
}); });
}).then((_) {
completer?.complete();
}).catchError((e) {
completer?.completeError(e);
}); });
} else { } else {
completer?.complete(); completer?.complete();
@ -124,40 +131,50 @@ class ConnectionManager {
connecting = Completer(); connecting = Completer();
_disconnect().then((_) { _disconnect().then((_) {
Logger.d("Socket connecting..."); Logger.d("Socket connecting...");
_socket = IOWebSocketChannel.connect( try {
_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()}");
_messageResolver["auth"]?.complete(); Logger.d("[Connection] Subscribing to events");
_messageResolver.remove("auth"); sendSocketMessage(
if (_token != null) { type: "subscribe_events",
if (!connecting.isCompleted) connecting.complete(); additionalData: {"event_type": "state_changed"},
).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") { },
Logger.d("[Received] <== ${data.toString()}"); cancelOnError: true,
_messageResolver["auth"]?.completeError(HAError("${data["message"]}", actions: [HAErrorAction.loginAgain()])); onDone: () => _handleSocketClose(connecting),
_messageResolver.remove("auth"); onError: (e) => _handleSocketError(e, connecting)
if (!connecting.isCompleted) connecting.completeError(HAError("${data["message"]}", actions: [HAErrorAction.tryAgain(title: "Retry"), HAErrorAction.loginAgain(title: "Relogin")])); );
} else { } catch(exeption) {
_handleMessage(data); connecting.completeError(HAError("${exeption.toString()}"));
} }
},
cancelOnError: true,
onDone: () => _handleSocketClose(connecting),
onError: (e) => _handleSocketError(e, connecting)
);
}); });
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.");
if (!connectionCompleter.isCompleted) { _disconnect().then((_) {
isConnected = false; if (!connectionCompleter.isCompleted) {
connectionCompleter.completeError(HAError("Disconnected", actions: [HAErrorAction.reconnect()])); isConnected = false;
} else { connectionCompleter.completeError(HAError("Disconnected", actions: [HAErrorAction.reconnect()]));
_disconnect().then((_) { }
Timer(Duration(seconds: 5), () { eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant")));
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");
if (!connectionCompleter.isCompleted) { _disconnect().then((_) {
isConnected = false; if (!connectionCompleter.isCompleted) {
connectionCompleter.completeError(HAError("Unable to connect to Home Assistant")); isConnected = false;
} else { connectionCompleter.completeError(HAError("Disconnected", actions: [HAErrorAction.reconnect()]));
_disconnect().then((_) { }
Timer(Duration(seconds: 5), () { eventBus.fire(ShowErrorEvent(HAError("Unable to connect to Home Assistant")));
Logger.d("Trying to reconnect..."); });
_connect().catchError((e) {
isConnected = false;
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);

View File

@ -27,7 +27,6 @@ class LocationManager {
_isRunning = prefs.getBool("location-enabled") ?? false; _isRunning = prefs.getBool("location-enabled") ?? false;
if (_isRunning) { if (_isRunning) {
await _startLocationService(); await _startLocationService();
updateDeviceLocation(false);
} }
} }
@ -83,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", "haClientLocationTracking-0$i",
tag: backgroundTaskTag, tag: backgroundTaskTag,
inputData: { inputData: {
"webhookId": webhookId, "webhookId": webhookId,
@ -108,14 +107,9 @@ class LocationManager {
await workManager.Workmanager.cancelByTag(backgroundTaskTag); await workManager.Workmanager.cancelByTag(backgroundTaskTag);
} }
updateDeviceLocation(bool force) async { updateDeviceLocation() async {
if (!force && !_isRunning) {
Logger.d("[Foreground location] Not enabled. Aborting.");
return;
}
Logger.d("[Foreground location] Started"); Logger.d("[Foreground location] Started");
//Logger.d("[Foreground location] Forcing Android location manager..."); Geolocator geolocator = Geolocator();
Geolocator geolocator = Geolocator()..forceAndroidLocationManager = true;
var battery = Battery(); var battery = Battery();
String webhookId = ConnectionManager().webhookId; String webhookId = ConnectionManager().webhookId;
String httpWebHost = ConnectionManager().httpWebHost; String httpWebHost = ConnectionManager().httpWebHost;
@ -155,14 +149,15 @@ class LocationManager {
void updateDeviceLocationIsolate() { void updateDeviceLocationIsolate() {
workManager.Workmanager.executeTask((backgroundTask, data) { workManager.Workmanager.executeTask((backgroundTask, data) {
//print("[Background $backgroundTask] Started"); print("[Background $backgroundTask] Started");
Geolocator geolocator = Geolocator()..forceAndroidLocationManager = true; final SentryClient sentryBackgroundClient = SentryClient(dsn: "https://5c868e5ef26947e2b61b189e391ec31b@sentry.io/1836366");
Geolocator geolocator = Geolocator();
var battery = Battery(); var battery = Battery();
int batteryLevel = 100; int batteryLevel = 100;
String webhookId = data["webhookId"]; String webhookId = data["webhookId"];
String httpWebHost = data["httpWebHost"]; String httpWebHost = data["httpWebHost"];
if (webhookId != null && webhookId.isNotEmpty) { if (webhookId != null && webhookId.isNotEmpty) {
//print("[Background $backgroundTask] hour=$battery"); print("[Background $backgroundTask] hour=$battery");
String url = "$httpWebHost/api/webhook/$webhookId"; String url = "$httpWebHost/api/webhook/$webhookId";
Map<String, String> headers = {}; Map<String, String> headers = {};
headers["Content-Type"] = "application/json"; headers["Content-Type"] = "application/json";
@ -174,36 +169,28 @@ void updateDeviceLocationIsolate() {
"battery": batteryLevel "battery": batteryLevel
} }
}; };
//print("[Background $backgroundTask] Getting battery level..."); print("[Background $backgroundTask] Getting battery level...");
battery.batteryLevel.then((val) => data["data"]["battery"] = val).whenComplete((){ battery.batteryLevel.then((val) => data["data"]["battery"] = val).whenComplete((){
//print("[Background $backgroundTask] Getting device location..."); print("[Background $backgroundTask] Getting device location...");
geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high, locationPermissionLevel: GeolocationPermission.locationAlways).then((location) { geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high, locationPermissionLevel: GeolocationPermission.locationAlways).then((location) {
//print("[Background $backgroundTask] Got location: ${location.latitude} ${location.longitude}");
if (location != null) { if (location != null) {
print("[Background $backgroundTask] Got location: ${location.latitude} ${location.longitude}");
data["data"]["gps"] = [location.latitude, location.longitude]; data["data"]["gps"] = [location.latitude, location.longitude];
data["data"]["gps_accuracy"] = location.accuracy; data["data"]["gps_accuracy"] = location.accuracy;
//print("[Background $backgroundTask] Sending data home..."); print("[Background $backgroundTask] Sending data home.");
http.post( http.post(
url, url,
headers: headers, headers: headers,
body: json.encode(data) body: json.encode(data)
); );
} else {
throw "Can't get device location. Location is null";
} }
}).catchError((e) { }).catchError((e) {
//print("[Background $backgroundTask] Error getting current location: ${e.toString()}. Trying last known..."); sentryBackgroundClient.captureException(
geolocator.getLastKnownPosition(desiredAccuracy: LocationAccuracy.medium).then((location){ exception: "${e.toString()}"
//print("[Background $backgroundTask] Got last known location: ${location.latitude} ${location.longitude}"); );
if (location != null) { print("[Background $backgroundTask] Error getting current location: ${e.toString()}");
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

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

View File

@ -94,7 +94,7 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
_switchLocationTrackingState(bool state) async { _switchLocationTrackingState(bool state) async {
if (state) { if (state) {
await LocationManager().updateDeviceLocation(true); await LocationManager().updateDeviceLocation();
} }
await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval); await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
setState(() { setState(() {

View File

@ -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);
@ -122,8 +126,6 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
_showInfoBottomBar(progress: true,); _showInfoBottomBar(progress: true,);
ConnectionManager().init(loadSettings: false, forceReconnect: false).then((_){ ConnectionManager().init(loadSettings: false, forceReconnect: false).then((_){
_fetchData(); _fetchData();
LocationManager().updateDeviceLocation(false);
//StartupUserMessagesManager().checkMessagesToShow();
}, onError: (e) { }, onError: (e) {
_setErrorState(e); _setErrorState(e);
}); });
@ -330,7 +332,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);
} }

View File

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

View File

@ -23,6 +23,10 @@ class Logger {
return inDebugMode; return inDebugMode;
} }
static void p(data) {
print(data);
}
static void e(String message) { static void e(String message) {
_writeToLog("Error", message); _writeToLog("Error", message);
} }

View File

@ -1,7 +1,7 @@
name: hass_client name: hass_client
description: Home Assistant Android Client description: Home Assistant Android Client
version: 0.7.2+720 version: 0.7.6+760
environment: environment:
@ -18,7 +18,7 @@ dependencies:
url_launcher: any url_launcher: any
date_format: any date_format: any
charts_flutter: ^0.8.1 charts_flutter: ^0.8.1
flutter_markdown: any flutter_markdown: 0.3.0
in_app_purchase: ^0.2.1+4 in_app_purchase: ^0.2.1+4
flutter_custom_tabs: ^0.6.0 flutter_custom_tabs: ^0.6.0
firebase_messaging: ^5.1.6 firebase_messaging: ^5.1.6
@ -27,8 +27,9 @@ 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
share: share:
git: git:
url: https://github.com/d-silveira/flutter-share.git url: https://github.com/d-silveira/flutter-share.git