Resolves #318 add mobile_app integration
This commit is contained in:
parent
e1d9d9f304
commit
5b99ade088
@ -18,7 +18,8 @@ class Connection {
|
||||
String _token;
|
||||
String _tempToken;
|
||||
String oauthUrl;
|
||||
String deviceName;
|
||||
String webhookId;
|
||||
String registeredAppVersion;
|
||||
bool useLovelace = true;
|
||||
bool settingsLoaded = false;
|
||||
bool get isAuthenticated => _token != null;
|
||||
@ -43,11 +44,13 @@ class Connection {
|
||||
useLovelace = prefs.getBool('use-lovelace') ?? true;
|
||||
_domain = prefs.getString('hassio-domain');
|
||||
_port = prefs.getString('hassio-port');
|
||||
webhookId = prefs.getString('app-webhook-id');
|
||||
registeredAppVersion = prefs.getString('registered-app-version');
|
||||
displayHostname = "$_domain:$_port";
|
||||
_webSocketAPIEndpoint =
|
||||
"${prefs.getString('hassio-protocol')}://$_domain:$_port/api/websocket";
|
||||
httpWebHost =
|
||||
"${prefs.getString('hassio-res-protocol')}://$_domain${(_port == '433' || _port == '80') ? '' : ':'+_port}";
|
||||
"${prefs.getString('hassio-res-protocol')}://$_domain:$_port";
|
||||
if ((_domain == null) || (_port == null) ||
|
||||
(_domain.isEmpty) || (_port.isEmpty)) {
|
||||
completer.completeError(HAError.checkConnectionSettings());
|
||||
@ -57,15 +60,12 @@ class Connection {
|
||||
final storage = new FlutterSecureStorage();
|
||||
try {
|
||||
_token = await storage.read(key: "hacl_llt");
|
||||
Logger.e("Long-lived token read successful: $_token");
|
||||
Logger.e("Long-lived token read successful");
|
||||
} catch (e) {
|
||||
Logger.e("Cannt read secure storage. Need to relogin.");
|
||||
_token = null;
|
||||
await storage.delete(key: "hacl_llt");
|
||||
}
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
deviceName = androidInfo.model;
|
||||
oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent(
|
||||
'http://ha-client.homemade.systems/')}&redirect_uri=${Uri
|
||||
.encodeComponent(
|
||||
@ -393,7 +393,7 @@ class Connection {
|
||||
body: data
|
||||
).then((response) {
|
||||
Logger.d("[Received] <== ${response.statusCode}, ${response.body}");
|
||||
if (response.statusCode == 200) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300 ) {
|
||||
completer.complete(response.body);
|
||||
} else {
|
||||
completer.completeError({"code": response.statusCode, "message": "${response.body}"});
|
||||
|
29
lib/device.class.dart
Normal file
29
lib/device.class.dart
Normal file
@ -0,0 +1,29 @@
|
||||
part of 'main.dart';
|
||||
|
||||
class Device {
|
||||
|
||||
static final Device _instance = Device._internal();
|
||||
|
||||
factory Device() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
String unicDeviceId;
|
||||
String manufacturer;
|
||||
String model;
|
||||
String osName;
|
||||
String osVersion;
|
||||
|
||||
Device._internal();
|
||||
|
||||
loadDeviceInfo() {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
deviceInfo.androidInfo.then((androidInfo) {
|
||||
unicDeviceId = "${androidInfo.model.toLowerCase().replaceAll(' ', '_')}_${androidInfo.androidId}";
|
||||
manufacturer = "${androidInfo.manufacturer}";
|
||||
model = "${androidInfo.model}";
|
||||
osName = "Android";
|
||||
osVersion = "${androidInfo.version.release}";
|
||||
});
|
||||
}
|
||||
}
|
@ -46,10 +46,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: new Text(_title),
|
||||
),
|
||||
body: HomeAssistantModel(
|
||||
homeAssistant: widget.homeAssistant,
|
||||
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
|
||||
),
|
||||
body: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
Widget _buildColorControl(LightEntity entity) {
|
||||
if (entity.supportColor) {
|
||||
HSVColor savedColor = HomeAssistantModel.of(context)?.homeAssistant?.savedColor;
|
||||
HSVColor savedColor = HomeAssistant().savedColor;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
@ -187,10 +187,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
child: Text('Copy color'),
|
||||
onPressed: _tmpColor == null ? null : () {
|
||||
setState(() {
|
||||
HomeAssistantModel
|
||||
.of(context)
|
||||
.homeAssistant
|
||||
.savedColor = _tmpColor;
|
||||
HomeAssistant().savedColor = _tmpColor;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -15,26 +15,6 @@ class EntityModel extends InheritedWidget {
|
||||
return context.inheritFromWidgetOfExactType(EntityModel);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(InheritedWidget oldWidget) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class HomeAssistantModel extends InheritedWidget {
|
||||
|
||||
const HomeAssistantModel({
|
||||
Key key,
|
||||
@required this.homeAssistant,
|
||||
@required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final HomeAssistant homeAssistant;
|
||||
|
||||
static HomeAssistantModel of(BuildContext context) {
|
||||
return context.inheritFromWidgetOfExactType(HomeAssistantModel);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(InheritedWidget oldWidget) {
|
||||
return true;
|
||||
|
@ -2,6 +2,12 @@ part of 'main.dart';
|
||||
|
||||
class HomeAssistant {
|
||||
|
||||
static final HomeAssistant _instance = HomeAssistant._internal();
|
||||
|
||||
factory HomeAssistant() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
EntityCollection entities;
|
||||
HomeAssistantUI ui;
|
||||
Map _instanceConfig = {};
|
||||
@ -27,8 +33,9 @@ class HomeAssistant {
|
||||
bool get isNoViews => ui == null || ui.isEmpty;
|
||||
bool get isMobileAppEnabled => _instanceConfig["components"] != null && (_instanceConfig["components"] as List).contains("mobile_app");
|
||||
|
||||
HomeAssistant() {
|
||||
HomeAssistant._internal() {
|
||||
Connection().onStateChangeCallback = _handleEntityStateChange;
|
||||
Device().loadDeviceInfo();
|
||||
}
|
||||
|
||||
Completer _fetchCompleter;
|
||||
@ -49,6 +56,7 @@ class HomeAssistant {
|
||||
futures.add(_getServices());
|
||||
futures.add(_getUserInfo());
|
||||
futures.add(_getPanels());
|
||||
futures.add(checkAppRegistration());
|
||||
futures.add(Connection().sendSocketMessage(
|
||||
type: "subscribe_events",
|
||||
additionalData: {"event_type": "state_changed"},
|
||||
@ -75,6 +83,71 @@ class HomeAssistant {
|
||||
});
|
||||
}
|
||||
|
||||
Future checkAppRegistration({bool forceRegister: false, bool forceUpdate: false}) {
|
||||
Completer completer = Completer();
|
||||
var registrationData = {
|
||||
"app_version": "$appVersion",
|
||||
"device_name": "$userName's ${Device().model}",
|
||||
"manufacturer": Device().manufacturer,
|
||||
"model": Device().model,
|
||||
"os_name": Device().osName,
|
||||
"os_version": Device().osVersion,
|
||||
"app_data": {
|
||||
"push_notification_key": "d"
|
||||
}
|
||||
};
|
||||
if (Connection().webhookId == null || forceRegister) {
|
||||
Logger.d("Mobile app was not registered yet or need to be reseted. Registering...");
|
||||
registrationData.addAll({
|
||||
"app_id": "ha_client",
|
||||
"app_name": "$appName",
|
||||
"supports_encryption": false,
|
||||
});
|
||||
Connection().sendHTTPPost(
|
||||
endPoint: "/api/mobile_app/registrations",
|
||||
includeAuthHeader: true,
|
||||
data: json.encode(registrationData)
|
||||
).then((response) {
|
||||
Logger.d("Processing registration responce...");
|
||||
var responseObject = json.decode(response);
|
||||
Logger.d(responseObject.toString());
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
prefs.setString("app-webhook-id", responseObject["webhook_id"]);
|
||||
prefs.setString("registered-app-version", "$appVersion");
|
||||
completer.complete();
|
||||
});
|
||||
}).catchError((e) {
|
||||
completer.complete();
|
||||
Logger.e("Error registering the app: ${e.toString()}");
|
||||
});
|
||||
return completer.future;
|
||||
} else if (Connection().registeredAppVersion != appVersion || forceUpdate) {
|
||||
Logger.d("Registered app version is old. Registration need to be updated");
|
||||
var updateData = {
|
||||
"type": "update_registration",
|
||||
"data": registrationData
|
||||
};
|
||||
Connection().sendHTTPPost(
|
||||
endPoint: "/api/webhook/${Connection().webhookId}",
|
||||
includeAuthHeader: false,
|
||||
data: json.encode(updateData)
|
||||
).then((response) {
|
||||
Logger.d("App registration updated");
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
prefs.setString("registered-app-version", "$appVersion");
|
||||
completer.complete();
|
||||
});
|
||||
}).catchError((e) {
|
||||
completer.complete();
|
||||
Logger.e("Error updating app registering: ${e.toString()}");
|
||||
});
|
||||
return completer.future;
|
||||
} else {
|
||||
Logger.d("App is registered");
|
||||
return Future.value();
|
||||
}
|
||||
}
|
||||
|
||||
Future _getConfig() async {
|
||||
await Connection().sendSocketMessage(type: "get_config").then((data) {
|
||||
_instanceConfig = Map.from(data);
|
||||
|
@ -93,6 +93,7 @@ part 'mdi.class.dart';
|
||||
part 'entity_collection.class.dart';
|
||||
part 'auth_manager.class.dart';
|
||||
part 'connection.class.dart';
|
||||
part 'device.class.dart';
|
||||
part 'ui_class/ui.dart';
|
||||
part 'ui_class/view.class.dart';
|
||||
part 'ui_class/card.class.dart';
|
||||
@ -230,7 +231,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
_previousViewCount = currentViewCount;
|
||||
}
|
||||
}).catchError((e) {
|
||||
_setErrorState(e);
|
||||
if (e is HAError) {
|
||||
_setErrorState(e);
|
||||
} else {
|
||||
_setErrorState(HAError(e.toString()));
|
||||
}
|
||||
});
|
||||
eventBus.fire(RefreshDataFinishedEvent());
|
||||
}
|
||||
@ -289,11 +294,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
});
|
||||
}
|
||||
|
||||
_firebaseMessaging.getToken().then((String token) {
|
||||
Logger.d("Device name: ${json.encode(Connection().deviceName)}");
|
||||
/*_firebaseMessaging.getToken().then((String token) {
|
||||
Logger.d("Device name: ${json.encode(Connection().unicDeviceName)}");
|
||||
Connection().sendHTTPPost(
|
||||
endPoint: '/api/notify.ha-client',
|
||||
data: '{"token": "$token", "device": ${json.encode(Connection().deviceName)}}'
|
||||
data: '{"token": "$token", "device": ${json.encode(Connection().unicDeviceName)}}'
|
||||
).then((_) {
|
||||
Logger.d("Notificatin listener registered.");
|
||||
completer.complete();
|
||||
@ -304,7 +309,8 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
}).catchError((e) {
|
||||
Logger.e("Error registering notification listener: ${e.toString()}");
|
||||
completer.complete();
|
||||
});
|
||||
});*/
|
||||
completer.complete();
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@ -394,7 +400,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
new ListTile(
|
||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName(panel.icon)),
|
||||
title: Text("${panel.title}"),
|
||||
onTap: () => panel.handleOpen(context)
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
panel.handleOpen(context);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -752,10 +761,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
drawer: _buildAppDrawer(),
|
||||
primary: false,
|
||||
bottomNavigationBar: bottomBar,
|
||||
body: HomeAssistantModel(
|
||||
child: _buildScaffoldBody(false),
|
||||
homeAssistant: widget.homeAssistant
|
||||
),
|
||||
body: _buildScaffoldBody(false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -55,10 +55,49 @@ class _ConfigPanelWidgetState extends State<ConfigPanelWidget> {
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
ConfigurationItem(
|
||||
header: 'Mobile app',
|
||||
body: Padding(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, 0.0, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text("Registration", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Text("${HomeAssistant().userName}'s ${Device().model}, ${Device().osName} ${Device().osVersion}"),
|
||||
Container(height: 6.0,),
|
||||
Text("Reseting mobile app registration will not remove integration from Home Assistant but creates a new one with different device. If you want to reset mobile app registration completally you need to remove MobileApp from Configuretion -> Integrations of your Home Assistant."),
|
||||
Divider(),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () => resetRegistration(),
|
||||
child: Text("Reset registration")
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () => updateRegistration(),
|
||||
child: Text("Update registration")
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
resetRegistration() {
|
||||
HomeAssistant().checkAppRegistration(forceRegister: true).then((_) => Navigator.of(context).pop());
|
||||
}
|
||||
|
||||
updateRegistration() {
|
||||
HomeAssistant().checkAppRegistration(forceUpdate: true).then((_) => Navigator.of(context).pop());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
|
Reference in New Issue
Block a user