em.eventifyplus.com / uat.eventifyplus.com DNS points to K8s with broken TLS cert.
backend.eventifyplus.com → EC2 174.129.72.160 with valid Let's Encrypt cert.
This fixes the root cause of "Unable to connect" on all API calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace Image.network (no cache) with CachedNetworkImage in contributor_profile_screen
- Replace NetworkImage (no cache) with CachedNetworkImageProvider in desktop_topbar and contribute_screen (leaderboard avatars)
- Add maxWidthDiskCache + maxHeightDiskCache to all 23 CachedNetworkImage calls
- Add missing memCacheWidth/Height to review_card (36x36 avatar) and learn_more related events (140x100)
- Add dynamic memCache sizing to tier_avatar_ring based on widget size
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Affects snackbar messages in booking_screen, tickets_booked_screen,
calendar_screen, settings_screen. Also updates Privacy Policy subtitle
from 'Demo app' to 'Coming Soon'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds all fields to the edit profile bottom sheet:
- First Name / Last Name (side by side), Email, Phone
- Location section: Home District (locked with "Next change" date),
Place, Pincode, State, Country
- Saves all fields via update-profile API and persists to prefs
- Loads existing values from prefs on open; refreshes from status API
on every profile open so fields stay in sync with server
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without a content type header, http package defaults to
application/octet-stream which the server rejects. Derive MIME
from file extension using a lookup map (Dart 2 compatible).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ApiClient.uploadFile() — multipart POST to /v1/upload/file (60s timeout)
- ApiEndpoints.uploadFile — points to Node.js upload endpoint
- GamificationService.submitContribution() now uploads each picked image
to OneDrive via the server upload pipeline, then passes the returned
{ fileId, url, ... } objects as `media` in the submission body
(replaces broken behaviour of sending local device paths as strings)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace RepaintBoundary widget capture approach with a pure
dart:ui PictureRecorder + Canvas implementation.
- Add share_card_generator.dart: generates 1080×1920 PNG via
Canvas without embedding any widget in the tree
- Remove share_rank_card.dart (widget approach no longer needed)
- Remove GlobalKey, _buildHiddenShareCard, RepaintBoundary,
_fmtEp from profile_screen.dart
- Simplify desktop + mobile Stacks to direct ScrollViews
- Fix Android GPU compositing timing crash (no retry needed)
- Add avatarImage.dispose() to prevent GPU memory leak
- Guard byteData null return with StateError
- Replace MaterialIcons bolt with Unicode ⚡ (tree-shake safe)
- Align tier in share text with tier rendered on card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Share sheet now pre-fills:
"I'm a BRONZE Explorer on Eventify Plus! 7 EP earned.
Let's connect on the platform for more.
https://app.eventifyplus.com"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add isLeaderboardLoading flag separate from isLoading
- Add loadLeaderboard() method that fires independently of loadAll TTL
- Remove leaderboard from loadAll() Future.wait (failures in dashboard/shop
no longer silently zero-out leaderboard data)
- setDistrict / setTimePeriod now use isLeaderboardLoading
- contribute_screen calls loadLeaderboard() alongside loadAll() on mount
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enrich kerala_pincodes.json with lat/lng for all 463 entries via
pgeocode offline DB (453 exact matches + 10 district centroids).
Update SearchScreen _LocationItem to carry lat/lng fields, load them
from JSON on init, and pass them through every selection path
(_selectWithPincode, _selectAndClose, search result onTap).
Result: selecting Chavakkad (or any Kerala city) now pops
{label, pincode, lat:10.59322, lng:76.0297} → home_screen saves coords
to prefs → getEventsByLocation sends lat/lng to Django → haversine
filtering returns events within 10km radius, expanding to 25/50/100km
if fewer than 6 events found.
- Wrap Top Events skeleton Row in SingleChildScrollView to fix 225px
RenderFlex overflow when 3x 200px skeletons exceed container width
- Fix gamification service using POST for GET endpoints: dashboard,
leaderboard, and shop/items all use router.get() on the Node.js server
- CORS: add http://localhost:8080 to CORS_ALLOWED_ORIGINS (applied live
to eventify-django container + local settings.py)
Root cause: SearchScreen popped with a plain city label string; the
pincode was available in search results but discarded. home_screen only
saved the display label to prefs and never updated the 'pincode' key,
so every API call always sent {pincode:'all'} regardless of selection.
GPS path had the same issue — lat/lng were obtained but thrown away
after reverse-geocoding; only the label was passed back.
Fix:
- SearchScreen now pops with Map<String,dynamic> {label, pincode,
lat?, lng?} instead of a plain String
- Pincode results return their pincode; GPS returns actual coordinates;
popular city chips look up the first matching pincode from the
Kerala pincodes DB (fallback 'all' if not found)
- home_screen._openLocationSearch() saves pincode + lat/lng to prefs
and updates _pincode/_userLat/_userLng in state
- home_screen._loadUserDataAndEvents() prefers getEventsByLocation
(haversine) when GPS coords are saved, falls back to getEventsByPincode
- EventsService gains getEventsByLocation(lat, lng) which sends
latitude/longitude/radius_km to the existing Django haversine endpoint
and auto-expands radius 10→25→50→100 km until ≥ 6 events found
Changed getEventDetails to requiresAuth: false so guests can fetch
full event details without auth tokens. Added retry logic (2 attempts
with 1s delay) to _loadFullDetails for reliability on slow networks.
This ensures important_information, images, and other detail-only
fields are always fetched in the background after initial display.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixed HTML parser to strip <style> and <script> blocks entirely
(including their content) before extracting text. Previously, CSS
rules like "td {border: 1px solid...}" leaked into the parsed output.
Also added </div>, </p>, </li> as newline separators so div-wrapped
content (common in Django admin rich text) parses into separate items.
Added debug logging to _loadFullDetails for troubleshooting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unselected chips now have a 1.5px light gray (#E5E7EB) border so they
stand out against the white background. Selected chips get a matching
primary blue border. Also slightly increased shadow opacity for better
depth perception. Replaced deprecated withOpacity calls with withValues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- "View All" on "Events Around You" header now toggles between
horizontal scroll and expanded wrap grid showing all categories
- Tapping a category chip replaces all shelf sections with a
filtered vertical list of events for that category only
- Tapping "All Events" restores the shelf layout for all categories
- "View All" on each shelf header (Music, Festivals, etc.) selects
that category in the chips and shows its filtered event list
- Added AnimatedSwitcher for smooth transition between views
- Added AnimatedCrossFade for chip expand/collapse animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When navigating from the home screen, LearnMoreScreen now shows the
pre-loaded event data instantly, then silently fetches full details
from the event-details API in the background. This fills in fields
missing from the slim list endpoint (important_information, images,
important_info) without showing a loading spinner.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cleaned up _buildVenueSection: removed broken static map URL (empty
API key), removed unused map controls (directional pad, satellite
toggle). Native GoogleMap widget on mobile, simple fallback on web.
Pending: Google Maps API key in AndroidManifest.xml.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New feature: Users can view, submit, and interact with event reviews.
Components added:
- ReviewModel, ReviewStatsModel, ReviewListResponse (models)
- ReviewService with getReviews, submitReview, markHelpful, flagReview
- StarRatingInput (interactive 5-star picker with labels)
- StarDisplay (read-only fractional star display)
- ReviewSummary (average rating + distribution bars)
- ReviewForm (star picker + comment field + submit/update)
- ReviewCard (avatar, timestamp, expandable comment, helpful/flag)
- ReviewSection (main container with pagination and state mgmt)
Integration:
- Added to LearnMoreScreen (both mobile and desktop layouts)
- Review API endpoints point to app.eventifyplus.com Node.js backend
- EventModel updated with averageRating/reviewCount fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: Rewrote EventListAPI to query per-type with DB-level LIMIT
instead of loading all 734 events into memory. Added slim serializer
(32KB vs 154KB). Added DB indexes on event_type_id and pincode.
Frontend: Category chips now filter locally from _allEvents (instant,
no API call). Top Events and category sections always show all types
regardless of selected category. Added TTL caching for event types
(30min) and events (5min). Reduced API timeout from 30s to 10s.
Added memCacheHeight to all CachedNetworkImage widgets. Batched
setState calls from 5 to 2 during startup. Cached _eventDates getter.
Switched baseUrl to em.eventifyplus.com (Django via Nginx+SSL).
Added initialEvent param to LearnMoreScreen for instant detail views.
Resolved relative media URLs for category icons.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LearnMoreScreen now accepts an optional initialEvent parameter so it
can render immediately from already-loaded data instead of re-fetching
from the event-details API. This fixes the guest-mode flow where the
unauthenticated API call was failing. Also changed getEventDetails to
requiresAuth: true so logged-in users send their token when the API
path is used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When lat,lng coordinates are stored in SharedPreferences from
a previous session, reverse geocode them to a human-readable
location name (e.g. "Whitefield, Bengaluru") instead of showing
raw numbers like "10.57376,76.01188".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The guest button was nearly invisible (grey text, fontSize 13 on dark
background). Now uses white70, fontSize 15, TextButton with proper
tap padding. Also guards wishlist toggle on event detail page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New file: lib/core/auth/auth_guard.dart
Static AuthGuard class with isGuest flag and requireLogin() helper
that shows a login prompt bottom sheet when guests try protected actions.
login_screen.dart / desktop_login_screen.dart:
Added "Continue as Guest" button below sign-up link.
Sets AuthGuard.isGuest = true, then navigates to HomeScreen.
api_client.dart:
_buildAuthBody() and GET auth check no longer throw when token is missing.
If no token (guest), request proceeds without auth — backend decides.
home_screen.dart:
Bottom nav guards: tapping Contribute (index 2) or Profile (index 3)
as guest shows login prompt instead of navigating.
auth_service.dart:
AuthGuard.setGuest(false) called on successful login AND register
so guest flag is always cleared when user authenticates.
Guest CAN: browse home, calendar, search, filter, view event details.
Guest CANNOT: contribute, view profile, book events (prompts login).
All CachedNetworkImage instances in list/card contexts now decode at
2x rendered size instead of full resolution. A 3000x2000 event photo
previously decoded to ~24MB in GPU memory even when shown at 96px —
now decodes to <1MB.
Affected screens (16 CachedNetworkImage instances total):
- home_screen.dart: hero (800w), top card (300w), stacked (192w),
horizontal (440x360), full-width (800x400), search (112x112),
filter sheet (160x160), type icons (112x112)
- home_desktop_screen.dart: mini (128x128), grid (600x280), horiz (600x296)
- calendar_screen.dart: event card (400x300)
- profile_screen.dart: avatar (size*2), event tile (120x120)
learn_more_screen.dart intentionally unchanged — full-res for detail view.
Estimated memory reduction: ~500MB → ~30MB for a typical home screen.
UI/UX Pro Max + Flutter Expert audit of the home screen hero section.
viewportFraction 0.88
Adjacent cards peek 6% on each side — users see there is more content
to swipe without any instruction. Most impactful single-line UX change.
Overlay card design
Title and metadata (date + location) now live ON the image behind a
dark gradient (transparent → black 78%) at the bottom 65% of the card.
Previously the title was below the image in a split layout that wasted
space and felt disconnected. Card height increased 300 → 320px.
FEATURED glassmorphism badge
Top-left corner chip with BackdropFilter blur (sigmaX/Y 10) and a
white-border container gives each card a premium editorial feel.
Scale animation (AnimatedBuilder per card)
Active card scales to 1.0, adjacent cards to 0.94. The AnimatedBuilder
is placed inside itemBuilder so only the visible card rebuilds on each
scroll tick — not the PageView or any ancestor.
Auto-scroll resets on page change
onPageChanged now calls _startAutoScroll() which cancels the previous
timer and starts a fresh 3-second countdown. Users who swipe manually
always get a full 3 seconds to read before auto-advance continues.
Shimmer loading placeholder (_HeroShimmer)
New StatefulWidget added below HomeScreen — a LinearGradient scan-line
animated at 1400ms repeat. Replaces the flat Color(0xFF1A2A4A) box that
looked broken while images were loading.
profile_screen: SingleChildScrollView + Column eagerly built every event
card (all images, shadows, tiles) at once even when off-screen. Replaced
with CustomScrollView + SliverList so only visible tiles are built per
frame. Also switches to BouncingScrollPhysics for natural momentum.
contribute_screen: Each _formCard wrapped in RepaintBoundary so form
cards are isolated render layers — one card's repaint doesn't invalidate
its siblings. Added BouncingScrollPhysics to the form SingleChildScrollView.
calendar_screen: Blue gradient banner was Positioned(top:0) making it
sticky even as the user scrolled. Removed the fixed Positioned layer and
moved the gradient inside the CustomScrollView as the first sliver in a
Stack alongside the calendar card (which keeps its y=110 visual overlap).
Now the entire page — gradient, calendar, events — scrolls as one unit.
Three root causes of the perceived scroll/animation lag:
1. profile_screen.dart — AnimationController listener called setState() on
every animation frame (60fps × 2s = 120 full-tree rebuilds). The entire
ProfileScreen with its nested lists and images was rebuilding 60 times per
second just to update two small widgets (EXP bar + stat counters).
Fix: remove setState() from listeners entirely; wrap only the EXP bar
(LayoutBuilder) and stat row (IntrinsicHeight) in AnimatedBuilder so
only those two leaf widgets re-render per frame.
2. learn_more_screen.dart — PageView.onPageChanged called setState() on
every swipe, rebuilding the full event detail screen (blurred bg image,
map, about section, etc.) just to update the 6px dot indicators.
Fix: int _currentPage → ValueNotifier<int> _pageNotifier; wrap only the
dot row and the blurred background image in ValueListenableBuilder.
3. search_screen.dart — BackdropFilter(ImageFilter.blur) without a
RepaintBoundary forces Flutter to read every pixel behind the blur widget
and composite it every frame. When the user scrolls the underlying list,
the blur repaints continuously causing frame drops.
Fix: wrap BackdropFilter in RepaintBoundary to isolate its repaint layer.
VideoPlayerController.networkUrl(Uri.parse('assets/login-bg.mp4')) silently
fails because 'assets/login-bg.mp4' is not a valid HTTP URL — the video
never initializes and the login screen shows a plain black background.
Fix: switch to VideoPlayerController.asset() and register the file in
pubspec.yaml. The MP4 is gitignored (22 MB) and kept local for builds.
The mobile calendar layout had a split-height bug where the event list
at the bottom was squeezed into whatever pixel crumbs remained after the
calendar card and summary bar consumed their fixed space. On small phones
or 6-row months (~390px calendar), the events area could shrink to under
100px — barely one card, with no way to scroll.
Fix: replace Column + Expanded(ListView) with a CustomScrollView using
slivers so the full page — calendar card, summary bar, and event cards —
scrolls as one unified surface. SliverFillRemaining handles loading and
empty states so they always fill the visible viewport naturally.