feat: rebuild desktop UI to match Figma + website, hero slider improvements

- Desktop sidebar (262px, blue gradient, white pill nav), topbar (search + bell + avatar), responsive shell rewritten
- Desktop homepage: immersive hero with Ken Burns animation, pill category chips, date badge cards matching mvnew.eventifyplus.com/home
- Desktop calendar: 60/40 two-column layout with white background
- Desktop profile: full-width banner + 3-column event grids
- Desktop learn more: hero image + about/venue columns + gallery strip
- Desktop settings/contribute: polished to match design system
- Mobile hero slider: RepaintBoundary, animated dots with 44px tap targets, 5s auto-scroll, 8s post-swipe delay, shimmer loading, dynamic event type badge, human-readable dates
- Guest access: requiresAuth false on read endpoints
- Location fix: show place names instead of lat/lng coordinates
- Version 1.6.1+17

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 13:28:19 +05:30
parent 9dd78be03e
commit bc6fde1b90
21 changed files with 2938 additions and 1285 deletions

View File

@@ -13,6 +13,7 @@ import 'package:share_plus/share_plus.dart';
import '../core/app_decoration.dart';
import '../features/gamification/models/gamification_models.dart';
import '../features/gamification/providers/gamification_provider.dart';
import '../widgets/landscape_section_header.dart';
// ─────────────────────────────────────────────────────────────────────────────
// Tier colour map
@@ -138,156 +139,184 @@ class _ContributeScreenState extends State<ContributeScreen>
static const _desktopTabIcons = [Icons.edit_note, null, null];
Widget _buildDesktopLayout(BuildContext context, GamificationProvider provider) {
return Row(
children: [
Flexible(
flex: 2,
child: RepaintBoundary(
child: Container(
decoration: AppDecoration.blueGradient,
child: _buildContributeLeftPanel(context, provider),
),
),
),
Flexible(
flex: 3,
child: RepaintBoundary(
child: _buildContributeRightPanel(context, provider),
),
),
],
);
}
// ── Landscape left panel: contributor info + vertical nav ───────────────
Widget _buildContributeLeftPanel(BuildContext context, GamificationProvider provider) {
final profile = provider.profile;
final tier = profile?.tier ?? ContributorTier.BRONZE;
final lifetimeEp = profile?.lifetimeEp ?? 0;
final currentEp = profile?.currentEp ?? 0;
final currentRp = profile?.currentRp ?? 0;
// Calculate next tier threshold
const thresholds = [0, 100, 500, 1500, 5000];
final tierIdx = tier.index;
final nextThresh = tierIdx < 4 ? thresholds[tierIdx + 1] : thresholds[4];
final prevThresh = thresholds[tierIdx];
final progress = tierIdx >= 4 ? 1.0 : (lifetimeEp - prevThresh) / (nextThresh - prevThresh);
final tierColor = _tierColors[tier] ?? Colors.white;
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 900),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Title
const Text('Contributor Dashboard',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800, color: Color(0xFF111827))),
const SizedBox(height: 6),
const Text('Track your impact, earn rewards, and climb the ranks!',
style: TextStyle(fontSize: 14, color: Color(0xFF6B7280))),
const SizedBox(height: 24),
// ── Desktop Tab bar (3 tabs in blue pill) ──
Container(
decoration: BoxDecoration(
color: _primary,
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.all(5),
child: Row(
children: List.generate(_desktopTabs.length, (i) {
final isActive = _activeTab == i;
return Expanded(
child: GestureDetector(
onTap: () => setState(() => _activeTab = i),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: isActive ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (i == 0) ...[
Icon(Icons.edit_note, size: 18,
color: isActive ? _primary : Colors.white70),
const SizedBox(width: 6),
],
Text(
_desktopTabs[i],
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: isActive ? _primary : Colors.white,
),
),
],
),
),
),
);
}),
),
return SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 24),
// Title
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(
'Contributor\nDashboard',
style: TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.w800, height: 1.2),
),
const SizedBox(height: 20),
),
const SizedBox(height: 6),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(
'Track your impact & earn rewards',
style: TextStyle(color: Colors.white70, fontSize: 13),
),
),
const SizedBox(height: 24),
// ── Contributor Level card ──
Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
// Contributor Level badge
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF0F45CF), Color(0xFF3B82F6)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: tierColor.withOpacity(0.5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Contributor Level',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w700)),
const SizedBox(height: 4),
const Text('Start earning rewards by contributing!',
style: TextStyle(color: Colors.white70, fontSize: 13)),
],
Row(children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: tierColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: tierColor.withOpacity(0.6)),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text(tierLabel(tier),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13)),
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('$lifetimeEp pts',
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600)),
if (tierIdx < 4)
Text('Next: ${tierLabel(ContributorTier.values[tierIdx + 1])} (${thresholds[tierIdx + 1]} pts)',
style: const TextStyle(color: Colors.white70, fontSize: 13)),
],
),
const SizedBox(height: 8),
child: Text(tierLabel(tier), style: TextStyle(color: tierColor, fontWeight: FontWeight.w700, fontSize: 12)),
),
const Spacer(),
Text('$lifetimeEp pts', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 14)),
]),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: LinearProgressIndicator(
value: progress.clamp(0.0, 1.0),
minHeight: 8,
backgroundColor: Colors.white.withOpacity(0.2),
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
minHeight: 6,
backgroundColor: Colors.white24,
valueColor: AlwaysStoppedAnimation<Color>(tierColor),
),
),
if (tierIdx < 4) ...[
const SizedBox(height: 6),
Text(
'Next: ${tierLabel(ContributorTier.values[tierIdx + 1])} at ${thresholds[tierIdx + 1]} pts',
style: const TextStyle(color: Colors.white54, fontSize: 11),
),
],
],
),
),
const SizedBox(height: 20),
),
const SizedBox(height: 24),
// ── Desktop tab body ──
_buildDesktopTabBody(context, provider),
],
),
// Vertical tab navigation
...List.generate(_desktopTabs.length, (i) {
final isActive = _activeTab == i;
final icons = [Icons.edit_note, Icons.leaderboard_outlined, Icons.emoji_events_outlined];
return GestureDetector(
onTap: () => setState(() => _activeTab = i),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.fromLTRB(16, 0, 16, 8),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: isActive ? Colors.white : Colors.white.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
),
child: Row(children: [
Icon(icons[i], size: 20, color: isActive ? _primary : Colors.white70),
const SizedBox(width: 12),
Text(
_desktopTabs[i],
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: isActive ? _primary : Colors.white,
),
),
const Spacer(),
if (isActive) Icon(Icons.chevron_right, size: 18, color: _primary),
]),
),
);
}),
const SizedBox(height: 24),
],
),
),
);
}
// ── Landscape right panel: active tab content ────────────────────────────
Widget _buildContributeRightPanel(BuildContext context, GamificationProvider provider) {
String title;
String subtitle;
switch (_activeTab) {
case 1:
title = 'Leaderboard';
subtitle = 'Top contributors this month';
break;
case 2:
title = 'Achievements';
subtitle = 'Your earned badges';
break;
default:
title = 'Submit Event';
subtitle = 'Share events with the community';
}
return SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
LandscapeSectionHeader(title: title, subtitle: subtitle),
Expanded(
child: RepaintBoundary(
child: _buildDesktopTabBody(context, provider),
),
),
],
),
);
}
Widget _buildDesktopTabBody(BuildContext context, GamificationProvider provider) {
switch (_activeTab) {
case 0: