From d50623d24e47618f929cd55a1ae6210e8a9c6c1d Mon Sep 17 00:00:00 2001 From: Vus Khoza Date: Wed, 11 Jun 2025 23:43:18 +0200 Subject: [PATCH] progress on home page --- lib/screens/home_screen.dart | 528 +++++++++++++++++------------------ lib/theme/app_theme.dart | 2 + pubspec.lock | 8 + pubspec.yaml | 1 + 4 files changed, 263 insertions(+), 276 deletions(-) diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 958bf9f..9850937 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'dart:async'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -10,30 +12,72 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State with SingleTickerProviderStateMixin { - late AnimationController _animationController; - late Animation _slideAnimation; + late AnimationController _controller; + late final Animation _alignAnimation; + final List> _animations = []; + final List> _alignListAnimations = []; + + late bool _enabled = true; + late bool _loading = true; + Timer? _loadingTimer; @override void initState() { super.initState(); - _animationController = AnimationController( + _controller = AnimationController( + duration: const Duration(milliseconds: 600), vsync: this, - duration: const Duration(milliseconds: 250), - ); - _slideAnimation = Tween(begin: -1.0, end: 0.0).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic), - ); - _animationController.forward(); + )..forward(); + + _alignAnimation = Tween( + begin: Alignment.centerLeft, + end: Alignment.center, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); + + List tranListIndices = [0, 1]; + for (int i = 0; i < tranListIndices.length; i++) { + _alignListAnimations.add( + Tween(begin: Offset(0, -0.25), end: Offset.zero).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0.075 * i, 1.0, curve: Curves.easeOut), + ), + ), + ); + } + + List buttonIndices = [0, 1, 2]; + for (int i = 0; i < buttonIndices.length; i++) { + _animations.add( + Tween(begin: Offset(-0.15, 0), end: Offset.zero).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0.075 * i, 1.0, curve: Curves.easeOut), + ), + ), + ); + } + + // Simulate a delay to mimic loading state + _loadingTimer = Timer(const Duration(seconds: 1), () { + if (mounted) { + setState(() { + _loading = false; + }); + } + }); } @override void dispose() { - _animationController.dispose(); + _loadingTimer?.cancel(); + _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + final VoidCallback? onPressed = _enabled ? () {} : null; return Scaffold( body: SafeArea( child: CustomScrollView( @@ -54,127 +98,212 @@ class _HomeScreenState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Welcome back,', - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - const SizedBox(height: 4), - const Text( - 'John Doe', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, + AlignTransition( + alignment: _alignAnimation, + child: Skeletonizer( + enabled: _loading, + child: Text( + "USD 4.02", + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + ), + ), ), ), - const SizedBox(height: 24), - SlideTransition( - position: Tween( - begin: const Offset(0, 0), - end: const Offset(0, 1), - ).animate(_slideAnimation), - child: _buildBalanceCard(context), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SlideTransition( + position: _animations[0], + child: FilledButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.9), + ), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + vertical: 17.5, + horizontal: 30, + ), + ), + ), + onPressed: onPressed, + child: Row( + children: [ + Icon(Icons.electric_bolt, color: Colors.white), + const SizedBox(width: 8), + const Text( + 'Electricity', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + SlideTransition( + position: _animations[1], + child: OutlinedButton( + style: ButtonStyle( + side: WidgetStateProperty.all( + BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + ), + foregroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.primary, + ), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + vertical: 17.5, + horizontal: 30, + ), + ), + ), + onPressed: onPressed, + child: Row( + children: [ + Icon(Icons.phone_android), + const SizedBox(width: 8), + const Text( + 'Airtime', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ), + ), + SlideTransition( + position: _animations[2], + child: OutlinedButton( + style: ButtonStyle( + side: WidgetStateProperty.all( + BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + ), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + vertical: 17.5, + horizontal: 30, + ), + ), + ), + onPressed: onPressed, + child: Icon(Icons.add), + ), + ), + ], ), - const SizedBox(height: 24), - const Text( - 'Quick Actions', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Recent Activity', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + GestureDetector( + onTap: () { + context.go('/history'); + }, + child: const Text( + 'View All', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.normal, + ), + ), + ), + ], ), - const SizedBox(height: 16), - _buildQuickActions(context), - const SizedBox(height: 24), - const Text( - 'Recent Transactions', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - _buildRecentTransactions(), - ], - ), - ), - ), - ], - ), - ), - ); - } - - Widget _buildBalanceCard(BuildContext context) { - return Card( - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Available Balance', - style: TextStyle(color: Colors.black87, fontSize: 14), - ), - const SizedBox(height: 4), - const Text( - '\$0.00', - style: TextStyle( - color: Colors.black, - fontSize: 24, - fontWeight: FontWeight.bold, + const SizedBox(height: 20), + Skeletonizer( + enabled: _loading, + child: ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + SlideTransition( + position: _alignListAnimations[0], + child: ListTile( + leading: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.electric_bolt, + color: Theme.of(context).colorScheme.primary, + ), + ), + title: Text( + 'Electricity Bill', + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text( + 'Paid USD 50.00', + style: TextStyle(fontWeight: FontWeight.normal), + ), + trailing: Text( + '-USD 50.00', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + SlideTransition( + position: _alignListAnimations[1], + child: ListTile( + leading: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.phone_android, + color: Theme.of(context).colorScheme.primary, + ), + ), + title: Text( + 'Airtime Purchase', + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text( + 'Purchased USD 10.00', + style: TextStyle(fontWeight: FontWeight.normal), + ), + trailing: Text( + '-USD 10.00', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], ), ), ], ), - _buildActionButton( - context, - 'Add', - Icons.add_circle_outline, - () {}, - ), - ], - ), - ], - ), - ), - ); - } - - Widget _buildActionButton( - BuildContext context, - String label, - IconData icon, - VoidCallback onTap, - ) { - return InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: Colors.black87.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - Icon(icon, color: Colors.white, size: 18), - const SizedBox(width: 4), - Text( - label, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - fontSize: 14, ), ), ], @@ -182,157 +311,4 @@ class _HomeScreenState extends State ), ); } - - Widget _buildQuickActions(BuildContext context) { - return GridView.count( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - crossAxisCount: 2, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - childAspectRatio: 2, - children: [ - _buildAnimatedQuickActionItem( - context, - 'Electricity', - Icons.electric_bolt_outlined, - () {}, - 0, - ), - _buildAnimatedQuickActionItem( - context, - 'Airtime', - Icons.phone_android_outlined, - () {}, - 1, - ), - ], - ); - } - - Widget _buildAnimatedQuickActionItem( - BuildContext context, - String label, - IconData icon, - VoidCallback onTap, - int index, - ) { - return SlideTransition( - position: Tween( - begin: const Offset(-1, 0), - end: const Offset(0, 0), - ).animate( - CurvedAnimation( - parent: _animationController, - curve: Interval(0.4 + (index * 0.1), 0.8 + (index * 0.1)), - ), - ), - child: _buildQuickActionItem(context, label, icon, onTap), - ); - } - - Widget _buildQuickActionItem( - BuildContext context, - String label, - IconData icon, - VoidCallback onTap, - ) { - return InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - icon, - color: Theme.of(context).colorScheme.secondary, - size: 28, - ), - ), - const SizedBox(height: 8), - Text( - label, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.tertiary, - ), - ), - ], - ), - ); - } - - Widget _buildRecentTransactions() { - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: 3, - itemBuilder: (context, index) { - return SlideTransition( - position: Tween( - begin: const Offset(-1, 0), - end: Offset.zero, - ).animate( - CurvedAnimation( - parent: _animationController, - curve: Interval( - 0.5 + (index * 0.1), - 0.7 + (index * 0.1), - curve: Curves.easeOutCubic, - ), - ), - ), - child: ListTile( - leading: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.tertiary.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - index == 0 - ? Icons.phone_android - : index == 1 - ? Icons.wifi - : Icons.receipt_long, - color: Theme.of(context).colorScheme.tertiary, - ), - ), - title: Text( - index == 0 - ? 'Mobile Bill' - : index == 1 - ? 'Internet Bill' - : 'Electricity Bill', - ), - subtitle: Text( - index == 0 - ? 'Yesterday' - : index == 1 - ? '2 days ago' - : '3 days ago', - style: const TextStyle(fontSize: 12), - ), - trailing: Text( - index == 0 - ? '-\$50.00' - : index == 1 - ? '-\$75.00' - : '-\$120.00', - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontWeight: FontWeight.bold, - ), - ), - ), - ); - }, - ); - } } diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index a6cf051..be90ccc 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; class AppTheme { static const _primaryColor = Color(0xFFD4AF37); // Gold @@ -9,6 +10,7 @@ class AppTheme { static const _errorColor = Color(0xFFB3261E); static final ThemeData lightTheme = ThemeData( + textTheme: GoogleFonts.assistantTextTheme(), useMaterial3: true, colorScheme: ColorScheme.light( primary: _primaryColor, diff --git a/pubspec.lock b/pubspec.lock index 6aea886..c433588 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + skeletonizer: + dependency: "direct main" + description: + name: skeletonizer + sha256: "0dcacc51c144af4edaf37672072156f49e47036becbc394d7c51850c5c1e884b" + url: "https://pub.dev" + source: hosted + version: "1.4.3" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 00ffcaa..8808aea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: flutter_svg: ^2.0.9 google_fonts: ^6.1.0 uuid: ^4.3.3 + skeletonizer: ^1.4.3 dev_dependencies: flutter_test: