finalcommitform4
This commit is contained in:
@@ -42,6 +42,7 @@ class ClientSettingsPage extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
child: const Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SettingsProfileHeader(),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user