Game Prize and Logout

This commit is contained in:
efrilm
2025-09-18 10:39:54 +07:00
parent 006486bc2a
commit 3c596461c6
31 changed files with 4189 additions and 677 deletions
@@ -1,15 +1,40 @@
import 'package:dio/dio.dart';
import 'dart:developer';
import 'package:auto_route/auto_route.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../presentation/router/app_router.gr.dart';
import '../../constant/local_storage_key.dart';
import '../errors/unauthorized_error.dart';
class UnauthorizedInterceptor extends Interceptor {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401 ||
err.response?.statusCode == 403 ||
err.response?.statusCode == 419) {
await _handleTokenExpired();
return super.onError(UnauthorizedError(err, null), handler);
}
super.onError(err, handler);
}
Future<void> _handleTokenExpired() async {
// Clear stored token
final prefs = await SharedPreferences.getInstance();
await prefs.remove(LocalStorageKey.token);
await prefs.remove(LocalStorageKey.user);
await prefs.clear(); // Optional: clear all user data
log('handleTokenExpired');
// Navigate to login page
final context = navigatorKey.currentContext;
if (context != null) {
// Option 1: Navigate and remove all previous routes
context.router.replaceAll([LoginRoute()]);
}
}
}
+11
View File
@@ -1,4 +1,8 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../injection.dart';
import '../constant/local_storage_key.dart';
void dismissKeyboard(BuildContext context) {
final currentFocus = FocusScope.of(context);
@@ -13,3 +17,10 @@ String getNormalizePhone(String phoneNumber) {
: phoneNumber;
return '62$normalizedPhone';
}
Map<String, dynamic> getAuthorizationHeader() {
return {
'Authorization':
'Bearer ${getIt<SharedPreferences>().getString(LocalStorageKey.token)}',
};
}
+88 -13
View File
@@ -1,19 +1,22 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../../presentation/pages/mini_games/ferris_wheel/data/model.dart';
import '../../domain/game/game.dart';
import '../theme/theme.dart';
class WheelPainter extends CustomPainter {
final List<WheelSection> sections;
final List<GamePrize> gamePrizes;
final Color Function(GamePrize prize, int index) getPrizeColor;
WheelPainter({required this.sections});
WheelPainter({required this.gamePrizes, required this.getPrizeColor});
@override
void paint(Canvas canvas, Size size) {
if (gamePrizes.isEmpty) return;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2;
final sectionAngle = 2 * math.pi / sections.length;
final sectionAngle = 2 * math.pi / gamePrizes.length;
// Draw outer white border
final outerBorderPaint = Paint()
@@ -34,12 +37,13 @@ class WheelPainter extends CustomPainter {
canvas.drawCircle(center, radius - 20, innerWhitePaint);
// Draw sections
for (int i = 0; i < sections.length; i++) {
for (int i = 0; i < gamePrizes.length; i++) {
final prize = gamePrizes[i];
final startAngle = i * sectionAngle - math.pi / 2;
// Section background
final sectionPaint = Paint()
..color = sections[i].color
..color = getPrizeColor(prize, i)
..style = PaintingStyle.fill;
canvas.drawArc(
@@ -67,15 +71,16 @@ class WheelPainter extends CustomPainter {
canvas.save();
canvas.translate(iconPosition.dx, iconPosition.dy);
// Draw icon using TextPainter to simulate Icon widget
final iconText = _getIconText(sections[i].icon);
// Get icon from metadata or use default
final iconData = _getIconFromMetadata(prize.metadata);
final iconText = _getIconText(iconData);
final iconTextPainter = TextPainter(
text: TextSpan(
text: iconText,
style: TextStyle(
fontFamily: 'MaterialIcons',
fontSize: 20,
color: sections[i].color,
color: getPrizeColor(prize, i),
fontWeight: FontWeight.normal,
),
),
@@ -103,7 +108,7 @@ class WheelPainter extends CustomPainter {
final textPainter = TextPainter(
text: TextSpan(
text: sections[i].prize,
text: prize.name,
style: const TextStyle(
color: Colors.white,
fontSize: 10,
@@ -119,6 +124,42 @@ class WheelPainter extends CustomPainter {
);
canvas.restore();
// Draw stock indicator if stock is low
if (prize.stock <= 5 && prize.stock > 0) {
final stockAngle = startAngle + sectionAngle / 2;
final stockPosition = Offset(
center.dx + (radius - 35) * math.cos(stockAngle),
center.dy + (radius - 35) * math.sin(stockAngle),
);
final stockPaint = Paint()
..color = AppColor.error
..style = PaintingStyle.fill;
canvas.drawCircle(stockPosition, 8, stockPaint);
canvas.save();
canvas.translate(stockPosition.dx, stockPosition.dy);
final stockTextPainter = TextPainter(
text: TextSpan(
text: '!',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
stockTextPainter.layout();
stockTextPainter.paint(
canvas,
Offset(-stockTextPainter.width / 2, -stockTextPainter.height / 2),
);
canvas.restore();
}
}
// Draw white dots around the outer edge
@@ -141,7 +182,7 @@ class WheelPainter extends CustomPainter {
..style = PaintingStyle.stroke
..strokeWidth = 2;
for (int i = 0; i < sections.length; i++) {
for (int i = 0; i < gamePrizes.length; i++) {
final angle = i * sectionAngle - math.pi / 2;
final lineStart = Offset(
center.dx + (radius - 130) * math.cos(angle),
@@ -155,6 +196,34 @@ class WheelPainter extends CustomPainter {
}
}
IconData _getIconFromMetadata(Map<String, dynamic> metadata) {
final iconName = metadata['icon'] as String?;
switch (iconName?.toLowerCase()) {
case 'card_giftcard':
return Icons.card_giftcard;
case 'monetization_on':
return Icons.monetization_on;
case 'redeem':
return Icons.redeem;
case 'attach_money':
return Icons.attach_money;
case 'account_balance_wallet':
return Icons.account_balance_wallet;
case 'diamond':
return Icons.diamond;
case 'star':
return Icons.star;
case 'emoji_events':
return Icons.emoji_events;
case 'refresh':
return Icons.refresh;
case 'visibility':
return Icons.visibility;
default:
return Icons.card_giftcard; // Default icon
}
}
String _getIconText(IconData icon) {
// Convert IconData to Unicode string for drawing
switch (icon.codePoint) {
@@ -174,11 +243,17 @@ class WheelPainter extends CustomPainter {
return String.fromCharCode(0xe8a1);
case 0xe57d: // Icons.monetization_on
return String.fromCharCode(0xe57d);
case 0xe5ca: // Icons.diamond
return String.fromCharCode(0xe5ca);
case 0xe838: // Icons.star
return String.fromCharCode(0xe838);
case 0xe2a3: // Icons.emoji_events
return String.fromCharCode(0xe2a3);
default:
return String.fromCharCode(0xe87c); // Default star icon
return String.fromCharCode(0xe151); // Default card_giftcard icon
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
+3
View File
@@ -5,4 +5,7 @@ class ApiPath {
static String setPassword = '/api/v1/customer-auth/register/set-password';
static String login = '/api/v1/customer-auth/login';
static String resend = '/api/v1/customer-auth/resend-otp';
// Marketing
static String gamePrizeByGameId = '/api/v1/marketing/game-prizes/game';
}