This commit is contained in:
Hillel Coren 2019-10-02 11:17:08 +03:00
parent 1792adf4f7
commit 3c5d04433a
6 changed files with 278 additions and 159 deletions

View File

@ -0,0 +1,74 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/ui/settings/device_settings_list_vm.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class DeviceSettingsList extends StatelessWidget {
const DeviceSettingsList({
Key key,
@required this.viewModel,
}) : super(key: key);
final DeviceSettingsListVM viewModel;
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
SwitchListTile(
title: Text(AppLocalization.of(context).darkMode),
value: viewModel.enableDarkMode,
onChanged: (value) => viewModel.onDarkModeChanged(context, value),
secondary: Icon(FontAwesomeIcons.moon),
activeColor: Theme.of(context).accentColor,
),
FutureBuilder(
future: viewModel.authenticationSupported,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data == true) {
return SwitchListTile(
title:
Text(AppLocalization.of(context).biometricAuthentication),
value: viewModel.requireAuthentication,
onChanged: (value) =>
viewModel.onRequireAuthenticationChanged(context, value),
secondary: Icon(viewModel.requireAuthentication
? FontAwesomeIcons.lock
: FontAwesomeIcons.unlockAlt),
activeColor: Theme.of(context).accentColor,
);
} else {
return SizedBox();
}
},
),
viewModel.state.selectedCompany.isModuleEnabled(EntityType.task)
? SwitchListTile(
title: Text(AppLocalization.of(context).autoStartTasks),
value: viewModel.autoStartTasks,
onChanged: (value) =>
viewModel.onAutoStartTasksChanged(context, value),
secondary: Icon(FontAwesomeIcons.clock),
activeColor: Theme.of(context).accentColor,
)
: SizedBox(),
ListTile(
leading: Icon(FontAwesomeIcons.syncAlt),
title: Text(AppLocalization.of(context).refreshData),
onTap: () {
viewModel.onRefreshTap(context);
},
),
ListTile(
leading: Icon(FontAwesomeIcons.powerOff),
title: Text(AppLocalization.of(context).logout),
onTap: () {
viewModel.onLogoutTap(context);
},
),
],
);
}
}

View File

@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/loading_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/app_builder.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:local_auth/local_auth.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/ui/settings/device_settings_list.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DeviceSettingsListBuilder extends StatelessWidget {
const DeviceSettingsListBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, DeviceSettingsListVM>(
converter: DeviceSettingsListVM.fromStore,
builder: (context, viewModel) {
return DeviceSettingsList(viewModel: viewModel);
},
);
}
}
class DeviceSettingsListVM {
DeviceSettingsListVM({
@required this.state,
@required this.onLogoutTap,
@required this.onRefreshTap,
@required this.onDarkModeChanged,
@required this.enableDarkMode,
@required this.autoStartTasks,
@required this.onAutoStartTasksChanged,
@required this.onRequireAuthenticationChanged,
@required this.requireAuthentication,
@required this.authenticationSupported,
});
static DeviceSettingsListVM fromStore(Store<AppState> store) {
void _refreshData(BuildContext context) async {
final completer = snackBarCompleter(
context, AppLocalization.of(context).refreshComplete,
shouldPop: true);
store.dispatch(RefreshData(
platform: getPlatform(context),
completer: completer,
));
await showDialog<AlertDialog>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => SimpleDialog(
children: <Widget>[LoadingDialog()],
));
AppBuilder.of(context).rebuild();
store.dispatch(LoadDashboard());
}
void _confirmLogout(BuildContext context) {
final localization = AppLocalization.of(context);
showDialog<AlertDialog>(
context: context,
builder: (BuildContext context) => AlertDialog(
semanticLabel: localization.areYouSure,
title: Text(localization.areYouSure),
actions: <Widget>[
new FlatButton(
child: Text(localization.cancel.toUpperCase()),
onPressed: () {
Navigator.pop(context);
}),
new FlatButton(
child: Text(localization.ok.toUpperCase()),
onPressed: () {
store.dispatch(UserLogout(context));
})
],
),
);
}
return DeviceSettingsListVM(
state: store.state,
onLogoutTap: (BuildContext context) => _confirmLogout(context),
onRefreshTap: (BuildContext context) => _refreshData(context),
onDarkModeChanged: (BuildContext context, bool value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(kSharedPrefEnableDarkMode, value);
store.dispatch(UserSettingsChanged(enableDarkMode: value));
AppBuilder.of(context).rebuild();
},
onAutoStartTasksChanged: (BuildContext context, bool value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(kSharedPrefAutoStartTasks, value);
store.dispatch(UserSettingsChanged(autoStartTasks: value));
AppBuilder.of(context).rebuild();
},
onRequireAuthenticationChanged: (BuildContext context, bool value) async {
bool authenticated = false;
try {
authenticated = await LocalAuthentication()
.authenticateWithBiometrics(
localizedReason:
AppLocalization.of(context).authenticateToChangeSetting,
useErrorDialogs: true,
stickyAuth: false);
} catch (e) {
print(e);
}
if (authenticated) {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(kSharedPrefRequireAuthentication, value);
store.dispatch(UserSettingsChanged(requireAuthentication: value));
} else {}
},
autoStartTasks: store.state.uiState.autoStartTasks,
enableDarkMode: store.state.uiState.enableDarkMode,
requireAuthentication: store.state.uiState.requireAuthentication,
//authenticationSupported: LocalAuthentication().canCheckBiometrics,
// TODO remove this once issue is resolved:
// https://github.com/flutter/flutter/issues/24339
authenticationSupported: Future<bool>(() async {
bool enable = false;
try {
enable = await LocalAuthentication().canCheckBiometrics;
} catch (e) {
// do nothing
}
return enable;
}),
);
}
final AppState state;
final Function(BuildContext context) onLogoutTap;
final Function(BuildContext context) onRefreshTap;
final Function(BuildContext context, bool value) onDarkModeChanged;
final Function(BuildContext context, bool value) onAutoStartTasksChanged;
final bool enableDarkMode;
final bool autoStartTasks;
final Function(BuildContext context, bool value)
onRequireAuthenticationChanged;
final bool requireAuthentication;
final Future<bool> authenticationSupported;
}

View File

@ -1,8 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_list_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/settings_list_vm.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
class SettingsList extends StatelessWidget { class SettingsList extends StatelessWidget {
@ -15,58 +13,12 @@ class SettingsList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
return ListView( return ListView(
children: <Widget>[ children: <Widget>[
SwitchListTile(
title: Text(AppLocalization.of(context).darkMode),
value: viewModel.enableDarkMode,
onChanged: (value) => viewModel.onDarkModeChanged(context, value),
secondary: Icon(FontAwesomeIcons.moon),
activeColor: Theme.of(context).accentColor,
),
FutureBuilder(
future: viewModel.authenticationSupported,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data == true) {
return SwitchListTile(
title:
Text(AppLocalization.of(context).biometricAuthentication),
value: viewModel.requireAuthentication,
onChanged: (value) =>
viewModel.onRequireAuthenticationChanged(context, value),
secondary: Icon(viewModel.requireAuthentication
? FontAwesomeIcons.lock
: FontAwesomeIcons.unlockAlt),
activeColor: Theme.of(context).accentColor,
);
} else {
return SizedBox();
}
},
),
viewModel.state.selectedCompany.isModuleEnabled(EntityType.task)
? SwitchListTile(
title: Text(AppLocalization.of(context).autoStartTasks),
value: viewModel.autoStartTasks,
onChanged: (value) =>
viewModel.onAutoStartTasksChanged(context, value),
secondary: Icon(FontAwesomeIcons.clock),
activeColor: Theme.of(context).accentColor,
)
: SizedBox(),
ListTile( ListTile(
leading: Icon(FontAwesomeIcons.syncAlt), title: Text(localization.companyDetails),
title: Text(AppLocalization.of(context).refreshData),
onTap: () {
viewModel.onRefreshTap(context);
},
),
ListTile(
leading: Icon(FontAwesomeIcons.powerOff),
title: Text(AppLocalization.of(context).logout),
onTap: () {
viewModel.onLogoutTap(context);
},
), ),
], ],
); );

View File

@ -6,12 +6,13 @@ import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart'; import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/loading_dialog.dart'; import 'package:invoiceninja_flutter/ui/app/dialogs/loading_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/app_builder.dart'; import 'package:invoiceninja_flutter/ui/app/app_builder.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_list.dart';
import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_list.dart'; import 'package:invoiceninja_flutter/ui/settings/device_settings_list.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart'; import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -33,120 +34,13 @@ class SettingsListBuilder extends StatelessWidget {
class SettingsListVM { class SettingsListVM {
SettingsListVM({ SettingsListVM({
@required this.state, @required this.state,
@required this.onLogoutTap,
@required this.onRefreshTap,
@required this.onDarkModeChanged,
@required this.enableDarkMode,
@required this.autoStartTasks,
@required this.onAutoStartTasksChanged,
@required this.onRequireAuthenticationChanged,
@required this.requireAuthentication,
@required this.authenticationSupported,
}); });
static SettingsListVM fromStore(Store<AppState> store) { static SettingsListVM fromStore(Store<AppState> store) {
void _refreshData(BuildContext context) async {
final completer = snackBarCompleter(
context, AppLocalization.of(context).refreshComplete,
shouldPop: true);
store.dispatch(RefreshData(
platform: getPlatform(context),
completer: completer,
));
await showDialog<AlertDialog>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => SimpleDialog(
children: <Widget>[LoadingDialog()],
));
AppBuilder.of(context).rebuild();
store.dispatch(LoadDashboard());
}
void _confirmLogout(BuildContext context) {
final localization = AppLocalization.of(context);
showDialog<AlertDialog>(
context: context,
builder: (BuildContext context) => AlertDialog(
semanticLabel: localization.areYouSure,
title: Text(localization.areYouSure),
actions: <Widget>[
new FlatButton(
child: Text(localization.cancel.toUpperCase()),
onPressed: () {
Navigator.pop(context);
}),
new FlatButton(
child: Text(localization.ok.toUpperCase()),
onPressed: () {
store.dispatch(UserLogout(context));
})
],
),
);
}
return SettingsListVM( return SettingsListVM(
state: store.state, state: store.state,
onLogoutTap: (BuildContext context) => _confirmLogout(context),
onRefreshTap: (BuildContext context) => _refreshData(context),
onDarkModeChanged: (BuildContext context, bool value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(kSharedPrefEnableDarkMode, value);
store.dispatch(UserSettingsChanged(enableDarkMode: value));
AppBuilder.of(context).rebuild();
},
onAutoStartTasksChanged: (BuildContext context, bool value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(kSharedPrefAutoStartTasks, value);
store.dispatch(UserSettingsChanged(autoStartTasks: value));
AppBuilder.of(context).rebuild();
},
onRequireAuthenticationChanged: (BuildContext context, bool value) async {
bool authenticated = false;
try {
authenticated = await LocalAuthentication()
.authenticateWithBiometrics(
localizedReason:
AppLocalization.of(context).authenticateToChangeSetting,
useErrorDialogs: true,
stickyAuth: false);
} catch (e) {
print(e);
}
if (authenticated) {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(kSharedPrefRequireAuthentication, value);
store.dispatch(UserSettingsChanged(requireAuthentication: value));
} else {}
},
autoStartTasks: store.state.uiState.autoStartTasks,
enableDarkMode: store.state.uiState.enableDarkMode,
requireAuthentication: store.state.uiState.requireAuthentication,
//authenticationSupported: LocalAuthentication().canCheckBiometrics,
// TODO remove this once issue is resolved:
// https://github.com/flutter/flutter/issues/24339
authenticationSupported: Future<bool>(() async {
bool enable = false;
try {
enable = await LocalAuthentication().canCheckBiometrics;
} catch (e) {
// do nothing
}
return enable;
}),
); );
} }
final AppState state; final AppState state;
final Function(BuildContext context) onLogoutTap;
final Function(BuildContext context) onRefreshTap;
final Function(BuildContext context, bool value) onDarkModeChanged;
final Function(BuildContext context, bool value) onAutoStartTasksChanged;
final bool enableDarkMode;
final bool autoStartTasks;
final Function(BuildContext context, bool value)
onRequireAuthenticationChanged;
final bool requireAuthentication;
final Future<bool> authenticationSupported;
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/app_drawer_vm.dart'; import 'package:invoiceninja_flutter/ui/app/app_drawer_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/device_settings_list_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_list_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/settings_list_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';

View File

@ -14,6 +14,20 @@ abstract class LocaleCodeAware {
mixin LocalizationsProvider on LocaleCodeAware { mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = { static final Map<String, Map<String, String>> _localizedValues = {
'en': { 'en': {
'company_details': 'Company Details',
'user_details': 'User Details',
'localization': 'Localization',
'online_payments': 'Online Payments',
'tax_rates': 'Tax Rates',
'notifications': 'Notifications',
'import_export': 'Import | Export',
'invoice_settings': 'Invoice Settings',
'invoice_design': 'Invoice Design',
'buy_now_buttons': 'Buy Now Buttons',
'email_settings': 'Email Settings',
'templates_and_reminders': 'Templates & Reminders',
'credit_cards_and_banks': 'Credit Cards & Banks',
'data_visualizations': 'Data Visualizations',
'price': 'Price', 'price': 'Price',
'email_sign_up': 'Email Sign Up', 'email_sign_up': 'Email Sign Up',
'google_sign_up': 'Google Sign Up', 'google_sign_up': 'Google Sign Up',
@ -14613,6 +14627,38 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get price => _localizedValues[localeCode]['price']; String get price => _localizedValues[localeCode]['price'];
String get companyDetails => _localizedValues[localeCode]['company_details'];
String get userDetails => _localizedValues[localeCode]['user_details'];
String get localization => _localizedValues[localeCode]['localization'];
String get onlinePayments => _localizedValues[localeCode]['online_payments'];
String get taxRates => _localizedValues[localeCode]['tax_rates'];
String get notifications => _localizedValues[localeCode]['notifications'];
String get importExport => _localizedValues[localeCode]['import_export'];
String get invoiceSettings =>
_localizedValues[localeCode]['invoice_settings'];
String get invoiceDesign => _localizedValues[localeCode]['invoice_design'];
String get buyNowButtons => _localizedValues[localeCode]['buy_now_buttons'];
String get emailSettings => _localizedValues[localeCode]['email_settings'];
String get templatesAndReminders =>
_localizedValues[localeCode]['templates_and_reminders'];
String get creditCardsAndBanks =>
_localizedValues[localeCode]['credit_cards_and_banks'];
String get dataVisualizations =>
_localizedValues[localeCode]['data_visualizations'];
String lookup(String key) { String lookup(String key) {
final lookupKey = toSnakeCase(key); final lookupKey = toSnakeCase(key);
return _localizedValues[localeCode][lookupKey] ?? return _localizedValues[localeCode][lookupKey] ??