refactor of usecases
This commit is contained in:
@@ -6,6 +6,7 @@ import 'src/domain/repositories/settings_repository_interface.dart';
|
||||
import 'src/domain/usecases/sign_out_usecase.dart';
|
||||
import 'src/presentation/blocs/client_settings_bloc.dart';
|
||||
import 'src/presentation/pages/client_settings_page.dart';
|
||||
import 'src/presentation/pages/edit_profile_page.dart';
|
||||
|
||||
/// A [Module] for the client settings feature.
|
||||
class ClientSettingsModule extends Module {
|
||||
@@ -30,5 +31,9 @@ class ClientSettingsModule extends Module {
|
||||
ClientPaths.childRoute(ClientPaths.settings, ClientPaths.settings),
|
||||
child: (_) => const ClientSettingsPage(),
|
||||
);
|
||||
r.child(
|
||||
'/edit-profile',
|
||||
child: (_) => const EditProfilePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,23 @@ class ClientSettingsBloc extends Bloc<ClientSettingsEvent, ClientSettingsState>
|
||||
: _signOutUseCase = signOutUseCase,
|
||||
super(const ClientSettingsInitial()) {
|
||||
on<ClientSettingsSignOutRequested>(_onSignOutRequested);
|
||||
on<ClientSettingsNotificationToggled>(_onNotificationToggled);
|
||||
}
|
||||
final SignOutUseCase _signOutUseCase;
|
||||
|
||||
void _onNotificationToggled(
|
||||
ClientSettingsNotificationToggled event,
|
||||
Emitter<ClientSettingsState> emit,
|
||||
) {
|
||||
if (event.type == 'push') {
|
||||
emit(state.copyWith(pushEnabled: event.isEnabled));
|
||||
} else if (event.type == 'email') {
|
||||
emit(state.copyWith(emailEnabled: event.isEnabled));
|
||||
} else if (event.type == 'sms') {
|
||||
emit(state.copyWith(smsEnabled: event.isEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSignOutRequested(
|
||||
ClientSettingsSignOutRequested event,
|
||||
Emitter<ClientSettingsState> emit,
|
||||
|
||||
@@ -10,3 +10,15 @@ abstract class ClientSettingsEvent extends Equatable {
|
||||
class ClientSettingsSignOutRequested extends ClientSettingsEvent {
|
||||
const ClientSettingsSignOutRequested();
|
||||
}
|
||||
|
||||
class ClientSettingsNotificationToggled extends ClientSettingsEvent {
|
||||
const ClientSettingsNotificationToggled({
|
||||
required this.type,
|
||||
required this.isEnabled,
|
||||
});
|
||||
final String type;
|
||||
final bool isEnabled;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[type, isEnabled];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,49 @@
|
||||
part of 'client_settings_bloc.dart';
|
||||
|
||||
abstract class ClientSettingsState extends Equatable {
|
||||
const ClientSettingsState();
|
||||
class ClientSettingsState extends Equatable {
|
||||
const ClientSettingsState({
|
||||
this.isLoading = false,
|
||||
this.isSignOutSuccess = false,
|
||||
this.errorMessage,
|
||||
this.pushEnabled = true,
|
||||
this.emailEnabled = false,
|
||||
this.smsEnabled = true,
|
||||
});
|
||||
|
||||
final bool isLoading;
|
||||
final bool isSignOutSuccess;
|
||||
final String? errorMessage;
|
||||
final bool pushEnabled;
|
||||
final bool emailEnabled;
|
||||
final bool smsEnabled;
|
||||
|
||||
ClientSettingsState copyWith({
|
||||
bool? isLoading,
|
||||
bool? isSignOutSuccess,
|
||||
String? errorMessage,
|
||||
bool? pushEnabled,
|
||||
bool? emailEnabled,
|
||||
bool? smsEnabled,
|
||||
}) {
|
||||
return ClientSettingsState(
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
isSignOutSuccess: isSignOutSuccess ?? this.isSignOutSuccess,
|
||||
errorMessage: errorMessage, // We reset error on copy
|
||||
pushEnabled: pushEnabled ?? this.pushEnabled,
|
||||
emailEnabled: emailEnabled ?? this.emailEnabled,
|
||||
smsEnabled: smsEnabled ?? this.smsEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[];
|
||||
List<Object?> get props => <Object?>[
|
||||
isLoading,
|
||||
isSignOutSuccess,
|
||||
errorMessage,
|
||||
pushEnabled,
|
||||
emailEnabled,
|
||||
smsEnabled,
|
||||
];
|
||||
}
|
||||
|
||||
class ClientSettingsInitial extends ClientSettingsState {
|
||||
@@ -12,18 +51,14 @@ class ClientSettingsInitial extends ClientSettingsState {
|
||||
}
|
||||
|
||||
class ClientSettingsLoading extends ClientSettingsState {
|
||||
const ClientSettingsLoading();
|
||||
const ClientSettingsLoading({super.pushEnabled, super.emailEnabled, super.smsEnabled}) : super(isLoading: true);
|
||||
}
|
||||
|
||||
class ClientSettingsSignOutSuccess extends ClientSettingsState {
|
||||
const ClientSettingsSignOutSuccess();
|
||||
const ClientSettingsSignOutSuccess() : super(isSignOutSuccess: true);
|
||||
}
|
||||
|
||||
class ClientSettingsError extends ClientSettingsState {
|
||||
|
||||
const ClientSettingsError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[message];
|
||||
const ClientSettingsError(String message) : super(errorMessage: message);
|
||||
String get message => errorMessage!;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
class EditProfilePage extends StatefulWidget {
|
||||
const EditProfilePage({super.key});
|
||||
|
||||
@override
|
||||
State<EditProfilePage> createState() => _EditProfilePageState();
|
||||
}
|
||||
|
||||
class _EditProfilePageState extends State<EditProfilePage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController _firstNameController;
|
||||
late TextEditingController _lastNameController;
|
||||
late TextEditingController _emailController;
|
||||
late TextEditingController _phoneController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Simulate current data
|
||||
_firstNameController = TextEditingController(text: 'John');
|
||||
_lastNameController = TextEditingController(text: 'Smith');
|
||||
_emailController = TextEditingController(text: 'john@smith.com');
|
||||
_phoneController = TextEditingController(text: '+1 (555) 123-4567');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_firstNameController.dispose();
|
||||
_lastNameController.dispose();
|
||||
_emailController.dispose();
|
||||
_phoneController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t.client_settings.edit_profile.title),
|
||||
elevation: 0,
|
||||
backgroundColor: UiColors.white,
|
||||
foregroundColor: UiColors.primary,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: UiColors.bgSecondary,
|
||||
child: const Icon(UiIcons.user, size: 40, color: UiColors.primary),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space2),
|
||||
decoration: const BoxDecoration(
|
||||
color: UiColors.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(UiIcons.edit, size: 16, color: UiColors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
Text(
|
||||
context.t.client_settings.edit_profile.first_name,
|
||||
style: UiTypography.footnote2b.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
UiTextField(
|
||||
controller: _firstNameController,
|
||||
hintText: 'First Name',
|
||||
validator: (String? val) => (val?.isEmpty ?? true) ? 'Required' : null,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
Text(
|
||||
context.t.client_settings.edit_profile.last_name,
|
||||
style: UiTypography.footnote2b.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
UiTextField(
|
||||
controller: _lastNameController,
|
||||
hintText: 'Last Name',
|
||||
validator: (String? val) => (val?.isEmpty ?? true) ? 'Required' : null,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
Text(
|
||||
context.t.client_settings.edit_profile.email,
|
||||
style: UiTypography.footnote2b.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
UiTextField(
|
||||
controller: _emailController,
|
||||
hintText: 'Email',
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (String? val) => (val?.isEmpty ?? true) ? 'Required' : null,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
Text(
|
||||
context.t.client_settings.edit_profile.phone,
|
||||
style: UiTypography.footnote2b.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
UiTextField(
|
||||
controller: _phoneController,
|
||||
hintText: 'Phone',
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space10),
|
||||
|
||||
UiButton.primary(
|
||||
text: context.t.client_settings.edit_profile.save_button,
|
||||
fullWidth: true,
|
||||
onPressed: () {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: context.t.client_settings.edit_profile.success_message,
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,42 +17,20 @@ class SettingsActions extends StatelessWidget {
|
||||
final TranslationsClientSettingsProfileEn labels =
|
||||
t.client_settings.profile;
|
||||
|
||||
// Yellow button style matching the prototype
|
||||
final ButtonStyle yellowStyle = ElevatedButton.styleFrom(
|
||||
backgroundColor: UiColors.accent,
|
||||
foregroundColor: UiColors.accentForeground,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate(<Widget>[
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
|
||||
// Edit Profile button (yellow)
|
||||
UiButton.primary(
|
||||
text: labels.edit_profile,
|
||||
style: yellowStyle,
|
||||
onPressed: () {},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Hubs button (yellow)
|
||||
UiButton.primary(
|
||||
text: labels.hubs,
|
||||
style: yellowStyle,
|
||||
onPressed: () => Modular.to.toClientHubs(),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Quick Links card
|
||||
_QuickLinksCard(labels: labels),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Notifications section
|
||||
_NotificationsSettingsCard(),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Log Out button (outlined)
|
||||
BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
|
||||
builder: (BuildContext context, ClientSettingsState state) {
|
||||
@@ -193,3 +171,93 @@ class _QuickLinkItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NotificationsSettingsCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
|
||||
builder: (context, state) {
|
||||
return Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(color: UiColors.border),
|
||||
),
|
||||
color: UiColors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_settings.preferences.title,
|
||||
style: UiTypography.footnote1b.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
_NotificationToggle(
|
||||
icon: UiIcons.bell,
|
||||
title: context.t.client_settings.preferences.push,
|
||||
value: state.pushEnabled,
|
||||
onChanged: (val) => ReadContext(context).read<ClientSettingsBloc>().add(
|
||||
ClientSettingsNotificationToggled(type: 'push', isEnabled: val),
|
||||
),
|
||||
),
|
||||
_NotificationToggle(
|
||||
icon: UiIcons.mail,
|
||||
title: context.t.client_settings.preferences.email,
|
||||
value: state.emailEnabled,
|
||||
onChanged: (val) => ReadContext(context).read<ClientSettingsBloc>().add(
|
||||
ClientSettingsNotificationToggled(type: 'email', isEnabled: val),
|
||||
),
|
||||
),
|
||||
_NotificationToggle(
|
||||
icon: UiIcons.phone,
|
||||
title: context.t.client_settings.preferences.sms,
|
||||
value: state.smsEnabled,
|
||||
onChanged: (val) => ReadContext(context).read<ClientSettingsBloc>().add(
|
||||
ClientSettingsNotificationToggled(type: 'sms', isEnabled: val),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NotificationToggle extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final bool value;
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
const _NotificationToggle({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 20, color: UiColors.iconSecondary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Text(title, style: UiTypography.footnote1m.textPrimary),
|
||||
],
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: value,
|
||||
activeColor: UiColors.primary,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,21 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 100),
|
||||
child: UiButton.secondary(
|
||||
text: labels.edit_profile,
|
||||
size: UiButtonSize.small,
|
||||
onPressed: () =>
|
||||
Modular.to.pushNamed('${ClientPaths.settings}/edit-profile'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: UiColors.white,
|
||||
side: const BorderSide(color: UiColors.white, width: 1.5),
|
||||
backgroundColor: UiColors.white.withValues(alpha: 0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user