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
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
// lib/features/booking/providers/checkout_provider.dart
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../../core/api/api_endpoints.dart';
|
||||
import '../../../core/utils/error_utils.dart';
|
||||
import '../models/booking_models.dart';
|
||||
import '../services/booking_service.dart';
|
||||
@@ -24,8 +28,11 @@ class CheckoutProvider extends ChangeNotifier {
|
||||
// Shipping
|
||||
ShippingDetails? shippingDetails;
|
||||
|
||||
// Coupon
|
||||
// Coupon / promo
|
||||
String? couponCode;
|
||||
double discountAmount = 0.0;
|
||||
String? promoMessage;
|
||||
bool promoApplied = false;
|
||||
|
||||
// Status
|
||||
bool loading = false;
|
||||
@@ -40,6 +47,9 @@ class CheckoutProvider extends ChangeNotifier {
|
||||
cart = [];
|
||||
shippingDetails = null;
|
||||
couponCode = null;
|
||||
discountAmount = 0.0;
|
||||
promoMessage = null;
|
||||
promoApplied = false;
|
||||
paymentId = null;
|
||||
error = null;
|
||||
loading = true;
|
||||
@@ -65,7 +75,7 @@ class CheckoutProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
double get subtotal => cart.fold(0, (sum, item) => sum + item.subtotal);
|
||||
double get total => subtotal; // expand with discount/tax later
|
||||
double get total => subtotal - discountAmount;
|
||||
|
||||
bool get hasItems => cart.isNotEmpty;
|
||||
|
||||
@@ -95,6 +105,62 @@ class CheckoutProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Apply a promo code against the backend.
|
||||
Future<bool> applyPromo(String code) async {
|
||||
if (code.trim().isEmpty) return false;
|
||||
loading = true;
|
||||
error = null;
|
||||
notifyListeners();
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final token = prefs.getString('access_token') ?? '';
|
||||
final response = await http.post(
|
||||
Uri.parse('${ApiEndpoints.baseUrl}/bookings/apply-promo/'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
body: jsonEncode({'code': code.trim(), 'event_id': eventId}),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
if (data['valid'] == true) {
|
||||
discountAmount = (data['discount_amount'] as num?)?.toDouble() ?? 0.0;
|
||||
couponCode = code.trim();
|
||||
promoMessage = data['message'] as String? ?? 'Promo applied!';
|
||||
promoApplied = true;
|
||||
notifyListeners();
|
||||
return true;
|
||||
} else {
|
||||
promoMessage = data['message'] as String? ?? 'Invalid promo code';
|
||||
promoApplied = false;
|
||||
discountAmount = 0.0;
|
||||
couponCode = null;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
promoMessage = 'Could not apply promo code';
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
promoMessage = 'Could not apply promo code';
|
||||
return false;
|
||||
} finally {
|
||||
loading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove applied promo code.
|
||||
void resetPromo() {
|
||||
discountAmount = 0.0;
|
||||
couponCode = null;
|
||||
promoMessage = null;
|
||||
promoApplied = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Process checkout on backend.
|
||||
Future<Map<String, dynamic>> processCheckout() async {
|
||||
loading = true;
|
||||
@@ -139,6 +205,9 @@ class CheckoutProvider extends ChangeNotifier {
|
||||
cart = [];
|
||||
shippingDetails = null;
|
||||
couponCode = null;
|
||||
discountAmount = 0.0;
|
||||
promoMessage = null;
|
||||
promoApplied = false;
|
||||
paymentId = null;
|
||||
error = null;
|
||||
loading = false;
|
||||
|
||||
Reference in New Issue
Block a user