diff --git a/lib/screens/calendar_screen.dart b/lib/screens/calendar_screen.dart index 436e046..44bded0 100644 --- a/lib/screens/calendar_screen.dart +++ b/lib/screens/calendar_screen.dart @@ -569,28 +569,11 @@ class _CalendarScreenState extends State { } // MOBILE layout - // Stack: extended gradient panel (below appbar) that visually extends behind the calendar. return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: Stack( children: [ - // Extended blue gradient panel behind calendar (matches reference) - Positioned( - top: 0, - left: 0, - right: 0, - child: Container( - height: 260, // controls how much gradient shows behind calendar - decoration: AppDecoration.blueGradient.copyWith( - borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30)), - boxShadow: [const BoxShadow(color: Colors.black12, blurRadius: 12, offset: Offset(0, 6))], - ), - // leave child empty — title and bell are placed above - child: const SizedBox.shrink(), - ), - ), - - // TOP APP BAR (title centered + notification at top-right) - unchanged placement + // TOP APP BAR stays fixed (title + bell icon) Positioned( top: 0, left: 0, @@ -637,15 +620,34 @@ class _CalendarScreenState extends State { ), ), - // CONTENT: whole page scrolls as one — calendar + summary + events + // CONTENT: gradient + calendar card scroll together as one unit CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ - // Space for app bar + gradient top - const SliverToBoxAdapter(child: SizedBox(height: 110)), - - // Calendar card - SliverToBoxAdapter(child: _calendarCard(context)), + // Gradient + calendar card in one scrollable Stack + // Gradient scrolls away with content; app bar remains fixed above + SliverToBoxAdapter( + child: Stack( + children: [ + // Blue gradient banner — scrolls with content + Container( + height: 260, + decoration: AppDecoration.blueGradient.copyWith( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 12, offset: Offset(0, 6))], + ), + ), + // Calendar card starts at y=110 (after app bar), overlapping gradient + Padding( + padding: const EdgeInsets.only(top: 110), + child: _calendarCard(context), + ), + ], + ), + ), // Selected date summary SliverToBoxAdapter(child: _selectedDateSummary(context)), diff --git a/lib/screens/contribute_screen.dart b/lib/screens/contribute_screen.dart index db482d3..e0c324d 100644 --- a/lib/screens/contribute_screen.dart +++ b/lib/screens/contribute_screen.dart @@ -1470,6 +1470,7 @@ class _ContributeScreenState extends State Widget _buildContributeTab(BuildContext context, GamificationProvider provider) { final theme = Theme.of(context); return SingleChildScrollView( + physics: const BouncingScrollPhysics(), padding: const EdgeInsets.fromLTRB(16, 20, 16, 32), child: Form( key: _formKey, @@ -1563,13 +1564,15 @@ class _ContributeScreenState extends State } Widget _formCard(List children) { - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(14), - boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: const Offset(0, 2))], + return RepaintBoundary( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14), + boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: const Offset(0, 2))], + ), + child: Column(children: children), ), - child: Column(children: children), ); } diff --git a/lib/screens/profile_screen.dart b/lib/screens/profile_screen.dart index a4d1f5f..2fc2974 100644 --- a/lib/screens/profile_screen.dart +++ b/lib/screens/profile_screen.dart @@ -1017,15 +1017,26 @@ class _ProfileScreenState extends State Widget build(BuildContext context) { final theme = Theme.of(context); const double headerHeight = 200.0; - const double cardTopOffset = 130.0; // card starts overlapping into header + const double cardTopOffset = 130.0; + + Widget sectionTitle(String text) => Padding( + padding: const EdgeInsets.fromLTRB(18, 16, 18, 12), + child: Text( + text, + style: theme.textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.w600, fontSize: 18), + ), + ); return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, - body: SingleChildScrollView( - child: Column( - children: [ - // Header + Profile Card overlap using Stack - Stack( + // CustomScrollView: only visible event cards are built — no full-tree Column renders + body: CustomScrollView( + physics: const BouncingScrollPhysics(), + slivers: [ + // Header gradient + Profile card overlap (same visual as before) + SliverToBoxAdapter( + child: Stack( children: [ _buildGradientHeader(context, headerHeight), Padding( @@ -1034,13 +1045,74 @@ class _ProfileScreenState extends State ), ], ), + ), - // Event sections - _buildEventSections(context), - - const SizedBox(height: 32), + // ── Ongoing Events ── + if (_ongoingEvents.isNotEmpty) ...[ + SliverToBoxAdapter(child: sectionTitle('Ongoing Events')), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 18), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (ctx, i) => _eventListTileFromModel(_ongoingEvents[i]), + childCount: _ongoingEvents.length, + ), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 24)), ], - ), + + // ── Upcoming Events ── + SliverToBoxAdapter(child: sectionTitle('Upcoming Events')), + if (_loadingEvents) + const SliverToBoxAdapter(child: SizedBox.shrink()) + else if (_upcomingEvents.isEmpty) + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), + child: Text('No upcoming events', + style: theme.textTheme.bodyMedium + ?.copyWith(color: theme.hintColor)), + ), + ) + else + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 18), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (ctx, i) => _eventListTileFromModel(_upcomingEvents[i]), + childCount: _upcomingEvents.length, + ), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 24)), + + // ── Past Events ── + SliverToBoxAdapter(child: sectionTitle('Past Events')), + if (_loadingEvents) + const SliverToBoxAdapter(child: SizedBox.shrink()) + else if (_pastEvents.isEmpty) + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), + child: Text('No past events', + style: theme.textTheme.bodyMedium + ?.copyWith(color: theme.hintColor)), + ), + ) + else + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 18), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (ctx, i) => _eventListTileFromModel(_pastEvents[i], faded: true), + childCount: _pastEvents.length, + ), + ), + ), + + const SliverToBoxAdapter(child: SizedBox(height: 32)), + ], ), ); }