feat: Phase 3 — 26 medium-priority gaps implemented
P3-A/K Profile: Eventify ID glassmorphic badge (tap-to-copy), DiceBear
Notionists avatar via TierAvatarRing, district picker (14 pills)
with 183-day cooldown lock, multipart photo upload to server
P3-B Home: Top Events converted to PageView scroll-snap
(viewportFraction 0.9 + PageScrollPhysics)
P3-C Event detail: contributor widget (tier ring + name + navigation),
related events horizontal row; added getEventsByCategory() to
EventsService; added contributorId/Name/Tier fields to EventModel
P3-D Kerala pincodes: 463-entry JSON (all 14 districts), registered as
asset, async-loaded in SearchScreen replacing hardcoded 32 cities
P3-E Checkout: promo code field + Apply/Remove button in Step 2,
discountAmount subtracted from total, applyPromo()/resetPromo()
methods in CheckoutProvider
P3-F/G Gamification: reward cycle countdown + EP→RP progress bar (blue→
amber) in contribute + profile screens; TierAvatarRing in podium
and all leaderboard rows; GlassCard current-user stats card at
top of leaderboard tab
P3-H New ContributorProfileScreen: tier ring, stats, submission grid
with status chips; getDashboardForUser() in GamificationService;
wired from leaderboard row taps
P3-I Achievements: 11 default badges (up from 6), 6 new icon map
entries; progress % labels already confirmed present
P3-J Reviews: CustomPainter circular arc rating ring (amber, 84px)
replaces large rating number in ReviewSummary
P3-L Share rank card: RepaintBoundary → PNG capture → Share.shareXFiles;
share button wired in profile header and leaderboard tab
P3-M SafeArea audit: home bottom nav, contribute/achievements scroll
padding, profile CustomScrollView top inset
New files: tier_avatar_ring.dart, glass_card.dart,
eventify_bottom_sheet.dart, contributor_profile_screen.dart,
share_rank_card.dart, assets/data/kerala_pincodes.json
New dep: path_provider ^2.1.0
2026-04-04 17:17:36 +05:30
import ' dart:io ' ;
import ' dart:ui ' as ui ;
import ' package:flutter/material.dart ' ;
import ' package:flutter/rendering.dart ' ;
import ' package:path_provider/path_provider.dart ' ;
import ' package:share_plus/share_plus.dart ' ;
import ' ../../widgets/tier_avatar_ring.dart ' ;
class ShareRankCard extends StatefulWidget {
final String username ;
final String tier ;
final int rank ;
final int ep ;
final int rewardPoints ;
const ShareRankCard ( {
super . key ,
required this . username ,
required this . tier ,
required this . rank ,
required this . ep ,
this . rewardPoints = 0 ,
} ) ;
@ override
State < ShareRankCard > createState ( ) = > _ShareRankCardState ( ) ;
}
class _ShareRankCardState extends State < ShareRankCard > {
final GlobalKey _boundaryKey = GlobalKey ( ) ;
bool _sharing = false ;
static const _tierGradients = {
' Bronze ' : [ Color ( 0xFF92400E ) , Color ( 0xFFD97706 ) ] ,
' Silver ' : [ Color ( 0xFF475569 ) , Color ( 0xFF94A3B8 ) ] ,
' Gold ' : [ Color ( 0xFF92400E ) , Color ( 0xFFFBBF24 ) ] ,
' Platinum ' : [ Color ( 0xFF4C1D95 ) , Color ( 0xFF8B5CF6 ) ] ,
' Diamond ' : [ Color ( 0xFF1E3A8A ) , Color ( 0xFF60A5FA ) ] ,
} ;
List < Color > get _gradient {
return _tierGradients [ widget . tier ] ? ? [ const Color ( 0xFF0F172A ) , const Color ( 0xFF1E293B ) ] ;
}
Future < void > _share ( ) async {
if ( _sharing ) return ;
setState ( ( ) = > _sharing = true ) ;
try {
final boundary = _boundaryKey . currentContext ! . findRenderObject ( ) as RenderRepaintBoundary ;
final image = await boundary . toImage ( pixelRatio: 3.0 ) ;
final byteData = await image . toByteData ( format: ui . ImageByteFormat . png ) ;
if ( byteData = = null ) return ;
final bytes = byteData . buffer . asUint8List ( ) ;
final tempDir = await getTemporaryDirectory ( ) ;
final file = File ( ' ${ tempDir . path } /eventify_rank_ ${ widget . username } .png ' ) ;
await file . writeAsBytes ( bytes ) ;
await Share . shareXFiles (
[ XFile ( file . path ) ] ,
2026-04-08 14:16:33 +05:30
subject: ' My Eventify Rank ' ,
text: ' I \' m a ${ widget . tier . toUpperCase ( ) } Explorer on Eventify Plus! ${ widget . ep } EP earned. Let \' s connect on the platform for more. \n \n https://app.eventifyplus.com ' ,
feat: Phase 3 — 26 medium-priority gaps implemented
P3-A/K Profile: Eventify ID glassmorphic badge (tap-to-copy), DiceBear
Notionists avatar via TierAvatarRing, district picker (14 pills)
with 183-day cooldown lock, multipart photo upload to server
P3-B Home: Top Events converted to PageView scroll-snap
(viewportFraction 0.9 + PageScrollPhysics)
P3-C Event detail: contributor widget (tier ring + name + navigation),
related events horizontal row; added getEventsByCategory() to
EventsService; added contributorId/Name/Tier fields to EventModel
P3-D Kerala pincodes: 463-entry JSON (all 14 districts), registered as
asset, async-loaded in SearchScreen replacing hardcoded 32 cities
P3-E Checkout: promo code field + Apply/Remove button in Step 2,
discountAmount subtracted from total, applyPromo()/resetPromo()
methods in CheckoutProvider
P3-F/G Gamification: reward cycle countdown + EP→RP progress bar (blue→
amber) in contribute + profile screens; TierAvatarRing in podium
and all leaderboard rows; GlassCard current-user stats card at
top of leaderboard tab
P3-H New ContributorProfileScreen: tier ring, stats, submission grid
with status chips; getDashboardForUser() in GamificationService;
wired from leaderboard row taps
P3-I Achievements: 11 default badges (up from 6), 6 new icon map
entries; progress % labels already confirmed present
P3-J Reviews: CustomPainter circular arc rating ring (amber, 84px)
replaces large rating number in ReviewSummary
P3-L Share rank card: RepaintBoundary → PNG capture → Share.shareXFiles;
share button wired in profile header and leaderboard tab
P3-M SafeArea audit: home bottom nav, contribute/achievements scroll
padding, profile CustomScrollView top inset
New files: tier_avatar_ring.dart, glass_card.dart,
eventify_bottom_sheet.dart, contributor_profile_screen.dart,
share_rank_card.dart, assets/data/kerala_pincodes.json
New dep: path_provider ^2.1.0
2026-04-04 17:17:36 +05:30
) ;
} catch ( e ) {
if ( mounted ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
const SnackBar ( content: Text ( ' Could not share rank card ' ) ) ,
) ;
}
} finally {
if ( mounted ) setState ( ( ) = > _sharing = false ) ;
}
}
@ override
Widget build ( BuildContext context ) {
return Column (
mainAxisSize: MainAxisSize . min ,
children: [
RepaintBoundary (
key: _boundaryKey ,
child: Container (
width: 320 ,
padding: const EdgeInsets . all ( 24 ) ,
decoration: BoxDecoration (
gradient: const LinearGradient (
begin: Alignment . topLeft ,
end: Alignment . bottomRight ,
colors: [ Color ( 0xFF0F172A ) , Color ( 0xFF1E293B ) ] ,
) ,
borderRadius: BorderRadius . circular ( 20 ) ,
) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
// Tier gradient header bar
Container (
height: 6 ,
decoration: BoxDecoration (
gradient: LinearGradient ( colors: _gradient ) ,
borderRadius: BorderRadius . circular ( 3 ) ,
) ,
) ,
const SizedBox ( height: 20 ) ,
// Avatar
TierAvatarRing ( username: widget . username , tier: widget . tier , size: 80 ) ,
const SizedBox ( height: 12 ) ,
// Username
Text (
widget . username ,
style: const TextStyle ( fontSize: 20 , fontWeight: FontWeight . w800 , color: Colors . white ) ,
) ,
const SizedBox ( height: 4 ) ,
// Tier badge
Container (
padding: const EdgeInsets . symmetric ( horizontal: 12 , vertical: 4 ) ,
decoration: BoxDecoration (
gradient: LinearGradient ( colors: _gradient ) ,
borderRadius: BorderRadius . circular ( 12 ) ,
) ,
child: Text (
widget . tier . isEmpty ? ' Contributor ' : widget . tier ,
style: const TextStyle ( fontSize: 12 , fontWeight: FontWeight . w700 , color: Colors . white ) ,
) ,
) ,
const SizedBox ( height: 20 ) ,
// Stats row
Row (
mainAxisAlignment: MainAxisAlignment . spaceEvenly ,
children: [
_stat ( ' Rank ' , ' # ${ widget . rank } ' ) ,
Container ( width: 1 , height: 40 , color: Colors . white12 ) ,
_stat ( ' EP ' , ' ${ widget . ep } ' ) ,
Container ( width: 1 , height: 40 , color: Colors . white12 ) ,
_stat ( ' RP ' , ' ${ widget . rewardPoints } ' ) ,
] ,
) ,
const SizedBox ( height: 20 ) ,
// Branding
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: const [
Icon ( Icons . bolt , size: 14 , color: Color ( 0xFF3B82F6 ) ) ,
SizedBox ( width: 4 ) ,
Text (
' EVENTIFY ' ,
style: TextStyle (
fontSize: 12 ,
fontWeight: FontWeight . w900 ,
color: Color ( 0xFF3B82F6 ) ,
letterSpacing: 2 ,
) ,
) ,
] ,
) ,
] ,
) ,
) ,
) ,
const SizedBox ( height: 16 ) ,
ElevatedButton . icon (
onPressed: _sharing ? null : _share ,
icon: _sharing
? const SizedBox (
width: 16 ,
height: 16 ,
child: CircularProgressIndicator ( strokeWidth: 2 , color: Colors . white ) ,
)
: const Icon ( Icons . share , size: 18 ) ,
label: Text ( _sharing ? ' Sharing... ' : ' Share Rank Card ' ) ,
style: ElevatedButton . styleFrom (
backgroundColor: const Color ( 0xFF1D4ED8 ) ,
foregroundColor: Colors . white ,
padding: const EdgeInsets . symmetric ( horizontal: 24 , vertical: 12 ) ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 10 ) ) ,
) ,
) ,
] ,
) ;
}
Widget _stat ( String label , String value ) {
return Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text (
value ,
style: const TextStyle ( fontSize: 18 , fontWeight: FontWeight . w800 , color: Colors . white ) ,
) ,
const SizedBox ( height: 2 ) ,
Text (
label ,
style: const TextStyle ( fontSize: 11 , color: Color ( 0xFF94A3B8 ) ) ,
) ,
] ,
) ;
}
}