Code 1 :
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class AppSpacing {
static const double xs = 4.0;
static const double sm = 8.0;
static const double md = 16.0;
static const double lg = 24.0;
static const double xl = 32.0;
static const double xxl = 48.0;
static const EdgeInsets paddingXs = EdgeInsets.all(xs);
static const EdgeInsets paddingSm = EdgeInsets.all(sm);
static const EdgeInsets paddingMd = EdgeInsets.all(md);
static const EdgeInsets paddingLg = EdgeInsets.all(lg);
static const EdgeInsets paddingXl = EdgeInsets.all(xl);
static const EdgeInsets horizontalXs = EdgeInsets.symmetric(horizontal: xs);
static const EdgeInsets horizontalSm = EdgeInsets.symmetric(horizontal: sm);
static const EdgeInsets horizontalMd = EdgeInsets.symmetric(horizontal: md);
static const EdgeInsets horizontalLg = EdgeInsets.symmetric(horizontal: lg);
static const EdgeInsets horizontalXl = EdgeInsets.symmetric(horizontal: xl);
static const EdgeInsets verticalXs = EdgeInsets.symmetric(vertical: xs);
static const EdgeInsets verticalSm = EdgeInsets.symmetric(vertical: sm);
static const EdgeInsets verticalMd = EdgeInsets.symmetric(vertical: md);
static const EdgeInsets verticalLg = EdgeInsets.symmetric(vertical: lg);
static const EdgeInsets verticalXl = EdgeInsets.symmetric(vertical: xl);
}
class AppRadius {
static const double sm = 8.0;
static const double md = 12.0;
static const double lg = 16.0;
static const double xl = 24.0;
}
extension TextStyleContext on BuildContext {
TextTheme get textStyles => Theme.of(this).textTheme;
}
extension TextStyleExtensions on TextStyle {
TextStyle get bold => copyWith(fontWeight: FontWeight.bold);
TextStyle get semiBold => copyWith(fontWeight: FontWeight.w600);
TextStyle get medium => copyWith(fontWeight: FontWeight.w500);
TextStyle get normal => copyWith(fontWeight: FontWeight.w400);
TextStyle get light => copyWith(fontWeight: FontWeight.w300);
TextStyle withColor(Color color) => copyWith(color: color);
TextStyle withSize(double size) => copyWith(fontSize: size);
}
/// Minimal Mauritanian-inspired palette (flag: green + gold), kept intentionally calm.
class LightModeColors {
static const lightPrimary = Color(0xFF0F6B3A);
static const lightOnPrimary = Color(0xFFFFFFFF);
static const lightPrimaryContainer = Color(0xFFDDF4E8);
static const lightOnPrimaryContainer = Color(0xFF062716);
static const lightSecondary = Color(0xFFD6A100);
static const lightOnSecondary = Color(0xFF1B1400);
static const lightTertiary = Color(0xFF1B2A24);
static const lightOnTertiary = Color(0xFFFFFFFF);
static const lightError = Color(0xFFBA1A1A);
static const lightOnError = Color(0xFFFFFFFF);
static const lightErrorContainer = Color(0xFFFFDAD6);
static const lightOnErrorContainer = Color(0xFF410002);
static const lightSurface = Color(0xFFFFFFFF);
static const lightOnSurface = Color(0xFF0E1512);
static const lightBackground = Color(0xFFF7F5EF);
static const lightSurfaceVariant = Color(0xFFE9E6DD);
static const lightOnSurfaceVariant = Color(0xFF3C4A44);
static const lightOutline = Color(0xFF7C8B84);
static const lightShadow = Color(0xFF000000);
static const lightInversePrimary = Color(0xFF8AD8AE);
}
class DarkModeColors {
static const darkPrimary = Color(0xFF4CD58C);
static const darkOnPrimary = Color(0xFF062816);
static const darkPrimaryContainer = Color(0xFF0A3D22);
static const darkOnPrimaryContainer = Color(0xFFCFF3E0);
static const darkSecondary = Color(0xFFFFD16A);
static const darkOnSecondary = Color(0xFF261A00);
static const darkTertiary = Color(0xFFE7E2D6);
static const darkOnTertiary = Color(0xFF13130F);
static const darkError = Color(0xFFFFB4AB);
static const darkOnError = Color(0xFF690005);
static const darkErrorContainer = Color(0xFF93000A);
static const darkOnErrorContainer = Color(0xFFFFDAD6);
static const darkSurface = Color(0xFF0D1411);
static const darkOnSurface = Color(0xFFEAF1ED);
static const darkSurfaceVariant = Color(0xFF1C2622);
static const darkOnSurfaceVariant = Color(0xFFB8C7C0);
static const darkOutline = Color(0xFF5D7067);
static const darkShadow = Color(0xFF000000);
static const darkInversePrimary = Color(0xFF0F6B3A);
}
class FontSizes {
static const double displayLarge = 57.0;
static const double displayMedium = 45.0;
static const double displaySmall = 36.0;
static const double headlineLarge = 32.0;
static const double headlineMedium = 28.0;
static const double headlineSmall = 24.0;
static const double titleLarge = 22.0;
static const double titleMedium = 16.0;
static const double titleSmall = 14.0;
static const double labelLarge = 14.0;
static const double labelMedium = 12.0;
static const double labelSmall = 11.0;
static const double bodyLarge = 16.0;
static const double bodyMedium = 14.0;
static const double bodySmall = 12.0;
}
ThemeData get lightTheme => ThemeData(
useMaterial3: true,
splashFactory: NoSplash.splashFactory,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
colorScheme: const ColorScheme.light(
primary: LightModeColors.lightPrimary,
onPrimary: LightModeColors.lightOnPrimary,
primaryContainer: LightModeColors.lightPrimaryContainer,
onPrimaryContainer: LightModeColors.lightOnPrimaryContainer,
secondary: LightModeColors.lightSecondary,
onSecondary: LightModeColors.lightOnSecondary,
tertiary: LightModeColors.lightTertiary,
onTertiary: LightModeColors.lightOnTertiary,
error: LightModeColors.lightError,
onError: LightModeColors.lightOnError,
errorContainer: LightModeColors.lightErrorContainer,
onErrorContainer: LightModeColors.lightOnErrorContainer,
surface: LightModeColors.lightSurface,
onSurface: LightModeColors.lightOnSurface,
surfaceContainerHighest: LightModeColors.lightSurfaceVariant,
onSurfaceVariant: LightModeColors.lightOnSurfaceVariant,
outline: LightModeColors.lightOutline,
shadow: LightModeColors.lightShadow,
inversePrimary: LightModeColors.lightInversePrimary,
),
brightness: Brightness.light,
scaffoldBackgroundColor: LightModeColors.lightBackground,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
foregroundColor: LightModeColors.lightOnSurface,
elevation: 0,
scrolledUnderElevation: 0,
),
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: LightModeColors.lightOutline.withValues(alpha: 0.20), width: 1),
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))),
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 18, vertical: 14)),
textStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontWeight: FontWeight.w700)),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))),
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 18, vertical: 14)),
textStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontWeight: FontWeight.w700)),
),
),
textTheme: _buildTextTheme(Brightness.light),
);
ThemeData get darkTheme => ThemeData(
useMaterial3: true,
splashFactory: NoSplash.splashFactory,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
colorScheme: const ColorScheme.dark(
primary: DarkModeColors.darkPrimary,
onPrimary: DarkModeColors.darkOnPrimary,
primaryContainer: DarkModeColors.darkPrimaryContainer,
onPrimaryContainer: DarkModeColors.darkOnPrimaryContainer,
secondary: DarkModeColors.darkSecondary,
onSecondary: DarkModeColors.darkOnSecondary,
tertiary: DarkModeColors.darkTertiary,
onTertiary: DarkModeColors.darkOnTertiary,
error: DarkModeColors.darkError,
onError: DarkModeColors.darkOnError,
errorContainer: DarkModeColors.darkErrorContainer,
onErrorContainer: DarkModeColors.darkOnErrorContainer,
surface: DarkModeColors.darkSurface,
onSurface: DarkModeColors.darkOnSurface,
surfaceContainerHighest: DarkModeColors.darkSurfaceVariant,
onSurfaceVariant: DarkModeColors.darkOnSurfaceVariant,
outline: DarkModeColors.darkOutline,
shadow: DarkModeColors.darkShadow,
inversePrimary: DarkModeColors.darkInversePrimary,
),
brightness: Brightness.dark,
scaffoldBackgroundColor: DarkModeColors.darkSurface,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
foregroundColor: DarkModeColors.darkOnSurface,
elevation: 0,
scrolledUnderElevation: 0,
),
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: DarkModeColors.darkOutline.withValues(alpha: 0.20), width: 1),
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))),
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 18, vertical: 14)),
textStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontWeight: FontWeight.w700)),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))),
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 18, vertical: 14)),
textStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontWeight: FontWeight.w700)),
),
),
textTheme: _buildTextTheme(Brightness.dark),
);
TextTheme _buildTextTheme(Brightness brightness) {
// Keep Inter for a modern look; Arabic glyph support relies on system fallback.
return TextTheme(
displayLarge: GoogleFonts.inter(fontSize: FontSizes.displayLarge, fontWeight: FontWeight.w400, letterSpacing: -0.25),
displayMedium: GoogleFonts.inter(fontSize: FontSizes.displayMedium, fontWeight: FontWeight.w400),
displaySmall: GoogleFonts.inter(fontSize: FontSizes.displaySmall, fontWeight: FontWeight.w400),
headlineLarge: GoogleFonts.inter(fontSize: FontSizes.headlineLarge, fontWeight: FontWeight.w600, letterSpacing: -0.5),
headlineMedium: GoogleFonts.inter(fontSize: FontSizes.headlineMedium, fontWeight: FontWeight.w600),
headlineSmall: GoogleFonts.inter(fontSize: FontSizes.headlineSmall, fontWeight: FontWeight.w600),
titleLarge: GoogleFonts.inter(fontSize: FontSizes.titleLarge, fontWeight: FontWeight.w600),
titleMedium: GoogleFonts.inter(fontSize: FontSizes.titleMedium, fontWeight: FontWeight.w500),
titleSmall: GoogleFonts.inter(fontSize: FontSizes.titleSmall, fontWeight: FontWeight.w500),
labelLarge: GoogleFonts.inter(fontSize: FontSizes.labelLarge, fontWeight: FontWeight.w500, letterSpacing: 0.1),
labelMedium: GoogleFonts.inter(fontSize: FontSizes.labelMedium, fontWeight: FontWeight.w500, letterSpacing: 0.5),
labelSmall: GoogleFonts.inter(fontSize: FontSizes.labelSmall, fontWeight: FontWeight.w500, letterSpacing: 0.5),
bodyLarge: GoogleFonts.inter(fontSize: FontSizes.bodyLarge, fontWeight: FontWeight.w400, letterSpacing: 0.15),
bodyMedium: GoogleFonts.inter(fontSize: FontSizes.bodyMedium, fontWeight: FontWeight.w400, letterSpacing: 0.25),
bodySmall: GoogleFonts.inter(fontSize: FontSizes.bodySmall, fontWeight: FontWeight.w400, letterSpacing: 0.4),
);
}
Code 2 :
import 'package:flutter/material.dart';
import 'package:banck/theme.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _hideBalance = true;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Directionality(
textDirection: TextDirection.rtl,
child: SafeArea(
child: CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.sm, AppSpacing.md, 0),
sliver: SliverToBoxAdapter(
child: Row(
children: [
_TopIconButton(icon: Icons.menu_rounded, label: 'القائمة', onPressed: () => _showQuickSheet(context)),
const Spacer(),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('mebanck', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800, color: scheme.tertiary)),
const SizedBox(height: 2),
Text('محفظة بسيطة', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant, height: 1.2)),
],
),
const SizedBox(width: AppSpacing.md),
_TopIconButton(icon: Icons.notifications_none_rounded, label: 'إشعارات', onPressed: () => _showSnack(context, 'لا توجد إشعارات الآن')),
],
),
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.lg, AppSpacing.md, 0),
sliver: SliverToBoxAdapter(
child: TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 520),
curve: Curves.easeOutCubic,
tween: Tween(begin: 0, end: 1),
builder: (context, t, child) => Opacity(opacity: t, child: Transform.translate(offset: Offset(0, (1 - t) * 10), child: child)),
child: BalancePanel(
isHidden: _hideBalance,
onToggle: () => setState(() => _hideBalance = !_hideBalance),
balanceText: _hideBalance ? '•••••' : '12,450 MRU',
subtitle: 'الرصيد المتاح',
),
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.lg)),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
sliver: SliverToBoxAdapter(
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => _showSnack(context, 'إرسال (قريباً)'),
icon: Icon(Icons.call_made_rounded, color: scheme.primary),
label: Text('إرسال', style: TextStyle(color: scheme.primary)),
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: FilledButton.icon(
onPressed: () => _showSnack(context, 'استلام (قريباً)'),
icon: Icon(Icons.call_received_rounded, color: scheme.onPrimary),
label: Text('استلام', style: TextStyle(color: scheme.onPrimary)),
),
),
],
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.lg)),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
sliver: SliverToBoxAdapter(
child: ServicesTwoColumns(
onTap: (id) {
switch (id) {
case 'pay':
_showSnack(context, 'الدفع (قريباً)');
return;
case 'transfer':
_showSnack(context, 'التحويل (قريباً)');
return;
case 'topup':
_showSnack(context, 'شحن الهاتف (قريباً)');
return;
case 'cashout':
_showSnack(context, 'سحب (قريباً)');
return;
case 'bills':
_showSnack(context, 'الفواتير (قريباً)');
return;
case 'more':
_showMoreServices(context);
return;
}
},
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.lg, AppSpacing.md, AppSpacing.xxl),
child: Container(
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
padding: const EdgeInsets.all(AppSpacing.md),
child: Row(
children: [
Container(
width: 42,
height: 42,
decoration: BoxDecoration(color: scheme.primaryContainer, borderRadius: BorderRadius.circular(14)),
child: Icon(Icons.shield_outlined, color: scheme.onPrimaryContainer, size: 22),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('نصيحة', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 2),
Text('لا تشارك رمزك السري، وفعّل القفل إن توفر.', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant, height: 1.4)),
],
),
),
],
),
),
),
),
],
),
),
);
}
void _showSnack(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message, textDirection: TextDirection.rtl), behavior: SnackBarBehavior.floating, showCloseIcon: true),
);
}
void _showQuickSheet(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
showModalBottomSheet<void>(
context: context,
showDragHandle: true,
backgroundColor: scheme.surface,
builder: (context) => Padding(
padding: const EdgeInsets.fromLTRB(AppSpacing.lg, 0, AppSpacing.lg, AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('إجراءات سريعة', style: Theme.of(context).textTheme.titleLarge, textDirection: TextDirection.rtl),
const SizedBox(height: AppSpacing.md),
Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: [
_QuickChip(icon: Icons.qr_code_scanner_rounded, label: 'QR', onTap: () => Navigator.of(context).pop()),
_QuickChip(icon: Icons.person_add_alt_1_rounded, label: 'مستفيد جديد', onTap: () => Navigator.of(context).pop()),
_QuickChip(icon: Icons.support_agent_rounded, label: 'مساعدة', onTap: () => Navigator.of(context).pop()),
],
),
],
),
),
);
}
void _showMoreServices(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
showModalBottomSheet<void>(
context: context,
showDragHandle: true,
backgroundColor: scheme.surface,
builder: (context) => Padding(
padding: const EdgeInsets.fromLTRB(AppSpacing.lg, 0, AppSpacing.lg, AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('خدمات أكثر', style: Theme.of(context).textTheme.titleLarge, textDirection: TextDirection.rtl),
const SizedBox(height: AppSpacing.md),
_SheetRow(icon: Icons.storefront_outlined, title: 'دفع للتجار', onTap: () => Navigator.of(context).pop()),
_SheetRow(icon: Icons.school_outlined, title: 'رسوم المدرسة', onTap: () => Navigator.of(context).pop()),
_SheetRow(icon: Icons.directions_bus_outlined, title: 'مواصلات', onTap: () => Navigator.of(context).pop()),
],
),
),
);
}
}
class BalancePanel extends StatelessWidget {
const BalancePanel({super.key, required this.isHidden, required this.onToggle, required this.balanceText, required this.subtitle});
final bool isHidden;
final VoidCallback onToggle;
final String balanceText;
final String subtitle;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final gradient = LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [scheme.primary, scheme.primary.withValues(alpha: 0.86)],
);
return Container(
decoration: BoxDecoration(gradient: gradient, borderRadius: BorderRadius.circular(AppRadius.xl)),
padding: const EdgeInsets.all(AppSpacing.lg),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(subtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onPrimary.withValues(alpha: 0.90), height: 1.2)),
const SizedBox(height: 8),
Text(balanceText, style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: scheme.onPrimary, fontWeight: FontWeight.w900)),
const SizedBox(height: 12),
Row(
children: [
_BalanceToggle(isHidden: isHidden, onPressed: onToggle),
const SizedBox(width: 10),
Text('إخفاء/إظهار', style: Theme.of(context).textTheme.labelMedium?.copyWith(color: scheme.onPrimary.withValues(alpha: 0.92))),
],
),
],
),
),
const SizedBox(width: AppSpacing.md),
Container(
width: 64,
height: 64,
decoration: BoxDecoration(color: scheme.onPrimary.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(18)),
child: Icon(Icons.account_balance_wallet_rounded, color: scheme.onPrimary, size: 30),
),
],
),
);
}
}
class _BalanceToggle extends StatefulWidget {
const _BalanceToggle({required this.isHidden, required this.onPressed});
final bool isHidden;
final VoidCallback onPressed;
@override
State<_BalanceToggle> createState() => _BalanceToggleState();
}
class _BalanceToggleState extends State<_BalanceToggle> {
bool _pressed = false;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return GestureDetector(
onTapDown: (_) => setState(() => _pressed = true),
onTapCancel: () => setState(() => _pressed = false),
onTapUp: (_) => setState(() => _pressed = false),
onTap: widget.onPressed,
child: AnimatedContainer(
duration: const Duration(milliseconds: 160),
curve: Curves.easeOut,
width: 62,
height: 34,
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: scheme.onPrimary.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(40),
border: Border.all(color: scheme.onPrimary.withValues(alpha: _pressed ? 0.45 : 0.25)),
),
child: Align(
alignment: widget.isHidden ? Alignment.centerLeft : Alignment.centerRight,
child: Container(
width: 26,
height: 26,
decoration: BoxDecoration(color: scheme.onPrimary, borderRadius: BorderRadius.circular(26)),
child: Icon(widget.isHidden ? Icons.visibility_off_rounded : Icons.visibility_rounded, size: 16, color: scheme.primary),
),
),
),
);
}
}
class ServicesTwoColumns extends StatelessWidget {
const ServicesTwoColumns({super.key, required this.onTap});
final void Function(String id) onTap;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Column(
children: [
ServiceFrame(id: 'pay', icon: Icons.payments_outlined, title: 'الدفع', subtitle: 'للتجار', onTap: onTap),
const SizedBox(height: AppSpacing.md),
ServiceFrame(id: 'topup', icon: Icons.phone_iphone_rounded, title: 'شحن', subtitle: 'الهاتف', onTap: onTap),
const SizedBox(height: AppSpacing.md),
ServiceFrame(id: 'bills', icon: Icons.receipt_long_outlined, title: 'فواتير', subtitle: 'كهرباء/ماء', onTap: onTap),
],
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
children: [
ServiceFrame(id: 'transfer', icon: Icons.swap_horiz_rounded, title: 'تحويل', subtitle: 'إرسال/استلام', onTap: onTap),
const SizedBox(height: AppSpacing.md),
ServiceFrame(id: 'cashout', icon: Icons.local_atm_outlined, title: 'سحب', subtitle: 'نقاط خدمة', onTap: onTap),
const SizedBox(height: AppSpacing.md),
ServiceFrame(id: 'more', icon: Icons.grid_view_rounded, title: 'المزيد', subtitle: 'خدمات', onTap: onTap),
],
),
),
],
);
}
}
class ServiceFrame extends StatefulWidget {
const ServiceFrame({super.key, required this.id, required this.icon, required this.title, required this.subtitle, required this.onTap});
final String id;
final IconData icon;
final String title;
final String subtitle;
final void Function(String id) onTap;
@override
State<ServiceFrame> createState() => _ServiceFrameState();
}
class _ServiceFrameState extends State<ServiceFrame> {
bool _pressed = false;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: () => widget.onTap(widget.id),
onTapDown: (_) => setState(() => _pressed = true),
onTapCancel: () => setState(() => _pressed = false),
onTapUp: (_) => setState(() => _pressed = false),
child: AnimatedScale(
duration: const Duration(milliseconds: 130),
curve: Curves.easeOut,
scale: _pressed ? 0.985 : 1,
child: AnimatedContainer(
duration: const Duration(milliseconds: 170),
curve: Curves.easeOut,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: 14),
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: scheme.outline.withValues(alpha: _pressed ? 0.22 : 0.14)),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(color: scheme.primaryContainer, borderRadius: BorderRadius.circular(12)),
child: Icon(widget.icon, color: scheme.onPrimaryContainer, size: 20),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.title, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800)),
const SizedBox(height: 2),
Text(widget.subtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant, height: 1.2), maxLines: 1, overflow: TextOverflow.ellipsis),
],
),
),
Icon(Icons.chevron_left_rounded, color: scheme.onSurfaceVariant),
],
),
),
),
);
}
}
class _TopIconButton extends StatelessWidget {
const _TopIconButton({required this.icon, required this.label, required this.onPressed});
final IconData icon;
final String label;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Semantics(
button: true,
label: label,
child: GestureDetector(
onTap: onPressed,
child: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: scheme.outline.withValues(alpha: 0.12)),
),
child: Icon(icon, color: scheme.onSurface),
),
),
);
}
}
class _QuickChip extends StatelessWidget {
const _QuickChip({required this.icon, required this.label, required this.onTap});
final IconData icon;
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(999),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 18, color: scheme.onSurface),
const SizedBox(width: 8),
Text(label, style: Theme.of(context).textTheme.labelLarge),
],
),
),
);
}
}
class _SheetRow extends StatelessWidget {
const _SheetRow({required this.icon, required this.title, required this.onTap});
final IconData icon;
final String title;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
child: GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: scheme.outline.withValues(alpha: 0.12)),
),
padding: const EdgeInsets.all(AppSpacing.md),
child: Row(
children: [
Container(
width: 42,
height: 42,
decoration: BoxDecoration(color: scheme.primaryContainer, borderRadius: BorderRadius.circular(14)),
child: Icon(icon, color: scheme.onPrimaryContainer),
),
const SizedBox(width: AppSpacing.md),
Expanded(child: Text(title, style: Theme.of(context).textTheme.titleMedium, textDirection: TextDirection.rtl)),
Icon(Icons.chevron_left_rounded, color: scheme.onSurfaceVariant),
],
),
),
),
);
}
}
Code 3 :
import 'package:flutter/material.dart';
import 'package:banck/theme.dart';
class HistoryPage extends StatelessWidget {
const HistoryPage({super.key});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Directionality(
textDirection: TextDirection.rtl,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('السجل', style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w900, color: scheme.tertiary)),
const SizedBox(height: AppSpacing.sm),
Text('آخر العمليات (نموذج تجريبي).', style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: scheme.onSurfaceVariant, height: 1.5)),
const SizedBox(height: AppSpacing.lg),
Expanded(
child: ListView.separated(
itemCount: 8,
separatorBuilder: (_, __) => const SizedBox(height: AppSpacing.sm),
itemBuilder: (context, index) {
final isIn = index.isEven;
return _TxnRow(
icon: isIn ? Icons.call_received_rounded : Icons.call_made_rounded,
title: isIn ? 'استلام' : 'إرسال',
subtitle: 'اليوم • 12:${30 + index}',
amount: isIn ? '+ 1,200' : '- 250',
amountColor: isIn ? scheme.secondary : scheme.onSurface,
);
},
),
),
],
),
),
),
);
}
}
class _TxnRow extends StatelessWidget {
const _TxnRow({required this.icon, required this.title, required this.subtitle, required this.amount, required this.amountColor});
final IconData icon;
final String title;
final String subtitle;
final String amount;
final Color amountColor;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
child: Row(
children: [
Container(
width: 42,
height: 42,
decoration: BoxDecoration(color: scheme.primaryContainer, borderRadius: BorderRadius.circular(14)),
child: Icon(icon, color: scheme.onPrimaryContainer, size: 20),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800)),
const SizedBox(height: 2),
Text(subtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant)),
],
),
),
Text(amount, style: Theme.of(context).textTheme.titleMedium?.copyWith(color: amountColor, fontWeight: FontWeight.w900)),
],
),
);
}
}
Code 4:
import 'package:flutter/material.dart';
import 'package:banck/theme.dart';
class QrPage extends StatelessWidget {
const QrPage({super.key});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Directionality(
textDirection: TextDirection.rtl,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('QR', style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w900, color: scheme.tertiary)),
const SizedBox(height: AppSpacing.sm),
Text('اعرض رمزك ليستلم الآخرون منك، أو امسح لاحقاً عند إضافة الكاميرا.', style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: scheme.onSurfaceVariant, height: 1.5)),
const SizedBox(height: AppSpacing.lg),
Expanded(
child: Container(
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
child: Center(
child: Container(
width: 230,
height: 230,
decoration: BoxDecoration(
color: scheme.primaryContainer,
borderRadius: BorderRadius.circular(22),
),
child: Stack(
children: [
Center(child: Icon(Icons.qr_code_2_rounded, size: 130, color: scheme.onPrimaryContainer)),
Positioned(
right: 14,
top: 14,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(999),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.verified_rounded, size: 16, color: scheme.primary),
const SizedBox(width: 6),
Text('mebanck', style: Theme.of(context).textTheme.labelMedium?.copyWith(fontWeight: FontWeight.w800)),
],
),
),
),
],
),
),
),
),
),
const SizedBox(height: AppSpacing.md),
FilledButton.icon(
onPressed: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('سيتم إضافة المسح الحقيقي لاحقاً', textDirection: TextDirection.rtl), behavior: SnackBarBehavior.floating),
),
icon: Icon(Icons.qr_code_scanner_rounded, color: scheme.onPrimary),
label: Text('مسح (قريباً)', style: TextStyle(color: scheme.onPrimary)),
),
],
),
),
),
);
}
}
Code 5 import 'package:flutter/material.dart';
import 'package:banck/theme.dart';
class BankPage extends StatelessWidget {
const BankPage({super.key});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Directionality(
textDirection: TextDirection.rtl,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('الحساب', style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w900, color: scheme.tertiary)),
const SizedBox(height: AppSpacing.sm),
Text('معلومات مختصرة للحساب (واجهة قابلة للتطوير).', style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: scheme.onSurfaceVariant, height: 1.5)),
const SizedBox(height: AppSpacing.lg),
_InfoCard(icon: Icons.account_balance_wallet_rounded, title: 'محفظة mebanck', subtitle: 'MRU • حساب شخصي', trailing: '49055137'),
const SizedBox(height: AppSpacing.sm),
_InfoCard(icon: Icons.location_on_outlined, title: 'المدينة', subtitle: 'نواكشوط', trailing: 'MR'),
],
),
),
),
);
}
}
class _InfoCard extends StatelessWidget {
const _InfoCard({required this.icon, required this.title, required this.subtitle, required this.trailing});
final IconData icon;
final String title;
final String subtitle;
final String trailing;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
child: Row(
children: [
Container(
width: 42,
height: 42,
decoration: BoxDecoration(color: scheme.primaryContainer, borderRadius: BorderRadius.circular(14)),
child: Icon(icon, color: scheme.onPrimaryContainer, size: 20),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800)),
const SizedBox(height: 2),
Text(subtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant)),
],
),
),
Text(trailing, style: Theme.of(context).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w900, color: scheme.tertiary)),
],
),
);
}
}
Code 6:import 'package:flutter/material.dart';
import 'package:banck/theme.dart';
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Directionality(
textDirection: TextDirection.rtl,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('حسابي', style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w900, color: scheme.tertiary)),
const SizedBox(height: AppSpacing.lg),
Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
child: Row(
children: [
CircleAvatar(radius: 26, backgroundColor: scheme.primaryContainer, child: Icon(Icons.person_rounded, color: scheme.onPrimaryContainer)),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('مستخدم', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w900)),
const SizedBox(height: 2),
Text('بدون تسجيل دخول', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant)),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(color: scheme.primaryContainer, borderRadius: BorderRadius.circular(999)),
child: Text('MRU', style: Theme.of(context).textTheme.labelMedium?.copyWith(color: scheme.onPrimaryContainer, fontWeight: FontWeight.w900)),
),
],
),
),
const SizedBox(height: AppSpacing.lg),
_SettingTile(icon: Icons.security_rounded, title: 'الأمان', subtitle: 'رمز وقفل', onTap: () => _snack(context, 'الأمان (قريباً)')),
_SettingTile(icon: Icons.language_rounded, title: 'اللغة', subtitle: 'العربية / الفرنسية', onTap: () => _snack(context, 'تغيير اللغة (قريباً)')),
_SettingTile(icon: Icons.support_agent_rounded, title: 'مساعدة', subtitle: 'الدعم', onTap: () => _snack(context, 'الدعم (قريباً)')),
],
),
),
),
);
}
void _snack(BuildContext context, String message) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message, textDirection: TextDirection.rtl), behavior: SnackBarBehavior.floating),
);
}
class _SettingTile extends StatelessWidget {
const _SettingTile({required this.icon, required this.title, required this.subtitle, required this.onTap});
final IconData icon;
final String title;
final String subtitle;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
child: GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: scheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: scheme.outline.withValues(alpha: 0.14)),
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(color: scheme.primaryContainer, borderRadius: BorderRadius.circular(14)),
child: Icon(icon, color: scheme.onPrimaryContainer),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w900)),
const SizedBox(height: 2),
Text(subtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant, height: 1.2)),
],
),
),
Icon(Icons.chevron_left_rounded, color: scheme.onSurfaceVariant),
],
),
),
),
);
}
}
Code 7 :
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:banck/nav.dart';
import 'package:banck/theme.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'mebanck',
debugShowCheckedModeBanner: false,
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.system,
locale: const Locale('ar'),
supportedLocales: const [Locale('ar'), Locale('fr'), Locale('en')],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
routerConfig: AppRouter.router,
);
}
}