1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2025-06-19 08:15:16 +00:00

Refactor settings files

This commit is contained in:
2020-05-12 18:42:33 +02:00
parent 9e5e93da39
commit 57e504540a
8 changed files with 8 additions and 8 deletions

View File

@ -0,0 +1,225 @@
import 'package:comunic/helpers/database/users_database_helper.dart';
import 'package:comunic/helpers/settings_helper.dart';
import 'package:comunic/models/account_image_settings.dart';
import 'package:comunic/ui/widgets/async_screen_widget.dart';
import 'package:comunic/ui/widgets/network_image_widget.dart';
import 'package:comunic/utils/account_utils.dart';
import 'package:comunic/utils/files_utils.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:identicon/identicon.dart';
import 'package:random_string/random_string.dart';
import 'package:settings_ui/settings_ui.dart';
/// Account image settings section
///
/// @author Pierre Hubert
class AccountImageSettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(tr("Account image settings")),
),
body: _AccountImageSettingsBody(),
);
}
}
class _AccountImageSettingsBody extends StatefulWidget {
@override
__AccountImageSettingsBodyState createState() =>
__AccountImageSettingsBodyState();
}
class __AccountImageSettingsBodyState extends State<_AccountImageSettingsBody> {
AccountImageSettings _settings;
final _key = GlobalKey<AsyncScreenWidgetState>();
@override
void dispose() {
// Remove current user information to force refresh of account image
UsersDatabaseHelper().delete(userID());
super.dispose();
}
@override
Widget build(BuildContext context) {
return AsyncScreenWidget(
key: _key,
onReload: () async =>
_settings = await SettingsHelper.getAccountImageSettings(),
onBuild: () => _buildLayout(),
errorMessage: tr("Could not get account image settings!"),
);
}
Widget _buildLayout() {
return SettingsList(
sections: [
SettingsSection(
title: tr("General"),
tiles: _settings.hasImage
? _buildHasAccountImageTiles()
: _buildNoAccountImageTiles(),
)
],
);
}
/// When user has no account image yet
List<SettingsTile> _buildNoAccountImageTiles() {
return [
SettingsTile(
title: tr("No account image yet..."),
leading: NetworkImageWidget(
url: _settings.imageURL,
width: 40,
),
),
// Upload new account image
SettingsTile(
title: tr("Upload an account image"),
onTap: () => _uploadAccountImage(),
),
// Generate a random account image
SettingsTile(
title: tr("Generate a random account image"),
onTap: () => _generateRandomAccountImage(),
),
];
}
/// When the user has an account image
List<SettingsTile> _buildHasAccountImageTiles() {
return [
// Current account image (if any)
SettingsTile(
title: tr("Current account image"),
leading: NetworkImageWidget(
url: _settings.imageURL,
width: 40,
),
),
// Upload new account image
SettingsTile(
title: tr("Upload new account image"),
onTap: () => _uploadAccountImage(),
),
// Generate a random account image
SettingsTile(
title: tr("Generate a random account image"),
onTap: () => _generateRandomAccountImage(),
),
// Change account image visibility
SettingsTile(
title: tr("Change account image visibility"),
onTap: () => _chooseAccountImageVisibility(),
subtitle: tr("Current level: %level%", args: {
"level":
_settings.visibility == AccountImageVisibilityLevels.EVERYONE
? tr("Everyone")
: _settings.visibility ==
AccountImageVisibilityLevels.COMUNIC_USERS
? tr("Comunic users")
: tr("My friends only"),
})),
// Delete account image
SettingsTile(
title: tr("Delete account image"),
onTap: () => _deleteAccountImage(),
),
];
}
/// Upload a new account image
void _uploadAccountImage() async {
final image = await pickImage(context);
if (image == null) return;
if (!await SettingsHelper.uploadAccountImage(image)) {
showSimpleSnack(context, tr("Could not upload your account image!"));
return;
}
_key.currentState.refresh();
}
/// Generate a random account image
void _generateRandomAccountImage() async {
// Generate emoticon
final bytes = Identicon().generate(randomString(10));
if (!await SettingsHelper.uploadAccountImageFromMemory(bytes)) {
showSimpleSnack(
context, tr("Could not upload your generated account image!"));
return;
}
_key.currentState.refresh();
}
/// Change account image visibility
void _chooseAccountImageVisibility() async {
final newLevel = await showDialog<AccountImageVisibilityLevels>(
context: context,
builder: (c) => AlertDialog(
title: Text(tr("Account image visiblity")),
content:
Text(tr("Please choose new account image visibility level:")),
actions: <Widget>[
MaterialButton(
child: Text(tr("Everyone")),
onPressed: () =>
Navigator.pop(c, AccountImageVisibilityLevels.EVERYONE),
),
MaterialButton(
child: Text(tr("Connected users")),
onPressed: () => Navigator.pop(
c, AccountImageVisibilityLevels.COMUNIC_USERS),
),
MaterialButton(
child: Text(tr("My friends")),
onPressed: () => Navigator.pop(
c, AccountImageVisibilityLevels.FRIENDS_ONLY),
)
],
));
if (newLevel == null) return;
if (!await SettingsHelper.setAccountImageVisibilityLevel(newLevel)) {
showSimpleSnack(
context, tr("Could not update account image visibility level!"));
return;
}
_key.currentState.refresh();
}
/// Delete user account image
void _deleteAccountImage() async {
if (!await showConfirmDialog(
context: context,
message: tr("Do you really want to delete your account image ?")))
return;
if (!await SettingsHelper.deleteAccountImage()) {
showSimpleSnack(context, tr("Could not user account image!"));
return;
}
_key.currentState.refresh();
}
}

View File

@ -0,0 +1,93 @@
import 'package:comunic/helpers/account_helper.dart';
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:settings_ui/settings_ui.dart';
/// Account privacy settings
///
/// @author Pierre HUBERT
class AccountPrivacySettings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(tr("Privacy settings"))),
body: _AccountPrivacyScreen(),
);
}
}
class _AccountPrivacyScreen extends StatefulWidget {
@override
__AccountPrivacyScreenState createState() => __AccountPrivacyScreenState();
}
class __AccountPrivacyScreenState extends State<_AccountPrivacyScreen> {
@override
Widget build(BuildContext context) {
return SettingsList(sections: [
SettingsSection(title: tr("Privacy settings"), tiles: [
SettingsTile(
title: tr("Delete your account"),
subtitle:
tr("Permanently delete your account and all data related to it."),
onTap: _deleteAccount,
)
])
]);
}
/// Permanently delete user account
void _deleteAccount() async {
try {
// First dialog
if (!await showConfirmDialog(
context: context,
message: tr(
"Do you really want to delete your account? This operation CAN NOT be reverted!")))
return;
// Ask user password dialog
final password = await showUserPasswordDialog(context);
if (password == null) return;
// Last chance dialog
if (await showCupertinoDialog<bool>(
context: context,
builder: (c) => _LastChanceDeleteAccountDialog()) !=
true) return;
// Delete account
await AccountHelper.deleteAccount(password);
} catch (e, stack) {
print("Could not delete user account! $e\n$stack");
showSimpleSnack(context, tr("Could not delete your account!"));
}
}
}
class _LastChanceDeleteAccountDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoAlertDialog(
title: Text(tr("Delete your account")),
content: Text(tr(
"Let us ask you one last time. Do you really want to delete your account? If you decide to do so, your data will be permanently removed from our servers, so we will not be able to recover your account. If you decide to proceed, the deletion process will start immediatly and you will automatically get disconnected from your account.")),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text(tr("Cancel")),
onPressed: () => Navigator.of(context).pop(false),
),
CupertinoDialogAction(
isDestructiveAction: true,
child: Text(tr("Confirm")),
onPressed: () => Navigator.of(context).pop(true),
),
],
);
}
}

View File

@ -0,0 +1,216 @@
import 'package:comunic/helpers/account_helper.dart';
import 'package:comunic/helpers/settings_helper.dart';
import 'package:comunic/models/security_settings.dart';
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:settings_ui/settings_ui.dart';
/// Account security settings
///
/// @author Pierre HUBERT
class AccountSecuritySettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(tr("Security settings")),
),
body: _AccountSecuritySettingsScreenBody(),
);
}
}
class _AccountSecuritySettingsScreenBody extends StatefulWidget {
@override
__AccountSecuritySettingsScreenBodyState createState() =>
__AccountSecuritySettingsScreenBodyState();
}
class __AccountSecuritySettingsScreenBodyState
extends State<_AccountSecuritySettingsScreenBody> {
@override
Widget build(BuildContext context) {
return SettingsList(
sections: [
SettingsSection(
title: tr("Password"),
tiles: [
SettingsTile(
title: tr("Change password"),
onTap: _changePassword,
),
SettingsTile(
title: tr("Change your security questions"),
subtitle: tr(
"Your security questions can be used to recover an access to your account when you loose your password..."),
onTap: _changeSecurityQuestions,
),
SettingsTile(
title: tr("Disconnect all your devices"),
subtitle: tr(
"Disconnect all your devices from Comunic, including the current one. Use this option if one of the device you use for Comunic was stolen."),
onTap: _disconnectAllDevices,
),
],
)
],
);
}
/// Change current user password
void _changePassword() async {
try {
final currPassword = await showUserPasswordDialog(context);
if (currPassword == null) return;
final newPassword = await showInputNewPassword(context);
if (newPassword == null) return;
await SettingsHelper.changePassword(currPassword, newPassword);
showSimpleSnack(
context, tr("Your password has been successfully changed!"));
} catch (e, stack) {
print("Could not update current user password! $e\n$stack");
showSimpleSnack(context, tr("Could not update password!"));
}
}
/// Change security questions
void _changeSecurityQuestions() async {
try {
final password = await showUserPasswordDialog(context);
if (password == null) return;
final settings = await SettingsHelper.getSecuritySettings(password);
final newSettings = await showDialog<SecuritySettings>(
context: context,
builder: (c) => _SecurityQuestionsDialog(settings: settings));
if (newSettings == null) return;
await SettingsHelper.setSecuritySettings(password, newSettings);
showSimpleSnack(context,
tr("You security questions have been successfully updated!"));
} catch (e, stack) {
print("Could not update security questions!$e\n$stack");
showSimpleSnack(context, tr("Could not update security questions!"));
}
}
/// Disconnect all devices
void _disconnectAllDevices() async {
try {
if (!await showConfirmDialog(
context: context,
message: tr(
"Do you really want to disconnect all your devices from Comunic ?")))
return;
await AccountHelper.disconnectAllDevices();
} catch (e, stack) {
print("Could not disconnect user on all devices! $e\n$stack");
showSimpleSnack(
context, tr("Could not disconnect you from all your devices!"));
}
}
}
class _SecurityQuestionsDialog extends StatefulWidget {
final SecuritySettings settings;
const _SecurityQuestionsDialog({Key key, @required this.settings})
: assert(settings != null),
super(key: key);
@override
__SecurityQuestionsDialogState createState() =>
__SecurityQuestionsDialogState();
}
class __SecurityQuestionsDialogState extends State<_SecurityQuestionsDialog> {
TextEditingController _controllerQuestion1;
TextEditingController _controllerAnswer1;
TextEditingController _controllerQuestion2;
TextEditingController _controllerAnswer2;
SecuritySettings get _newSettings => SecuritySettings(
securityQuestion1: _controllerQuestion1.text,
securityAnswer1: _controllerAnswer1.text,
securityQuestion2: _controllerQuestion2.text,
securityAnswer2: _controllerAnswer2.text,
);
@override
void initState() {
_controllerQuestion1 =
TextEditingController(text: widget.settings.securityQuestion1);
_controllerAnswer1 =
TextEditingController(text: widget.settings.securityAnswer1);
_controllerQuestion2 =
TextEditingController(text: widget.settings.securityQuestion2);
_controllerAnswer2 =
TextEditingController(text: widget.settings.securityAnswer2);
super.initState();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(tr("Update security questions")),
content: AutoSizeDialogContentWidget(child: _buildContent()),
actions: <Widget>[
MaterialButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(tr("Cancel").toUpperCase()),
),
MaterialButton(
onPressed: () => Navigator.of(context).pop(_newSettings),
child: Text(tr("Update").toUpperCase()),
)
],
);
}
Widget _buildContent() {
return Column(
children: <Widget>[
Text(tr(
"Note: Your two questions and answers MUST be completed in order to be able to recover your account using your security questions!")),
_buildTextField(
controller: _controllerQuestion1, label: tr("Question 1")),
_buildTextField(controller: _controllerAnswer1, label: tr("Answer 1")),
_buildTextField(
controller: _controllerQuestion2, label: tr("Question 2")),
_buildTextField(controller: _controllerAnswer2, label: tr("Answer 2")),
],
);
}
Widget _buildTextField({
@required TextEditingController controller,
@required String label,
}) {
return TextField(
controller: controller,
onChanged: (s) => setState(() {}),
decoration: InputDecoration(
alignLabelWithHint: true,
labelText: label,
errorText: controller.text.isNotEmpty && controller.text.length < 5
? tr("Unsafe value!")
: null,
),
);
}
}

View File

@ -0,0 +1,97 @@
import 'package:comunic/ui/routes/settings/account_image_settings.dart';
import 'package:comunic/ui/routes/settings/account_privacy_settings.dart';
import 'package:comunic/ui/routes/settings/account_security_settings.dart';
import 'package:comunic/ui/routes/settings/application_settings.dart';
import 'package:comunic/ui/routes/settings/custom_emojies_account_settings.dart';
import 'package:comunic/ui/routes/settings/general_account_settings.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:flutter/material.dart';
import 'package:settings_ui/settings_ui.dart';
/// Account settings route
///
/// @author Pierre HUBERT
class AccountSettingsRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(tr("Settings")),
),
body: _AccountSettingsBody(),
);
}
}
class _AccountSettingsBody extends StatefulWidget {
@override
__AccountSettingsBodyState createState() => __AccountSettingsBodyState();
}
class __AccountSettingsBodyState extends State<_AccountSettingsBody> {
@override
Widget build(BuildContext context) {
return SettingsList(
sections: [
SettingsSection(
title: tr("Account settings"),
tiles: [
// General settings
SettingsTile(
title: tr("General settings"),
subtitle: tr("Configure the main settings of your account"),
leading: Icon(Icons.settings),
onTap: () => _openSection(GeneralAccountSettingsScreen()),
),
// Emoticons
SettingsTile(
title: tr("Custom emojis"),
subtitle: tr("Set your own emoticon shorcuts"),
leading: Icon(Icons.insert_emoticon),
onTap: () => _openSection(CustomEmojisAccountSettings()),
),
// Account image
SettingsTile(
title: tr("Account image"),
subtitle: tr("Customize your account image"),
leading: Icon(Icons.account_circle),
onTap: () => _openSection(AccountImageSettingsScreen()),
),
// Security settings
SettingsTile(
title: tr("Security"),
subtitle: tr("Manage security options of your account"),
leading: Icon(Icons.lock),
onTap: () => _openSection(AccountSecuritySettingsScreen()),
),
// Privacy settings
SettingsTile(
title: tr("Privacy"),
subtitle: tr("Here you can make actions to protect your privacy"),
leading: Icon(Icons.security),
onTap: () => _openSection(AccountPrivacySettings()),
),
// Privacy settings
SettingsTile(
title: tr("Application settings"),
subtitle: tr("Manage local application settings"),
leading: Icon(Icons.smartphone),
onTap: () => _openSection(ApplicationSettings()),
),
],
)
],
);
}
/// Open a settings setings
void _openSection(Widget w) {
Navigator.of(context).push(MaterialPageRoute(builder: (c) => w));
}
}

View File

@ -0,0 +1,109 @@
import 'package:comunic/helpers/preferences_helper.dart';
import 'package:comunic/ui/widgets/async_screen_widget.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:settings_ui/settings_ui.dart';
/// Application settings
///
/// @author Pierre Hubert
class ApplicationSettings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(tr("Application settings"))),
body: _ApplicationSettingsScreen(),
);
}
}
class _ApplicationSettingsScreen extends StatefulWidget {
@override
__ApplicationSettingsScreenState createState() =>
__ApplicationSettingsScreenState();
}
class __ApplicationSettingsScreenState
extends State<_ApplicationSettingsScreen> {
PreferencesHelper _preferencesHelper;
Future<void> _refresh() async {
_preferencesHelper = await PreferencesHelper.getInstance();
}
@override
Widget build(BuildContext context) => AsyncScreenWidget(
onReload: _refresh,
onBuild: _buildSections,
errorMessage: tr("Could not load settings!"));
Widget _buildSections() {
return SettingsList(
sections: [_buildAppearanceSection(), _buildGeneralSection()],
);
}
/// Appearance section
SettingsSection _buildAppearanceSection() => SettingsSection(
title: tr("Appearance"),
tiles: [
_PreferencesSettingsTile(
preferencesKey: PreferencesKeyList.ENABLE_DARK_THEME,
title: tr("Enable dark theme"),
subtitle: null,
onChange: _updatedSettings,
helper: _preferencesHelper,
),
],
);
/// General section
SettingsSection _buildGeneralSection() => SettingsSection(
title: tr("General"),
tiles: [
SettingsTile(
title: tr("About this application"),
subtitle: tr("Learn more about us"),
onTap: () => showAboutAppDialog(context),
)
],
);
/// Apply new settings
_updatedSettings() {
setState(() {});
}
}
class _PreferencesSettingsTile extends SettingsTile {
final PreferencesKeyList preferencesKey;
final String title;
final String subtitle;
final Function onChange;
final PreferencesHelper helper;
const _PreferencesSettingsTile({
@required this.preferencesKey,
@required this.title,
@required this.subtitle,
@required this.onChange,
@required this.helper,
});
@override
Widget build(BuildContext context) {
return SettingsTile.switchTile(
title: title,
subtitle: subtitle,
onToggle: _doChange,
switchValue: helper.getBool(preferencesKey),
);
}
void _doChange(bool value) async {
await helper.setBool(preferencesKey, value);
onChange();
}
}

View File

@ -0,0 +1,223 @@
import 'dart:io';
import 'package:comunic/helpers/settings_helper.dart';
import 'package:comunic/helpers/users_helper.dart';
import 'package:comunic/lists/custom_emojies_list.dart';
import 'package:comunic/models/custom_emoji.dart';
import 'package:comunic/models/new_emoji.dart';
import 'package:comunic/models/user.dart';
import 'package:comunic/ui/widgets/async_screen_widget.dart';
import 'package:comunic/ui/widgets/network_image_widget.dart';
import 'package:comunic/utils/account_utils.dart';
import 'package:comunic/utils/files_utils.dart';
import 'package:comunic/utils/input_utils.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';
/// Emojies account settings
///
/// @author Pierre Hubert
class CustomEmojisAccountSettings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(tr("Custom emojies settings")),
),
body: _CustomEmojiesAccountBody(),
);
}
}
class _CustomEmojiesAccountBody extends StatefulWidget {
@override
_CustomEmojiesAccountBodyState createState() =>
_CustomEmojiesAccountBodyState();
}
class _CustomEmojiesAccountBodyState extends State<_CustomEmojiesAccountBody> {
User _user;
final _key = GlobalKey<AsyncScreenWidgetState>();
Future<void> _reload() async {
_user = await UsersHelper().getSingleWithThrow(
userID(),
forceDownload: true,
);
}
@override
Widget build(BuildContext context) {
return AsyncScreenWidget(
key: _key,
onReload: _reload,
onBuild: _buildSettings,
errorMessage: tr("Could not refresh user information!"),
showOldDataWhileUpdating: true,
);
}
Widget _buildSettings() {
return Stack(
children: [_buildList(), _buildAddButton()],
);
}
Widget _buildList() {
return ListView(
children: _user.customEmojies
.map((u) => ListTile(
leading: NetworkImageWidget(url: u.url, width: 50),
title: Text(u.shortcut),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _deleteEmoji(u)),
))
.toList());
}
Widget _buildAddButton() {
return Positioned(
child: FloatingActionButton(
onPressed: _addEmoji,
child: Icon(Icons.add),
),
right: 20,
bottom: 20,
);
}
/// Add a custom emoji
void _addEmoji() async {
try {
final newEmoji = await showDialog<NewEmoji>(
context: context,
builder: (c) => _NewCustomEmojiDialog(
currentList: _user.customEmojies,
),
);
if (newEmoji == null) return;
await SettingsHelper.uploadNewCustomEmoji(newEmoji);
} catch (e, stack) {
print("Could not add a new emoji: $e\n$stack");
showSimpleSnack(context, tr("Could not upload emoji!"));
}
_key.currentState.refresh();
}
/// Ask for confirmation before deleting permanently an emoji
void _deleteEmoji(CustomEmoji u) async {
try {
if (!await showConfirmDialog(
context: context,
message: tr("Do you really want to delete this custom emoji ?")))
return;
await SettingsHelper.deleteCustomEmoji(u.id);
} catch (e, stack) {
print("Could not delete custom emoji! $e\n$stack");
showSimpleSnack(context, tr("Could not delete custom emoji!"));
}
_key.currentState.refresh();
}
}
/// Dialog used to upload new custom emojies
class _NewCustomEmojiDialog extends StatefulWidget {
final CustomEmojiesList currentList;
const _NewCustomEmojiDialog({
Key key,
@required this.currentList,
}) : assert(currentList != null),
super(key: key);
@override
_NewCustomEmojiDialogState createState() => _NewCustomEmojiDialogState();
}
class _NewCustomEmojiDialogState extends State<_NewCustomEmojiDialog> {
final _controller = TextEditingController();
File _file;
bool get _hasImage => _file != null;
String get _shortcut => _controller.text;
bool get _shortcutValid =>
_shortcut.isNotEmpty &&
validateShortcut(_shortcut) &&
!widget.currentList.hasShortcut(_shortcut);
bool get _valid => _hasImage && _shortcutValid;
NewEmoji get _emoji => NewEmoji(shortcut: _shortcut, image: _file);
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(tr("Add new emoji")),
content: ConstrainedBox(
constraints:
BoxConstraints(maxHeight: MediaQuery.of(context).size.height - 50),
child: SingleChildScrollView(
child: _buildBody(),
),
),
actions: <Widget>[
MaterialButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(tr("Cancel").toUpperCase()),
),
MaterialButton(
onPressed: _valid ? () => Navigator.of(context).pop(_emoji) : null,
child: Text(tr("Add").toUpperCase()),
),
],
);
}
Widget _buildBody() {
return Column(
children: <Widget>[
TextField(
controller: _controller,
onChanged: (s) => setState(() {}),
decoration: InputDecoration(
alignLabelWithHint: true,
labelText: tr("Shortcut"),
hintText: tr(":yourShortcut:"),
errorText: _shortcut.isNotEmpty && !_shortcutValid
? tr("Invalid shortcut!")
: null,
),
),
MaterialButton(
onPressed: _pickImage,
child: Text(_hasImage ? tr("Replace image") : tr("Add image")),
)
],
);
}
void _pickImage() async {
try {
final image = await pickImage(context);
if (image == null) return;
setState(() {
_file = image;
});
} catch (e, stack) {
print("Could not pick an image! $e\n$stack");
}
}
}

View File

@ -0,0 +1,239 @@
import 'package:comunic/enums/user_page_visibility.dart';
import 'package:comunic/helpers/database/users_database_helper.dart';
import 'package:comunic/helpers/settings_helper.dart';
import 'package:comunic/models/general_settings.dart';
import 'package:comunic/ui/dialogs/multi_choices_dialog.dart';
import 'package:comunic/ui/dialogs/virtual_directory_dialog.dart';
import 'package:comunic/ui/widgets/async_screen_widget.dart';
import 'package:comunic/ui/widgets/settings/multi_choices_settings_tile.dart';
import 'package:comunic/ui/widgets/settings/text_settings_edit_tile.dart';
import 'package:comunic/utils/account_utils.dart';
import 'package:comunic/utils/input_utils.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:settings_ui/settings_ui.dart';
/// General account settings
///
/// @author Pierre HUBERT
class GeneralAccountSettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("General settings"),
),
body: _GeneralAccountSettingsBody(),
);
}
}
class _GeneralAccountSettingsBody extends StatefulWidget {
@override
__GeneralAccountSettingsBodyState createState() =>
__GeneralAccountSettingsBodyState();
}
class __GeneralAccountSettingsBodyState
extends State<_GeneralAccountSettingsBody> {
GeneralSettings _settings;
final _key = GlobalKey<AsyncScreenWidgetState>();
@override
void dispose() {
// Remove current user information to force refresh of account image
UsersDatabaseHelper().delete(userID());
super.dispose();
}
@override
Widget build(BuildContext context) {
return AsyncScreenWidget(
key: _key,
onReload: () async =>
_settings = await SettingsHelper.getGeneralSettings(),
onBuild: _buildSettings,
errorMessage: tr("Could not load general settings!"),
showOldDataWhileUpdating: true,
);
}
Widget _buildSettings() {
return SettingsList(
sections: [
SettingsSection(
title: tr("Main account information"),
tiles: _mainInformationTiles(),
),
SettingsSection(
title: tr("Your page settings"),
tiles: _pageSettingsTiles(),
)
],
);
}
List<SettingsTile> _mainInformationTiles() {
return [
SettingsTile(
title: tr("User ID"),
subtitle: "${userID()}",
),
SettingsTile(
title: tr("Email address"),
subtitle: _settings.email,
),
// First name
TextEditSettingsTile(
title: tr("First name"),
currValue: _settings.firstName,
onChanged: (s) {
_settings.firstName = s;
_updateSettings();
},
checkInput: (s) => s.length >= 3,
),
// Last name
TextEditSettingsTile(
title: tr("Last name"),
currValue: _settings.lastName,
onChanged: (s) {
_settings.lastName = s;
_updateSettings();
},
checkInput: (s) => s.length >= 3,
),
// Emails settings
SettingsTile.switchTile(
title: tr("Allow comunic to send emails"),
onToggle: (s) {
_settings.allowComunicEmails = s;
_updateSettings();
},
switchValue: _settings.allowComunicEmails,
),
];
}
List<MultiChoiceEntry> get _visibilityChoices => [
MultiChoiceEntry(
id: UserPageVisibility.PRIVATE,
title: tr("Private"),
subtitle: tr("Private, accessible only to your friends")),
MultiChoiceEntry(
id: UserPageVisibility.PUBLIC,
title: tr("Public"),
subtitle: tr("Public, accessible to all Comunic members")),
MultiChoiceEntry(
id: UserPageVisibility.OPEN,
title: tr("Open"),
subtitle:
tr("Accessible to everyone, including non-Comunic users")),
];
/// Build page settings tile
List<SettingsTile> _pageSettingsTiles() {
return [
// Page visibility
MultiChoicesSettingsTile(
title: tr("Page visibility"),
choices: _visibilityChoices,
currentValue: _settings.pageVisibility,
onChanged: (v) {
_settings.pageVisibility = v;
_updateSettings();
}),
// Allow comments on user page ?
SettingsTile.switchTile(
title: tr("Allow comments on your page"),
onToggle: (v) {
_settings.allowComments = v;
_updateSettings();
},
switchValue: _settings.allowComments,
),
// Allow posts from friends
SettingsTile.switchTile(
title: tr("Allow posts from your friends on your page"),
onToggle: (v) {
_settings.allowPostsFromFriends = v;
_updateSettings();
},
switchValue: _settings.allowPostsFromFriends,
),
// Make friends list public
SettingsTile.switchTile(
title: tr("Make your friends list public"),
onToggle: (v) {
_settings.publicFriendsList = v;
_updateSettings();
},
switchValue: _settings.publicFriendsList,
),
// Personal website
TextEditSettingsTile(
title: tr("Personal website URL (optional)"),
currValue: _settings.personalWebsite,
onChanged: (v) {
_settings.personalWebsite = v;
_updateSettings();
},
checkInput: (s) => validateUrl(s),
allowEmptyValues: true,
),
// Public notes
TextEditSettingsTile(
title: tr("Public note (optional)"),
currValue: _settings.publicNote,
onChanged: (v) {
_settings.publicNote = v;
_updateSettings();
},
allowEmptyValues: true,
maxLength: 255,
maxLines: 3,
),
// Virtual directory
SettingsTile(
title: tr("Virtual directory (optional)"),
subtitle: _settings.virtualDirectory,
onTap: () async {
final dir = await showVirtualDirectoryDialog(
context: context,
initialDirectory: _settings.virtualDirectory,
type: VirtualDirectoryTargetType.USER,
id: userID());
if (dir == null) return;
_settings.virtualDirectory = dir;
_updateSettings();
},
),
];
}
/// Apply new settings
Future<void> _updateSettings() async {
try {
await SettingsHelper.updateGeneralSettings(_settings);
} catch (e, stack) {
print("Error while updating settings! $e/n$stack");
showSimpleSnack(context, tr("Could not update general settings!"));
}
_key.currentState.refresh();
}
}