app_onboarding_guide 0.0.1
app_onboarding_guide: ^0.0.1 copied to clipboard
A guided tour and onboarding helper that helps you to present your app and its features in a beautiful, simple and customizable way.
example/lib/main.dart
// ignore_for_file: avoid_print, deprecated_member_use
import 'package:flutter/material.dart';
import 'package:app_onboarding_guide/app_onboarding_guide.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Onboarding Guide Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6366F1), // Indigo
primary: const Color(0xFF4F46E5), // Premium Indigo
secondary: const Color(0xFFF59E0B), // Warm Amber
surface: const Color(0xFFF8FAFC), // Slate 50
),
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
late AppOnboardingGuide appOnboardingGuide;
// Onboarding target keys
final GlobalKey keyProfileHeader = GlobalKey();
final GlobalKey keyBalanceOverview = GlobalKey();
final GlobalKey keyQuickActions = GlobalKey();
final GlobalKey keySettingsCard = GlobalKey();
final GlobalKey keyTransactionsSection = GlobalKey();
final GlobalKey keyStartTourButton = GlobalKey();
// Customizer state options
bool _isArabic = false;
bool _showIndicator = true;
StepIndicatorStyle _stepIndicatorStyle = StepIndicatorStyle.dots;
bool _autoScroll = true;
double _opacityShadow = 0.8;
bool _pulseEnable = true;
bool _rippleEnable = false;
bool _enableSwipeNavigation = true;
bool _enableKeyboardNavigation = true;
bool _glowEnable = true;
double _glowRadius = 16.0;
// Translation dictionary
final Map<String, Map<String, String>> _translations = {
'en': {
'app_title': 'Onboarding Guide',
'dashboard': 'Customizer Dashboard',
'start_tour': 'Start Onboarding Tour',
'profile_title': 'User Profile',
'user_name': 'John Doe',
'user_role': 'Senior Developer',
'balance_title': 'Available Balance',
'balance_amount': r'$12,540.50',
'balance_subtitle': 'Active checking account',
'quick_actions': 'Quick Actions',
'action_add': 'Add Money',
'action_send': 'Send',
'action_request': 'Request',
'recent_transactions': 'Recent Transactions',
'tx_grocery': 'Grocery Store',
'tx_electric': 'Electric Bill',
'tx_salary': 'Salary Deposit',
'tx_grocery_desc': 'Weekly shopping',
'tx_electric_desc': 'Utility payment',
'tx_salary_desc': 'Direct deposit credit',
'settings_title': 'Customize Tour Settings',
'lang_rtl': 'Arabic Language (RTL)',
'show_indicator': 'Show Step Indicator',
'indicator_style': 'Indicator Style',
'dots': 'Dots',
'bar': 'Progress Bar',
'numbered': 'Numbered Text',
'auto_scroll': 'Auto-Scroll to Offscreen',
'opacity': 'Overlay Opacity',
'pulse': 'Pulse Animation',
'ripple': 'Sonar Ripple Waves',
'swipe': 'Swipe Navigation',
'keyboard': 'Keyboard Shortcuts',
'spotlight_glow': 'Spotlight Glow',
'glow_radius': 'Glow Radius',
'tour_p1_title': 'User Profile Header',
'tour_p1_desc': 'This card displays your profile avatar, name, and role in the organization.',
'tour_p2_title': 'Account Balance',
'tour_p2_desc': 'This widget tracks your current available funds and updates in real-time.',
'tour_p3_title': 'Operations Shortcuts',
'tour_p3_desc': 'Quickly transfer funds, request payments, or deposit cash with these buttons.',
'tour_p4_title': 'Off-screen Transactions',
'tour_p4_desc': 'Your recent expenditures log. Notice how the page automatically scrolled down to reveal this!',
'tour_p5_title': 'Customizer Configuration',
'tour_p5_desc': 'You can configure settings like overlay opacity, indicator styles, and pulse effects here.',
'tour_skip': 'SKIP',
'tour_back_to_start': 'Go to start',
'tour_prev': 'Prev',
'tour_next': 'Next',
'tour_finish': 'Finish',
},
'ar': {
'app_title': 'دليل البدء والتوجيه',
'dashboard': 'لوحة التحكم بالمخصص',
'start_tour': 'بدء الجولة التعريفية',
'profile_title': 'ملف تعريف المستخدم',
'user_name': 'جون دو',
'user_role': 'مطور برمجيات أول',
'balance_title': 'الرصيد المتاح',
'balance_amount': '١٢،٥٤٠.٥٠ \$',
'balance_subtitle': 'الحساب الجاري النشط',
'quick_actions': 'إجراءات سريعة',
'action_add': 'إضافة أموال',
'action_send': 'إرسال ثانٍ',
'action_request': 'طلب مال',
'recent_transactions': 'المعاملات الأخيرة',
'tx_grocery': 'متجر البقالة',
'tx_electric': 'فاتورة الكهرباء',
'tx_salary': 'إيداع الراتب',
'tx_grocery_desc': 'التسوق الأسبوعي',
'tx_electric_desc': 'سداد فاتورة الخدمات',
'tx_salary_desc': 'رصيد إيداع مباشر',
'settings_title': 'تخصيص إعدادات الجولة',
'lang_rtl': 'اللغة العربية (يمين إلى يسار)',
'show_indicator': 'عرض مؤشر الخطوات',
'indicator_style': 'نمط مؤشر التقدم',
'dots': 'نقاط',
'bar': 'شريط تقدم',
'numbered': 'أرقام الخطوات',
'auto_scroll': 'التمرير التلقائي للعناصر البعيدة',
'opacity': 'شفافية غطاء التظليل',
'pulse': 'تأثير النبض البصري',
'ripple': 'تموجات السونار الدائرية',
'swipe': 'التنقل بالسحب الجانبي',
'keyboard': 'مفاتيح لوحة المفاتيح',
'spotlight_glow': 'وهج تسليط الضوء',
'glow_radius': 'نصف قطر التوهج',
'tour_p1_title': 'ترويسة ملف المستخدم',
'tour_p1_desc': 'تعرض هذه البطاقة صورتك الرمزية واسمك ودورك في المؤسسة.',
'tour_p2_title': 'رصيد الحساب المالي',
'tour_p2_desc': 'يتتبع هذا العنصر أموالك الحالية المتاحة ويقوم بالتحديث في الوقت الفعلي.',
'tour_p3_title': 'اختصارات العمليات السريعة',
'tour_p3_desc': 'قم بنقل الأموال أو طلب المدفوعات أو إيداع النقد بسرعة باستخدام هذه الأزرار.',
'tour_p4_title': 'المعاملات خارج الشاشة',
'tour_p4_desc': 'سجل نفقاتك الأخيرة. لاحظ كيف قامت الصفحة بالتمرير لأسفل تلقائيًا للكشف عن هذا العنصر!',
'tour_p5_title': 'تكوين مخصص الجولة',
'tour_p5_desc': 'يمكنك تكوين إعدادات مثل شفافية الغطاء ونمط المؤشر وتأثيرات النبض من هنا.',
'tour_skip': 'تخطي',
'tour_back_to_start': 'الذهاب للبداية',
'tour_prev': 'السابق',
'tour_next': 'التالي',
'tour_finish': 'إنهاء',
}
};
String t(String key) {
final lang = _isArabic ? 'ar' : 'en';
return _translations[lang]?[key] ?? key;
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
showTutorial();
});
}
void showTutorial() {
createTutorial();
appOnboardingGuide.show(context: context);
}
void createTutorial() {
appOnboardingGuide = AppOnboardingGuide(
targets: _createTargets(),
colorShadow: const Color(0xFF0F172A), // Slate 900
opacityShadow: _opacityShadow,
textSkip: t('tour_skip'),
alignSkip: AlignmentDirectional.bottomEnd,
hideSkip: false,
pulseEnable: _pulseEnable,
rippleEnable: _rippleEnable,
rippleColor: const Color(0xFF818CF8), // Indigo light ripple
showStepIndicator: _showIndicator,
stepIndicatorStyle: _stepIndicatorStyle,
stepIndicatorAlignment: Alignment.bottomCenter,
stepIndicatorActiveColor: const Color(0xFFF59E0B), // Amber active indicator
stepIndicatorInactiveColor: Colors.white.withOpacity(0.3),
enableSwipeNavigation: _enableSwipeNavigation,
enableKeyboardNavigation: _enableKeyboardNavigation,
autoScroll: _autoScroll,
scrollDuration: const Duration(milliseconds: 400),
scrollCurve: Curves.easeInOut,
textDirection: _isArabic ? TextDirection.rtl : TextDirection.ltr,
onFinish: () => print("Tour finished"),
onSkip: () {
print("Tour skipped");
return true;
},
);
}
List<TargetFocus> _createTargets() {
List<TargetFocus> targets = [];
// 1. Profile Header Target
targets.add(
TargetFocus(
identify: "profileHeaderTarget",
keyTarget: keyProfileHeader,
contents: [
TargetContent(
align: ContentAlign.bottom,
builder: (context, controller) {
return _buildTourCard(
controller: controller,
title: t('tour_p1_title'),
desc: t('tour_p1_desc'),
stepIndex: 0,
totalSteps: 4,
);
},
),
],
shape: ShapeLightFocus.RRect,
radius: 12,
glowEnable: _glowEnable,
glowRadius: _glowRadius,
glowColor: const Color(0xFF818CF8),
),
);
// 2. Balance Overview Target
targets.add(
TargetFocus(
identify: "balanceOverviewTarget",
keyTarget: keyBalanceOverview,
contents: [
TargetContent(
align: ContentAlign.bottom,
builder: (context, controller) {
return _buildTourCard(
controller: controller,
title: t('tour_p2_title'),
desc: t('tour_p2_desc'),
stepIndex: 1,
totalSteps: 4,
);
},
),
],
shape: ShapeLightFocus.RRect,
radius: 12,
glowEnable: _glowEnable,
glowRadius: _glowRadius,
glowColor: const Color(0xFFF59E0B),
),
);
// 3. Quick Actions Row Target
targets.add(
TargetFocus(
identify: "quickActionsTarget",
keyTarget: keyQuickActions,
contents: [
TargetContent(
align: ContentAlign.top,
builder: (context, controller) {
return _buildTourCard(
controller: controller,
title: t('tour_p3_title'),
desc: t('tour_p3_desc'),
stepIndex: 2,
totalSteps: 4,
);
},
),
],
shape: ShapeLightFocus.RRect,
radius: 16,
glowEnable: _glowEnable,
glowRadius: _glowRadius,
glowColor: const Color(0xFF10B981),
),
);
// 4. Off-screen Recent Transactions (tests auto-scroll)
targets.add(
TargetFocus(
identify: "transactionsSectionTarget",
keyTarget: keyTransactionsSection,
contents: [
TargetContent(
align: ContentAlign.top,
builder: (context, controller) {
return _buildTourCard(
controller: controller,
title: t('tour_p4_title'),
desc: t('tour_p4_desc'),
stepIndex: 3,
totalSteps: 4,
);
},
),
],
shape: ShapeLightFocus.RRect,
radius: 12,
glowEnable: _glowEnable,
glowRadius: _glowRadius,
glowColor: const Color(0xFFEF4444),
),
);
return targets;
}
Widget _buildTourCard({
required AppOnboardingGuideController controller,
required String title,
required String desc,
required int stepIndex,
required int totalSteps,
}) {
final isFirst = stepIndex == 0;
final isLast = stepIndex == totalSteps - 1;
return Container(
width: MediaQuery.of(context).size.width - 40,
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: const Color(0xFF1E293B), // Slate 800
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.15)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFF4F46E5), // Indigo 600
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${stepIndex + 1}/$totalSteps',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
const SizedBox(height: 12),
Text(
desc,
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.85),
height: 1.4,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (!isFirst)
TextButton(
onPressed: () => controller.previous(),
child: Text(
t('tour_prev'),
style: const TextStyle(color: Colors.white70),
),
),
if (isFirst)
TextButton(
onPressed: () => appOnboardingGuide.goTo(3),
child: Text(
'${t('tour_next')} ${t('recent_transactions')}',
style: const TextStyle(color: Colors.amber),
),
),
const SizedBox(width: 8),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4F46E5),
foregroundColor: Colors.white,
),
onPressed: () {
if (isLast) {
controller.skip();
} else {
controller.next();
}
},
child: Text(isLast ? t('tour_finish') : t('tour_next')),
),
],
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: _isArabic ? TextDirection.rtl : TextDirection.ltr,
child: Scaffold(
appBar: AppBar(
title: Text(
t('app_title'),
style: const TextStyle(fontWeight: FontWeight.w800, color: Colors.white),
),
backgroundColor: const Color(0xFF4F46E5),
centerTitle: true,
elevation: 2,
actions: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: DropdownButton<String>(
value: _isArabic ? 'ar' : 'en',
dropdownColor: const Color(0xFF4F46E5),
icon: const Icon(Icons.language, color: Colors.white),
underline: const SizedBox(),
items: const [
DropdownMenuItem(
value: 'en',
child: Text('EN', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
DropdownMenuItem(
value: 'ar',
child: Text('عربي', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
],
onChanged: (langCode) {
if (langCode != null) {
setState(() {
_isArabic = (langCode == 'ar');
});
}
},
),
),
],
),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. Profile Header Card
Card(
key: keyProfileHeader,
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
const CircleAvatar(
radius: 30,
backgroundColor: Color(0xFF818CF8),
child: Icon(Icons.person, size: 36, color: Colors.white),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t('user_name'),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1E293B)),
),
const SizedBox(height: 4),
Text(
t('user_role'),
style: const TextStyle(fontSize: 14, color: Color(0xFF64748B)),
),
],
),
),
IconButton(
icon: const Icon(Icons.edit, color: Color(0xFF4F46E5)),
onPressed: () {},
)
],
),
),
),
const SizedBox(height: 16),
// 2. Balance Overview Card
Card(
key: keyBalanceOverview,
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
color: const Color(0xFF4F46E5), // Premium Indigo Background
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t('balance_title'),
style: TextStyle(fontSize: 14, color: Colors.white.withOpacity(0.8)),
),
const SizedBox(height: 8),
Text(
t('balance_amount'),
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w800, color: Colors.white, letterSpacing: -0.5),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
t('balance_subtitle'),
style: TextStyle(fontSize: 12, color: Colors.white.withOpacity(0.7)),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: const [
Icon(Icons.shield, color: Colors.white, size: 12),
SizedBox(width: 4),
Text('Secure', style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
],
),
),
],
),
],
),
),
),
const SizedBox(height: 16),
// 3. Quick Actions Card
Card(
key: keyQuickActions,
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Text(
t('quick_actions'),
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Color(0xFF475569)),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildActionButton(Icons.add_circle, t('action_add'), const Color(0xFF10B981)),
_buildActionButton(Icons.send, t('action_send'), const Color(0xFF3B82F6)),
_buildActionButton(Icons.request_page, t('action_request'), const Color(0xFFF59E0B)),
],
),
],
),
),
),
const SizedBox(height: 16),
// 4. Customizer Settings Dashboard
Card(
key: keySettingsCard,
elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t('settings_title'),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1E293B)),
),
const Divider(height: 24, thickness: 1),
// Language switch
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('lang_rtl')),
value: _isArabic,
onChanged: (val) {
setState(() {
_isArabic = val;
});
},
),
// Indicator Toggle
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('show_indicator')),
value: _showIndicator,
onChanged: (val) {
setState(() {
_showIndicator = val;
});
},
),
if (_showIndicator) ...[
// Indicator Style selection dropdown
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(t('indicator_style')),
DropdownButton<StepIndicatorStyle>(
value: _stepIndicatorStyle,
onChanged: (val) {
if (val != null) {
setState(() {
_stepIndicatorStyle = val;
});
}
},
items: [
DropdownMenuItem(
value: StepIndicatorStyle.dots,
child: Text(t('dots')),
),
DropdownMenuItem(
value: StepIndicatorStyle.bar,
child: Text(t('bar')),
),
DropdownMenuItem(
value: StepIndicatorStyle.numbered,
child: Text(t('numbered')),
),
],
),
],
),
),
],
// Auto Scroll Toggle
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('auto_scroll')),
value: _autoScroll,
onChanged: (val) {
setState(() {
_autoScroll = val;
});
},
),
// Pulse Toggle
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('pulse')),
value: _pulseEnable,
onChanged: (val) {
setState(() {
_pulseEnable = val;
});
},
),
// Sonar Ripple Toggle
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('ripple')),
value: _rippleEnable,
onChanged: (val) {
setState(() {
_rippleEnable = val;
});
},
),
// Swipe Navigation Toggle
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('swipe')),
value: _enableSwipeNavigation,
onChanged: (val) {
setState(() {
_enableSwipeNavigation = val;
});
},
),
// Keyboard Navigation Toggle
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('keyboard')),
value: _enableKeyboardNavigation,
onChanged: (val) {
setState(() {
_enableKeyboardNavigation = val;
});
},
),
// Spotlight Glow Toggle
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(t('spotlight_glow')),
value: _glowEnable,
onChanged: (val) {
setState(() {
_glowEnable = val;
});
},
),
if (_glowEnable) ...[
// Glow Radius Slider
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${t('glow_radius')}: ${_glowRadius.toStringAsFixed(1)}'),
Slider(
value: _glowRadius,
min: 5.0,
max: 40.0,
onChanged: (val) {
setState(() {
_glowRadius = val;
});
},
),
],
),
),
],
// Shadow Opacity Slider
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${t('opacity')}: ${_opacityShadow.toStringAsFixed(2)}'),
Slider(
value: _opacityShadow,
min: 0.1,
max: 0.95,
onChanged: (val) {
setState(() {
_opacityShadow = val;
});
},
),
],
),
),
const SizedBox(height: 16),
// Start Guide Button
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
key: keyStartTourButton,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4F46E5),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
onPressed: showTutorial,
icon: const Icon(Icons.play_circle_fill),
label: Text(
t('start_tour'),
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
),
),
],
),
),
),
const SizedBox(height: 60),
// Scroll Spacing section: Creates height between sections to verify scroll visibility
Container(
height: 250,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.indigo.shade50, Colors.amber.shade50],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: Opacity(
opacity: 0.3,
child: Icon(Icons.arrow_downward_rounded, size: 80, color: Colors.indigo.shade300),
),
),
),
const SizedBox(height: 60),
// 5. Recent Transactions (Target at bottom of ScrollView)
Card(
key: keyTransactionsSection,
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t('recent_transactions'),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1E293B)),
),
const Divider(height: 20),
_buildTransactionItem(
icon: Icons.shopping_basket,
color: Colors.pink,
title: t('tx_grocery'),
desc: t('tx_grocery_desc'),
amount: '-\$45.20',
),
_buildTransactionItem(
icon: Icons.electric_bolt,
color: Colors.amber.shade700,
title: t('tx_electric'),
desc: t('tx_electric_desc'),
amount: '-\$120.00',
),
_buildTransactionItem(
icon: Icons.account_balance_wallet,
color: Colors.green,
title: t('tx_salary'),
desc: t('tx_salary_desc'),
amount: '+\$4,200.00',
isIncome: true,
),
],
),
),
),
],
),
),
),
),
),
);
}
Widget _buildActionButton(IconData icon, String label, Color color) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 24,
backgroundColor: color.withOpacity(0.12),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF475569)),
),
],
);
}
Widget _buildTransactionItem({
required IconData icon,
required Color color,
required String title,
required String desc,
required String amount,
bool isIncome = false,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
CircleAvatar(
backgroundColor: color.withOpacity(0.1),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1E293B)),
),
Text(
desc,
style: const TextStyle(fontSize: 12, color: Color(0xFF64748B)),
),
],
),
),
Text(
amount,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: isIncome ? const Color(0xFF10B981) : const Color(0xFFEF4444),
),
),
],
),
);
}
}