diff --git a/lib/screens/profile_screen.dart b/lib/screens/profile_screen.dart index 159b700..e5cb871 100644 --- a/lib/screens/profile_screen.dart +++ b/lib/screens/profile_screen.dart @@ -19,7 +19,8 @@ class ProfileScreen extends StatefulWidget { State createState() => _ProfileScreenState(); } -class _ProfileScreenState extends State { +class _ProfileScreenState extends State + with SingleTickerProviderStateMixin { String _username = ''; String _email = 'not provided'; String _profileImage = ''; @@ -33,15 +34,96 @@ class _ProfileScreenState extends State { bool _loadingEvents = true; - // Gradient used for EXP bar and rainbow bar + // Gradient used for EXP bar and rainbow bar (6-color rainbow) static const LinearGradient _expGradient = LinearGradient( - colors: [Color(0xFFA855F7), Color(0xFFEAB308), Color(0xFF3B82F6)], + colors: [ + Color(0xFFA855F7), // purple-500 + Color(0xFFEC4899), // pink-500 + Color(0xFFF97316), // orange-500 + Color(0xFFEAB308), // yellow-500 + Color(0xFF22C55E), // green-500 + Color(0xFF3B82F6), // blue-500 + ], ); + // Animation state + late AnimationController _animController; + double _expProgress = 0.0; + int _animatedLikes = 0; + int _animatedPosts = 0; + int _animatedViews = 0; + bool _isFollowing = false; + String _title = 'Product Designer who focuses on simplicity & usability.'; + + // Target stat values + static const int _targetLikes = 72900; + static const int _targetPosts = 828; + static const int _targetViews = 342900; + @override void initState() { super.initState(); + + // Animation controller for EXP bar + stat counters + _animController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 2000), + ); + _loadProfile(); + _startAnimations(); + } + + @override + void dispose() { + _animController.dispose(); + super.dispose(); + } + + void _startAnimations() { + // Delay to match React's setTimeout(500ms) + Future.delayed(const Duration(milliseconds: 500), () { + if (!mounted) return; + + // Animate EXP bar: 0 → 0.65 with ease-out over 1.3s + final expTween = Tween(begin: 0.0, end: 0.65); + final expAnim = CurvedAnimation( + parent: _animController, + curve: const Interval(0.0, 0.65, curve: Curves.easeOut), + ); + expAnim.addListener(() { + if (mounted) { + setState(() { + _expProgress = expTween.evaluate(expAnim); + }); + } + }); + + // Animate stat counters: 0 → target over full 2s + _animController.addListener(() { + if (!mounted) return; + final t = _animController.value; + setState(() { + _animatedLikes = (t * _targetLikes).round(); + _animatedPosts = (t * _targetPosts).round(); + _animatedViews = (t * _targetViews).round(); + }); + }); + + _animController.forward(); + }); + } + + /// Format large numbers: ≥1M → "X.XM", ≥1K → "X.XK", else raw + String _formatNumber(int n) { + if (n >= 1000000) { + final val = n / 1000000; + return '${val.toStringAsFixed(1)}M'; + } else if (n >= 1000) { + final val = n / 1000; + return '${val.toStringAsFixed(1)}K'; + } + return n.toString(); } // ───────── Data Loading (unchanged) ───────── @@ -585,20 +667,36 @@ class _ProfileScreenState extends State { ), ), ), - // Edit pencil button (top-right) + // Follow / Following toggle button (top-right) Positioned( top: 8, right: 8, child: GestureDetector( - onTap: () => _openEditDialog(), - child: Container( - width: 36, - height: 36, + onTap: () => setState(() => _isFollowing = !_isFollowing), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(8), + color: _isFollowing ? Colors.white : Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + _isFollowing ? 'Following \u2713' : 'Follow +', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: _isFollowing + ? AppConstants.textSecondary + : AppConstants.textPrimary, + ), ), - child: const Icon(Icons.edit, color: Colors.white, size: 18), ), ), ), @@ -641,7 +739,7 @@ class _ProfileScreenState extends State { ); } - // ───────── EXP Progress Bar ───────── + // ───────── Animated EXP Progress Bar ───────── Widget _buildExpBar() { return Padding( @@ -658,12 +756,29 @@ class _ProfileScreenState extends State { ), const SizedBox(width: 8), Expanded( - child: Container( - height: 8, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - gradient: _expGradient, - ), + child: LayoutBuilder( + builder: (context, constraints) { + final fullWidth = constraints.maxWidth; + final filledWidth = fullWidth * _expProgress; + return Container( + height: 8, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Colors.grey.shade200, // gray track + ), + child: Align( + alignment: Alignment.centerLeft, + child: Container( + width: filledWidth, + height: 8, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + gradient: _expGradient, + ), + ), + ), + ); + }, ), ), ], @@ -671,7 +786,7 @@ class _ProfileScreenState extends State { ); } - // ───────── Stats Row ───────── + // ───────── Stats Row (animated counters + top/bottom borders) ───────── Widget _buildStatsRow() { Widget statColumn(String value, String label) { @@ -701,16 +816,23 @@ class _ProfileScreenState extends State { ); } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + border: Border( + top: BorderSide(color: Colors.grey.shade200, width: 1), + bottom: BorderSide(color: Colors.grey.shade200, width: 1), + ), + ), child: IntrinsicHeight( child: Row( children: [ - statColumn('1.2K', 'Likes'), + statColumn(_formatNumber(_animatedLikes), 'Likes'), VerticalDivider(color: Colors.grey.shade200, thickness: 1, width: 1), - statColumn('45', 'Posts'), + statColumn(_formatNumber(_animatedPosts), 'Posts'), VerticalDivider(color: Colors.grey.shade200, thickness: 1, width: 1), - statColumn('3.4K', 'Views'), + statColumn(_formatNumber(_animatedViews), 'Views'), ], ), ), @@ -795,6 +917,21 @@ class _ProfileScreenState extends State { ), ), ), + const SizedBox(height: 6), + + // Title / Bio + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + _title, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppConstants.textSecondary, + height: 1.5, + ), + ), + ), const SizedBox(height: 4), // Email @@ -802,10 +939,10 @@ class _ProfileScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( _email, - style: const TextStyle( - fontSize: 14, + style: TextStyle( + fontSize: 13, fontWeight: FontWeight.w300, - color: AppConstants.textSecondary, + color: Colors.grey.shade400, ), ), ),