Error messages refactored

This commit is contained in:
estevez-dev 2019-09-03 23:25:39 +03:00
parent 620aa3b8d8
commit 8efeb3da8a
8 changed files with 330 additions and 68 deletions

View File

@ -4,13 +4,11 @@ import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugins.androidalarmmanager.AlarmService;
public class Application extends FlutterApplication implements PluginRegistrantCallback {
@Override
public void onCreate() {
super.onCreate();
AlarmService.setPluginRegistrant(this);
}
@Override

View File

@ -96,4 +96,28 @@ class CardType {
static const conditional = "conditional";
static const alarmPanel = "alarm-panel";
static const markdown = "markdown";
}
class UserError {
final int code;
final String message;
UserError({@required this.code, this.message: ""});
}
class ErrorCode {
static const UNKNOWN = 0;
static const NOT_CONFIGURED = 1;
static const AUTH_INVALID = 2;
static const NO_MOBILE_APP_COMPONENT = 3;
static const ERROR_GETTING_CONFIG = 4;
static const ERROR_GETTING_STATES = 5;
static const ERROR_GETTING_LOVELACE_CONFIG = 6;
static const ERROR_GETTING_PANELS = 7;
static const CONNECTION_TIMEOUT = 8;
static const DISCONNECTED = 9;
static const UNABLE_TO_CONNECT = 10;
static const GENERAL_AUTH_ERROR = 11;
static const AUTH_ERROR = 12;
static const NOT_LOGGED_IN = 13;
}

View File

@ -68,7 +68,7 @@ class HomeAssistant {
_fetchCompleter.complete();
MobileAppIntegrationManager.checkAppRegistration();
} else {
_fetchCompleter.completeError(HAError("Mobile app component not found", actions: [HAErrorAction.tryAgain(), HAErrorAction(type: HAErrorActionType.URL ,title: "Help",url: "http://ha-client.homemade.systems/docs#mobile-app")]));
_fetchCompleter.completeError(UserError(code: ErrorCode.NO_MOBILE_APP_COMPONENT));
}
}).catchError((e) {
_fetchCompleter.completeError(e);
@ -89,7 +89,7 @@ class HomeAssistant {
await ConnectionManager().sendSocketMessage(type: "get_config").then((data) {
_instanceConfig = Map.from(data);
}).catchError((e) {
throw HAError("Error getting config: ${e}");
throw UserError(code: ErrorCode.ERROR_GETTING_CONFIG, message: "$e");
});
}
@ -97,26 +97,26 @@ class HomeAssistant {
await ConnectionManager().sendSocketMessage(type: "get_states").then(
(data) => entities.parse(data)
).catchError((e) {
throw HAError("Error getting states: $e");
throw UserError(code: ErrorCode.ERROR_GETTING_STATES, message: "$e");
});
}
Future _getLovelace() async {
await ConnectionManager().sendSocketMessage(type: "lovelace/config").then((data) => _rawLovelaceData = data).catchError((e) {
throw HAError("Error getting lovelace config: $e");
throw UserError(code: ErrorCode.ERROR_GETTING_LOVELACE_CONFIG, message: "$e");
});
}
Future _getUserInfo() async {
_userName = null;
await ConnectionManager().sendSocketMessage(type: "auth/current_user").then((data) => _userName = data["name"]).catchError((e) {
Logger.w("Can't get user info: ${e}");
Logger.w("Can't get user info: $e");
});
}
Future _getServices() async {
await ConnectionManager().sendSocketMessage(type: "get_services").then((data) => Logger.d("Services received")).catchError((e) {
Logger.w("Can't get services: ${e}");
Logger.w("Can't get services: $e");
});
}
@ -136,7 +136,7 @@ class HomeAssistant {
);
});
}).catchError((e) {
throw HAError("Error getting panels list: $e");
throw UserError(code: ErrorCode.ERROR_GETTING_PANELS, message: "$e");
});
}

View File

@ -114,6 +114,7 @@ part 'ui_widgets/card_widget.dart';
part 'ui_widgets/card_header_widget.dart';
part 'panels/config_panel_widget.dart';
part 'panels/widgets/link_to_web_config.dart';
part 'user_error_screen.widget.dart';
EventBus eventBus = new EventBus();
@ -217,8 +218,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
StreamSubscription _reloadUISubscription;
StreamSubscription _showPageSubscription;
int _previousViewCount;
bool _showLoginButton = false;
//bool _showLoginButton = false;
bool _preventAppRefresh = false;
UserError _userError;
@override
void initState() {
@ -292,25 +294,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
}
void _fullLoad() async {
setState(() {
_userError = null;
});
_showInfoBottomBar(progress: true,);
_subscribe().then((_) {
ConnectionManager().init(loadSettings: true, forceReconnect: true).then((__){
_fetchData();
StartupUserMessagesManager().checkMessagesToShow();
}, onError: (e) {
_setErrorState(e);
}, onError: (code) {
_setErrorState(code);
});
});
}
void _quickLoad() {
setState(() {
_userError = null;
});
_hideBottomBar();
_showInfoBottomBar(progress: true,);
ConnectionManager().init(loadSettings: false, forceReconnect: false).then((_){
_fetchData();
StartupUserMessagesManager().checkMessagesToShow();
}, onError: (e) {
_setErrorState(e);
}, onError: (code) {
_setErrorState(code);
});
}
@ -323,12 +329,8 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
_viewsTabController = TabController(vsync: this, length: currentViewCount);
_previousViewCount = currentViewCount;
}
}).catchError((e) {
if (e is HAError) {
_setErrorState(e);
} else {
_setErrorState(HAError(e.toString()));
}
}).catchError((code) {
_setErrorState(code);
});
eventBus.fire(RefreshDataFinishedEvent());
}
@ -371,7 +373,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
}
if (_reloadUISubscription == null) {
_reloadUISubscription = eventBus.on<ReloadUIEvent>().listen((event){
_quickLoad();
if (event.full)
_fullLoad();
else
_quickLoad();
});
}
if (_showPopupDialogSubscription == null) {
@ -421,20 +426,20 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
if (_showErrorSubscription == null) {
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
_showErrorBottomBar(event.error);
_setErrorState(event.error);
});
}
if (_startAuthSubscription == null) {
_startAuthSubscription = eventBus.on<StartAuthEvent>().listen((event){
setState(() {
_showLoginButton = event.showButton;
});
if (event.showButton) {
if (event.starting) {
_showOAuth();
} else {
_preventAppRefresh = false;
Navigator.of(context).pop();
setState(() {
_userError = null;
});
}
});
}
@ -448,17 +453,27 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
void _showOAuth() {
_preventAppRefresh = true;
_setErrorState(UserError(code: ErrorCode.NOT_LOGGED_IN));
Navigator.of(context).pushNamed('/login');
}
_setErrorState(HAError e) {
if (e == null) {
_setErrorState(error) {
if (error is UserError) {
setState(() {
_userError = error;
});
} else {
setState(() {
_userError = UserError(code: ErrorCode.UNKNOWN);
});
}
/*if (e == null) {
_showErrorBottomBar(
HAError("Unknown error")
);
} else {
_showErrorBottomBar(e);
}
}*/
}
void _showPopupDialog({String title, String body, var onPositive, var onNegative, String positiveText, String negativeText}) {
@ -739,6 +754,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
}
}
/*
void _showErrorBottomBar(HAError error) {
TextStyle textStyle = TextStyle(
color: Colors.blue,
@ -803,7 +819,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
_bottomBarText = "${error.message}";
_showBottomBar = true;
});
}
}*/
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@ -814,18 +830,33 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
child: new Text("Reload"),
value: "reload",
));
List<Widget> emptyBody = [
/*List<Widget> emptyBody = [
Text("."),
];
];*/
if (ConnectionManager().isAuthenticated) {
_showLoginButton = false;
//_showLoginButton = false;
popupMenuItems.add(
PopupMenuItem<String>(
child: new Text("Logout"),
value: "logout",
));
}
if (_showLoginButton) {
Widget bodyWidget;
if (_userError != null) {
bodyWidget = UserErrorScreen(error: _userError);
} else if (empty) {
bodyWidget = Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator()
],
);
} else {
bodyWidget = HomeAssistant().buildViews(context, _viewsTabController);
}
/*if (_showLoginButton) {
emptyBody = [
FlatButton(
child: Text("Login with Home Assistant", style: TextStyle(fontSize: 16.0, color: Colors.white)),
@ -833,7 +864,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
onPressed: () => _fullLoad(),
)
];
}
}*/
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
@ -869,7 +900,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
_scaffoldKey.currentState.openDrawer();
},
),
bottom: empty ? null : TabBar(
bottom: (empty || _userError != null) ? null : TabBar(
controller: _viewsTabController,
tabs: buildUIViewTabs(),
isScrollable: true,
@ -878,15 +909,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
];
},
body: empty ?
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: emptyBody
),
)
:
HomeAssistant().buildViews(context, _viewsTabController),
body: bodyWidget,
);
}

View File

@ -33,7 +33,7 @@ class AuthManager {
//flutterWebviewPlugin.close();
Logger.e("Error getting temp token: ${e.toString()}");
eventBus.fire(StartAuthEvent(oauthUrl, false));
completer.completeError(HAError("Error getting temp token"));
completer.completeError(UserError(code: ErrorCode.AUTH_ERROR));
});
}
});

View File

@ -40,6 +40,7 @@ class ConnectionManager {
if (loadSettings) {
Logger.e("Loading settings...");
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
useLovelace = prefs.getBool('use-lovelace') ?? true;
_domain = prefs.getString('hassio-domain');
_port = prefs.getString('hassio-port');
@ -51,14 +52,12 @@ class ConnectionManager {
"${prefs.getString('hassio-res-protocol')}://$_domain:$_port";
if ((_domain == null) || (_port == null) ||
(_domain.isEmpty) || (_port.isEmpty)) {
completer.completeError(HAError.checkConnectionSettings());
completer.completeError(UserError(code: ErrorCode.NOT_CONFIGURED));
stopInit = true;
} else {
//_token = prefs.getString('hassio-token');
final storage = new FlutterSecureStorage();
try {
_token = await storage.read(key: "hacl_llt");
Logger.e("Long-lived token read successful");
} catch (e) {
Logger.e("Cannt read secure storage. Need to relogin.");
_token = null;
@ -73,7 +72,7 @@ class ConnectionManager {
} else {
if ((_domain == null) || (_port == null) ||
(_domain.isEmpty) || (_port.isEmpty)) {
completer.completeError(HAError.checkConnectionSettings());
completer.completeError(UserError(code: ErrorCode.NOT_CONFIGURED));
stopInit = true;
}
}
@ -101,7 +100,7 @@ class ConnectionManager {
if (forceReconnect || !isConnected) {
_connect().timeout(connectTimeout, onTimeout: () {
_disconnect().then((_) {
completer?.completeError(HAError("Connection timeout"));
completer?.completeError(UserError(code: ErrorCode.CONNECTION_TIMEOUT));
});
}).then((_) {
completer?.complete();
@ -146,10 +145,11 @@ class ConnectionManager {
}
} else if (data["type"] == "auth_invalid") {
Logger.d("[Received] <== ${data.toString()}");
_messageResolver["auth"]?.completeError(HAError("${data["message"]}", actions: [HAErrorAction.loginAgain()]));
_messageResolver["auth"]?.completeError(UserError(code: ErrorCode.AUTH_INVALID, message: "${data["message"]}"));
_messageResolver.remove("auth");
//TODO dont logout, show variants to User
logout().then((_) {
if (!connecting.isCompleted) connecting.completeError(HAError("${data["message"]}", actions: [HAErrorAction.loginAgain()]));
if (!connecting.isCompleted) connecting.completeError(UserError(code: ErrorCode.AUTH_INVALID, message: "${data["message"]}"));
});
} else {
_handleMessage(data);
@ -214,14 +214,14 @@ class ConnectionManager {
Logger.d("Socket disconnected.");
if (!connectionCompleter.isCompleted) {
isConnected = false;
connectionCompleter.completeError(HAError("Disconnected", actions: [HAErrorAction.reconnect()]));
connectionCompleter.completeError(UserError(code: ErrorCode.DISCONNECTED));
} 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(UserError(code: ErrorCode.UNABLE_TO_CONNECT));
});
});
});
@ -232,14 +232,14 @@ class ConnectionManager {
Logger.e("Socket stream Error: $e");
if (!connectionCompleter.isCompleted) {
isConnected = false;
connectionCompleter.completeError(HAError("Unable to connect to Home Assistant"));
connectionCompleter.completeError(UserError(code: ErrorCode.UNABLE_TO_CONNECT));
} 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(UserError(code: ErrorCode.UNABLE_TO_CONNECT)));
});
});
});
@ -275,7 +275,7 @@ class ConnectionManager {
});
}).catchError((e) => completer.completeError(e));
} else {
completer.completeError(HAError("General login error"));
completer.completeError(UserError(code: ErrorCode.GENERAL_AUTH_ERROR));
}
return completer.future;
}
@ -309,8 +309,9 @@ class ConnectionManager {
throw e;
});
}).catchError((e) {
//TODO dont logout, show variants
logout();
completer.completeError(HAError("Authentication error: $e", actions: [HAErrorAction.loginAgain()]));
completer.completeError(UserError(code: ErrorCode.AUTH_ERROR, message: "$e"));
});
return completer.future;
}
@ -333,7 +334,7 @@ class ConnectionManager {
String rawMessage = json.encode(dataObject);
if (!isConnected) {
_connect().timeout(connectTimeout, onTimeout: (){
_completer.completeError(HAError("No connection to Home Assistant", actions: [HAErrorAction.reconnect()]));
_completer.completeError(UserError(code: ErrorCode.UNABLE_TO_CONNECT));
}).then((_) {
Logger.d("[Sending] ==> ${auth ? "type="+dataObject['type'] : rawMessage}");
_socket.sink.add(rawMessage);

View File

@ -0,0 +1,214 @@
part of 'main.dart';
class UserErrorScreen extends StatelessWidget {
final UserError error;
const UserErrorScreen({Key key, this.error}) : super(key: key);
void _goToAppSettings(BuildContext context) {
Navigator.pushNamed(context, '/connection-settings');
}
void _reload() {
eventBus.fire(ReloadUIEvent(true));
}
void _disableLovelace() {
SharedPreferences.getInstance().then((prefs){
prefs.setBool("use-lovelace", false);
eventBus.fire(ReloadUIEvent(true));
});
}
void _reLogin() {
ConnectionManager().logout().then((_) => eventBus.fire(ReloadUIEvent(true)));
}
@override
Widget build(BuildContext context) {
String errorText;
List<Widget> buttons = [];
switch (this.error.code) {
case ErrorCode.AUTH_ERROR: {
errorText = "There was an error logging in to Home Assistant";
buttons.add(RaisedButton(
onPressed: () => _reload(),
child: Text("Retry"),
));
buttons.add(RaisedButton(
onPressed: () => _reLogin(),
child: Text("Login again"),
));
break;
}
case ErrorCode.UNABLE_TO_CONNECT: {
errorText = "Unable to connect to Home Assistant";
buttons.addAll(<Widget>[
RaisedButton(
onPressed: () => _reload(),
child: Text("Retry")
),
Container(width: 15.0,),
RaisedButton(
onPressed: () => _goToAppSettings(context),
child: Text("Check application settings"),
)
]
);
break;
}
case ErrorCode.AUTH_INVALID: {
errorText = "${error.message ?? "Can't login to Home Assistant"}";
buttons.addAll(<Widget>[
RaisedButton(
onPressed: () => _reload(),
child: Text("Retry")
),
Container(width: 15.0,),
RaisedButton(
onPressed: () => _reLogin(),
child: Text("Login again"),
)
]
);
break;
}
case ErrorCode.GENERAL_AUTH_ERROR: {
buttons.addAll(<Widget>[
RaisedButton(
onPressed: () => _reload(),
child: Text("Retry")
),
Container(width: 15.0,),
RaisedButton(
onPressed: () => _reLogin(),
child: Text("Login again"),
)
]
);
break;
}
case ErrorCode.DISCONNECTED: {
errorText = "Disconnected";
buttons.addAll(<Widget>[
RaisedButton(
onPressed: () => _reload(),
child: Text("Reconnect")
),
Container(width: 15.0,),
RaisedButton(
onPressed: () => _goToAppSettings(context),
child: Text("Check application settings"),
)
]
);
break;
}
case ErrorCode.CONNECTION_TIMEOUT: {
errorText = "Connection timeout";
buttons.addAll(<Widget>[
RaisedButton(
onPressed: () => _reload(),
child: Text("Reconnect")
),
Container(width: 15.0,),
RaisedButton(
onPressed: () => _goToAppSettings(context),
child: Text("Check application settings"),
)
]
);
break;
}
case ErrorCode.NOT_CONFIGURED: {
errorText = "Looks like HA Client is not configured yet.";
buttons.add(RaisedButton(
onPressed: () => _goToAppSettings(context),
child: Text("Open application settings"),
));
break;
}
case ErrorCode.ERROR_GETTING_PANELS:
case ErrorCode.ERROR_GETTING_CONFIG:
case ErrorCode.ERROR_GETTING_STATES: {
errorText = "Couldn't get data from Home Assistant. ${error.message ?? ""}";
buttons.add(RaisedButton(
onPressed: () => _reload(),
child: Text("Try again"),
));
break;
}
case ErrorCode.ERROR_GETTING_LOVELACE_CONFIG: {
errorText = "Couldn't get Lovelace UI config. You can try to disable it and use group-based UI istead.";
buttons.addAll(<Widget>[
RaisedButton(
onPressed: () => _reload(),
child: Text("Retry"),
),
Container(width: 15.0,),
RaisedButton(
onPressed: () => _disableLovelace(),
child: Text("Disable Lovelace UI"),
)
]);
break;
}
case ErrorCode.NOT_LOGGED_IN: {
errorText = "You are not logged in yet. Please login.";
buttons.add(RaisedButton(
onPressed: () => _reload(),
child: Text("Login"),
));
break;
}
default: {
errorText = "???";
buttons.add(RaisedButton(
onPressed: () => _reload(),
child: Text("Reload"),
));
}
}
return Padding(
padding: EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 100.0, bottom: 20.0),
child: Icon(
Icons.error,
color: Colors.redAccent,
size: 48.0
)
),
Text(
errorText,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black45, fontSize: Sizes.largeFontSize),
softWrap: true,
maxLines: 5,
),
Container(height: Sizes.rowPadding,),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: buttons.isNotEmpty ? buttons : Container(height: 0.0, width: 0.0,),
)
],
),
)
],
),
);
}
}

View File

@ -45,7 +45,7 @@ class Logger {
}
class HAError {
/*class HAError {
String message;
final List<HAErrorAction> actions;
@ -87,7 +87,7 @@ class HAErrorActionType {
static const LOGOUT = 2;
static const URL = 3;
static const OPEN_CONNECTION_SETTINGS = 4;
}
}*/
class StateChangedEvent {
String entityId;
@ -112,14 +112,16 @@ class RefreshDataFinishedEvent {
}
class ReloadUIEvent {
ReloadUIEvent();
final bool full;
ReloadUIEvent(this.full);
}
class StartAuthEvent {
String oauthUrl;
bool showButton;
bool starting;
StartAuthEvent(this.oauthUrl, this.showButton);
StartAuthEvent(this.oauthUrl, this.starting);
}
class ServiceCallEvent {
@ -165,7 +167,7 @@ class ShowPageEvent {
}
class ShowErrorEvent {
final HAError error;
final UserError error;
ShowErrorEvent(this.error);
}