finalcommitform4

This commit is contained in:
2026-02-19 16:09:54 +05:30
parent da8f9a4436
commit 9e9eb0f374
21 changed files with 799 additions and 234 deletions

View File

@@ -42,6 +42,7 @@ class ClientSettingsPage extends StatelessWidget {
}
},
child: const Scaffold(
backgroundColor: UiColors.bgMenu,
body: CustomScrollView(
slivers: <Widget>[
SettingsProfileHeader(),

View File

@@ -14,27 +14,46 @@ class SettingsActions extends StatelessWidget {
@override
/// Builds the settings actions UI.
Widget build(BuildContext context) {
// Get the translations for the client settings profile.
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),
/// TODO: FEATURE_NOT_YET_IMPLEMENTED
// Edit profile is not yet implemented
// Edit Profile button (yellow)
UiButton.primary(
text: labels.edit_profile,
style: yellowStyle,
onPressed: () {},
),
const SizedBox(height: UiConstants.space4),
// Hubs button
// Hubs button (yellow)
UiButton.primary(
text: labels.hubs,
style: yellowStyle,
onPressed: () => Modular.to.toClientHubs(),
),
const SizedBox(height: UiConstants.space4),
// Log out button
// Quick Links card
_QuickLinksCard(labels: labels),
const SizedBox(height: UiConstants.space4),
// Log Out button (outlined)
BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
builder: (BuildContext context, ClientSettingsState state) {
return UiButton.secondary(
@@ -45,17 +64,11 @@ class SettingsActions extends StatelessWidget {
);
},
),
const SizedBox(height: UiConstants.space8),
]),
),
);
}
/// Handles the sign-out button click event.
void _onSignoutClicked(BuildContext context) {
ReadContext(
context,
).read<ClientSettingsBloc>().add(const ClientSettingsSignOutRequested());
}
/// Shows a confirmation dialog for signing out.
Future<void> _showSignOutDialog(BuildContext context) {
@@ -74,13 +87,10 @@ class SettingsActions extends StatelessWidget {
style: UiTypography.body2r.textSecondary,
),
actions: <Widget>[
// Log out button
UiButton.secondary(
text: t.client_settings.profile.log_out,
onPressed: () => _onSignoutClicked(context),
),
// Cancel button
UiButton.secondary(
text: t.common.cancel,
onPressed: () => Modular.to.pop(),
@@ -89,4 +99,97 @@ class SettingsActions extends StatelessWidget {
),
);
}
/// Handles the sign-out button click event.
void _onSignoutClicked(BuildContext context) {
ReadContext(context)
.read<ClientSettingsBloc>()
.add(const ClientSettingsSignOutRequested());
}
}
/// Quick Links card — inline here since it's always part of SettingsActions ordering.
class _QuickLinksCard extends StatelessWidget {
final TranslationsClientSettingsProfileEn labels;
const _QuickLinksCard({required this.labels});
@override
Widget build(BuildContext context) {
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(
labels.quick_links,
style: UiTypography.footnote1b.textPrimary,
),
const SizedBox(height: UiConstants.space3),
_QuickLinkItem(
icon: UiIcons.nfc,
title: labels.clock_in_hubs,
onTap: () => Modular.to.toClientHubs(),
),
_QuickLinkItem(
icon: UiIcons.building,
title: labels.billing_payments,
onTap: () => Modular.to.toClientBilling(),
),
],
),
),
);
}
}
/// A single quick link row item.
class _QuickLinkItem extends StatelessWidget {
final IconData icon;
final String title;
final VoidCallback onTap;
const _QuickLinkItem({
required this.icon,
required this.title,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: UiConstants.radiusMd,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space3,
horizontal: UiConstants.space2,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
Icon(icon, size: 20, color: UiColors.iconSecondary),
const SizedBox(width: UiConstants.space3),
Text(title, style: UiTypography.footnote1m.textPrimary),
],
),
const Icon(
UiIcons.chevronRight,
size: 20,
color: UiColors.iconThird,
),
],
),
),
);
}
}

View File

@@ -11,7 +11,6 @@ class SettingsProfileHeader extends StatelessWidget {
const SettingsProfileHeader({super.key});
@override
/// Builds the profile header UI.
Widget build(BuildContext context) {
final TranslationsClientSettingsProfileEn labels = t.client_settings.profile;
final dc.ClientSession? session = dc.ClientSessionStore.instance.session;
@@ -23,78 +22,115 @@ class SettingsProfileHeader extends StatelessWidget {
? businessName.trim()[0].toUpperCase()
: 'C';
return SliverAppBar(
backgroundColor: UiColors.bgSecondary,
expandedHeight: 140,
pinned: true,
elevation: 0,
shape: const Border(bottom: BorderSide(color: UiColors.border, width: 1)),
leading: IconButton(
icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
onPressed: () => Modular.to.toClientHome(),
),
flexibleSpace: FlexibleSpaceBar(
background: Container(
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space8),
margin: const EdgeInsets.only(top: UiConstants.space24),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
spacing: UiConstants.space4,
children: <Widget>[
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: UiColors.border, width: 2),
color: UiColors.white,
return SliverToBoxAdapter(
child: Container(
width: double.infinity,
padding: const EdgeInsets.only(bottom: 36),
decoration: const BoxDecoration(
color: UiColors.primary,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// ── Top bar: back arrow + title ──────────────────
SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space2,
),
child: CircleAvatar(
backgroundColor: UiColors.primary.withValues(alpha: 0.1),
backgroundImage:
photoUrl != null && photoUrl.isNotEmpty
? NetworkImage(photoUrl)
: null,
child:
photoUrl != null && photoUrl.isNotEmpty
? null
: Text(
avatarLetter,
style: UiTypography.headline1m.copyWith(
color: UiColors.primary,
),
),
child: Row(
children: <Widget>[
GestureDetector(
onTap: () => Modular.to.toClientHome(),
child: const Icon(
UiIcons.arrowLeft,
color: UiColors.white,
size: 22,
),
),
const SizedBox(width: UiConstants.space3),
Text(
labels.title,
style: UiTypography.body1b.copyWith(
color: UiColors.white,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(businessName, style: UiTypography.body1b.textPrimary),
const SizedBox(height: UiConstants.space1),
Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: UiConstants.space1,
children: <Widget>[
Icon(
UiIcons.mail,
size: 14,
color: UiColors.textSecondary,
),
Text(
email,
style: UiTypography.footnote1r.textSecondary,
),
],
),
const SizedBox(height: UiConstants.space6),
// ── Avatar ───────────────────────────────────────
Container(
width: 88,
height: 88,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: UiColors.white,
border: Border.all(
color: UiColors.white.withValues(alpha: 0.6),
width: 3,
),
boxShadow: [
BoxShadow(
color: UiColors.black.withValues(alpha: 0.15),
blurRadius: 16,
offset: const Offset(0, 6),
),
],
),
],
),
child: ClipOval(
child: photoUrl != null && photoUrl.isNotEmpty
? Image.network(photoUrl, fit: BoxFit.cover)
: Center(
child: Text(
avatarLetter,
style: UiTypography.headline1m.copyWith(
color: UiColors.primary,
fontSize: 32,
),
),
),
),
),
const SizedBox(height: UiConstants.space4),
// ── Business Name ─────────────────────────────────
Text(
businessName,
style: UiTypography.headline3m.copyWith(
color: UiColors.white,
),
),
const SizedBox(height: UiConstants.space2),
// ── Email ─────────────────────────────────────────
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
UiIcons.mail,
size: 14,
color: UiColors.white.withValues(alpha: 0.75),
),
const SizedBox(width: 6),
Text(
email,
style: UiTypography.footnote1r.copyWith(
color: UiColors.white.withValues(alpha: 0.75),
),
),
],
),
],
),
),
title: Text(labels.title, style: UiTypography.body1b.textPrimary),
);
}
}