redesign for settings page

This commit is contained in:
Arseni
2026-03-13 23:01:57 +03:00
parent 70bd7a6214
commit d601f245d4
36 changed files with 1151 additions and 262 deletions

View File

@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pweb/controllers/settings/profile_actions.dart';
import 'package:pweb/pages/settings/profile/actions/buttons.dart';
import 'package:pweb/pages/settings/profile/actions/constants.dart';
import 'package:pweb/pages/settings/profile/actions/content.dart';
class ProfileActionsSectionBody extends StatelessWidget {
const ProfileActionsSectionBody({
super.key,
required this.nameLabel,
required this.languageLabel,
required this.passwordLabel,
required this.passwordSuccessText,
required this.passwordErrorText,
required this.oldPasswordLabel,
required this.newPasswordLabel,
required this.confirmPasswordLabel,
required this.savePasswordLabel,
});
final String nameLabel;
final String languageLabel;
final String passwordLabel;
final String passwordSuccessText;
final String passwordErrorText;
final String oldPasswordLabel;
final String newPasswordLabel;
final String confirmPasswordLabel;
final String savePasswordLabel;
@override
Widget build(BuildContext context) {
final controller = context.watch<ProfileActionsController>();
final expandedSection = controller.expandedSection;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ProfileActionButtons(
nameLabel: nameLabel,
languageLabel: languageLabel,
passwordLabel: passwordLabel,
),
if (expandedSection != null) ...[
const SizedBox(height: ProfileActionsLayoutConstants.contentGap),
ProfileActionsContent(
section: expandedSection,
passwordSuccessText: passwordSuccessText,
passwordErrorText: passwordErrorText,
oldPasswordLabel: oldPasswordLabel,
newPasswordLabel: newPasswordLabel,
confirmPasswordLabel: confirmPasswordLabel,
savePasswordLabel: savePasswordLabel,
),
],
],
);
}
}

View File

@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
class ProfileActionButton extends StatelessWidget {
const ProfileActionButton({
super.key,
required this.icon,
required this.label,
required this.isSelected,
required this.onPressed,
});
final IconData icon;
final String label;
final bool isSelected;
final VoidCallback onPressed;
static const _buttonPadding = EdgeInsets.symmetric(
horizontal: 28,
vertical: 24,
);
static const _iconSize = 28.0;
static const _contentGap = 12.0;
static const _borderRadius = 16.0;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final backgroundColor = colorScheme.onSecondary;
final borderColor = isSelected
? colorScheme.primary
: colorScheme.onPrimary;
final textColor = colorScheme.primary;
return TextButton(
onPressed: onPressed,
style: TextButton.styleFrom(
padding: _buttonPadding,
backgroundColor: backgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_borderRadius),
side: BorderSide(color: borderColor),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: textColor, size: _iconSize),
const SizedBox(height: _contentGap),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:pweb/pages/settings/profile/actions/buttons_layout.dart';
import 'package:pweb/pages/settings/profile/actions/language_button.dart';
import 'package:pweb/pages/settings/profile/actions/name_button.dart';
import 'package:pweb/pages/settings/profile/actions/password_button.dart';
class ProfileActionButtons extends StatelessWidget {
const ProfileActionButtons({
super.key,
required this.nameLabel,
required this.languageLabel,
required this.passwordLabel,
});
final String nameLabel;
final String languageLabel;
final String passwordLabel;
@override
Widget build(BuildContext context) {
return ProfileActionButtonsLayout(
children: [
ProfileNameActionButton(label: nameLabel),
ProfileLanguageActionButton(label: languageLabel),
ProfilePasswordActionButton(label: passwordLabel),
],
);
}
}

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:pweb/pages/settings/profile/actions/constants.dart';
class ProfileActionButtonsLayout extends StatelessWidget {
const ProfileActionButtonsLayout({super.key, required this.children});
final List<Widget> children;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final isCompact =
constraints.maxWidth <
ProfileActionsLayoutConstants.compactBreakpoint;
if (isCompact) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: ProfileActionsLayoutConstants.buttonWidth,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: _buildChildren(isCompact: true),
),
),
);
}
return Row(
mainAxisSize: MainAxisSize.min,
children: _buildChildren(isCompact: false),
);
},
);
}
List<Widget> _buildChildren({required bool isCompact}) {
return [
for (var index = 0; index < children.length; index++) ...[
SizedBox(
width: isCompact
? double.infinity
: ProfileActionsLayoutConstants.buttonWidth,
child: children[index],
),
if (index != children.length - 1)
SizedBox(
width: isCompact ? 0 : ProfileActionsLayoutConstants.buttonGap,
height: isCompact ? ProfileActionsLayoutConstants.buttonGap : 0,
),
],
];
}
}

View File

@@ -0,0 +1,6 @@
class ProfileActionsLayoutConstants {
static const buttonGap = 12.0;
static const contentGap = 16.0;
static const buttonWidth = 180.0;
static const compactBreakpoint = buttonWidth * 3 + buttonGap * 2;
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:pweb/models/settings/profile_action_section.dart';
import 'package:pweb/pages/settings/profile/account/locale.dart';
import 'package:pweb/pages/settings/profile/account/password/password.dart';
class ProfileActionsContent extends StatelessWidget {
const ProfileActionsContent({
super.key,
required this.section,
required this.passwordSuccessText,
required this.passwordErrorText,
required this.oldPasswordLabel,
required this.newPasswordLabel,
required this.confirmPasswordLabel,
required this.savePasswordLabel,
});
final ProfileActionSection section;
final String passwordSuccessText;
final String passwordErrorText;
final String oldPasswordLabel;
final String newPasswordLabel;
final String confirmPasswordLabel;
final String savePasswordLabel;
@override
Widget build(BuildContext context) {
switch (section) {
case ProfileActionSection.language:
return const LocalePicker();
case ProfileActionSection.password:
return AccountPassword(
successText: passwordSuccessText,
errorText: passwordErrorText,
oldPasswordLabel: oldPasswordLabel,
newPasswordLabel: newPasswordLabel,
confirmPasswordLabel: confirmPasswordLabel,
savePassword: savePasswordLabel,
);
}
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pweb/controllers/settings/profile_actions.dart';
import 'package:pweb/models/settings/profile_action_section.dart';
import 'package:pweb/pages/settings/profile/actions/button.dart';
class ProfileLanguageActionButton extends StatelessWidget {
const ProfileLanguageActionButton({super.key, required this.label});
final String label;
@override
Widget build(BuildContext context) {
final controller = context.watch<ProfileActionsController>();
return ProfileActionButton(
icon: Icons.language_outlined,
label: label,
isSelected: controller.isExpanded(ProfileActionSection.language),
onPressed: () => controller.toggle(ProfileActionSection.language),
);
}
}

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pweb/controllers/settings/profile_actions.dart';
import 'package:pweb/pages/settings/profile/actions/button.dart';
class ProfileNameActionButton extends StatelessWidget {
const ProfileNameActionButton({super.key, required this.label});
final String label;
@override
Widget build(BuildContext context) {
final controller = context.watch<ProfileActionsController>();
return ProfileActionButton(
icon: Icons.edit_outlined,
label: label,
isSelected: controller.isEditingName,
onPressed: controller.toggleNameEditing,
);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pweb/controllers/settings/profile_actions.dart';
import 'package:pweb/models/settings/profile_action_section.dart';
import 'package:pweb/pages/settings/profile/actions/button.dart';
class ProfilePasswordActionButton extends StatelessWidget {
const ProfilePasswordActionButton({super.key, required this.label});
final String label;
@override
Widget build(BuildContext context) {
final controller = context.watch<ProfileActionsController>();
return ProfileActionButton(
icon: Icons.lock_outline,
label: label,
isSelected: controller.isExpanded(ProfileActionSection.password),
onPressed: () => controller.toggle(ProfileActionSection.password),
);
}
}

View File

@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pweb/controllers/auth/account_name.dart';
import 'package:pweb/controllers/settings/profile_actions.dart';
import 'package:pweb/pages/settings/profile/actions/body.dart';
class ProfileActionsSection extends StatelessWidget {
const ProfileActionsSection({
super.key,
required this.nameLabel,
required this.languageLabel,
required this.passwordLabel,
required this.passwordSuccessText,
required this.passwordErrorText,
required this.oldPasswordLabel,
required this.newPasswordLabel,
required this.confirmPasswordLabel,
required this.savePasswordLabel,
});
final String nameLabel;
final String languageLabel;
final String passwordLabel;
final String passwordSuccessText;
final String passwordErrorText;
final String oldPasswordLabel;
final String newPasswordLabel;
final String confirmPasswordLabel;
final String savePasswordLabel;
@override
Widget build(BuildContext context) {
return ChangeNotifierProxyProvider<
AccountNameController,
ProfileActionsController
>(
create: (_) => ProfileActionsController(),
update: (_, accountNameController, controller) =>
controller!..updateAccountNameController(accountNameController),
child: ProfileActionsSectionBody(
nameLabel: nameLabel,
languageLabel: languageLabel,
passwordLabel: passwordLabel,
passwordSuccessText: passwordSuccessText,
passwordErrorText: passwordErrorText,
oldPasswordLabel: oldPasswordLabel,
newPasswordLabel: newPasswordLabel,
confirmPasswordLabel: confirmPasswordLabel,
savePasswordLabel: savePasswordLabel,
),
);
}
}