diff --git a/lib/data_model.dart b/lib/data_model.dart index 397d47b..2d15bde 100644 --- a/lib/data_model.dart +++ b/lib/data_model.dart @@ -6,6 +6,12 @@ class StateChangedEvent { StateChangedEvent(this.entityId); } +class SettingsChangedEvent { + bool reconnect; + + SettingsChangedEvent(this.reconnect); +} + class HassioDataModel { String _hassioAPIEndpoint; String _hassioPassword; @@ -56,7 +62,7 @@ class HassioDataModel { closeConnection() { if (_hassioChannel?.closeCode == null) { - _hassioChannel.sink?.close(); + _hassioChannel?.sink?.close(); } _hassioChannel = null; } @@ -111,7 +117,7 @@ class HassioDataModel { _sendSubscribe(); connectionCompleter.complete(); } else if (data["type"] == "auth_invalid") { - connectionCompleter.completeError({message: "Auth error: ${data["message"]}"}); + connectionCompleter.completeError({"errorCode": 6, "errorMessage": "${data["message"]}"}); } else if (data["type"] == "result") { if (data["id"] == _configMessageId) { _parseConfig(data); diff --git a/lib/main.dart b/lib/main.dart index 8b18b39..fe726e9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -51,8 +51,10 @@ class _MainPageState extends State with WidgetsBindingObserver { Map _instanceConfig; int _uiViewsCount = 0; String _instanceHost; - int _fetchErrorCode = 0; - bool loading = true; + int _errorCodeToBeShown = 0; + String _lastErrorMessage = ""; + StreamSubscription _stateSubscription; + bool _isLoading = true; Map _stateIconColors = { "on": Colors.amber, "off": Colors.blueGrey, @@ -65,7 +67,14 @@ class _MainPageState extends State with WidgetsBindingObserver { void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - _init(); + eventBus.on().listen((event) { + debugPrint("Settings change event: reconnect=${event.reconnect}"); + setState(() { + _errorCodeToBeShown = 0; + }); + _initConnection(); + }); + _initConnection(); } @override @@ -76,16 +85,29 @@ class _MainPageState extends State with WidgetsBindingObserver { } } - _init() async { + _initConnection() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String domain = prefs.getString('hassio-domain'); String port = prefs.getString('hassio-port'); _instanceHost = "$domain:$port"; - String _hassioAPIEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket"; - String _hassioPassword = prefs.getString('hassio-password'); - _dataModel = HassioDataModel(_hassioAPIEndpoint, _hassioPassword); + String apiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket"; + String apiPassword = prefs.getString('hassio-password'); + if ((domain == null) || (port == null) || (apiEndpoint == null) || (apiPassword == null) || + (domain.length == 0) || (port.length == 0) || (apiEndpoint.length == 0) || (apiPassword.length == 0)) { + setState(() { + _errorCodeToBeShown = 5; + }); + } else { + if (_dataModel != null) _dataModel.closeConnection(); + _createConnection(apiEndpoint, apiPassword); + } + } + + _createConnection(String apiEndpoint, String apiPassword) { + _dataModel = HassioDataModel(apiEndpoint, apiPassword); _refreshData(); - eventBus.on().listen((event) { + if (_stateSubscription != null) _stateSubscription.cancel(); + _stateSubscription = eventBus.on().listen((event) { debugPrint("State change event for ${event.entityId}"); setState(() { _entitiesData = _dataModel.entities; @@ -95,9 +117,9 @@ class _MainPageState extends State with WidgetsBindingObserver { _refreshData() async { setState(() { - loading = true; + _isLoading = true; }); - _fetchErrorCode = 0; + _errorCodeToBeShown = 0; if (_dataModel != null) { await _dataModel.fetch().then((result) { setState(() { @@ -105,12 +127,13 @@ class _MainPageState extends State with WidgetsBindingObserver { _entitiesData = _dataModel.entities; _uiStructure = _dataModel.uiStructure; _uiViewsCount = _uiStructure.length; - loading = false; + _isLoading = false; }); }).catchError((e) { setState(() { - _fetchErrorCode = e["errorCode"] != null ? e["errorCode"] : 2; - loading = false; + _errorCodeToBeShown = e["errorCode"] != null ? e["errorCode"] : 99; + _lastErrorMessage = e["errorMessage"] ?? "Unknown error"; + _isLoading = false; }); }); } @@ -259,7 +282,7 @@ class _MainPageState extends State with WidgetsBindingObserver { Row titleRow = Row( children: [Text(_instanceConfig != null ? _instanceConfig["location_name"] : "")], ); - if (loading) { + if (_isLoading) { titleRow.children.add(Padding( child: JumpingDotsProgressIndicator( fontSize: 26.0, @@ -297,35 +320,61 @@ class _MainPageState extends State with WidgetsBindingObserver { ); } - _getErrorMessageByCode(int code, bool short) { - String message = short ? "Unknown error" : "Unknown error"; - switch (code) { - case 1: { - message = short ? "Unable to connect" : "Unable to connect\n Please check your internet connection and Home Assistant instance state"; - break; - } - } - return message; - } - _checkShowInfo(BuildContext context) { - if (_fetchErrorCode > 0) { - String text = _getErrorMessageByCode(_fetchErrorCode, true); + if (_errorCodeToBeShown > 0) { + String message = _lastErrorMessage; SnackBarAction action; - switch (_fetchErrorCode) { + switch (_errorCodeToBeShown) { case 1: { action = SnackBarAction( label: "Retry", - onPressed: _refreshData, + onPressed: () { + _scaffoldKey?.currentState?.hideCurrentSnackBar(); + _refreshData(); + }, ); break; } + + case 5: { + message = "Check connection settings"; + action = SnackBarAction( + label: "Open", + onPressed: () { + _scaffoldKey?.currentState?.hideCurrentSnackBar(); + Navigator.pushNamed(context, '/connection-settings'); + }, + ); + break; + } + + case 6: { + action = SnackBarAction( + label: "Settings", + onPressed: () { + _scaffoldKey?.currentState?.hideCurrentSnackBar(); + Navigator.pushNamed(context, '/connection-settings'); + }, + ); + break; + } + + case 7: { + action = SnackBarAction( + label: "Retry", + onPressed: () { + _scaffoldKey?.currentState?.hideCurrentSnackBar(); + _refreshData(); + }, + ); + break; + } } Timer(Duration(seconds: 1), () { _scaffoldKey.currentState.hideCurrentSnackBar(); _scaffoldKey.currentState.showSnackBar( SnackBar( - content: Text("$text"), + content: Text("$message (code: $_errorCodeToBeShown)"), action: action, duration: Duration(hours: 1), ) @@ -363,7 +412,7 @@ class _MainPageState extends State with WidgetsBindingObserver { Icon( _createMDIfromCode(MaterialDesignIcons.getCustomIconByName("mdi:home-assistant")), size: 100.0, - color: _fetchErrorCode == 0 ? Colors.blue : Colors.redAccent, + color: _errorCodeToBeShown == 0 ? Colors.blue : Colors.redAccent, ), ] ), diff --git a/lib/settings.dart b/lib/settings.dart index 8fffbf8..22542c0 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -11,7 +11,7 @@ class ConnectionSettingsPage extends StatefulWidget { class _ConnectionSettingsPageState extends State { String _hassioDomain = ""; - String _hassioPort = ""; + String _hassioPort = "8123"; String _hassioPassword = ""; String _socketProtocol = "wss"; @@ -26,20 +26,18 @@ class _ConnectionSettingsPageState extends State { setState(() { _hassioDomain = prefs.getString("hassio-domain"); - _hassioPort = prefs.getString("hassio-port"); + _hassioPort = prefs.getString("hassio-port") ?? '8123'; _hassioPassword = prefs.getString("hassio-password"); - _socketProtocol = prefs.getString("hassio-protocol"); + _socketProtocol = prefs.getString("hassio-protocol") ?? 'wss'; }); } _saveSettings() async { - debugPrint("Saving settings...."); SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setString("hassio-domain", _hassioDomain); prefs.setString("hassio-port", _hassioPort); prefs.setString("hassio-password", _hassioPassword); prefs.setString("hassio-protocol", _socketProtocol); - debugPrint("Done saving settings...."); } @override @@ -47,8 +45,10 @@ class _ConnectionSettingsPageState extends State { return new Scaffold( appBar: new AppBar( leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){ - _saveSettings(); - Navigator.pop(context); + _saveSettings().then((r){ + Navigator.pop(context); + }); + eventBus.fire(SettingsChangedEvent(true)); }), // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title.