mirror of
https://gitlab.com/comunic/comunicmobile
synced 2025-06-19 08:15:16 +00:00
Refactor settings files
This commit is contained in:
225
lib/ui/routes/settings/account_image_settings.dart
Normal file
225
lib/ui/routes/settings/account_image_settings.dart
Normal 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();
|
||||
}
|
||||
}
|
93
lib/ui/routes/settings/account_privacy_settings.dart
Normal file
93
lib/ui/routes/settings/account_privacy_settings.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
216
lib/ui/routes/settings/account_security_settings.dart
Normal file
216
lib/ui/routes/settings/account_security_settings.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
97
lib/ui/routes/settings/account_settings_route.dart
Normal file
97
lib/ui/routes/settings/account_settings_route.dart
Normal 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));
|
||||
}
|
||||
}
|
109
lib/ui/routes/settings/application_settings.dart
Normal file
109
lib/ui/routes/settings/application_settings.dart
Normal 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();
|
||||
}
|
||||
}
|
223
lib/ui/routes/settings/custom_emojies_account_settings.dart
Normal file
223
lib/ui/routes/settings/custom_emojies_account_settings.dart
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
239
lib/ui/routes/settings/general_account_settings.dart
Normal file
239
lib/ui/routes/settings/general_account_settings.dart
Normal 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();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user