Settings page and theme selection

This commit is contained in:
Yegor Vialov 2020-04-11 16:09:35 +00:00
parent 68d14bd13d
commit 5f23e108a1
9 changed files with 532 additions and 328 deletions

View File

@ -102,14 +102,16 @@ part 'entities/alarm_control_panel/widgets/alarm_control_panel_controls.widget.d
part 'entities/vacuum/vacuum_entity.class.dart'; part 'entities/vacuum/vacuum_entity.class.dart';
part 'entities/vacuum/widgets/vacuum_controls.dart'; part 'entities/vacuum/widgets/vacuum_controls.dart';
part 'entities/vacuum/widgets/vacuum_state_button.dart'; part 'entities/vacuum/widgets/vacuum_state_button.dart';
part 'pages/settings.page.dart'; part 'pages/settings/connection_settings.part.dart';
part 'pages/purchase.page.dart'; part 'pages/purchase.page.dart';
part 'pages/widgets/product_purchase.widget.dart'; part 'pages/widgets/product_purchase.widget.dart';
part 'pages/widgets/page_loading_indicator.dart'; 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 'pages/main/main.page.dart'; part 'pages/main/main.page.dart';
part 'pages/integration_settings.page.dart'; part 'pages/settings/integration_settings.part.dart';
part 'pages/settings/app_settings.page.dart';
part 'pages/settings/lookandfeel_settings.part.dart';
part 'pages/zha_page.dart'; part 'pages/zha_page.dart';
part 'home_assistant.class.dart'; part 'home_assistant.class.dart';
part 'pages/log.page.dart'; part 'pages/log.page.dart';
@ -173,8 +175,14 @@ void main() async {
Crashlytics.instance.recordFlutterError(details); Crashlytics.instance.recordFlutterError(details);
}; };
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
AppTheme theme = AppTheme.values[prefs.getInt('app-theme') ?? AppTheme.defaultTheme];
runZoned(() { runZoned(() {
runApp(new HAClientApp()); runApp(new HAClientApp(
theme: theme,
));
}, onError: (error, stack) { }, onError: (error, stack) {
_reportError(error, stack); _reportError(error, stack);
}); });
@ -182,22 +190,34 @@ void main() async {
class HAClientApp extends StatefulWidget { class HAClientApp extends StatefulWidget {
final AppTheme theme;
const HAClientApp({Key key, this.theme: AppTheme.defaultTheme}) : super(key: key);
@override @override
_HAClientAppState createState() => new _HAClientAppState(); _HAClientAppState createState() => new _HAClientAppState();
} }
class _HAClientAppState extends State<HAClientApp> { class _HAClientAppState extends State<HAClientApp> {
StreamSubscription<List<PurchaseDetails>> _subscription; StreamSubscription<List<PurchaseDetails>> _purchaseUpdateSubscription;
StreamSubscription _themeChangeSubscription;
AppTheme _currentTheme = AppTheme.defaultTheme;
@override @override
void initState() { void initState() {
InAppPurchaseConnection.enablePendingPurchases(); InAppPurchaseConnection.enablePendingPurchases();
final Stream purchaseUpdates = final Stream purchaseUpdates =
InAppPurchaseConnection.instance.purchaseUpdatedStream; InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdates.listen((purchases) { _purchaseUpdateSubscription = purchaseUpdates.listen((purchases) {
_handlePurchaseUpdates(purchases); _handlePurchaseUpdates(purchases);
}); });
_currentTheme = widget.theme;
_themeChangeSubscription = eventBus.on<ChangeThemeEvent>().listen((event){
setState(() {
_currentTheme = event.theme;
});
});
workManager.Workmanager.initialize( workManager.Workmanager.initialize(
updateDeviceLocationIsolate, updateDeviceLocationIsolate,
isInDebugMode: false isInDebugMode: false
@ -226,14 +246,15 @@ class _HAClientAppState extends State<HAClientApp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new MaterialApp( return new MaterialApp(
title: appName, title: appName,
theme: HAClientTheme().lightTheme, theme: HAClientTheme().getThemeData(_currentTheme),
darkTheme: HAClientTheme().darkTheme, darkTheme: HAClientTheme().darkTheme,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
initialRoute: "/", initialRoute: "/",
routes: { routes: {
"/": (context) => MainPage(title: 'HA Client'), "/": (context) => MainPage(title: 'HA Client'),
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"), "/app-settings": (context) => AppSettingsPage(),
"/integration-settings": (context) => IntegrationSettingsPage(title: "Integration settings"), "/connection-settings": (context) => AppSettingsPage(showSection: AppSettingsSection.connectionSettings),
"/integration-settings": (context) => AppSettingsPage(showSection: AppSettingsSection.integrationSettings),
"/putchase": (context) => PurchasePage(title: "Support app development"), "/putchase": (context) => PurchasePage(title: "Support app development"),
"/play-media": (context) => PlayMediaPage( "/play-media": (context) => PlayMediaPage(
mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}", mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}",
@ -278,7 +299,8 @@ class _HAClientAppState extends State<HAClientApp> {
@override @override
void dispose() { void dispose() {
_subscription.cancel(); _purchaseUpdateSubscription.cancel();
_themeChangeSubscription.cancel();
super.dispose(); super.dispose();
} }
} }

View File

@ -1,10 +1,8 @@
part of '../main.dart'; part of '../main.dart';
class HAClientTheme { enum AppTheme {darkTheme, defaultTheme, haTheme}
static const DEFAULT = 0; class HAClientTheme {
static const DARK = 1;
static const HOMEASSISTANT = 2;
static const TextTheme textTheme = TextTheme( static const TextTheme textTheme = TextTheme(
display1: TextStyle(fontSize: 34, fontWeight: FontWeight.normal), display1: TextStyle(fontSize: 34, fontWeight: FontWeight.normal),
@ -80,6 +78,22 @@ class HAClientTheme {
HAClientTheme._internal(); HAClientTheme._internal();
ThemeData getThemeData(AppTheme theme) {
switch (theme) {
case AppTheme.darkTheme:
return darkTheme;
break;
case AppTheme.defaultTheme:
return lightTheme;
break;
case AppTheme.haTheme:
return lightTheme;
break;
default:
return lightTheme;
}
}
final ThemeData lightTheme = ThemeData.from( final ThemeData lightTheme = ThemeData.from(
colorScheme: ColorScheme( colorScheme: ColorScheme(
primary: Color.fromRGBO(112, 154, 193, 1), primary: Color.fromRGBO(112, 154, 193, 1),

View File

@ -1,202 +0,0 @@
part of '../main.dart';
class IntegrationSettingsPage extends StatefulWidget {
IntegrationSettingsPage({Key key, this.title}) : super(key: key);
final String title;
@override
_IntegrationSettingsPageState createState() => new _IntegrationSettingsPageState();
}
class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
int _locationInterval = LocationManager().defaultUpdateIntervalMinutes;
bool _locationTrackingEnabled = false;
bool _wait = false;
@override
void initState() {
super.initState();
_loadSettings();
}
_loadSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
SharedPreferences.getInstance().then((prefs) {
setState(() {
_locationTrackingEnabled = prefs.getBool("location-enabled") ?? false;
_locationInterval = prefs.getInt("location-interval") ?? LocationManager().defaultUpdateIntervalMinutes;
if (_locationInterval % 5 != 0) {
_locationInterval = 5 * (_locationInterval ~/ 5);
}
});
});
}
void incLocationInterval() {
if (_locationInterval < 720) {
setState(() {
_locationInterval = _locationInterval + 5;
});
}
}
void decLocationInterval() {
if (_locationInterval > 5) {
setState(() {
_locationInterval = _locationInterval - 5;
});
}
}
restart() {
eventBus.fire(ShowPopupDialogEvent(
title: "Are you sure you want to restart Home Assistant?",
body: "This will restart your Home Assistant server.",
positiveText: "Sure. Make it so",
negativeText: "What?? No!",
onPositive: () {
ConnectionManager().callService(domain: "homeassistant", service: "restart");
},
));
}
stop() {
eventBus.fire(ShowPopupDialogEvent(
title: "Are you sure you want to STOP Home Assistant?",
body: "This will STOP your Home Assistant server. It means that your web interface as well as HA Client will not work untill you'll find a way to start your server using ssh or something.",
positiveText: "Sure. Make it so",
negativeText: "What?? No!",
onPositive: () {
ConnectionManager().callService(domain: "homeassistant", service: "stop");
},
));
}
updateRegistration() {
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true);
}
resetRegistration() {
eventBus.fire(ShowPopupDialogEvent(
title: "Waaaait",
body: "If you don't whant to have duplicate integrations and entities in your HA for your current device, first you need to remove MobileApp integration from Integration settings in HA and restart server.",
positiveText: "Done it already",
negativeText: "Ok, I will",
onPositive: () {
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true, forceRegister: true);
},
));
}
_switchLocationTrackingState(bool state) async {
if (state) {
await LocationManager().updateDeviceLocation();
}
await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
setState(() {
_wait = false;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
Navigator.pop(context);
}),
title: new Text(widget.title),
),
body: ListView(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
Text("Location tracking", style: Theme.of(context).textTheme.title),
Container(height: Sizes.rowPadding,),
InkWell(
onTap: () => Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.app/docs#location-tracking"),
child: Text(
"Please read documentation!",
style: Theme.of(context).textTheme.subhead.copyWith(
color: Colors.blue,
decoration: TextDecoration.underline
)
),
),
Container(height: Sizes.rowPadding,),
Row(
children: <Widget>[
Text("Enable device location tracking"),
Switch(
value: _locationTrackingEnabled,
onChanged: _wait ? null : (value) {
setState(() {
_locationTrackingEnabled = value;
_wait = true;
});
_switchLocationTrackingState(value);
},
),
],
),
Container(height: Sizes.rowPadding,),
Text("Location update interval in minutes:"),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
//Expanded(child: Container(),),
FlatButton(
padding: EdgeInsets.all(0.0),
child: Text("-", style: Theme.of(context).textTheme.title),
onPressed: () => decLocationInterval(),
),
Text("$_locationInterval", style: Theme.of(context).textTheme.title),
FlatButton(
padding: EdgeInsets.all(0.0),
child: Text("+", style: Theme.of(context).textTheme.title),
onPressed: () => incLocationInterval(),
),
],
),
Divider(),
Text("Integration status", style: Theme.of(context).textTheme.title),
Container(height: Sizes.rowPadding,),
Text(
"${HomeAssistant().userName}'s ${DeviceInfoManager().model}, ${DeviceInfoManager().osName} ${DeviceInfoManager().osVersion}",
style: Theme.of(context).textTheme.subtitle,
),
Container(height: 6.0,),
Text("Here you can manually check if HA Client integration with your Home Assistant works fine. As mobileApp integration in Home Assistant is still in development, this is not 100% correct check."),
//Divider(),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
RaisedButton(
color: Colors.blue,
onPressed: () => updateRegistration(),
child: Text("Check integration", style: Theme.of(context).textTheme.button)
),
Container(width: 10.0,),
RaisedButton(
color: Colors.redAccent,
onPressed: () => resetRegistration(),
child: Text("Reset integration", style: Theme.of(context).textTheme.button)
)
],
),
]
),
);
}
@override
void dispose() {
LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
super.dispose();
}
}

View File

@ -366,20 +366,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
} }
menuItems.addAll([ menuItems.addAll([
Divider(), Divider(),
ListTile(
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:server-network")),
title: Text("Connection settings"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/connection-settings');
},
),
ListTile( ListTile(
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:cellphone-settings-variant")), leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:cellphone-settings-variant")),
title: Text("Integration settings"), title: Text("App settings"),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pushNamed('/integration-settings'); Navigator.of(context).pushNamed('/app-settings');
}, },
) )
]); ]);

View File

@ -0,0 +1,104 @@
part of '../../main.dart';
enum AppSettingsSection {menu, connectionSettings, integrationSettings, lookAndFeel}
class AppSettingsPage extends StatefulWidget {
final AppSettingsSection showSection;
AppSettingsPage({Key key, this.showSection: AppSettingsSection.menu}) : super(key: key);
@override
_AppSettingsPageState createState() => new _AppSettingsPageState();
}
class _AppSettingsPageState extends State<AppSettingsPage> {
var _currentSection;
@override
void initState() {
super.initState();
_currentSection = widget.showSection;
}
Widget _buildMenuItem(BuildContext context, IconData icon,String title, AppSettingsSection section) {
return ListTile(
title: Text(title, style: Theme.of(context).textTheme.subhead),
leading: Icon(icon),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
setState(() {
_currentSection = section;
});
},
);
}
Widget _buildMenu(BuildContext context) {
return ListView(
children: <Widget>[
_buildMenuItem(context, MaterialDesignIcons.getIconDataFromIconName('mdi:network'), 'Connection settings', AppSettingsSection.connectionSettings),
_buildMenuItem(context, MaterialDesignIcons.getIconDataFromIconName('mdi:cellphone-android'), 'Integration settings', AppSettingsSection.integrationSettings),
_buildMenuItem(context, MaterialDesignIcons.getIconDataFromIconName('mdi:brush'), 'Look and feel', AppSettingsSection.lookAndFeel),
],
);
}
@override
Widget build(BuildContext context) {
Widget section;
String title;
switch (_currentSection) {
case AppSettingsSection.menu: {
section = _buildMenu(context);
title = 'App settings';
break;
}
case AppSettingsSection.connectionSettings: {
section = ConnectionSettingsPage();
title = 'App settings - Connection';
break;
}
case AppSettingsSection.integrationSettings: {
section = IntegrationSettingsPage();
title = 'App settings - Integration';
break;
}
case AppSettingsSection.lookAndFeel: {
section = LookAndFeelSettingsPage();
title = 'App settings - Look&Feel';
break;
}
default:
title = ':(';
section = PageLoadingIndicator();
}
return WillPopScope(
child: Scaffold(
appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
if (_currentSection == AppSettingsSection.menu) {
Navigator.pop(context);
} else {
setState(() {
_currentSection = AppSettingsSection.menu;
});
}
}),
title: Text(title),
),
body: section
),
onWillPop: () {
if (_currentSection == AppSettingsSection.menu) {
return Future.value(true);
} else {
setState(() {
_currentSection = AppSettingsSection.menu;
});
return Future.value(false);
}
},
);
}
}

View File

@ -1,4 +1,4 @@
part of '../main.dart'; part of '../../main.dart';
class ConnectionSettingsPage extends StatefulWidget { class ConnectionSettingsPage extends StatefulWidget {
ConnectionSettingsPage({Key key, this.title}) : super(key: key); ConnectionSettingsPage({Key key, this.title}) : super(key: key);
@ -108,115 +108,109 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return ListView(
appBar: new AppBar( scrollDirection: Axis.vertical,
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){ padding: const EdgeInsets.all(20.0),
Navigator.pop(context); children: <Widget>[
}), Text(
title: new Text(widget.title), "Connection settings",
actions: <Widget>[ style: Theme.of(context).textTheme.headline,
IconButton( ),
icon: Icon(Icons.check), new Row(
onPressed: (){ children: [
if (_checkConfigChanged()) { Text("Use ssl (HTTPS)"),
Logger.d("Settings changed. Saving..."); Switch(
_saveSettings().then((r) { value: (_newSocketProtocol == "wss"),
Navigator.pop(context); onChanged: (value) {
eventBus.fire(SettingsChangedEvent(true)); setState(() {
_newSocketProtocol = value ? "wss" : "ws";
}); });
} else { },
Logger.d("Settings was not changed"); )
Navigator.pop(context); ],
} ),
} new TextField(
) decoration: InputDecoration(
], labelText: "Home Assistant domain or ip address"
),
body: ListView(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
Text(
"Connection settings",
style: Theme.of(context).textTheme.headline,
), ),
new Row( controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioDomain)),
children: [ onChanged: (value) {
Text("Use ssl (HTTPS)"), _newHassioDomain = value;
Switch( }
value: (_newSocketProtocol == "wss"), ),
onChanged: (value) { new TextField(
setState(() { decoration: InputDecoration(
_newSocketProtocol = value ? "wss" : "ws"; labelText: "Home Assistant port (default is 8123)"
});
},
)
],
), ),
new TextField( controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioPort)),
decoration: InputDecoration( onChanged: (value) {
labelText: "Home Assistant domain or ip address" _newHassioPort = value;
), }
controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioDomain)), ),
onChanged: (value) { new Text(
_newHassioDomain = value; "Try ports 80 and 443 if default is not working and you don't know why.",
} style: Theme.of(context).textTheme.caption,
), ),
new TextField( Padding(
decoration: InputDecoration( padding: EdgeInsets.only(top: 20.0),
labelText: "Home Assistant port (default is 8123)" child: Text(
), "UI",
controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioPort)),
onChanged: (value) {
_newHassioPort = value;
}
),
new Text(
"Try ports 80 and 443 if default is not working and you don't know why.",
style: Theme.of(context).textTheme.caption,
),
Padding(
padding: EdgeInsets.only(top: 20.0),
child: Text(
"UI",
style: Theme.of(context).textTheme.headline,
),
),
new Row(
children: [
Text("Use Lovelace UI"),
Switch(
value: _newUseLovelace,
onChanged: (value) {
setState(() {
_newUseLovelace = value;
});
},
)
],
),
Text(
"Authentication settings",
style: Theme.of(context).textTheme.headline, style: Theme.of(context).textTheme.headline,
), ),
Container(height: 10.0,), ),
Text( new Row(
"You can leave this field blank to make app generate new long-lived token automatically by asking you to login to your Home Assistant. Use this field only if you still want to use manually generated long-lived token. Leave it blank if you don't understand what we are talking about.", children: [
style: Theme.of(context).textTheme.body1.copyWith( Text("Use Lovelace UI"),
color: Colors.redAccent Switch(
), value: _newUseLovelace,
),
new TextField(
decoration: InputDecoration(
labelText: "Long-lived token"
),
controller: TextEditingController.fromValue(TextEditingValue(text: _newLongLivedToken)),
onChanged: (value) { onChanged: (value) {
_newLongLivedToken = value; setState(() {
} _newUseLovelace = value;
});
},
)
],
),
Text(
"Authentication settings",
style: Theme.of(context).textTheme.headline,
),
Container(height: 10.0,),
Text(
"You can leave this field blank to make app generate new long-lived token automatically by asking you to login to your Home Assistant. Use this field only if you still want to use manually generated long-lived token. Leave it blank if you don't understand what we are talking about.",
style: Theme.of(context).textTheme.body1.copyWith(
color: Colors.redAccent
), ),
], ),
), new TextField(
decoration: InputDecoration(
labelText: "Long-lived token"
),
controller: TextEditingController.fromValue(TextEditingValue(text: _newLongLivedToken)),
onChanged: (value) {
_newLongLivedToken = value;
}
),
Container(
height: Sizes.rowPadding,
),
RaisedButton(
child: Text('Apply', style: Theme.of(context).textTheme.button),
color: Theme.of(context).primaryColorDark,
onPressed: () {
if (_checkConfigChanged()) {
Logger.d("Settings changed. Saving...");
_saveSettings().then((r) {
Navigator.pop(context);
eventBus.fire(SettingsChangedEvent(true));
});
} else {
Logger.d("Settings was not changed");
Navigator.pop(context);
}
},
)
],
); );
} }

View File

@ -0,0 +1,194 @@
part of '../../main.dart';
class IntegrationSettingsPage extends StatefulWidget {
IntegrationSettingsPage({Key key, this.title}) : super(key: key);
final String title;
@override
_IntegrationSettingsPageState createState() => new _IntegrationSettingsPageState();
}
class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
int _locationInterval = LocationManager().defaultUpdateIntervalMinutes;
bool _locationTrackingEnabled = false;
bool _wait = false;
@override
void initState() {
super.initState();
_loadSettings();
}
_loadSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
SharedPreferences.getInstance().then((prefs) {
setState(() {
_locationTrackingEnabled = prefs.getBool("location-enabled") ?? false;
_locationInterval = prefs.getInt("location-interval") ?? LocationManager().defaultUpdateIntervalMinutes;
if (_locationInterval % 5 != 0) {
_locationInterval = 5 * (_locationInterval ~/ 5);
}
});
});
}
void incLocationInterval() {
if (_locationInterval < 720) {
setState(() {
_locationInterval = _locationInterval + 5;
});
}
}
void decLocationInterval() {
if (_locationInterval > 5) {
setState(() {
_locationInterval = _locationInterval - 5;
});
}
}
restart() {
eventBus.fire(ShowPopupDialogEvent(
title: "Are you sure you want to restart Home Assistant?",
body: "This will restart your Home Assistant server.",
positiveText: "Sure. Make it so",
negativeText: "What?? No!",
onPositive: () {
ConnectionManager().callService(domain: "homeassistant", service: "restart");
},
));
}
stop() {
eventBus.fire(ShowPopupDialogEvent(
title: "Are you sure you want to STOP Home Assistant?",
body: "This will STOP your Home Assistant server. It means that your web interface as well as HA Client will not work untill you'll find a way to start your server using ssh or something.",
positiveText: "Sure. Make it so",
negativeText: "What?? No!",
onPositive: () {
ConnectionManager().callService(domain: "homeassistant", service: "stop");
},
));
}
updateRegistration() {
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true);
}
resetRegistration() {
eventBus.fire(ShowPopupDialogEvent(
title: "Waaaait",
body: "If you don't whant to have duplicate integrations and entities in your HA for your current device, first you need to remove MobileApp integration from Integration settings in HA and restart server.",
positiveText: "Done it already",
negativeText: "Ok, I will",
onPositive: () {
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true, forceRegister: true);
},
));
}
_switchLocationTrackingState(bool state) async {
if (state) {
await LocationManager().updateDeviceLocation();
}
await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
setState(() {
_wait = false;
});
}
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
Text("Location tracking", style: Theme.of(context).textTheme.title),
Container(height: Sizes.rowPadding,),
InkWell(
onTap: () => Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.app/docs#location-tracking"),
child: Text(
"Please read documentation!",
style: Theme.of(context).textTheme.subhead.copyWith(
color: Colors.blue,
decoration: TextDecoration.underline
)
),
),
Container(height: Sizes.rowPadding,),
Row(
children: <Widget>[
Text("Enable device location tracking"),
Switch(
value: _locationTrackingEnabled,
onChanged: _wait ? null : (value) {
setState(() {
_locationTrackingEnabled = value;
_wait = true;
});
_switchLocationTrackingState(value);
},
),
],
),
Container(height: Sizes.rowPadding,),
Text("Location update interval in minutes:"),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
//Expanded(child: Container(),),
FlatButton(
padding: EdgeInsets.all(0.0),
child: Text("-", style: Theme.of(context).textTheme.title),
onPressed: () => decLocationInterval(),
),
Text("$_locationInterval", style: Theme.of(context).textTheme.title),
FlatButton(
padding: EdgeInsets.all(0.0),
child: Text("+", style: Theme.of(context).textTheme.title),
onPressed: () => incLocationInterval(),
),
],
),
Divider(),
Text("Integration status", style: Theme.of(context).textTheme.title),
Container(height: Sizes.rowPadding,),
Text(
"${HomeAssistant().userName}'s ${DeviceInfoManager().model}, ${DeviceInfoManager().osName} ${DeviceInfoManager().osVersion}",
style: Theme.of(context).textTheme.subtitle,
),
Container(height: 6.0,),
Text("Here you can manually check if HA Client integration with your Home Assistant works fine. As mobileApp integration in Home Assistant is still in development, this is not 100% correct check."),
//Divider(),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
RaisedButton(
color: Colors.blue,
onPressed: () => updateRegistration(),
child: Text("Check integration", style: Theme.of(context).textTheme.button)
),
Container(width: 10.0,),
RaisedButton(
color: Colors.redAccent,
onPressed: () => resetRegistration(),
child: Text("Reset integration", style: Theme.of(context).textTheme.button)
)
],
),
]
);
}
@override
void dispose() {
LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
super.dispose();
}
}

View File

@ -0,0 +1,79 @@
part of '../../main.dart';
class LookAndFeelSettingsPage extends StatefulWidget {
LookAndFeelSettingsPage({Key key, this.title}) : super(key: key);
final String title;
@override
_LookAndFeelSettingsPageState createState() => new _LookAndFeelSettingsPageState();
}
class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> {
AppTheme _currentTheme;
bool _changed = false;
@override
void initState() {
super.initState();
_loadSettings();
}
_loadSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
SharedPreferences.getInstance().then((prefs) {
setState(() {
_currentTheme = AppTheme.values[prefs.getInt("app-theme") ?? AppTheme.defaultTheme];
});
});
}
_saveSettings(AppTheme theme) {
SharedPreferences.getInstance().then((prefs) {
prefs.setInt('app-theme', theme.index);
setState(() {
_currentTheme = theme;
eventBus.fire(ChangeThemeEvent(_currentTheme));
});
});
}
Map appThemeName = {
AppTheme.defaultTheme: 'Default',
AppTheme.haTheme: 'Home Assistant theme',
AppTheme.darkTheme: 'Dark theme'
};
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
Text("Application scheme:", style: Theme.of(context).textTheme.body2),
Container(height: Sizes.rowPadding),
DropdownButton<AppTheme>(
value: _currentTheme,
iconSize: 30.0,
isExpanded: true,
style: Theme.of(context).textTheme.title,
//hint: Text("Select ${caption.toLowerCase()}"),
items: AppTheme.values.map((value) {
return new DropdownMenuItem<AppTheme>(
value: value,
child: Text('${appThemeName[value]}'),
);
}).toList(),
onChanged: (theme) => _saveSettings(theme),
)
]
);
}
@override
void dispose() {
super.dispose();
}
}

View File

@ -28,6 +28,13 @@ class ReloadUIEvent {
ReloadUIEvent(); ReloadUIEvent();
} }
class ChangeThemeEvent {
final AppTheme theme;
ChangeThemeEvent(this.theme);
}
class StartAuthEvent { class StartAuthEvent {
String oauthUrl; String oauthUrl;
bool showButton; bool showButton;