Settings page and theme selection
This commit is contained in:
parent
68d14bd13d
commit
5f23e108a1
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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');
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
104
lib/pages/settings/app_settings.page.dart
Normal file
104
lib/pages/settings/app_settings.page.dart
Normal 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
194
lib/pages/settings/integration_settings.part.dart
Normal file
194
lib/pages/settings/integration_settings.part.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
79
lib/pages/settings/lookandfeel_settings.part.dart
Normal file
79
lib/pages/settings/lookandfeel_settings.part.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user