Compare commits
2 Commits
3020c4e626
...
c0527a086c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0527a086c | ||
|
|
8eae79f72b |
@@ -8,6 +8,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:worker/features/cart/presentation/pages/cart_page.dart';
|
import 'package:worker/features/cart/presentation/pages/cart_page.dart';
|
||||||
import 'package:worker/features/favorites/presentation/pages/favorites_page.dart';
|
import 'package:worker/features/favorites/presentation/pages/favorites_page.dart';
|
||||||
|
import 'package:worker/features/loyalty/presentation/pages/loyalty_page.dart';
|
||||||
|
import 'package:worker/features/loyalty/presentation/pages/points_history_page.dart';
|
||||||
import 'package:worker/features/loyalty/presentation/pages/rewards_page.dart';
|
import 'package:worker/features/loyalty/presentation/pages/rewards_page.dart';
|
||||||
import 'package:worker/features/main/presentation/pages/main_scaffold.dart';
|
import 'package:worker/features/main/presentation/pages/main_scaffold.dart';
|
||||||
import 'package:worker/features/orders/presentation/pages/order_detail_page.dart';
|
import 'package:worker/features/orders/presentation/pages/order_detail_page.dart';
|
||||||
@@ -101,6 +103,16 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Loyalty Route
|
||||||
|
GoRoute(
|
||||||
|
path: RouteNames.loyalty,
|
||||||
|
name: RouteNames.loyalty,
|
||||||
|
pageBuilder: (context, state) => MaterialPage(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: const LoyaltyPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Loyalty Rewards Route
|
// Loyalty Rewards Route
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/loyalty/rewards',
|
path: '/loyalty/rewards',
|
||||||
@@ -111,6 +123,16 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Points History Route
|
||||||
|
GoRoute(
|
||||||
|
path: RouteNames.pointsHistory,
|
||||||
|
name: 'loyalty_points_history',
|
||||||
|
pageBuilder: (context, state) => MaterialPage(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: const PointsHistoryPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Orders Route
|
// Orders Route
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteNames.orders,
|
path: RouteNames.orders,
|
||||||
|
|||||||
@@ -169,8 +169,7 @@ class HomePage extends ConsumerWidget {
|
|||||||
QuickAction(
|
QuickAction(
|
||||||
icon: Icons.history,
|
icon: Icons.history,
|
||||||
label: 'Lịch sử điểm',
|
label: 'Lịch sử điểm',
|
||||||
onTap: () =>
|
onTap: () => context.push(RouteNames.pointsHistory),
|
||||||
_showComingSoon(context, 'Lịch sử điểm', l10n),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
/// Data Source: Points History Local Data Source
|
||||||
|
///
|
||||||
|
/// Handles local storage operations for loyalty points history using Hive.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:hive_ce/hive.dart';
|
||||||
|
import 'package:worker/core/constants/storage_constants.dart';
|
||||||
|
import 'package:worker/core/database/models/enums.dart';
|
||||||
|
import 'package:worker/features/loyalty/data/models/loyalty_point_entry_model.dart';
|
||||||
|
|
||||||
|
/// Points History Local Data Source
|
||||||
|
///
|
||||||
|
/// Provides methods to interact with locally stored points history data.
|
||||||
|
class PointsHistoryLocalDataSource {
|
||||||
|
Box? _entriesBox;
|
||||||
|
|
||||||
|
/// Get Hive box for points entries
|
||||||
|
Future<Box> get entriesBox async {
|
||||||
|
if (_entriesBox != null) return _entriesBox!;
|
||||||
|
|
||||||
|
// Use the box already opened by HiveService
|
||||||
|
if (Hive.isBoxOpen(HiveBoxNames.loyaltyBox)) {
|
||||||
|
_entriesBox = Hive.box(HiveBoxNames.loyaltyBox);
|
||||||
|
} else {
|
||||||
|
_entriesBox = await Hive.openBox(HiveBoxNames.loyaltyBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _entriesBox!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all points entries
|
||||||
|
Future<List<LoyaltyPointEntryModel>> getAllEntries() async {
|
||||||
|
final box = await entriesBox;
|
||||||
|
final entries = box.values
|
||||||
|
.whereType<LoyaltyPointEntryModel>()
|
||||||
|
.toList();
|
||||||
|
entries.sort((a, b) => b.timestamp.compareTo(a.timestamp)); // Newest first
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get entry by ID
|
||||||
|
Future<LoyaltyPointEntryModel?> getEntryById(String entryId) async {
|
||||||
|
final box = await entriesBox;
|
||||||
|
try {
|
||||||
|
return box.values
|
||||||
|
.whereType<LoyaltyPointEntryModel>()
|
||||||
|
.firstWhere((entry) => entry.entryId == entryId);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Entry not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save entry
|
||||||
|
Future<void> saveEntry(LoyaltyPointEntryModel entry) async {
|
||||||
|
final box = await entriesBox;
|
||||||
|
await box.put(entry.entryId, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save multiple entries
|
||||||
|
Future<void> saveEntries(List<LoyaltyPointEntryModel> entries) async {
|
||||||
|
final box = await entriesBox;
|
||||||
|
final Map<String, LoyaltyPointEntryModel> entriesMap = {
|
||||||
|
for (var entry in entries) entry.entryId: entry,
|
||||||
|
};
|
||||||
|
await box.putAll(entriesMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete entry
|
||||||
|
Future<void> deleteEntry(String entryId) async {
|
||||||
|
final box = await entriesBox;
|
||||||
|
await box.delete(entryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all entries
|
||||||
|
Future<void> clearEntries() async {
|
||||||
|
final box = await entriesBox;
|
||||||
|
await box.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seed mock data for development
|
||||||
|
Future<void> seedMockEntries() async {
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
final mockEntries = [
|
||||||
|
LoyaltyPointEntryModel(
|
||||||
|
entryId: 'entry_001',
|
||||||
|
userId: 'user_001',
|
||||||
|
points: 3,
|
||||||
|
entryType: EntryType.earn,
|
||||||
|
source: EntrySource.purchase,
|
||||||
|
description: 'Giao dịch mua hàng 00083',
|
||||||
|
referenceId: 'order_00083',
|
||||||
|
referenceType: 'order',
|
||||||
|
complaint: null,
|
||||||
|
complaintStatus: ComplaintStatus.none,
|
||||||
|
balanceAfter: 604,
|
||||||
|
expiryDate: now.add(const Duration(days: 365)),
|
||||||
|
timestamp: DateTime(2023, 9, 28, 17, 23, 18),
|
||||||
|
),
|
||||||
|
LoyaltyPointEntryModel(
|
||||||
|
entryId: 'entry_002',
|
||||||
|
userId: 'user_001',
|
||||||
|
points: 0,
|
||||||
|
entryType: EntryType.earn,
|
||||||
|
source: EntrySource.purchase,
|
||||||
|
description: 'Giao dịch mua hàng 00081',
|
||||||
|
referenceId: 'order_00081',
|
||||||
|
referenceType: 'order',
|
||||||
|
complaint: null,
|
||||||
|
complaintStatus: ComplaintStatus.none,
|
||||||
|
balanceAfter: 604,
|
||||||
|
expiryDate: now.add(const Duration(days: 365)),
|
||||||
|
timestamp: DateTime(2023, 9, 27, 17, 23, 18),
|
||||||
|
),
|
||||||
|
LoyaltyPointEntryModel(
|
||||||
|
entryId: 'entry_003',
|
||||||
|
userId: 'user_001',
|
||||||
|
points: -5,
|
||||||
|
entryType: EntryType.expiry,
|
||||||
|
source: EntrySource.promotion,
|
||||||
|
description: 'Điểm thưởng hết hạn',
|
||||||
|
referenceId: null,
|
||||||
|
referenceType: null,
|
||||||
|
complaint: null,
|
||||||
|
complaintStatus: ComplaintStatus.none,
|
||||||
|
balanceAfter: 604,
|
||||||
|
expiryDate: null,
|
||||||
|
timestamp: DateTime(2023, 9, 20, 17, 23, 18),
|
||||||
|
),
|
||||||
|
LoyaltyPointEntryModel(
|
||||||
|
entryId: 'entry_004',
|
||||||
|
userId: 'user_001',
|
||||||
|
points: -500,
|
||||||
|
entryType: EntryType.redeem,
|
||||||
|
source: EntrySource.giftRedemption,
|
||||||
|
description: 'Đổi Voucher HSG',
|
||||||
|
referenceId: 'gift_001',
|
||||||
|
referenceType: 'gift',
|
||||||
|
complaint: null,
|
||||||
|
complaintStatus: ComplaintStatus.none,
|
||||||
|
balanceAfter: 604,
|
||||||
|
expiryDate: null,
|
||||||
|
timestamp: DateTime(2023, 9, 19, 17, 23, 18),
|
||||||
|
),
|
||||||
|
LoyaltyPointEntryModel(
|
||||||
|
entryId: 'entry_005',
|
||||||
|
userId: 'user_001',
|
||||||
|
points: 5,
|
||||||
|
entryType: EntryType.earn,
|
||||||
|
source: EntrySource.referral,
|
||||||
|
description: 'Giới thiệu người dùng',
|
||||||
|
referenceId: null,
|
||||||
|
referenceType: null,
|
||||||
|
complaint: null,
|
||||||
|
complaintStatus: ComplaintStatus.none,
|
||||||
|
balanceAfter: 604,
|
||||||
|
expiryDate: now.add(const Duration(days: 365)),
|
||||||
|
timestamp: DateTime(2023, 9, 10, 17, 23, 18),
|
||||||
|
),
|
||||||
|
LoyaltyPointEntryModel(
|
||||||
|
entryId: 'entry_006',
|
||||||
|
userId: 'user_001',
|
||||||
|
points: -200,
|
||||||
|
entryType: EntryType.redeem,
|
||||||
|
source: EntrySource.giftRedemption,
|
||||||
|
description: 'Đổi quà',
|
||||||
|
referenceId: 'gift_002',
|
||||||
|
referenceType: 'gift',
|
||||||
|
complaint: null,
|
||||||
|
complaintStatus: ComplaintStatus.none,
|
||||||
|
balanceAfter: 604,
|
||||||
|
expiryDate: null,
|
||||||
|
timestamp: DateTime(2023, 9, 5, 17, 23, 18),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await saveEntries(mockEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get transaction amount from description (for purchase entries)
|
||||||
|
int? getTransactionAmount(String description) {
|
||||||
|
if (description.contains('Giao dịch mua hàng 00083')) {
|
||||||
|
return 100000000; // 100M VND
|
||||||
|
} else if (description.contains('Giao dịch mua hàng 00081')) {
|
||||||
|
return 200000000; // 200M VND
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
473
lib/features/loyalty/presentation/pages/loyalty_page.dart
Normal file
473
lib/features/loyalty/presentation/pages/loyalty_page.dart
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
/// Page: Loyalty Page
|
||||||
|
///
|
||||||
|
/// Main loyalty program page displaying member card, progress, and features.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
|
import 'package:worker/core/theme/colors.dart';
|
||||||
|
import 'package:worker/features/loyalty/presentation/providers/loyalty_points_provider.dart';
|
||||||
|
|
||||||
|
/// Loyalty Page
|
||||||
|
///
|
||||||
|
/// Features:
|
||||||
|
/// - Diamond member card with QR code
|
||||||
|
/// - Progress bar to next tier
|
||||||
|
/// - Quick action menu items
|
||||||
|
/// - Current tier benefits
|
||||||
|
class LoyaltyPage extends ConsumerWidget {
|
||||||
|
const LoyaltyPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final loyaltyPoints = ref.watch(loyaltyPointsProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF4F6F8),
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text(
|
||||||
|
'Hội viên thân thiết',
|
||||||
|
style: TextStyle(color: Colors.black),
|
||||||
|
),
|
||||||
|
elevation: AppBarSpecs.elevation,
|
||||||
|
backgroundColor: AppColors.white,
|
||||||
|
foregroundColor: AppColors.grey900,
|
||||||
|
centerTitle: false,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Member Card
|
||||||
|
_buildMemberCard(loyaltyPoints),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Progress Card
|
||||||
|
_buildProgressCard(),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Loyalty Features Menu
|
||||||
|
..._buildLoyaltyMenu(context),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Current Benefits Card
|
||||||
|
_buildBenefitsCard(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Diamond Member Card
|
||||||
|
Widget _buildMemberCard(LoyaltyPointsState loyaltyPoints) {
|
||||||
|
return Container(
|
||||||
|
height: 200,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFF4A00E0), Color(0xFF8E2DE2)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color(0xFF4A00E0).withValues(alpha: 0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Top Row: Brand and Valid Through
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'EUROTILE',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'ARCHITECT MEMBERSHIP',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withValues(alpha: 0.9),
|
||||||
|
fontSize: 11,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Valid through',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withValues(alpha: 0.8),
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
const Text(
|
||||||
|
'31/12/2025',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const Spacer(),
|
||||||
|
|
||||||
|
// Bottom Row: User Info and QR Code
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'La Nguyen Quynh',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'CLASS: DIAMOND',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withValues(alpha: 0.9),
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Points: ${loyaltyPoints.availablePoints}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withValues(alpha: 0.9),
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: QrImageView(
|
||||||
|
data: '0983441099',
|
||||||
|
version: QrVersions.auto,
|
||||||
|
size: 60,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Progress Card
|
||||||
|
Widget _buildProgressCard() {
|
||||||
|
return Card(
|
||||||
|
elevation: 5,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Tiến trình lên hạng',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Current and Next Tier
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Hạng hiện tại: DIAMOND',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Hạng kế tiếp: PLATINUM',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Progress Bar
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: 0.65,
|
||||||
|
minHeight: 8,
|
||||||
|
backgroundColor: AppColors.grey100,
|
||||||
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
|
Color(0xFF4A00E0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Points to Next Tier
|
||||||
|
Center(
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: const TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: 'Còn '),
|
||||||
|
TextSpan(
|
||||||
|
text: '2,250 điểm',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: ' nữa để lên hạng Platinum'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Loyalty Menu Items
|
||||||
|
List<Widget> _buildLoyaltyMenu(BuildContext context) {
|
||||||
|
final menuItems = [
|
||||||
|
{
|
||||||
|
'icon': Icons.card_giftcard,
|
||||||
|
'title': 'Đổi quà tặng',
|
||||||
|
'subtitle': 'Sử dụng điểm để đổi quà hấp dẫn',
|
||||||
|
'route': '/loyalty/rewards',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.add_circle_outline,
|
||||||
|
'title': 'Ghi nhận điểm',
|
||||||
|
'subtitle': 'Gửi hóa đơn để nhận điểm thưởng',
|
||||||
|
'route': null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.history,
|
||||||
|
'title': 'Lịch sử điểm',
|
||||||
|
'subtitle': 'Xem chi tiết cộng/trừ điểm',
|
||||||
|
'route': '/loyalty/points-history',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.person_add,
|
||||||
|
'title': 'Giới thiệu bạn bè',
|
||||||
|
'subtitle': 'Nhận thưởng khi giới thiệu thành công',
|
||||||
|
'route': null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.inventory_2_outlined,
|
||||||
|
'title': 'Quà của tôi',
|
||||||
|
'subtitle': 'Xem voucher và quà tặng đã đổi',
|
||||||
|
'route': null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.diamond_outlined,
|
||||||
|
'title': 'Quyền lợi hội viên',
|
||||||
|
'subtitle': 'Xem các ưu đãi dành cho hạng của bạn',
|
||||||
|
'route': null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return menuItems.map((item) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
side: const BorderSide(color: AppColors.grey100),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (item['route'] != null) {
|
||||||
|
context.push(item['route'] as String);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('${item['title']} - Đang phát triển')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Icon
|
||||||
|
Container(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primaryBlue.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
item['icon'] as IconData,
|
||||||
|
color: AppColors.primaryBlue,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
|
||||||
|
// Title and Subtitle
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item['title'] as String,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
item['subtitle'] as String,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Arrow
|
||||||
|
const Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Benefits Card
|
||||||
|
Widget _buildBenefitsCard() {
|
||||||
|
return Card(
|
||||||
|
elevation: 1,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Quyền lợi hạng Diamond',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildBenefitItem('Chiết khấu 15% cho tất cả sản phẩm'),
|
||||||
|
_buildBenefitItem('Giao hàng miễn phí cho đơn từ 5 triệu'),
|
||||||
|
_buildBenefitItem('Ưu tiên xử lý đơn hàng'),
|
||||||
|
_buildBenefitItem('Tặng 500 điểm vào ngày sinh nhật'),
|
||||||
|
_buildBenefitItem('Tư vấn thiết kế miễn phí'),
|
||||||
|
_buildBenefitItem('Mời tham gia sự kiện VIP độc quyền', isLast: true),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Benefit Item
|
||||||
|
Widget _buildBenefitItem(String text, {bool isLast = false}) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: isLast ? 0 : 12),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
size: 20,
|
||||||
|
color: Color(0xFF4A00E0),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
329
lib/features/loyalty/presentation/pages/points_history_page.dart
Normal file
329
lib/features/loyalty/presentation/pages/points_history_page.dart
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
/// Page: Points History Page
|
||||||
|
///
|
||||||
|
/// Displays history of points earned and spent transactions.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
|
import 'package:worker/core/database/models/enums.dart';
|
||||||
|
import 'package:worker/core/theme/colors.dart';
|
||||||
|
import 'package:worker/features/loyalty/data/datasources/points_history_local_datasource.dart';
|
||||||
|
import 'package:worker/features/loyalty/data/models/loyalty_point_entry_model.dart';
|
||||||
|
import 'package:worker/features/loyalty/presentation/providers/points_history_provider.dart';
|
||||||
|
|
||||||
|
/// Points History Page
|
||||||
|
///
|
||||||
|
/// Features:
|
||||||
|
/// - Filter section with date range
|
||||||
|
/// - List of transaction cards
|
||||||
|
/// - Each card shows: description, date, amount, points change, new balance
|
||||||
|
/// - Complaint button for each transaction
|
||||||
|
class PointsHistoryPage extends ConsumerWidget {
|
||||||
|
const PointsHistoryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final historyAsync = ref.watch(pointsHistoryProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF4F6F8),
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'Lịch sử điểm',
|
||||||
|
style: TextStyle(color: Colors.black),
|
||||||
|
),
|
||||||
|
elevation: AppBarSpecs.elevation,
|
||||||
|
backgroundColor: AppColors.white,
|
||||||
|
foregroundColor: AppColors.grey900,
|
||||||
|
centerTitle: false,
|
||||||
|
),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
await ref.read(pointsHistoryProvider.notifier).refresh();
|
||||||
|
},
|
||||||
|
child: historyAsync.when(
|
||||||
|
data: (entries) {
|
||||||
|
if (entries.isEmpty) {
|
||||||
|
return _buildEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Filter Section
|
||||||
|
_buildFilterSection(),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Transaction List
|
||||||
|
...entries.map((entry) => _buildTransactionCard(context, ref, entry)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, stack) => _buildErrorState(error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build filter section
|
||||||
|
Widget _buildFilterSection() {
|
||||||
|
return Card(
|
||||||
|
elevation: 1,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Bộ lọc',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.filter_list,
|
||||||
|
color: AppColors.primaryBlue,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'Thời gian hiệu lực: 01/01/2023 - 31/12/2023',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build transaction card
|
||||||
|
Widget _buildTransactionCard(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
LoyaltyPointEntryModel entry,
|
||||||
|
) {
|
||||||
|
final dateFormatter = DateFormat('dd/MM/yyyy HH:mm:ss');
|
||||||
|
final currencyFormatter = NumberFormat.currency(
|
||||||
|
locale: 'vi_VN',
|
||||||
|
symbol: 'VND',
|
||||||
|
decimalDigits: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get transaction amount if it's a purchase
|
||||||
|
final datasource = ref.read(pointsHistoryLocalDataSourceProvider);
|
||||||
|
final transactionAmount = datasource.getTransactionAmount(entry.description);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 1,
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Top row: Description and Complaint button
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Description
|
||||||
|
Text(
|
||||||
|
entry.description,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.primaryBlue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
|
||||||
|
// Timestamp
|
||||||
|
Text(
|
||||||
|
'Thời gian: ${dateFormatter.format(entry.timestamp)}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Transaction amount (if purchase)
|
||||||
|
if (transactionAmount != null) ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'Giao dịch: ${currencyFormatter.format(transactionAmount)}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Complaint button
|
||||||
|
OutlinedButton(
|
||||||
|
onPressed: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Chức năng khiếu nại đang phát triển')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
side: const BorderSide(color: AppColors.grey500),
|
||||||
|
foregroundColor: AppColors.grey900,
|
||||||
|
textStyle: const TextStyle(fontSize: 12),
|
||||||
|
minimumSize: const Size(0, 32),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Khiếu nại'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Bottom row: Points change and new balance
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
// Points change
|
||||||
|
Text(
|
||||||
|
entry.points > 0 ? '+${entry.points}' : '${entry.points}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: entry.points > 0
|
||||||
|
? AppColors.success
|
||||||
|
: entry.points < 0
|
||||||
|
? AppColors.danger
|
||||||
|
: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
|
||||||
|
// New balance
|
||||||
|
Text(
|
||||||
|
'Điểm mới: ${entry.balanceAfter}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.primaryBlue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build empty state
|
||||||
|
Widget _buildEmptyState() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.history,
|
||||||
|
size: 80,
|
||||||
|
color: AppColors.grey500.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Chưa có lịch sử điểm',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'Kéo xuống để làm mới',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build error state
|
||||||
|
Widget _buildErrorState(Object error) {
|
||||||
|
print(error.toString());
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
size: 80,
|
||||||
|
color: AppColors.danger.withValues(alpha: 0.7),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Có lỗi xảy ra',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
error.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/// Providers: Points History
|
||||||
|
///
|
||||||
|
/// Riverpod providers for managing points history state.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:worker/features/loyalty/data/datasources/points_history_local_datasource.dart';
|
||||||
|
import 'package:worker/features/loyalty/data/models/loyalty_point_entry_model.dart';
|
||||||
|
|
||||||
|
part 'points_history_provider.g.dart';
|
||||||
|
|
||||||
|
/// Points History Local Data Source Provider
|
||||||
|
@riverpod
|
||||||
|
PointsHistoryLocalDataSource pointsHistoryLocalDataSource(Ref ref) {
|
||||||
|
return PointsHistoryLocalDataSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Points History Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all points history entries from local data source.
|
||||||
|
@riverpod
|
||||||
|
class PointsHistory extends _$PointsHistory {
|
||||||
|
@override
|
||||||
|
Future<List<LoyaltyPointEntryModel>> build() async {
|
||||||
|
final datasource = ref.read(pointsHistoryLocalDataSourceProvider);
|
||||||
|
|
||||||
|
// Seed mock data on first load
|
||||||
|
final entries = await datasource.getAllEntries();
|
||||||
|
if (entries.isEmpty) {
|
||||||
|
await datasource.seedMockEntries();
|
||||||
|
return await datasource.getAllEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh points history
|
||||||
|
Future<void> refresh() async {
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
state = await AsyncValue.guard(() async {
|
||||||
|
return await ref.read(pointsHistoryLocalDataSourceProvider).getAllEntries();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'points_history_provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
/// Points History Local Data Source Provider
|
||||||
|
|
||||||
|
@ProviderFor(pointsHistoryLocalDataSource)
|
||||||
|
const pointsHistoryLocalDataSourceProvider =
|
||||||
|
PointsHistoryLocalDataSourceProvider._();
|
||||||
|
|
||||||
|
/// Points History Local Data Source Provider
|
||||||
|
|
||||||
|
final class PointsHistoryLocalDataSourceProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
PointsHistoryLocalDataSource,
|
||||||
|
PointsHistoryLocalDataSource,
|
||||||
|
PointsHistoryLocalDataSource
|
||||||
|
>
|
||||||
|
with $Provider<PointsHistoryLocalDataSource> {
|
||||||
|
/// Points History Local Data Source Provider
|
||||||
|
const PointsHistoryLocalDataSourceProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'pointsHistoryLocalDataSourceProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$pointsHistoryLocalDataSourceHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<PointsHistoryLocalDataSource> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PointsHistoryLocalDataSource create(Ref ref) {
|
||||||
|
return pointsHistoryLocalDataSource(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(PointsHistoryLocalDataSource value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<PointsHistoryLocalDataSource>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$pointsHistoryLocalDataSourceHash() =>
|
||||||
|
r'324e4d6d12e0f1ec3f77de8e0fd60f69eaa8c7ce';
|
||||||
|
|
||||||
|
/// Points History Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all points history entries from local data source.
|
||||||
|
|
||||||
|
@ProviderFor(PointsHistory)
|
||||||
|
const pointsHistoryProvider = PointsHistoryProvider._();
|
||||||
|
|
||||||
|
/// Points History Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all points history entries from local data source.
|
||||||
|
final class PointsHistoryProvider
|
||||||
|
extends
|
||||||
|
$AsyncNotifierProvider<PointsHistory, List<LoyaltyPointEntryModel>> {
|
||||||
|
/// Points History Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all points history entries from local data source.
|
||||||
|
const PointsHistoryProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'pointsHistoryProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$pointsHistoryHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
PointsHistory create() => PointsHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$pointsHistoryHash() => r'e812d6f6707f02c15a1263ae5b5ee912f269358d';
|
||||||
|
|
||||||
|
/// Points History Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all points history entries from local data source.
|
||||||
|
|
||||||
|
abstract class _$PointsHistory
|
||||||
|
extends $AsyncNotifier<List<LoyaltyPointEntryModel>> {
|
||||||
|
FutureOr<List<LoyaltyPointEntryModel>> build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref =
|
||||||
|
this.ref
|
||||||
|
as $Ref<
|
||||||
|
AsyncValue<List<LoyaltyPointEntryModel>>,
|
||||||
|
List<LoyaltyPointEntryModel>
|
||||||
|
>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<
|
||||||
|
AsyncValue<List<LoyaltyPointEntryModel>>,
|
||||||
|
List<LoyaltyPointEntryModel>
|
||||||
|
>,
|
||||||
|
AsyncValue<List<LoyaltyPointEntryModel>>,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:worker/core/theme/colors.dart';
|
import 'package:worker/core/theme/colors.dart';
|
||||||
import 'package:worker/features/home/presentation/pages/home_page.dart';
|
import 'package:worker/features/home/presentation/pages/home_page.dart';
|
||||||
|
import 'package:worker/features/loyalty/presentation/pages/loyalty_page.dart';
|
||||||
import 'package:worker/features/main/presentation/providers/current_page_provider.dart';
|
import 'package:worker/features/main/presentation/providers/current_page_provider.dart';
|
||||||
import 'package:worker/features/promotions/presentation/pages/promotions_page.dart';
|
import 'package:worker/features/promotions/presentation/pages/promotions_page.dart';
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ class MainScaffold extends ConsumerWidget {
|
|||||||
// Define pages
|
// Define pages
|
||||||
final pages = [
|
final pages = [
|
||||||
const HomePage(),
|
const HomePage(),
|
||||||
_buildComingSoonPage('Hội viên'), // Loyalty
|
const LoyaltyPage(), // Loyalty
|
||||||
const PromotionsPage(),
|
const PromotionsPage(),
|
||||||
_buildComingSoonPage('Thông báo'), // Notifications
|
_buildComingSoonPage('Thông báo'), // Notifications
|
||||||
_buildComingSoonPage('Cài đặt'), // Account
|
_buildComingSoonPage('Cài đặt'), // Account
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ final class QuotesLocalDataSourceProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$quotesLocalDataSourceHash() =>
|
String _$quotesLocalDataSourceHash() =>
|
||||||
r'02a822db926d8d80460bcc27a08ea494dff6c441';
|
r'675d9ae15bf5e3dcbb12c1a893c8a73fbfb8c2ee';
|
||||||
|
|
||||||
/// Quotes Provider
|
/// Quotes Provider
|
||||||
///
|
///
|
||||||
@@ -288,7 +288,7 @@ final class FilteredQuotesProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$filteredQuotesHash() => r'77076cfa483cb81cc56972bca6a3c1e97861165c';
|
String _$filteredQuotesHash() => r'ce6fc7db1d4f2e90431e7258a3faef2c55db80d5';
|
||||||
|
|
||||||
/// Quotes Count by Status Provider
|
/// Quotes Count by Status Provider
|
||||||
|
|
||||||
@@ -335,4 +335,4 @@ final class QuotesCountByStatusProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$quotesCountByStatusHash() =>
|
String _$quotesCountByStatusHash() =>
|
||||||
r'474b62ad0ccf890df1c33c64a17f9a0f428f676e';
|
r'9a2f2f10dd392505d0d51428f45390f16952763d';
|
||||||
|
|||||||
Reference in New Issue
Block a user