refactor of usecases

This commit is contained in:
2026-02-23 17:18:50 +05:30
parent 56666ece30
commit 13f8003bda
37 changed files with 1563 additions and 105 deletions

View File

@@ -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(),
);
}
}

View File

@@ -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,

View File

@@ -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];
}

View File

@@ -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!;
}

View File

@@ -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);
}
},
),
],
),
),
),
);
}
}

View File

@@ -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,
),
],
);
}
}

View File

@@ -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),
),
),
),
],
),
),