Update default location to Thrissur and remove Whitefield, Bengaluru
This commit is contained in:
@@ -121,26 +121,56 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Build
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
int _mainTab = 0; // 0: Contribute, 1: Leaderboard, 2: Achievements
|
||||
@override
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<GamificationProvider>(
|
||||
builder: (context, provider, _) {
|
||||
if (provider.isLoading && provider.profile == null) {
|
||||
return const Scaffold(
|
||||
backgroundColor: _pageBg,
|
||||
body: Center(child: BouncingLoader(color: _blue)),
|
||||
backgroundColor: _blue,
|
||||
body: Center(child: BouncingLoader(color: Colors.white)),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
backgroundColor: _pageBg,
|
||||
backgroundColor: Colors.white, // Changed from _blue
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildStatsBar(provider),
|
||||
_buildTierRoadmap(provider),
|
||||
_buildTabBar(),
|
||||
Expanded(child: _buildTabContent(provider)),
|
||||
],
|
||||
bottom: false,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeader(provider),
|
||||
Transform.translate(
|
||||
offset: const Offset(0, -24),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_mainTab == 0) ...[
|
||||
_buildStatsBar(provider),
|
||||
_buildTierRoadmap(provider),
|
||||
const SizedBox(height: 12),
|
||||
_buildTabBar(),
|
||||
_buildTabContent(provider),
|
||||
] else if (_mainTab == 1) ...[
|
||||
_buildLeaderboardTab(provider),
|
||||
] else if (_mainTab == 2) ...[
|
||||
_buildAchievementsTab(provider),
|
||||
],
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -148,6 +178,534 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// LEADERBOARD TAB
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
Widget _buildLeaderboardTab(GamificationProvider provider) {
|
||||
final leaderboard = provider.leaderboard;
|
||||
final currentPeriod = provider.leaderboardTimePeriod;
|
||||
final currentDistrict = provider.leaderboardDistrict;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
// Time Period Toggle
|
||||
Center(
|
||||
child: Container(
|
||||
height: 48,
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedAlign(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
alignment: currentPeriod == 'all_time' ? Alignment.centerLeft : Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Container(
|
||||
width: 144,
|
||||
decoration: BoxDecoration(
|
||||
color: _blue,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => provider.setTimePeriod('all_time'),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'All Time',
|
||||
style: TextStyle(
|
||||
color: currentPeriod == 'all_time' ? Colors.white : _subText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => provider.setTimePeriod('this_month'),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'This Month',
|
||||
style: TextStyle(
|
||||
color: currentPeriod == 'this_month' ? Colors.white : _subText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// District Chips
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildDistrictChip(provider, 'Overall Kerala'),
|
||||
..._districts.where((d) => d != 'Other').map((d) => _buildDistrictChip(provider, d)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Leaderboard List
|
||||
if (provider.isLoading && leaderboard.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 60),
|
||||
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
)
|
||||
else if (leaderboard.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 60),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.emoji_events_outlined, size: 48, color: Colors.grey.shade300),
|
||||
const SizedBox(height: 12),
|
||||
Text('No rankings available for this area.', style: TextStyle(color: Colors.grey.shade400)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 20),
|
||||
itemCount: leaderboard.length,
|
||||
separatorBuilder: (_, __) => Divider(height: 1, color: _border.withValues(alpha: 0.5)),
|
||||
itemBuilder: (context, index) {
|
||||
final entry = leaderboard[index];
|
||||
return _buildLeaderboardTile(entry);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 100), // Bottom padding
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDistrictChip(GamificationProvider provider, String district) {
|
||||
final isSelected = provider.leaderboardDistrict == district;
|
||||
return GestureDetector(
|
||||
onTap: () => provider.setDistrict(district),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? _blue : Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: isSelected ? _blue : _border),
|
||||
),
|
||||
child: Text(
|
||||
district,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.white : _darkText,
|
||||
fontSize: 13,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLeaderboardTile(LeaderboardEntry entry) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 32,
|
||||
child: Text(
|
||||
'${entry.rank}',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: entry.rank <= 3 ? _blue : _subText,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: _lightBlueBg,
|
||||
backgroundImage: entry.avatarUrl != null ? NetworkImage(entry.avatarUrl!) : null,
|
||||
child: entry.avatarUrl == null
|
||||
? const Icon(Icons.person_outline, color: _blue, size: 20)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.username,
|
||||
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.normal, color: _darkText),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${entry.lifetimeEp} pts',
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF10B981), // Emerald green
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// ACHIEVEMENTS TAB
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
Widget _buildAchievementsTab(GamificationProvider provider) {
|
||||
final achievements = provider.achievements;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (achievements.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 60),
|
||||
child: Center(
|
||||
child: Text('No achievements found.', style: TextStyle(color: _subText)),
|
||||
),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: achievements.map((badge) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: _buildAchievementCard(badge),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAchievementCard(AchievementBadge badge) {
|
||||
final bool isLocked = !badge.isUnlocked;
|
||||
final Color iconColor = _getAchievementColor(badge.iconName);
|
||||
final IconData iconData = _getAchievementIcon(badge.iconName);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: _border.withValues(alpha: 0.8)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Large Icon Container
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: isLocked ? Colors.grey.shade100 : iconColor.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isLocked ? Icons.lock_outline : iconData,
|
||||
color: isLocked ? Colors.grey.shade400 : iconColor,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Title with Lock Icon if needed
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
badge.title,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: isLocked ? Colors.grey : _darkText,
|
||||
),
|
||||
),
|
||||
if (isLocked) ...[
|
||||
const SizedBox(width: 8),
|
||||
Icon(Icons.lock_outline, size: 18, color: Colors.grey.shade300),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
// Description
|
||||
Text(
|
||||
badge.description,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isLocked ? Colors.grey.shade400 : _subText,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
|
||||
// Progress Section
|
||||
if (!badge.isUnlocked && badge.progress > 0) ...[
|
||||
const SizedBox(height: 24),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: 6,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: badge.progress,
|
||||
child: Container(
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: _blue,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'${(badge.progress * 100).toInt()}%',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getAchievementColor(String iconName) {
|
||||
switch (iconName.toLowerCase()) {
|
||||
case 'star': return const Color(0xFF3B82F6); // Blue
|
||||
case 'crown': return const Color(0xFFF59E0B); // Amber
|
||||
case 'fire': return const Color(0xFFEF4444); // Red
|
||||
case 'verified': return const Color(0xFF10B981); // Emerald
|
||||
case 'community': return const Color(0xFF8B5CF6); // Purple
|
||||
case 'expert': return const Color(0xFF6366F1); // Indigo
|
||||
default: return _blue;
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getAchievementIcon(String iconName) {
|
||||
switch (iconName.toLowerCase()) {
|
||||
case 'star': return Icons.star_rounded;
|
||||
case 'crown': return Icons.emoji_events_rounded;
|
||||
case 'fire': return Icons.local_fire_department_rounded;
|
||||
case 'verified': return Icons.verified_rounded;
|
||||
case 'community': return Icons.people_alt_rounded;
|
||||
case 'expert': return Icons.workspace_premium_rounded;
|
||||
case 'precision': return Icons.gps_fixed_rounded;
|
||||
default: return Icons.stars_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// NEW BLUE HEADER DESIGN
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
Widget _buildHeader(GamificationProvider provider) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: _blue,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 32), // Increased bottom padding
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'Contributor Dashboard',
|
||||
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.normal),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'Track your impact, earn rewards, and climb\nthe ranks!',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.white70, fontSize: 14, height: 1.4),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_buildMainTabGlider(),
|
||||
const SizedBox(height: 20),
|
||||
_buildContributorLevelCard(provider),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMainTabGlider() {
|
||||
const labels = ['Contribute', 'Leaderboard', 'Achievements'];
|
||||
return Container(
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final tabWidth = constraints.maxWidth / 3;
|
||||
return Stack(
|
||||
children: [
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
left: tabWidth * _mainTab,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: tabWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: List.generate(3, (i) {
|
||||
final active = _mainTab == i;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _mainTab = i),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (i == 0) ...[
|
||||
Icon(Icons.edit_square, size: 16, color: active ? _blue : Colors.white),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
Text(
|
||||
labels[i],
|
||||
style: TextStyle(
|
||||
color: active ? _blue : Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContributorLevelCard(GamificationProvider provider) {
|
||||
final profile = provider.profile;
|
||||
final tier = profile?.tier ?? ContributorTier.BRONZE;
|
||||
final currentEp = profile?.lifetimeEp ?? 0;
|
||||
int nextThreshold = _tierThresholds.last;
|
||||
String nextTierLabel = 'Max';
|
||||
for (int i = 0; i < ContributorTier.values.length; i++) {
|
||||
if (currentEp < _tierThresholds[i]) {
|
||||
nextThreshold = _tierThresholds[i];
|
||||
nextTierLabel = tierLabel(ContributorTier.values[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
double progress = (currentEp / nextThreshold).clamp(0.0, 1.0);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('Contributor Level', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.normal)),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
decoration: BoxDecoration(color: Colors.white.withOpacity(0.25), borderRadius: BorderRadius.circular(20)),
|
||||
child: Text(tierLabel(tier), style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('Start earning rewards by\ncontributing!', style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14)),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('$currentEp pts', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.normal, fontSize: 13)),
|
||||
Text('Next: $nextTierLabel ($nextThreshold pts)', style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 12)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.white.withOpacity(0.2),
|
||||
valueColor: const AlwaysStoppedAnimation(Colors.white),
|
||||
minHeight: 8,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// 1. COMPACT STATS BAR
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -158,65 +716,65 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
final tierIcon = _tierIcons[tier] ?? Icons.shield_outlined;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
child: Row(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Tier pill
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _blue,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(tierIcon, color: tierColor, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
tierLabel(tier),
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: _blue,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(tierIcon, color: Colors.white, size: 14),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
tierLabel(tier).toUpperCase(),
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.normal, fontSize: 13, letterSpacing: 0.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Liquid EP
|
||||
Icon(Icons.bolt, color: _blue, size: 18),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${profile?.currentEp ?? 0}',
|
||||
style: const TextStyle(color: _darkText, fontWeight: FontWeight.w700, fontSize: 15),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Text('EP', style: TextStyle(color: _subText, fontSize: 12)),
|
||||
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// RP
|
||||
Icon(Icons.card_giftcard, color: _rpOrange, size: 18),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${profile?.currentRp ?? 0}',
|
||||
style: TextStyle(color: _rpOrange, fontWeight: FontWeight.w700, fontSize: 15),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Text('RP', style: TextStyle(color: _subText, fontSize: 12)),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Share button
|
||||
GestureDetector(
|
||||
onTap: () => _shareRank(provider),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(Icons.share_outlined, color: _subText, size: 18),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Liquid EP
|
||||
Icon(Icons.bolt_outlined, color: _blue, size: 18),
|
||||
const SizedBox(width: 4),
|
||||
Text('${profile?.currentEp ?? 0}', style: const TextStyle(color: _darkText, fontWeight: FontWeight.normal, fontSize: 15)),
|
||||
const SizedBox(width: 4),
|
||||
const Text('Liquid EP', style: TextStyle(color: _subText, fontSize: 12)),
|
||||
const SizedBox(width: 16),
|
||||
// RP
|
||||
Icon(Icons.card_giftcard_outlined, color: _rpOrange, size: 18),
|
||||
const SizedBox(width: 4),
|
||||
Text('${profile?.currentRp ?? 0}', style: TextStyle(color: _rpOrange, fontWeight: FontWeight.normal, fontSize: 15)),
|
||||
const SizedBox(width: 4),
|
||||
const Text('RP', style: TextStyle(color: _subText, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Share Rank button
|
||||
GestureDetector(
|
||||
onTap: () => _shareRank(provider),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: _border),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.ios_share_outlined, color: _blue, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Share Rank', style: TextStyle(color: _blue, fontWeight: FontWeight.normal, fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -337,7 +895,7 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
left: tabWidth * _activeTab + 4,
|
||||
top: 4,
|
||||
child: Container(
|
||||
width: tabWidth - 8,
|
||||
width: tabWidth > 8 ? tabWidth - 8 : 0,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: _blue,
|
||||
@@ -363,16 +921,20 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
children: [
|
||||
Icon(
|
||||
_tabIcons[i],
|
||||
size: 18,
|
||||
size: 16, // Slightly smaller icon
|
||||
color: isActive ? Colors.white : const Color(0xFF64748B),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
_tabLabels[i],
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isActive ? Colors.white : const Color(0xFF64748B),
|
||||
const SizedBox(width: 4), // Tighter spacing
|
||||
Flexible(
|
||||
child: Text(
|
||||
_tabLabels[i],
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 11, // Smaller font for better fit
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isActive ? Colors.white : const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -470,6 +1032,8 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
|
||||
return ListView.separated(
|
||||
key: const ValueKey('list'),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: submissions.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
@@ -757,7 +1321,8 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
Widget _dropdown(String value, List<String> items, ValueChanged<String?> onChanged) {
|
||||
return DropdownButtonFormField<String>(
|
||||
value: value,
|
||||
items: items.map((e) => DropdownMenuItem(value: e, child: Text(e, style: const TextStyle(fontSize: 14)))).toList(),
|
||||
isExpanded: true,
|
||||
items: items.map((e) => DropdownMenuItem(value: e, child: Text(e, style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis))).toList(),
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
|
||||
Reference in New Issue
Block a user