WIP: user messages
This commit is contained in:
parent
5f0bc83d67
commit
fb335e1100
822
lib/main.dart
822
lib/main.dart
@ -93,9 +93,13 @@ part 'pages/widgets/page_loading_indicator.dart';
|
|||||||
part 'pages/widgets/page_loading_error.dart';
|
part 'pages/widgets/page_loading_error.dart';
|
||||||
part 'pages/panel.page.dart';
|
part 'pages/panel.page.dart';
|
||||||
part 'home_assistant.class.dart';
|
part 'home_assistant.class.dart';
|
||||||
|
part 'pages/main.page.dart';
|
||||||
part 'pages/log.page.dart';
|
part 'pages/log.page.dart';
|
||||||
part 'pages/entity.page.dart';
|
part 'pages/entity.page.dart';
|
||||||
part 'utils.class.dart';
|
part 'pages/widgets/app_drawer.dart';
|
||||||
|
part 'pages/widgets/main_page_body.dart';
|
||||||
|
part 'utils/logger.dart';
|
||||||
|
part 'utils/event_bus_events.dart';
|
||||||
part 'mdi.class.dart';
|
part 'mdi.class.dart';
|
||||||
part 'entity_collection.class.dart';
|
part 'entity_collection.class.dart';
|
||||||
part 'managers/auth_manager.class.dart';
|
part 'managers/auth_manager.class.dart';
|
||||||
@ -114,7 +118,7 @@ part 'ui_widgets/card_widget.dart';
|
|||||||
part 'ui_widgets/card_header_widget.dart';
|
part 'ui_widgets/card_header_widget.dart';
|
||||||
part 'panels/config_panel_widget.dart';
|
part 'panels/config_panel_widget.dart';
|
||||||
part 'panels/widgets/link_to_web_config.dart';
|
part 'panels/widgets/link_to_web_config.dart';
|
||||||
part 'user_error_screen.widget.dart';
|
part 'pages/widgets/user_error_screen.widget.dart';
|
||||||
|
|
||||||
|
|
||||||
EventBus eventBus = new EventBus();
|
EventBus eventBus = new EventBus();
|
||||||
@ -194,817 +198,3 @@ class HAClientApp extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainPage extends StatefulWidget {
|
|
||||||
MainPage({Key key, this.title}) : super(key: key);
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_MainPageState createState() => new _MainPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MainPageState extends State<MainPage> with WidgetsBindingObserver, TickerProviderStateMixin {
|
|
||||||
|
|
||||||
StreamSubscription<List<PurchaseDetails>> _subscription;
|
|
||||||
StreamSubscription _stateSubscription;
|
|
||||||
StreamSubscription _settingsSubscription;
|
|
||||||
StreamSubscription _serviceCallSubscription;
|
|
||||||
StreamSubscription _showEntityPageSubscription;
|
|
||||||
StreamSubscription _showErrorSubscription;
|
|
||||||
StreamSubscription _startAuthSubscription;
|
|
||||||
StreamSubscription _showPopupDialogSubscription;
|
|
||||||
StreamSubscription _showPopupMessageSubscription;
|
|
||||||
StreamSubscription _reloadUISubscription;
|
|
||||||
StreamSubscription _showPageSubscription;
|
|
||||||
int _previousViewCount;
|
|
||||||
//bool _showLoginButton = false;
|
|
||||||
bool _preventAppRefresh = false;
|
|
||||||
UserError _userError;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final Stream purchaseUpdates =
|
|
||||||
InAppPurchaseConnection.instance.purchaseUpdatedStream;
|
|
||||||
_subscription = purchaseUpdates.listen((purchases) {
|
|
||||||
_handlePurchaseUpdates(purchases);
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
|
|
||||||
_firebaseMessaging.configure(
|
|
||||||
onLaunch: (data) {
|
|
||||||
Logger.d("Notification [onLaunch]: $data");
|
|
||||||
return Future.value();
|
|
||||||
},
|
|
||||||
onMessage: (data) {
|
|
||||||
Logger.d("Notification [onMessage]: $data");
|
|
||||||
return _showNotification(title: data["notification"]["title"], text: data["notification"]["body"]);
|
|
||||||
},
|
|
||||||
onResume: (data) {
|
|
||||||
Logger.d("Notification [onResume]: $data");
|
|
||||||
return Future.value();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
_firebaseMessaging.requestNotificationPermissions(const IosNotificationSettings(sound: true, badge: true, alert: true));
|
|
||||||
|
|
||||||
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
|
|
||||||
var initializationSettingsAndroid =
|
|
||||||
new AndroidInitializationSettings('mini_icon');
|
|
||||||
var initializationSettingsIOS = new IOSInitializationSettings(
|
|
||||||
onDidReceiveLocalNotification: null);
|
|
||||||
var initializationSettings = new InitializationSettings(
|
|
||||||
initializationSettingsAndroid, initializationSettingsIOS);
|
|
||||||
flutterLocalNotificationsPlugin.initialize(initializationSettings,
|
|
||||||
onSelectNotification: onSelectNotification);
|
|
||||||
|
|
||||||
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
|
|
||||||
Logger.d("Settings change event: reconnect=${event.reconnect}");
|
|
||||||
if (event.reconnect) {
|
|
||||||
_preventAppRefresh = false;
|
|
||||||
_fullLoad();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_fullLoad();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Future onSelectNotification(String payload) async {
|
|
||||||
if (payload != null) {
|
|
||||||
Logger.d('Notification clicked: ' + payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _showNotification({String title, String text}) async {
|
|
||||||
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
|
|
||||||
'ha_notify', 'Home Assistant notifications', 'Notifications from Home Assistant notify service',
|
|
||||||
importance: Importance.Max, priority: Priority.High);
|
|
||||||
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
|
|
||||||
var platformChannelSpecifics = new NotificationDetails(
|
|
||||||
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
|
||||||
0,
|
|
||||||
title ?? appName,
|
|
||||||
text,
|
|
||||||
platformChannelSpecifics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _fullLoad() async {
|
|
||||||
setState(() {
|
|
||||||
_userError = null;
|
|
||||||
});
|
|
||||||
_showInfoBottomBar(progress: true,);
|
|
||||||
_subscribe().then((_) {
|
|
||||||
ConnectionManager().init(loadSettings: true, forceReconnect: true).then((__){
|
|
||||||
_fetchData();
|
|
||||||
}, onError: (error) {
|
|
||||||
_setErrorState(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _quickLoad() {
|
|
||||||
setState(() {
|
|
||||||
_userError = null;
|
|
||||||
});
|
|
||||||
_hideBottomBar();
|
|
||||||
_showInfoBottomBar(progress: true,);
|
|
||||||
ConnectionManager().init(loadSettings: false, forceReconnect: false).then((_){
|
|
||||||
_fetchData();
|
|
||||||
}, onError: (error) {
|
|
||||||
_setErrorState(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetchData() async {
|
|
||||||
await HomeAssistant().fetchData().then((_) {
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}).catchError((code) {
|
|
||||||
_setErrorState(code);
|
|
||||||
});
|
|
||||||
eventBus.fire(RefreshDataFinishedEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
Logger.d("$state");
|
|
||||||
if (state == AppLifecycleState.resumed && ConnectionManager().settingsLoaded && !_preventAppRefresh) {
|
|
||||||
_quickLoad();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handlePurchaseUpdates(purchase) {
|
|
||||||
if (purchase is List<PurchaseDetails>) {
|
|
||||||
if (purchase[0].status == PurchaseStatus.purchased) {
|
|
||||||
eventBus.fire(ShowPopupMessageEvent(
|
|
||||||
title: "Thanks a lot!",
|
|
||||||
body: "Thank you for supporting HA Client development!",
|
|
||||||
buttonText: "Ok"
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
Logger.d("Purchase change handler: ${purchase[0].status}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger.e("Something wrong with purchase handling. Got: $purchase");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _subscribe() {
|
|
||||||
Completer completer = Completer();
|
|
||||||
if (_stateSubscription == null) {
|
|
||||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
|
||||||
if (event.needToRebuildUI) {
|
|
||||||
Logger.d("New entity. Need to rebuild UI");
|
|
||||||
_quickLoad();
|
|
||||||
} else {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (_reloadUISubscription == null) {
|
|
||||||
_reloadUISubscription = eventBus.on<ReloadUIEvent>().listen((event){
|
|
||||||
if (event.full)
|
|
||||||
_fullLoad();
|
|
||||||
else
|
|
||||||
_quickLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (_showPopupDialogSubscription == null) {
|
|
||||||
_showPopupDialogSubscription = eventBus.on<ShowPopupDialogEvent>().listen((event){
|
|
||||||
_showPopupDialog(
|
|
||||||
title: event.title,
|
|
||||||
body: event.body,
|
|
||||||
onPositive: event.onPositive,
|
|
||||||
onNegative: event.onNegative,
|
|
||||||
positiveText: event.positiveText,
|
|
||||||
negativeText: event.negativeText
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (_showPopupMessageSubscription == null) {
|
|
||||||
_showPopupMessageSubscription = eventBus.on<ShowPopupMessageEvent>().listen((event){
|
|
||||||
_showPopupDialog(
|
|
||||||
title: event.title,
|
|
||||||
body: event.body,
|
|
||||||
onPositive: event.onButtonClick,
|
|
||||||
positiveText: event.buttonText,
|
|
||||||
negativeText: null
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (_serviceCallSubscription == null) {
|
|
||||||
_serviceCallSubscription =
|
|
||||||
eventBus.on<ServiceCallEvent>().listen((event) {
|
|
||||||
_callService(event.domain, event.service, event.entityId,
|
|
||||||
event.additionalParams);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_showEntityPageSubscription == null) {
|
|
||||||
_showEntityPageSubscription =
|
|
||||||
eventBus.on<ShowEntityPageEvent>().listen((event) {
|
|
||||||
_showEntityPage(event.entity.entityId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_showPageSubscription == null) {
|
|
||||||
_showPageSubscription =
|
|
||||||
eventBus.on<ShowPageEvent>().listen((event) {
|
|
||||||
_showPage(event.path, event.goBackFirst);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_showErrorSubscription == null) {
|
|
||||||
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
|
|
||||||
_setErrorState(event.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_startAuthSubscription == null) {
|
|
||||||
_startAuthSubscription = eventBus.on<StartAuthEvent>().listen((event){
|
|
||||||
if (event.starting) {
|
|
||||||
_showOAuth();
|
|
||||||
} else {
|
|
||||||
_preventAppRefresh = false;
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
setState(() {
|
|
||||||
_userError = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_firebaseMessaging.getToken().then((String token) {
|
|
||||||
HomeAssistant().fcmToken = token;
|
|
||||||
completer.complete();
|
|
||||||
});
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showOAuth() {
|
|
||||||
_preventAppRefresh = true;
|
|
||||||
_setErrorState(UserError(code: ErrorCode.NOT_LOGGED_IN));
|
|
||||||
Navigator.of(context).pushNamed('/login');
|
|
||||||
}
|
|
||||||
|
|
||||||
_setErrorState(error) {
|
|
||||||
if (error is UserError) {
|
|
||||||
setState(() {
|
|
||||||
_showBottomBar = false;
|
|
||||||
_userError = error;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
_showBottomBar = false;
|
|
||||||
_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}) {
|
|
||||||
List<Widget> buttons = [];
|
|
||||||
buttons.add(FlatButton(
|
|
||||||
child: new Text("$positiveText"),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
if (onPositive != null) {
|
|
||||||
onPositive();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
));
|
|
||||||
if (negativeText != null) {
|
|
||||||
buttons.add(FlatButton(
|
|
||||||
child: new Text("$negativeText"),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
if (onNegative != null) {
|
|
||||||
onNegative();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// flutter defined function
|
|
||||||
showDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
// return object of type Dialog
|
|
||||||
return AlertDialog(
|
|
||||||
title: new Text("$title"),
|
|
||||||
content: new Text("$body"),
|
|
||||||
actions: buttons,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _callService(String domain, String service, String entityId, Map additionalParams) {
|
|
||||||
_showInfoBottomBar(
|
|
||||||
message: "Calling $domain.$service",
|
|
||||||
duration: Duration(seconds: 3)
|
|
||||||
);
|
|
||||||
ConnectionManager().callService(domain: domain, service: service, entityId: entityId, additionalServiceData: additionalParams).catchError((e) => _setErrorState(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showEntityPage(String entityId) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => EntityViewPage(entityId: entityId),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showPage(String path, bool goBackFirst) {
|
|
||||||
if (goBackFirst) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
path
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Tab> buildUIViewTabs() {
|
|
||||||
List<Tab> result = [];
|
|
||||||
|
|
||||||
if (HomeAssistant().ui.views.isNotEmpty) {
|
|
||||||
HomeAssistant().ui.views.forEach((HAView view) {
|
|
||||||
result.add(view.buildTab());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawer _buildAppDrawer() {
|
|
||||||
List<Widget> menuItems = [];
|
|
||||||
menuItems.add(
|
|
||||||
UserAccountsDrawerHeader(
|
|
||||||
accountName: Text(HomeAssistant().userName),
|
|
||||||
accountEmail: Text(ConnectionManager().displayHostname ?? "Not configured"),
|
|
||||||
onDetailsPressed: () {
|
|
||||||
final flutterWebViewPlugin = new FlutterWebviewPlugin();
|
|
||||||
flutterWebViewPlugin.onStateChanged.listen((viewState) async {
|
|
||||||
if (viewState.type == WebViewState.startLoad) {
|
|
||||||
Logger.d("[WebView] Injecting external auth JS");
|
|
||||||
rootBundle.loadString('assets/js/externalAuth.js').then((js){
|
|
||||||
flutterWebViewPlugin.evalJavascript(js.replaceFirst("[token]", ConnectionManager()._token));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
"/webview",
|
|
||||||
arguments: {
|
|
||||||
"url": "${ConnectionManager().httpWebHost}/profile?external_auth=1",
|
|
||||||
"title": "Profile"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
currentAccountPicture: CircleAvatar(
|
|
||||||
child: Text(
|
|
||||||
HomeAssistant().userAvatarText,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 32.0
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (HomeAssistant().panels.isNotEmpty) {
|
|
||||||
HomeAssistant().panels.forEach((Panel panel) {
|
|
||||||
if (!panel.isHidden) {
|
|
||||||
menuItems.add(
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName(panel.icon)),
|
|
||||||
title: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text("${panel.title}"),
|
|
||||||
Container(width: 4.0,),
|
|
||||||
panel.isWebView ? Text("webview", style: TextStyle(fontSize: 8.0, color: Colors.black45),) : Container(width: 1.0,)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
panel.handleOpen(context);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
menuItems.addAll([
|
|
||||||
Divider(),
|
|
||||||
ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:login-variant")),
|
|
||||||
title: Text("Connection settings"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pushNamed('/connection-settings');
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
menuItems.addAll([
|
|
||||||
Divider(),
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(Icons.insert_drive_file),
|
|
||||||
title: Text("Log"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pushNamed('/log-view');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:github-circle")),
|
|
||||||
title: Text("Report an issue"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Launcher.launchURL("https://github.com/estevez-dev/ha_client/issues/new");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Divider(),
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:food")),
|
|
||||||
title: Text("Support app development"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pushNamed('/putchase');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Divider(),
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(Icons.help),
|
|
||||||
title: Text("Help"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Launcher.launchURL("http://ha-client.homemade.systems/docs");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:discord")),
|
|
||||||
title: Text("Join Discord channel"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Launcher.launchURL("https://discord.gg/AUzEvwn");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
new AboutListTile(
|
|
||||||
aboutBoxChildren: <Widget>[
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Launcher.launchURL("http://ha-client.homemade.systems/");
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"ha-client.homemade.systems",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
decoration: TextDecoration.underline
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 10.0,
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.homemade.systems/terms_and_conditions");
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"Terms and Conditions",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
decoration: TextDecoration.underline
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 10.0,
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.homemade.systems/privacy_policy");
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"Privacy Policy",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
decoration: TextDecoration.underline
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
applicationName: appName,
|
|
||||||
applicationVersion: appVersion
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
return new Drawer(
|
|
||||||
child: ListView(
|
|
||||||
children: menuItems,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _hideBottomBar() {
|
|
||||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
|
||||||
setState(() {
|
|
||||||
_showBottomBar = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _bottomBarAction;
|
|
||||||
bool _showBottomBar = false;
|
|
||||||
String _bottomBarText;
|
|
||||||
bool _bottomBarProgress;
|
|
||||||
Color _bottomBarColor;
|
|
||||||
Timer _bottomBarTimer;
|
|
||||||
|
|
||||||
void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
|
|
||||||
_bottomBarTimer?.cancel();
|
|
||||||
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
|
||||||
_bottomBarColor = Colors.grey.shade50;
|
|
||||||
setState(() {
|
|
||||||
_bottomBarText = message;
|
|
||||||
_bottomBarProgress = progress;
|
|
||||||
_showBottomBar = true;
|
|
||||||
});
|
|
||||||
if (duration != null) {
|
|
||||||
_bottomBarTimer = Timer(duration, () {
|
|
||||||
_hideBottomBar();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
void _showErrorBottomBar(HAError error) {
|
|
||||||
TextStyle textStyle = TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
fontSize: Sizes.nameFontSize
|
|
||||||
);
|
|
||||||
_bottomBarColor = Colors.red.shade100;
|
|
||||||
List<Widget> actions = [];
|
|
||||||
error.actions.forEach((HAErrorAction action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case HAErrorActionType.FULL_RELOAD: {
|
|
||||||
actions.add(FlatButton(
|
|
||||||
child: Text("${action.title}", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
_fullLoad();
|
|
||||||
},
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HAErrorActionType.QUICK_RELOAD: {
|
|
||||||
actions.add(FlatButton(
|
|
||||||
child: Text("${action.title}", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
_quickLoad();
|
|
||||||
},
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HAErrorActionType.URL: {
|
|
||||||
actions.add(FlatButton(
|
|
||||||
child: Text("${action.title}", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
Launcher.launchURLInCustomTab(context: context, url: "${action.url}");
|
|
||||||
},
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HAErrorActionType.OPEN_CONNECTION_SETTINGS: {
|
|
||||||
actions.add(FlatButton(
|
|
||||||
child: Text("${action.title}", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pushNamed(context, '/connection-settings');
|
|
||||||
},
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (actions.isNotEmpty) {
|
|
||||||
_bottomBarAction = Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: actions,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_bottomBarProgress = false;
|
|
||||||
_bottomBarText = "${error.message}";
|
|
||||||
_showBottomBar = true;
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
|
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
|
||||||
|
|
||||||
Widget _buildScaffoldBody(bool empty) {
|
|
||||||
List<PopupMenuItem<String>> popupMenuItems = [];
|
|
||||||
|
|
||||||
popupMenuItems.add(PopupMenuItem<String>(
|
|
||||||
child: new Text("Reload"),
|
|
||||||
value: "reload",
|
|
||||||
));
|
|
||||||
/*List<Widget> emptyBody = [
|
|
||||||
Text("."),
|
|
||||||
];*/
|
|
||||||
if (ConnectionManager().isAuthenticated) {
|
|
||||||
//_showLoginButton = false;
|
|
||||||
popupMenuItems.add(
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
child: new Text("Logout"),
|
|
||||||
value: "logout",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
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)),
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () => _fullLoad(),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}*/
|
|
||||||
return NestedScrollView(
|
|
||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
|
||||||
return <Widget>[
|
|
||||||
SliverAppBar(
|
|
||||||
floating: true,
|
|
||||||
pinned: true,
|
|
||||||
primary: true,
|
|
||||||
title: Text(HomeAssistant().locationName ?? ""),
|
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
|
||||||
"mdi:dots-vertical"), color: Colors.white,),
|
|
||||||
onPressed: () {
|
|
||||||
showMenu(
|
|
||||||
position: RelativeRect.fromLTRB(MediaQuery.of(context).size.width, 70.0, 0.0, 0.0),
|
|
||||||
context: context,
|
|
||||||
items: popupMenuItems
|
|
||||||
).then((String val) {
|
|
||||||
if (val == "reload") {
|
|
||||||
_quickLoad();
|
|
||||||
} else if (val == "logout") {
|
|
||||||
HomeAssistant().logout().then((_) {
|
|
||||||
_quickLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
|
||||||
leading: IconButton(
|
|
||||||
icon: Icon(Icons.menu),
|
|
||||||
onPressed: () {
|
|
||||||
_scaffoldKey.currentState.openDrawer();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
bottom: (empty || _userError != null) ? null : TabBar(
|
|
||||||
controller: _viewsTabController,
|
|
||||||
tabs: buildUIViewTabs(),
|
|
||||||
isScrollable: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
];
|
|
||||||
},
|
|
||||||
body: bodyWidget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TabController _viewsTabController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Widget bottomBar;
|
|
||||||
if (_showBottomBar) {
|
|
||||||
List<Widget> bottomBarChildren = [];
|
|
||||||
if (_bottomBarText != null) {
|
|
||||||
bottomBarChildren.add(
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0,
|
|
||||||
Sizes.rowPadding),
|
|
||||||
child: Text(
|
|
||||||
"$_bottomBarText",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
softWrap: true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (_bottomBarProgress) {
|
|
||||||
bottomBarChildren.add(
|
|
||||||
CollectionScaleTransition(
|
|
||||||
children: <Widget>[
|
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.on),),
|
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.unavailable),),
|
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.off),),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (bottomBarChildren.isNotEmpty) {
|
|
||||||
bottomBar = Container(
|
|
||||||
color: _bottomBarColor,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: _bottomBarProgress ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: bottomBarChildren,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_bottomBarAction
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This method is rerun every time setState is called.
|
|
||||||
if (HomeAssistant().isNoViews) {
|
|
||||||
return Scaffold(
|
|
||||||
key: _scaffoldKey,
|
|
||||||
primary: false,
|
|
||||||
drawer: _buildAppDrawer(),
|
|
||||||
bottomNavigationBar: bottomBar,
|
|
||||||
body: _buildScaffoldBody(true)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Scaffold(
|
|
||||||
key: _scaffoldKey,
|
|
||||||
drawer: _buildAppDrawer(),
|
|
||||||
primary: false,
|
|
||||||
bottomNavigationBar: bottomBar,
|
|
||||||
body: _buildScaffoldBody(false),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
final flutterWebviewPlugin = new FlutterWebviewPlugin();
|
|
||||||
flutterWebviewPlugin.dispose();
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
_viewsTabController?.dispose();
|
|
||||||
_stateSubscription?.cancel();
|
|
||||||
_settingsSubscription?.cancel();
|
|
||||||
_serviceCallSubscription?.cancel();
|
|
||||||
_showPopupDialogSubscription?.cancel();
|
|
||||||
_showPopupMessageSubscription?.cancel();
|
|
||||||
_showEntityPageSubscription?.cancel();
|
|
||||||
_showErrorSubscription?.cancel();
|
|
||||||
_startAuthSubscription?.cancel();
|
|
||||||
_subscription?.cancel();
|
|
||||||
_showPageSubscription?.cancel();
|
|
||||||
_reloadUISubscription?.cancel();
|
|
||||||
//TODO disconnect
|
|
||||||
//widget.homeAssistant?.disconnect();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -26,7 +26,7 @@ class StartupUserMessagesManager {
|
|||||||
void _showSupportAppDevelopmentMessage() {
|
void _showSupportAppDevelopmentMessage() {
|
||||||
eventBus.fire(ShowPopupDialogEvent(
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
title: "Hi!",
|
title: "Hi!",
|
||||||
body: "As you may have noticed this app contains no ads. Also all app features are available for you for free. Following the principles of free and open source softwere this will not be changed in nearest future. But still you can support this application development materially. There is several options available, please check them in main menu -> Support app development. Thanks.",
|
body: "As you may have noticed this app contains no ads. Also all app features are available for you for free. Following the principles of free and open source softwere this will not be changed in nearest future. But still you can support this application development materially. There is one-time payment available as well as some subscription options, please check them in main menu -> Support app development. Thanks.",
|
||||||
positiveText: "Take me there",
|
positiveText: "Take me there",
|
||||||
negativeText: "Nope",
|
negativeText: "Nope",
|
||||||
onPositive: () {
|
onPositive: () {
|
||||||
|
486
lib/pages/main.page.dart
Normal file
486
lib/pages/main.page.dart
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class MainPage extends StatefulWidget {
|
||||||
|
MainPage({Key key, this.title}) : super(key: key);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MainPageState createState() => new _MainPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainPageState extends State<MainPage> with WidgetsBindingObserver, TickerProviderStateMixin {
|
||||||
|
|
||||||
|
StreamSubscription<List<PurchaseDetails>> _subscription;
|
||||||
|
StreamSubscription _stateSubscription;
|
||||||
|
StreamSubscription _settingsSubscription;
|
||||||
|
StreamSubscription _serviceCallSubscription;
|
||||||
|
StreamSubscription _showEntityPageSubscription;
|
||||||
|
StreamSubscription _showErrorSubscription;
|
||||||
|
StreamSubscription _startAuthSubscription;
|
||||||
|
StreamSubscription _showPopupDialogSubscription;
|
||||||
|
StreamSubscription _showPopupMessageSubscription;
|
||||||
|
StreamSubscription _reloadUISubscription;
|
||||||
|
StreamSubscription _showPageSubscription;
|
||||||
|
int _previousViewCount;
|
||||||
|
//bool _showLoginButton = false;
|
||||||
|
bool _preventAppRefresh = false;
|
||||||
|
UserError _userError;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final Stream purchaseUpdates =
|
||||||
|
InAppPurchaseConnection.instance.purchaseUpdatedStream;
|
||||||
|
_subscription = purchaseUpdates.listen((purchases) {
|
||||||
|
_handlePurchaseUpdates(purchases);
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
|
_firebaseMessaging.configure(
|
||||||
|
onLaunch: (data) {
|
||||||
|
Logger.d("Notification [onLaunch]: $data");
|
||||||
|
return Future.value();
|
||||||
|
},
|
||||||
|
onMessage: (data) {
|
||||||
|
Logger.d("Notification [onMessage]: $data");
|
||||||
|
return _showNotification(title: data["notification"]["title"], text: data["notification"]["body"]);
|
||||||
|
},
|
||||||
|
onResume: (data) {
|
||||||
|
Logger.d("Notification [onResume]: $data");
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_firebaseMessaging.requestNotificationPermissions(const IosNotificationSettings(sound: true, badge: true, alert: true));
|
||||||
|
|
||||||
|
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
|
||||||
|
var initializationSettingsAndroid =
|
||||||
|
new AndroidInitializationSettings('mini_icon');
|
||||||
|
var initializationSettingsIOS = new IOSInitializationSettings(
|
||||||
|
onDidReceiveLocalNotification: null);
|
||||||
|
var initializationSettings = new InitializationSettings(
|
||||||
|
initializationSettingsAndroid, initializationSettingsIOS);
|
||||||
|
flutterLocalNotificationsPlugin.initialize(initializationSettings,
|
||||||
|
onSelectNotification: onSelectNotification);
|
||||||
|
|
||||||
|
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
|
||||||
|
Logger.d("Settings change event: reconnect=${event.reconnect}");
|
||||||
|
if (event.reconnect) {
|
||||||
|
_preventAppRefresh = false;
|
||||||
|
_fullLoad();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_fullLoad();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Future onSelectNotification(String payload) async {
|
||||||
|
if (payload != null) {
|
||||||
|
Logger.d('Notification clicked: ' + payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _showNotification({String title, String text}) async {
|
||||||
|
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
|
||||||
|
'ha_notify', 'Home Assistant notifications', 'Notifications from Home Assistant notify service',
|
||||||
|
importance: Importance.Max, priority: Priority.High);
|
||||||
|
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
|
||||||
|
var platformChannelSpecifics = new NotificationDetails(
|
||||||
|
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
|
||||||
|
await flutterLocalNotificationsPlugin.show(
|
||||||
|
0,
|
||||||
|
title ?? appName,
|
||||||
|
text,
|
||||||
|
platformChannelSpecifics
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fullLoad() async {
|
||||||
|
//TODO show loading somewhere somehow
|
||||||
|
//_showInfoBottomBar(progress: true,);
|
||||||
|
_subscribe().then((_) {
|
||||||
|
ConnectionManager().init(loadSettings: true, forceReconnect: true).then((__){
|
||||||
|
_fetchData();
|
||||||
|
}, onError: (error) {
|
||||||
|
_setErrorState(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _quickLoad() {
|
||||||
|
//_hideBottomBar();
|
||||||
|
//TODO show loading somewhere somehow
|
||||||
|
//_showInfoBottomBar(progress: true,);
|
||||||
|
ConnectionManager().init(loadSettings: false, forceReconnect: false).then((_){
|
||||||
|
_fetchData();
|
||||||
|
}, onError: (error) {
|
||||||
|
_setErrorState(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_fetchData() async {
|
||||||
|
setState(() {
|
||||||
|
_userError = null;
|
||||||
|
});
|
||||||
|
await HomeAssistant().fetchData().then((_) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}).catchError((code) {
|
||||||
|
_setErrorState(code);
|
||||||
|
});
|
||||||
|
eventBus.fire(RefreshDataFinishedEvent());
|
||||||
|
StartupUserMessagesManager().checkMessagesToShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
Logger.d("$state");
|
||||||
|
if (state == AppLifecycleState.resumed && ConnectionManager().settingsLoaded && !_preventAppRefresh) {
|
||||||
|
_quickLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handlePurchaseUpdates(purchase) {
|
||||||
|
if (purchase is List<PurchaseDetails>) {
|
||||||
|
if (purchase[0].status == PurchaseStatus.purchased) {
|
||||||
|
eventBus.fire(ShowPopupMessageEvent(
|
||||||
|
title: "Thanks a lot!",
|
||||||
|
body: "Thank you for supporting HA Client development!",
|
||||||
|
buttonText: "Ok"
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
Logger.d("Purchase change handler: ${purchase[0].status}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.e("Something wrong with purchase handling. Got: $purchase");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _subscribe() {
|
||||||
|
Completer completer = Completer();
|
||||||
|
if (_stateSubscription == null) {
|
||||||
|
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||||
|
if (event.needToRebuildUI) {
|
||||||
|
Logger.d("New entity. Need to rebuild UI");
|
||||||
|
_quickLoad();
|
||||||
|
} else {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (_reloadUISubscription == null) {
|
||||||
|
_reloadUISubscription = eventBus.on<ReloadUIEvent>().listen((event){
|
||||||
|
if (event.full)
|
||||||
|
_fullLoad();
|
||||||
|
else
|
||||||
|
_quickLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (_showPopupDialogSubscription == null) {
|
||||||
|
_showPopupDialogSubscription = eventBus.on<ShowPopupDialogEvent>().listen((event){
|
||||||
|
_showPopupDialog(
|
||||||
|
title: event.title,
|
||||||
|
body: event.body,
|
||||||
|
onPositive: event.onPositive,
|
||||||
|
onNegative: event.onNegative,
|
||||||
|
positiveText: event.positiveText,
|
||||||
|
negativeText: event.negativeText
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (_showPopupMessageSubscription == null) {
|
||||||
|
_showPopupMessageSubscription = eventBus.on<ShowPopupMessageEvent>().listen((event){
|
||||||
|
_showPopupDialog(
|
||||||
|
title: event.title,
|
||||||
|
body: event.body,
|
||||||
|
onPositive: event.onButtonClick,
|
||||||
|
positiveText: event.buttonText,
|
||||||
|
negativeText: null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (_serviceCallSubscription == null) {
|
||||||
|
_serviceCallSubscription =
|
||||||
|
eventBus.on<ServiceCallEvent>().listen((event) {
|
||||||
|
_callService(event.domain, event.service, event.entityId,
|
||||||
|
event.additionalParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_showEntityPageSubscription == null) {
|
||||||
|
_showEntityPageSubscription =
|
||||||
|
eventBus.on<ShowEntityPageEvent>().listen((event) {
|
||||||
|
_showEntityPage(event.entity.entityId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_showPageSubscription == null) {
|
||||||
|
_showPageSubscription =
|
||||||
|
eventBus.on<ShowPageEvent>().listen((event) {
|
||||||
|
_showPage(event.path, event.goBackFirst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_showErrorSubscription == null) {
|
||||||
|
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
|
||||||
|
_setErrorState(event.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_startAuthSubscription == null) {
|
||||||
|
_startAuthSubscription = eventBus.on<StartAuthEvent>().listen((event){
|
||||||
|
if (event.starting) {
|
||||||
|
_showOAuth();
|
||||||
|
} else {
|
||||||
|
_preventAppRefresh = false;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
setState(() {
|
||||||
|
_userError = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_firebaseMessaging.getToken().then((String token) {
|
||||||
|
HomeAssistant().fcmToken = token;
|
||||||
|
completer.complete();
|
||||||
|
});
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showOAuth() {
|
||||||
|
_preventAppRefresh = true;
|
||||||
|
_setErrorState(UserError(code: ErrorCode.NOT_LOGGED_IN));
|
||||||
|
Navigator.of(context).pushNamed('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
_setErrorState(error) {
|
||||||
|
if (error is UserError) {
|
||||||
|
setState(() {
|
||||||
|
//_showBottomBar = false;
|
||||||
|
_userError = error;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
//_showBottomBar = false;
|
||||||
|
_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}) {
|
||||||
|
List<Widget> buttons = [];
|
||||||
|
buttons.add(FlatButton(
|
||||||
|
child: new Text("$positiveText"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
if (onPositive != null) {
|
||||||
|
onPositive();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
if (negativeText != null) {
|
||||||
|
buttons.add(FlatButton(
|
||||||
|
child: new Text("$negativeText"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
if (onNegative != null) {
|
||||||
|
onNegative();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// flutter defined function
|
||||||
|
showDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// return object of type Dialog
|
||||||
|
return AlertDialog(
|
||||||
|
title: new Text("$title"),
|
||||||
|
content: new Text("$body"),
|
||||||
|
actions: buttons,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _callService(String domain, String service, String entityId, Map additionalParams) {
|
||||||
|
//TODO show SnackBar
|
||||||
|
/*_showInfoBottomBar(
|
||||||
|
message: "Calling $domain.$service",
|
||||||
|
duration: Duration(seconds: 3)
|
||||||
|
);*/
|
||||||
|
ConnectionManager().callService(domain: domain, service: service, entityId: entityId, additionalServiceData: additionalParams).catchError((e) => _setErrorState(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEntityPage(String entityId) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => EntityViewPage(entityId: entityId),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showPage(String path, bool goBackFirst) {
|
||||||
|
if (goBackFirst) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*void _hideBottomBar() {
|
||||||
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
|
setState(() {
|
||||||
|
_showBottomBar = false;
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*Widget _bottomBarAction;
|
||||||
|
bool _showBottomBar = false;
|
||||||
|
String _bottomBarText;
|
||||||
|
bool _bottomBarProgress;
|
||||||
|
Color _bottomBarColor;
|
||||||
|
Timer _bottomBarTimer;*/
|
||||||
|
|
||||||
|
/*void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
|
||||||
|
_bottomBarTimer?.cancel();
|
||||||
|
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
||||||
|
_bottomBarColor = Colors.grey.shade50;
|
||||||
|
setState(() {
|
||||||
|
_bottomBarText = message;
|
||||||
|
_bottomBarProgress = progress;
|
||||||
|
_showBottomBar = true;
|
||||||
|
});
|
||||||
|
if (duration != null) {
|
||||||
|
_bottomBarTimer = Timer(duration, () {
|
||||||
|
_hideBottomBar();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
TabController _viewsTabController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget bottomBar;
|
||||||
|
if (_userError != null) {
|
||||||
|
bottomBar = UserErrorScreen(error: _userError,);
|
||||||
|
/*List<Widget> bottomBarChildren = [];
|
||||||
|
if (_bottomBarText != null) {
|
||||||
|
bottomBarChildren.add(
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0,
|
||||||
|
Sizes.rowPadding),
|
||||||
|
child: Text(
|
||||||
|
"$_bottomBarText",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
softWrap: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
/*if (_bottomBarProgress) {
|
||||||
|
bottomBarChildren.add(
|
||||||
|
CollectionScaleTransition(
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.on),),
|
||||||
|
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.unavailable),),
|
||||||
|
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.off),),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
/*if (bottomBarChildren.isNotEmpty) {
|
||||||
|
bottomBar = Container(
|
||||||
|
color: _bottomBarColor,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: _bottomBarProgress ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: bottomBarChildren,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_bottomBarAction
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
// This method is rerun every time setState is called.
|
||||||
|
if (HomeAssistant().isNoViews) {
|
||||||
|
return Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
primary: false,
|
||||||
|
drawer: AppDrawer(),
|
||||||
|
bottomNavigationBar: bottomBar,
|
||||||
|
body: MainPageBody(
|
||||||
|
empty: true,
|
||||||
|
onReload: () => _quickLoad(),
|
||||||
|
tabController: _viewsTabController,
|
||||||
|
onMenu: () => _scaffoldKey.currentState.openDrawer(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
drawer: AppDrawer(),
|
||||||
|
primary: false,
|
||||||
|
bottomNavigationBar: bottomBar,
|
||||||
|
body: MainPageBody(
|
||||||
|
empty: false,
|
||||||
|
onReload: () => _quickLoad(),
|
||||||
|
tabController: _viewsTabController,
|
||||||
|
onMenu: () => _scaffoldKey.currentState.openDrawer(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
final flutterWebviewPlugin = new FlutterWebviewPlugin();
|
||||||
|
flutterWebviewPlugin.dispose();
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
_viewsTabController?.dispose();
|
||||||
|
_stateSubscription?.cancel();
|
||||||
|
_settingsSubscription?.cancel();
|
||||||
|
_serviceCallSubscription?.cancel();
|
||||||
|
_showPopupDialogSubscription?.cancel();
|
||||||
|
_showPopupMessageSubscription?.cancel();
|
||||||
|
_showEntityPageSubscription?.cancel();
|
||||||
|
_showErrorSubscription?.cancel();
|
||||||
|
_startAuthSubscription?.cancel();
|
||||||
|
_subscription?.cancel();
|
||||||
|
_showPageSubscription?.cancel();
|
||||||
|
_reloadUISubscription?.cancel();
|
||||||
|
//TODO disconnect
|
||||||
|
//widget.homeAssistant?.disconnect();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
175
lib/pages/widgets/app_drawer.dart
Normal file
175
lib/pages/widgets/app_drawer.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class AppDrawer extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> menuItems = [];
|
||||||
|
menuItems.add(
|
||||||
|
UserAccountsDrawerHeader(
|
||||||
|
accountName: Text(HomeAssistant().userName),
|
||||||
|
accountEmail: Text(ConnectionManager().displayHostname ?? "Not configured"),
|
||||||
|
onDetailsPressed: () {
|
||||||
|
final flutterWebViewPlugin = new FlutterWebviewPlugin();
|
||||||
|
flutterWebViewPlugin.onStateChanged.listen((viewState) async {
|
||||||
|
if (viewState.type == WebViewState.startLoad) {
|
||||||
|
Logger.d("[WebView] Injecting external auth JS");
|
||||||
|
rootBundle.loadString('assets/js/externalAuth.js').then((js){
|
||||||
|
flutterWebViewPlugin.evalJavascript(js.replaceFirst("[token]", ConnectionManager()._token));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
"/webview",
|
||||||
|
arguments: {
|
||||||
|
"url": "${ConnectionManager().httpWebHost}/profile?external_auth=1",
|
||||||
|
"title": "Profile"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
currentAccountPicture: CircleAvatar(
|
||||||
|
child: Text(
|
||||||
|
HomeAssistant().userAvatarText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 32.0
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (HomeAssistant().panels.isNotEmpty) {
|
||||||
|
HomeAssistant().panels.forEach((Panel panel) {
|
||||||
|
if (!panel.isHidden) {
|
||||||
|
menuItems.add(
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName(panel.icon)),
|
||||||
|
title: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text("${panel.title}"),
|
||||||
|
Container(width: 4.0,),
|
||||||
|
panel.isWebView ? Text("webview", style: TextStyle(fontSize: 8.0, color: Colors.black45),) : Container(width: 1.0,)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
panel.handleOpen(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
menuItems.addAll([
|
||||||
|
Divider(),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:login-variant")),
|
||||||
|
title: Text("Connection settings"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pushNamed('/connection-settings');
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
menuItems.addAll([
|
||||||
|
Divider(),
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(Icons.insert_drive_file),
|
||||||
|
title: Text("Log"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pushNamed('/log-view');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:github-circle")),
|
||||||
|
title: Text("Report an issue"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Launcher.launchURL("https://github.com/estevez-dev/ha_client/issues/new");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:food")),
|
||||||
|
title: Text("Support app development"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pushNamed('/putchase');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(Icons.help),
|
||||||
|
title: Text("Help"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Launcher.launchURL("http://ha-client.homemade.systems/docs");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:discord")),
|
||||||
|
title: Text("Join Discord channel"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Launcher.launchURL("https://discord.gg/AUzEvwn");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new AboutListTile(
|
||||||
|
aboutBoxChildren: <Widget>[
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Launcher.launchURL("http://ha-client.homemade.systems/");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"ha-client.homemade.systems",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 10.0,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.homemade.systems/terms_and_conditions");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Terms and Conditions",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 10.0,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.homemade.systems/privacy_policy");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Privacy Policy",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
applicationName: appName,
|
||||||
|
applicationVersion: appVersion
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
return new Drawer(
|
||||||
|
child: ListView(
|
||||||
|
children: menuItems,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
111
lib/pages/widgets/main_page_body.dart
Normal file
111
lib/pages/widgets/main_page_body.dart
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class MainPageBody extends StatelessWidget {
|
||||||
|
|
||||||
|
final bool empty;
|
||||||
|
final onReload;
|
||||||
|
final onMenu;
|
||||||
|
final TabController tabController;
|
||||||
|
|
||||||
|
const MainPageBody({Key key, this.empty, this.onReload, this.tabController, this.onMenu}) : super(key: key);
|
||||||
|
|
||||||
|
List<Tab> buildUIViewTabs() {
|
||||||
|
List<Tab> result = [];
|
||||||
|
|
||||||
|
if (HomeAssistant().ui.views.isNotEmpty) {
|
||||||
|
HomeAssistant().ui.views.forEach((HAView view) {
|
||||||
|
//TODO Create a widget for that and pass view to it. An opposit way as it is implemented now
|
||||||
|
result.add(view.buildTab());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<PopupMenuItem<String>> popupMenuItems = [];
|
||||||
|
|
||||||
|
popupMenuItems.add(PopupMenuItem<String>(
|
||||||
|
child: new Text("Reload"),
|
||||||
|
value: "reload",
|
||||||
|
));
|
||||||
|
/*List<Widget> emptyBody = [
|
||||||
|
Text("."),
|
||||||
|
];*/
|
||||||
|
if (ConnectionManager().isAuthenticated) {
|
||||||
|
//_showLoginButton = false;
|
||||||
|
popupMenuItems.add(
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
child: new Text("Logout"),
|
||||||
|
value: "logout",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Widget bodyWidget;
|
||||||
|
if (empty) {
|
||||||
|
bodyWidget = Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
CircularProgressIndicator()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
bodyWidget = HomeAssistant().buildViews(context, tabController);
|
||||||
|
}
|
||||||
|
/*if (_showLoginButton) {
|
||||||
|
emptyBody = [
|
||||||
|
FlatButton(
|
||||||
|
child: Text("Login with Home Assistant", style: TextStyle(fontSize: 16.0, color: Colors.white)),
|
||||||
|
color: Colors.blue,
|
||||||
|
onPressed: () => _fullLoad(),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}*/
|
||||||
|
return NestedScrollView(
|
||||||
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
|
return <Widget>[
|
||||||
|
SliverAppBar(
|
||||||
|
floating: true,
|
||||||
|
pinned: true,
|
||||||
|
primary: true,
|
||||||
|
title: Text(HomeAssistant().locationName ?? ""),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
|
"mdi:dots-vertical"), color: Colors.white,),
|
||||||
|
onPressed: () {
|
||||||
|
showMenu(
|
||||||
|
position: RelativeRect.fromLTRB(MediaQuery.of(context).size.width, 70.0, 0.0, 0.0),
|
||||||
|
context: context,
|
||||||
|
items: popupMenuItems
|
||||||
|
).then((String val) {
|
||||||
|
if (val == "reload") {
|
||||||
|
onReload();
|
||||||
|
} else if (val == "logout") {
|
||||||
|
HomeAssistant().logout().then((_) {
|
||||||
|
onReload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(Icons.menu),
|
||||||
|
onPressed: () => onMenu(),
|
||||||
|
),
|
||||||
|
bottom: empty ? null : TabBar(
|
||||||
|
controller: tabController,
|
||||||
|
tabs: buildUIViewTabs(),
|
||||||
|
isScrollable: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
},
|
||||||
|
body: bodyWidget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of 'main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class UserErrorScreen extends StatelessWidget {
|
class UserErrorScreen extends StatelessWidget {
|
||||||
|
|
@ -1,173 +0,0 @@
|
|||||||
part of 'main.dart';
|
|
||||||
|
|
||||||
class Logger {
|
|
||||||
|
|
||||||
static List<String> _log = [];
|
|
||||||
|
|
||||||
static String getLog() {
|
|
||||||
String res = '';
|
|
||||||
_log.forEach((line) {
|
|
||||||
res += "$line\n";
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool get isInDebugMode {
|
|
||||||
bool inDebugMode = false;
|
|
||||||
|
|
||||||
assert(inDebugMode = true);
|
|
||||||
|
|
||||||
return inDebugMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void e(String message) {
|
|
||||||
_writeToLog("Error", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void w(String message) {
|
|
||||||
_writeToLog("Warning", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void d(String message) {
|
|
||||||
_writeToLog("Debug", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _writeToLog(String level, String message) {
|
|
||||||
if (isInDebugMode) {
|
|
||||||
debugPrint('$message');
|
|
||||||
}
|
|
||||||
DateTime t = DateTime.now();
|
|
||||||
_log.add("${formatDate(t, ["mm","dd"," ","HH",":","nn",":","ss"])} [$level] : $message");
|
|
||||||
if (_log.length > 100) {
|
|
||||||
_log.removeAt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*class HAError {
|
|
||||||
String message;
|
|
||||||
final List<HAErrorAction> actions;
|
|
||||||
|
|
||||||
HAError(this.message, {this.actions: const [HAErrorAction.tryAgain()]});
|
|
||||||
|
|
||||||
HAError.unableToConnect({this.actions = const [HAErrorAction.tryAgain()]}) {
|
|
||||||
this.message = "Unable to connect to Home Assistant";
|
|
||||||
}
|
|
||||||
|
|
||||||
HAError.disconnected({this.actions = const [HAErrorAction.reconnect()]}) {
|
|
||||||
this.message = "Disconnected";
|
|
||||||
}
|
|
||||||
|
|
||||||
HAError.checkConnectionSettings({this.actions = const [HAErrorAction.reload(), HAErrorAction(title: "Settings", type: HAErrorActionType.OPEN_CONNECTION_SETTINGS)]}) {
|
|
||||||
this.message = "Check connection settings";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HAErrorAction {
|
|
||||||
final String title;
|
|
||||||
final int type;
|
|
||||||
final String url;
|
|
||||||
|
|
||||||
const HAErrorAction({@required this.title, this.type: HAErrorActionType.FULL_RELOAD, this.url});
|
|
||||||
|
|
||||||
const HAErrorAction.tryAgain({this.title = "Try again", this.type = HAErrorActionType.FULL_RELOAD, this.url});
|
|
||||||
|
|
||||||
const HAErrorAction.reconnect({this.title = "Reconnect", this.type = HAErrorActionType.FULL_RELOAD, this.url});
|
|
||||||
|
|
||||||
const HAErrorAction.reload({this.title = "Reload", this.type = HAErrorActionType.FULL_RELOAD, this.url});
|
|
||||||
|
|
||||||
const HAErrorAction.loginAgain({this.title = "Login again", this.type = HAErrorActionType.FULL_RELOAD, this.url});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class HAErrorActionType {
|
|
||||||
static const FULL_RELOAD = 0;
|
|
||||||
static const QUICK_RELOAD = 1;
|
|
||||||
static const LOGOUT = 2;
|
|
||||||
static const URL = 3;
|
|
||||||
static const OPEN_CONNECTION_SETTINGS = 4;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
class StateChangedEvent {
|
|
||||||
String entityId;
|
|
||||||
String newState;
|
|
||||||
bool needToRebuildUI;
|
|
||||||
|
|
||||||
StateChangedEvent({
|
|
||||||
this.entityId,
|
|
||||||
this.newState,
|
|
||||||
this.needToRebuildUI: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsChangedEvent {
|
|
||||||
bool reconnect;
|
|
||||||
|
|
||||||
SettingsChangedEvent(this.reconnect);
|
|
||||||
}
|
|
||||||
|
|
||||||
class RefreshDataFinishedEvent {
|
|
||||||
RefreshDataFinishedEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReloadUIEvent {
|
|
||||||
final bool full;
|
|
||||||
|
|
||||||
ReloadUIEvent(this.full);
|
|
||||||
}
|
|
||||||
|
|
||||||
class StartAuthEvent {
|
|
||||||
String oauthUrl;
|
|
||||||
bool starting;
|
|
||||||
|
|
||||||
StartAuthEvent(this.oauthUrl, this.starting);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServiceCallEvent {
|
|
||||||
String domain;
|
|
||||||
String service;
|
|
||||||
String entityId;
|
|
||||||
Map<String, dynamic> additionalParams;
|
|
||||||
|
|
||||||
ServiceCallEvent(this.domain, this.service, this.entityId, this.additionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShowPopupDialogEvent {
|
|
||||||
final String title;
|
|
||||||
final String body;
|
|
||||||
final String positiveText;
|
|
||||||
final String negativeText;
|
|
||||||
final onPositive;
|
|
||||||
final onNegative;
|
|
||||||
|
|
||||||
ShowPopupDialogEvent({this.title, this.body, this.positiveText: "Ok", this.negativeText: "Cancel", this.onPositive, this.onNegative});
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShowPopupMessageEvent {
|
|
||||||
final String title;
|
|
||||||
final String body;
|
|
||||||
final String buttonText;
|
|
||||||
final onButtonClick;
|
|
||||||
|
|
||||||
ShowPopupMessageEvent({this.title, this.body, this.buttonText: "Ok", this.onButtonClick});
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShowEntityPageEvent {
|
|
||||||
Entity entity;
|
|
||||||
|
|
||||||
ShowEntityPageEvent(this.entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShowPageEvent {
|
|
||||||
final String path;
|
|
||||||
final bool goBackFirst;
|
|
||||||
|
|
||||||
ShowPageEvent({@required this.path, this.goBackFirst: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShowErrorEvent {
|
|
||||||
final UserError error;
|
|
||||||
|
|
||||||
ShowErrorEvent(this.error);
|
|
||||||
}
|
|
84
lib/utils/event_bus_events.dart
Normal file
84
lib/utils/event_bus_events.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class StateChangedEvent {
|
||||||
|
String entityId;
|
||||||
|
String newState;
|
||||||
|
bool needToRebuildUI;
|
||||||
|
|
||||||
|
StateChangedEvent({
|
||||||
|
this.entityId,
|
||||||
|
this.newState,
|
||||||
|
this.needToRebuildUI: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsChangedEvent {
|
||||||
|
bool reconnect;
|
||||||
|
|
||||||
|
SettingsChangedEvent(this.reconnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RefreshDataFinishedEvent {
|
||||||
|
RefreshDataFinishedEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReloadUIEvent {
|
||||||
|
final bool full;
|
||||||
|
|
||||||
|
ReloadUIEvent(this.full);
|
||||||
|
}
|
||||||
|
|
||||||
|
class StartAuthEvent {
|
||||||
|
String oauthUrl;
|
||||||
|
bool starting;
|
||||||
|
|
||||||
|
StartAuthEvent(this.oauthUrl, this.starting);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceCallEvent {
|
||||||
|
String domain;
|
||||||
|
String service;
|
||||||
|
String entityId;
|
||||||
|
Map<String, dynamic> additionalParams;
|
||||||
|
|
||||||
|
ServiceCallEvent(this.domain, this.service, this.entityId, this.additionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowPopupDialogEvent {
|
||||||
|
final String title;
|
||||||
|
final String body;
|
||||||
|
final String positiveText;
|
||||||
|
final String negativeText;
|
||||||
|
final onPositive;
|
||||||
|
final onNegative;
|
||||||
|
|
||||||
|
ShowPopupDialogEvent({this.title, this.body, this.positiveText: "Ok", this.negativeText: "Cancel", this.onPositive, this.onNegative});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowPopupMessageEvent {
|
||||||
|
final String title;
|
||||||
|
final String body;
|
||||||
|
final String buttonText;
|
||||||
|
final onButtonClick;
|
||||||
|
|
||||||
|
ShowPopupMessageEvent({this.title, this.body, this.buttonText: "Ok", this.onButtonClick});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowEntityPageEvent {
|
||||||
|
Entity entity;
|
||||||
|
|
||||||
|
ShowEntityPageEvent(this.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowPageEvent {
|
||||||
|
final String path;
|
||||||
|
final bool goBackFirst;
|
||||||
|
|
||||||
|
ShowPageEvent({@required this.path, this.goBackFirst: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowErrorEvent {
|
||||||
|
final UserError error;
|
||||||
|
|
||||||
|
ShowErrorEvent(this.error);
|
||||||
|
}
|
46
lib/utils/logger.dart
Normal file
46
lib/utils/logger.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
|
||||||
|
static List<String> _log = [];
|
||||||
|
|
||||||
|
static String getLog() {
|
||||||
|
String res = '';
|
||||||
|
_log.forEach((line) {
|
||||||
|
res += "$line\n";
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get isInDebugMode {
|
||||||
|
bool inDebugMode = false;
|
||||||
|
|
||||||
|
assert(inDebugMode = true);
|
||||||
|
|
||||||
|
return inDebugMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void e(String message) {
|
||||||
|
_writeToLog("Error", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void w(String message) {
|
||||||
|
_writeToLog("Warning", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void d(String message) {
|
||||||
|
_writeToLog("Debug", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _writeToLog(String level, String message) {
|
||||||
|
if (isInDebugMode) {
|
||||||
|
debugPrint('$message');
|
||||||
|
}
|
||||||
|
DateTime t = DateTime.now();
|
||||||
|
_log.add("${formatDate(t, ["mm","dd"," ","HH",":","nn",":","ss"])} [$level] : $message");
|
||||||
|
if (_log.length > 100) {
|
||||||
|
_log.removeAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user