diff --git a/lib/core/database/database.dart b/lib/core/database/database.dart index 325be9d..ad7f41e 100644 --- a/lib/core/database/database.dart +++ b/lib/core/database/database.dart @@ -22,4 +22,5 @@ export 'package:worker/core/database/models/cached_data.dart'; export 'package:worker/core/database/models/enums.dart'; // Auto-generated registrar -export 'package:worker/hive_registrar.g.dart'; +// TODO: Re-enable when build_runner generates this file successfully +// export 'package:worker/hive_registrar.g.dart'; diff --git a/lib/core/database/hive_service.dart b/lib/core/database/hive_service.dart index fd6ea00..3ee90e5 100644 --- a/lib/core/database/hive_service.dart +++ b/lib/core/database/hive_service.dart @@ -5,7 +5,8 @@ import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:worker/core/constants/storage_constants.dart'; -import 'package:worker/hive_registrar.g.dart'; +// TODO: Re-enable when build_runner generates this file successfully +// import 'package:worker/hive_registrar.g.dart'; /// Hive CE (Community Edition) Database Service /// @@ -93,7 +94,8 @@ class HiveService { // This automatically registers: // - CachedDataAdapter (typeId: 30) // - All enum adapters (typeIds: 20-29) - Hive.registerAdapters(); + // TODO: Re-enable when build_runner generates hive_registrar.g.dart successfully + // Hive.registerAdapters(); debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.memberTier) ? "✓" : "✗"} MemberTier adapter'); debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.userType) ? "✓" : "✗"} UserType adapter'); @@ -103,7 +105,7 @@ class HiveService { debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.transactionType) ? "✓" : "✗"} TransactionType adapter'); debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.giftStatus) ? "✓" : "✗"} GiftStatus adapter'); debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentStatus) ? "✓" : "✗"} PaymentStatus adapter'); - debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.notificationType) ? "✓" : "✗"} NotificationType adapter'); + // NotificationType adapter not needed - notification model uses String type debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentMethod) ? "✓" : "✗"} PaymentMethod adapter'); debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.cachedData) ? "✓" : "✗"} CachedData adapter'); diff --git a/lib/features/account/presentation/pages/account_page.dart b/lib/features/account/presentation/pages/account_page.dart new file mode 100644 index 0000000..acd39e8 --- /dev/null +++ b/lib/features/account/presentation/pages/account_page.dart @@ -0,0 +1,414 @@ +/// Account Page +/// +/// Displays user account information and settings menu. +/// Features: +/// - User profile card with avatar, name, role, tier, and phone +/// - Account menu section (personal info, orders, addresses, etc.) +/// - Support section (contact, FAQ, about) +/// - Logout button +library; + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:worker/core/constants/ui_constants.dart'; +import 'package:worker/core/router/app_router.dart'; +import 'package:worker/core/theme/colors.dart'; +import 'package:worker/features/account/presentation/widgets/account_menu_item.dart'; + +/// Account Page +/// +/// Main account/settings page accessible from the bottom navigation bar. +class AccountPage extends StatelessWidget { + const AccountPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF4F6F8), + body: SafeArea( + child: SingleChildScrollView( + child: Column( + spacing: AppSpacing.md, + children: [ + // Simple Header + _buildHeader(), + + + // User Profile Card + _buildProfileCard(context), + + + // Account Menu Section + _buildAccountMenu(context), + + + // Support Section + _buildSupportSection(context), + + + // Logout Button + _buildLogoutButton(context), + + const SizedBox(height: AppSpacing.lg), + ], + ), + ), + ), + ); + } + + /// Build simple header with title + Widget _buildHeader() { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: const Text( + 'Tài khoản', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF212121), + ), + ), + ); + } + + /// Build user profile card with avatar and info + Widget _buildProfileCard(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + padding: const EdgeInsets.all(AppSpacing.md), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(AppRadius.card), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Avatar with gradient background + Container( + width: 80, + height: 80, + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [Color(0xFF005B9A), Color(0xFF38B6FF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: const Center( + child: Text( + 'LQ', + style: TextStyle( + color: Colors.white, + fontSize: 32, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + const SizedBox(width: AppSpacing.md), + + // User info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'La Nguyen Quynh', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.grey900, + ), + ), + const SizedBox(height: 4), + const Text( + 'Kiến trúc sư · Hạng Diamond', + style: TextStyle( + fontSize: 13, + color: AppColors.grey500, + ), + ), + const SizedBox(height: 4), + const Text( + '0983 441 099', + style: TextStyle( + fontSize: 13, + color: AppColors.primaryBlue, + ), + ), + ], + ), + ), + ], + ), + ); + } + + /// Build account menu section + Widget _buildAccountMenu(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(AppRadius.card), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + AccountMenuItem( + icon: Icons.edit, + title: 'Thông tin cá nhân', + subtitle: 'Cập nhật thông tin tài khoản', + onTap: () { + _showComingSoon(context); + }, + ), + AccountMenuItem( + icon: Icons.history, + title: 'Lịch sử đơn hàng', + subtitle: 'Xem các đơn hàng đã đặt', + onTap: () { + context.push(RouteNames.orders); + }, + ), + AccountMenuItem( + icon: Icons.location_on, + title: 'Địa chỉ đã lưu', + subtitle: 'Quản lý địa chỉ giao hàng', + onTap: () { + _showComingSoon(context); + }, + ), + AccountMenuItem( + icon: Icons.notifications, + title: 'Cài đặt thông báo', + subtitle: 'Quản lý thông báo đẩy', + onTap: () { + _showComingSoon(context); + }, + ), + AccountMenuItem( + icon: Icons.lock, + title: 'Đổi mật khẩu', + subtitle: 'Cập nhật mật khẩu mới', + onTap: () { + _showComingSoon(context); + }, + ), + AccountMenuItem( + icon: Icons.language, + title: 'Ngôn ngữ', + subtitle: 'Tiếng Việt', + onTap: () { + _showComingSoon(context); + }, + ), + ], + ), + ); + } + + /// Build support section + Widget _buildSupportSection(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(AppRadius.card), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Section title + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.md, + AppSpacing.md, + AppSpacing.md, + AppSpacing.sm, + ), + child: const Text( + 'Hỗ trợ', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.grey900, + ), + ), + ), + + // Support menu items + AccountMenuItem( + icon: Icons.headset_mic, + title: 'Liên hệ hỗ trợ', + subtitle: 'Hotline: 1900 1234', + trailing: const Icon( + Icons.phone, + size: 20, + color: AppColors.grey500, + ), + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Hotline: 1900 1234'), + duration: Duration(seconds: 2), + ), + ); + }, + ), + AccountMenuItem( + icon: Icons.help_outline, + title: 'Câu hỏi thường gặp', + onTap: () { + _showComingSoon(context); + }, + ), + AccountMenuItem( + icon: Icons.info_outline, + title: 'Về ứng dụng', + subtitle: 'Phiên bản 1.0.0', + onTap: () { + _showAboutDialog(context); + }, + ), + ], + ), + ); + } + + /// Build logout button + Widget _buildLogoutButton(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () { + _showLogoutConfirmation(context); + }, + icon: const Icon(Icons.logout), + label: const Text('Đăng xuất'), + style: OutlinedButton.styleFrom( + foregroundColor: AppColors.danger, + side: const BorderSide(color: AppColors.danger, width: 1.5), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadius.button), + ), + ), + ), + ); + } + + /// Show coming soon message + void _showComingSoon(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Tính năng đang phát triển'), + duration: Duration(seconds: 1), + ), + ); + } + + /// Show about dialog + void _showAboutDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Về ứng dụng'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'EuroTile & Vasta Stone Worker', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + const Text('Phiên bản: 1.0.0'), + const SizedBox(height: 8), + Text( + 'Ứng dụng dành cho thầu thợ, kiến trúc sư, đại lý và môi giới trong ngành gạch ốp lát và nội thất.', + style: TextStyle( + fontSize: 14, + color: AppColors.grey500, + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Đóng'), + ), + ], + ), + ); + } + + /// Show logout confirmation dialog + void _showLogoutConfirmation(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Đăng xuất'), + content: const Text('Bạn có chắc chắn muốn đăng xuất?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Hủy'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Đã đăng xuất'), + duration: Duration(seconds: 1), + ), + ); + }, + style: TextButton.styleFrom( + foregroundColor: AppColors.danger, + ), + child: const Text('Đăng xuất'), + ), + ], + ), + ); + } +} diff --git a/lib/features/account/presentation/widgets/account_menu_item.dart b/lib/features/account/presentation/widgets/account_menu_item.dart new file mode 100644 index 0000000..01b9bfe --- /dev/null +++ b/lib/features/account/presentation/widgets/account_menu_item.dart @@ -0,0 +1,125 @@ +/// Account Menu Item Widget +/// +/// A reusable menu item component for the account page. +/// Displays an icon, title, optional subtitle, and trailing widget with tap interaction. +library; + +import 'package:flutter/material.dart'; +import 'package:worker/core/constants/ui_constants.dart'; +import 'package:worker/core/theme/colors.dart'; + +/// Account Menu Item Widget +/// +/// Creates a list item with: +/// - Circular icon background on the left +/// - Title and optional subtitle in the center +/// - Trailing widget (default: chevron right) on the right +/// - Tap ripple effect +class AccountMenuItem extends StatelessWidget { + /// Icon to display + final IconData icon; + + /// Title text (required) + final String title; + + /// Optional subtitle text + final String? subtitle; + + /// Tap callback + final VoidCallback onTap; + + /// Optional custom trailing widget (defaults to chevron right arrow) + final Widget? trailing; + + /// Icon background color (defaults to light blue) + final Color? iconBackgroundColor; + + /// Icon color (defaults to primary blue) + final Color? iconColor; + + const AccountMenuItem({ + super.key, + required this.icon, + required this.title, + this.subtitle, + required this.onTap, + this.trailing, + this.iconBackgroundColor, + this.iconColor, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + vertical: AppSpacing.md, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.grey100, + width: 1.0, + ), + ), + ), + child: Row( + children: [ + // Icon with circular background + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: iconBackgroundColor ?? AppColors.lightBlue.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + icon, + size: 20, + color: iconColor ?? AppColors.primaryBlue, + ), + ), + const SizedBox(width: AppSpacing.md), + + // Title and subtitle + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: AppColors.grey900, + ), + ), + if (subtitle != null) ...[ + const SizedBox(height: 4), + Text( + subtitle!, + style: const TextStyle( + fontSize: 13, + color: AppColors.grey500, + ), + ), + ], + ], + ), + ), + + // Trailing widget (default: chevron) + trailing ?? + const Icon( + Icons.chevron_right, + size: 20, + color: AppColors.grey500, + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/main/presentation/pages/main_scaffold.dart b/lib/features/main/presentation/pages/main_scaffold.dart index a77831f..87118eb 100644 --- a/lib/features/main/presentation/pages/main_scaffold.dart +++ b/lib/features/main/presentation/pages/main_scaffold.dart @@ -7,20 +7,22 @@ library; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:worker/core/theme/colors.dart'; +import 'package:worker/features/account/presentation/pages/account_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/news/presentation/pages/news_list_page.dart'; +import 'package:worker/features/notifications/presentation/pages/notifications_page.dart'; import 'package:worker/features/promotions/presentation/pages/promotions_page.dart'; /// Main Scaffold Page /// /// Manages bottom navigation and page switching for: /// - Home (index 0) -/// - Loyalty (index 1) - Coming soon -/// - Promotions (index 2) -/// - Notifications (index 3) - Coming soon -/// - Account (index 4) - Coming soon +/// - Loyalty (index 1) +/// - News (index 2) +/// - Notifications (index 3) +/// - Account (index 4) class MainScaffold extends ConsumerWidget { const MainScaffold({super.key}); @@ -33,8 +35,8 @@ class MainScaffold extends ConsumerWidget { const HomePage(), const LoyaltyPage(), // Loyalty const NewsListPage(), - _buildComingSoonPage('Thông báo'), // Notifications - _buildComingSoonPage('Cài đặt'), // Account + const NotificationsPage(), // Notifications + const AccountPage(), // Account ]; return Scaffold( @@ -147,72 +149,4 @@ class MainScaffold extends ConsumerWidget { ), ); } - - /// Build coming soon placeholder page - Widget _buildComingSoonPage(String title) { - return Scaffold( - backgroundColor: const Color(0xFFF4F6F8), - body: SafeArea( - child: Column( - children: [ - // Header - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Text( - title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF212121), - ), - ), - ), - // Coming soon content - Expanded( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.construction, - size: 80, - color: AppColors.grey500.withValues(alpha: 0.5), - ), - const SizedBox(height: 16), - const Text( - 'Đang phát triển', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: AppColors.grey500, - ), - ), - const SizedBox(height: 8), - const Text( - 'Tính năng này sẽ sớm ra mắt', - style: TextStyle( - fontSize: 14, - color: AppColors.grey500, - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } } diff --git a/lib/features/notifications/data/datasources/notification_local_datasource.dart b/lib/features/notifications/data/datasources/notification_local_datasource.dart new file mode 100644 index 0000000..d58dde4 --- /dev/null +++ b/lib/features/notifications/data/datasources/notification_local_datasource.dart @@ -0,0 +1,181 @@ +/// Notification Local Data Source +/// +/// Provides mock notification data for development. +/// Simulates API responses with JSON data. +library; + +/// Notification Local Data Source +/// +/// Returns raw JSON data as if from API. +class NotificationLocalDataSource { + /// Mock notifications data (as JSON from API) + final List> _mockNotifications = [ + // Points earned (General - unread) + { + 'notification_id': 'notif_001', + 'user_id': 'user_001', + 'type': 'loyalty_points_earned', + 'title': 'Chúc mừng! Bạn vừa nhận 500 điểm thưởng', + 'message': + 'Hoàn thành đơn hàng DH2023120801 và nhận 500 điểm vào tài khoản.', + 'data': {'points': 500, 'order_id': 'DH2023120801'}, + 'is_read': false, + 'is_pushed': true, + 'created_at': '2025-11-03T05:00:00.000Z', + 'read_at': null, + }, + + // Promotion (General - unread) + { + 'notification_id': 'notif_002', + 'user_id': 'user_001', + 'type': 'promotion_flash_sale', + 'title': 'Flash Sale cuối năm - Giảm đến 50%', + 'message': + 'Chương trình khuyến mãi đặc biệt chỉ còn 2 ngày. Nhanh tay săn ngay!', + 'data': {'promotion_id': 'PROMO_FLASH_DEC'}, + 'is_read': false, + 'is_pushed': true, + 'created_at': '2025-11-03T02:00:00.000Z', + 'read_at': null, + }, + + // Order shipping (Order - unread) + { + 'notification_id': 'notif_003', + 'user_id': 'user_001', + 'type': 'order_shipping', + 'title': 'Đơn hàng đang được giao', + 'message': + 'Đơn hàng DH2023120701 đang trên đường giao đến bạn. Dự kiến giao trong hôm nay.', + 'data': {'order_id': 'DH2023120701'}, + 'is_read': false, + 'is_pushed': true, + 'created_at': '2025-11-02T07:00:00.000Z', + 'read_at': null, + }, + + // Tier upgrade (General - read) + { + 'notification_id': 'notif_004', + 'user_id': 'user_001', + 'type': 'loyalty_tier_upgrade', + 'title': 'Sắp lên hạng Platinum', + 'message': + 'Bạn chỉ còn 2,250 điểm nữa để đạt hạng Platinum với nhiều ưu đãi hấp dẫn.', + 'data': { + 'current_tier': 'gold', + 'next_tier': 'platinum', + 'points_needed': 2250 + }, + 'is_read': true, + 'is_pushed': true, + 'created_at': '2025-11-01T07:00:00.000Z', + 'read_at': '2025-11-02T19:00:00.000Z', + }, + + // Event (General - read) + { + 'notification_id': 'notif_005', + 'user_id': 'user_001', + 'type': 'event_invitation', + 'title': 'Sự kiện VIP sắp diễn ra', + 'message': + 'Mời bạn tham gia sự kiện ra mắt bộ sưu tập gạch mới vào 15/12/2023 tại showroom.', + 'data': {'event_id': 'EVENT_DEC_2023', 'date': '2023-12-15'}, + 'is_read': true, + 'is_pushed': true, + 'created_at': '2025-10-31T07:00:00.000Z', + 'read_at': '2025-11-01T23:00:00.000Z', + }, + + // Order confirmed (Order - read) + { + 'notification_id': 'notif_006', + 'user_id': 'user_001', + 'type': 'order_confirmed', + 'title': 'Xác nhận đơn hàng thành công', + 'message': + 'Đơn hàng DH2023120601 đã được xác nhận. Chúng tôi sẽ sớm chuẩn bị và giao hàng.', + 'data': {'order_id': 'DH2023120601'}, + 'is_read': true, + 'is_pushed': true, + 'created_at': '2025-10-30T07:00:00.000Z', + 'read_at': '2025-10-31T21:00:00.000Z', + }, + + // Birthday (General - read) + { + 'notification_id': 'notif_007', + 'user_id': 'user_001', + 'type': 'birthday_reward', + 'title': 'Sinh nhật sắp đến', + 'message': + 'Chúc mừng sinh nhật! Bạn sẽ nhận 500 điểm thưởng vào ngày 20/12.', + 'data': {'birthday_date': '2023-12-20', 'points': 500}, + 'is_read': true, + 'is_pushed': true, + 'created_at': '2025-10-27T07:00:00.000Z', + 'read_at': '2025-10-28T17:00:00.000Z', + }, + ]; + + /// Get all notifications (returns JSON data) + Future>> getAllNotifications() async { + await Future.delayed(const Duration(milliseconds: 300)); + return List.from(_mockNotifications); + } + + /// Get notifications by category + Future>> getNotificationsByCategory( + String category) async { + await Future.delayed(const Duration(milliseconds: 200)); + + if (category == 'general') { + return _mockNotifications + .where((n) => + !(n['type'] as String).toLowerCase().contains('order') || + (n['type'] as String).toLowerCase().contains('loyalty') || + (n['type'] as String).toLowerCase().contains('promotion') || + (n['type'] as String).toLowerCase().contains('event') || + (n['type'] as String).toLowerCase().contains('birthday')) + .toList(); + } else if (category == 'order') { + return _mockNotifications + .where((n) => (n['type'] as String).toLowerCase().contains('order')) + .toList(); + } + + return List.from(_mockNotifications); + } + + /// Get unread count + Future getUnreadCount() async { + await Future.delayed(const Duration(milliseconds: 100)); + return _mockNotifications.where((n) => !(n['is_read'] as bool)).length; + } + + /// Mark notification as read + Future markAsRead(String notificationId) async { + await Future.delayed(const Duration(milliseconds: 150)); + + final index = _mockNotifications + .indexWhere((n) => n['notification_id'] == notificationId); + if (index != -1) { + _mockNotifications[index]['is_read'] = true; + _mockNotifications[index]['read_at'] = DateTime.now().toIso8601String(); + } + } + + /// Mark all notifications as read + Future markAllAsRead() async { + await Future.delayed(const Duration(milliseconds: 200)); + + for (var notification in _mockNotifications) { + if (!(notification['is_read'] as bool)) { + notification['is_read'] = true; + notification['read_at'] = DateTime.now().toIso8601String(); + } + } + } +} diff --git a/lib/features/notifications/data/models/notification_model.dart b/lib/features/notifications/data/models/notification_model.dart index 907d2a9..85b1127 100644 --- a/lib/features/notifications/data/models/notification_model.dart +++ b/lib/features/notifications/data/models/notification_model.dart @@ -1,56 +1,45 @@ -import 'dart:convert'; -import 'package:hive_ce/hive.dart'; -import 'package:worker/core/constants/storage_constants.dart'; +/// Notification Model +/// +/// Converts JSON data from API to domain entity. +library; -part 'notification_model.g.dart'; +import 'package:worker/features/notifications/domain/entities/notification.dart'; -@HiveType(typeId: HiveTypeIds.notificationModel) -class NotificationModel extends HiveObject { - NotificationModel({required this.notificationId, required this.userId, required this.type, required this.title, required this.message, this.data, required this.isRead, required this.isPushed, required this.createdAt, this.readAt}); - - @HiveField(0) final String notificationId; - @HiveField(1) final String userId; - @HiveField(2) final String type; - @HiveField(3) final String title; - @HiveField(4) final String message; - @HiveField(5) final String? data; - @HiveField(6) final bool isRead; - @HiveField(7) final bool isPushed; - @HiveField(8) final DateTime createdAt; - @HiveField(9) final DateTime? readAt; +/// Notification Model +/// +/// Handles JSON serialization/deserialization. +class NotificationModel { + /// Convert JSON to Notification entity + static Notification fromJson(Map json) { + return Notification( + notificationId: json['notification_id'] as String, + userId: json['user_id'] as String, + type: json['type'] as String, + title: json['title'] as String, + message: json['message'] as String, + data: json['data'] as Map?, + isRead: json['is_read'] as bool? ?? false, + isPushed: json['is_pushed'] as bool? ?? false, + createdAt: DateTime.parse(json['created_at'] as String), + readAt: json['read_at'] != null + ? DateTime.parse(json['read_at'] as String) + : null, + ); + } - factory NotificationModel.fromJson(Map json) => NotificationModel( - notificationId: json['notification_id'] as String, - userId: json['user_id'] as String, - type: json['type'] as String, - title: json['title'] as String, - message: json['message'] as String, - data: json['data'] != null ? jsonEncode(json['data']) : null, - isRead: json['is_read'] as bool? ?? false, - isPushed: json['is_pushed'] as bool? ?? false, - createdAt: DateTime.parse(json['created_at']?.toString() ?? ''), - readAt: json['read_at'] != null ? DateTime.parse(json['read_at']?.toString() ?? '') : null, - ); - - Map toJson() => { - 'notification_id': notificationId, - 'user_id': userId, - 'type': type, - 'title': title, - 'message': message, - 'data': data != null ? jsonDecode(data!) : null, - 'is_read': isRead, - 'is_pushed': isPushed, - 'created_at': createdAt.toIso8601String(), - 'read_at': readAt?.toIso8601String(), - }; - - Map? get dataMap { - if (data == null) return null; - try { - return jsonDecode(data!) as Map; - } catch (e) { - return null; - } + /// Convert Notification entity to JSON + static Map toJson(Notification notification) { + return { + 'notification_id': notification.notificationId, + 'user_id': notification.userId, + 'type': notification.type, + 'title': notification.title, + 'message': notification.message, + 'data': notification.data, + 'is_read': notification.isRead, + 'is_pushed': notification.isPushed, + 'created_at': notification.createdAt.toIso8601String(), + 'read_at': notification.readAt?.toIso8601String(), + }; } } diff --git a/lib/features/notifications/data/models/notification_model.g.dart b/lib/features/notifications/data/models/notification_model.g.dart deleted file mode 100644 index cd2575c..0000000 --- a/lib/features/notifications/data/models/notification_model.g.dart +++ /dev/null @@ -1,68 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'notification_model.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class NotificationModelAdapter extends TypeAdapter { - @override - final typeId = 20; - - @override - NotificationModel read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return NotificationModel( - notificationId: fields[0] as String, - userId: fields[1] as String, - type: fields[2] as String, - title: fields[3] as String, - message: fields[4] as String, - data: fields[5] as String?, - isRead: fields[6] as bool, - isPushed: fields[7] as bool, - createdAt: fields[8] as DateTime, - readAt: fields[9] as DateTime?, - ); - } - - @override - void write(BinaryWriter writer, NotificationModel obj) { - writer - ..writeByte(10) - ..writeByte(0) - ..write(obj.notificationId) - ..writeByte(1) - ..write(obj.userId) - ..writeByte(2) - ..write(obj.type) - ..writeByte(3) - ..write(obj.title) - ..writeByte(4) - ..write(obj.message) - ..writeByte(5) - ..write(obj.data) - ..writeByte(6) - ..write(obj.isRead) - ..writeByte(7) - ..write(obj.isPushed) - ..writeByte(8) - ..write(obj.createdAt) - ..writeByte(9) - ..write(obj.readAt); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is NotificationModelAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} diff --git a/lib/features/notifications/data/repositories/notification_repository_impl.dart b/lib/features/notifications/data/repositories/notification_repository_impl.dart new file mode 100644 index 0000000..8c71d30 --- /dev/null +++ b/lib/features/notifications/data/repositories/notification_repository_impl.dart @@ -0,0 +1,55 @@ +/// Notification Repository Implementation +/// +/// Concrete implementation of NotificationRepository. +/// Coordinates between local datasource and domain layer. +library; + +import 'package:worker/features/notifications/data/datasources/notification_local_datasource.dart'; +import 'package:worker/features/notifications/data/models/notification_model.dart'; +import 'package:worker/features/notifications/domain/entities/notification.dart'; +import 'package:worker/features/notifications/domain/repositories/notification_repository.dart'; + +/// Notification Repository Implementation +class NotificationRepositoryImpl implements NotificationRepository { + /// Local data source + final NotificationLocalDataSource localDataSource; + + /// Constructor + NotificationRepositoryImpl({required this.localDataSource}); + + @override + Future> getAllNotifications() async { + final jsonList = await localDataSource.getAllNotifications(); + return jsonList.map((json) => NotificationModel.fromJson(json)).toList() + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); // Sort by newest first + } + + @override + Future> getNotificationsByCategory(String category) async { + final jsonList = await localDataSource.getNotificationsByCategory(category); + return jsonList.map((json) => NotificationModel.fromJson(json)).toList() + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + } + + @override + Future getUnreadCount() async { + return await localDataSource.getUnreadCount(); + } + + @override + Future markAsRead(String notificationId) async { + await localDataSource.markAsRead(notificationId); + } + + @override + Future markAllAsRead() async { + await localDataSource.markAllAsRead(); + } + + @override + Future> refreshNotifications() async { + // For now, just fetch all notifications + // In production, this would fetch from server + return await getAllNotifications(); + } +} diff --git a/lib/features/notifications/domain/entities/notification.dart b/lib/features/notifications/domain/entities/notification.dart index 4213d64..a6ba25d 100644 --- a/lib/features/notifications/domain/entities/notification.dart +++ b/lib/features/notifications/domain/entities/notification.dart @@ -100,6 +100,32 @@ class Notification { return timeSinceCreated.inDays > 7; } + /// Get formatted time ago text (Vietnamese) + String get formattedTimeAgo { + final duration = timeSinceCreated; + + if (duration.inMinutes < 1) { + return 'Vừa xong'; + } else if (duration.inMinutes < 60) { + return '${duration.inMinutes} phút trước'; + } else if (duration.inHours < 24) { + return '${duration.inHours} giờ trước'; + } else if (duration.inDays == 1) { + return 'Hôm qua'; + } else if (duration.inDays < 7) { + return '${duration.inDays} ngày trước'; + } else if (duration.inDays < 30) { + final weeks = (duration.inDays / 7).floor(); + return '$weeks tuần trước'; + } else if (duration.inDays < 365) { + final months = (duration.inDays / 30).floor(); + return '$months tháng trước'; + } else { + final years = (duration.inDays / 365).floor(); + return '$years năm trước'; + } + } + /// Copy with method for immutability Notification copyWith({ String? notificationId, diff --git a/lib/features/notifications/domain/repositories/notification_repository.dart b/lib/features/notifications/domain/repositories/notification_repository.dart new file mode 100644 index 0000000..b31c94d --- /dev/null +++ b/lib/features/notifications/domain/repositories/notification_repository.dart @@ -0,0 +1,32 @@ +/// Notification Repository Interface +/// +/// Defines operations for managing notifications. +library; + +import 'package:worker/features/notifications/domain/entities/notification.dart'; + +/// Notification Repository +/// +/// Provides methods to: +/// - Fetch notifications (all, by category, unread) +/// - Mark notifications as read +/// - Get notification counts +abstract class NotificationRepository { + /// Get all notifications + Future> getAllNotifications(); + + /// Get notifications by category (general or order) + Future> getNotificationsByCategory(String category); + + /// Get unread notifications count + Future getUnreadCount(); + + /// Mark notification as read + Future markAsRead(String notificationId); + + /// Mark all notifications as read + Future markAllAsRead(); + + /// Refresh notifications (fetch from server) + Future> refreshNotifications(); +} diff --git a/lib/features/notifications/presentation/pages/notifications_page.dart b/lib/features/notifications/presentation/pages/notifications_page.dart new file mode 100644 index 0000000..e444c0b --- /dev/null +++ b/lib/features/notifications/presentation/pages/notifications_page.dart @@ -0,0 +1,283 @@ +/// Notifications Page +/// +/// Displays user notifications with category tabs (General/Orders). +/// Features: +/// - Tab navigation for filtering notifications +/// - Unread notification indicators (blue left border) +/// - Pull-to-refresh +/// - Empty state when no notifications +library; + +import 'package:flutter/material.dart' hide Notification; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:worker/core/constants/ui_constants.dart'; +import 'package:worker/core/theme/colors.dart'; +import 'package:worker/features/notifications/domain/entities/notification.dart'; +import 'package:worker/features/notifications/presentation/providers/notifications_provider.dart'; +import 'package:worker/features/notifications/presentation/widgets/notification_card.dart'; + +/// Notifications Page +/// +/// Main notifications screen accessible from bottom navigation. +class NotificationsPage extends HookConsumerWidget { + const NotificationsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // Use Flutter hooks for local state management + final selectedCategory = useState('general'); + final notificationsAsync = ref.watch( + filteredNotificationsProvider(selectedCategory.value), + ); + + return Scaffold( + backgroundColor: const Color(0xFFF4F6F8), + body: SafeArea( + child: Column( + children: [ + // Header + _buildHeader(), + + // Tabs + _buildTabs(context, selectedCategory), + + // Notifications List + Expanded( + child: notificationsAsync.when( + data: (notifications) => _buildNotificationsList( + context, + ref, + notifications, + selectedCategory, + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => + _buildErrorState(ref, selectedCategory), + ), + ), + ], + ), + ), + ); + } + + /// Build header + Widget _buildHeader() { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: const Text( + 'Thông báo', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF212121), + ), + ), + ); + } + + /// Build category tabs + Widget _buildTabs( + BuildContext context, + ValueNotifier selectedCategory, + ) { + return Container( + padding: const EdgeInsets.all(AppSpacing.md), + child: Row( + children: [ + Expanded( + child: _buildTabButton( + context, + selectedCategory, + label: 'Chung', + category: 'general', + isSelected: selectedCategory.value == 'general', + ), + ), + const SizedBox(width: AppSpacing.sm), + Expanded( + child: _buildTabButton( + context, + selectedCategory, + label: 'Đơn hàng', + category: 'order', + isSelected: selectedCategory.value == 'order', + ), + ), + ], + ), + ); + } + + /// Build tab button + Widget _buildTabButton( + BuildContext context, + ValueNotifier selectedCategory, { + required String label, + required String category, + required bool isSelected, + }) { + return ElevatedButton( + onPressed: () { + selectedCategory.value = category; + }, + style: ElevatedButton.styleFrom( + backgroundColor: isSelected ? AppColors.primaryBlue : Colors.white, + foregroundColor: isSelected ? Colors.white : const Color(0xFF64748B), + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadius.button), + side: BorderSide( + color: isSelected ? AppColors.primaryBlue : const Color(0xFFE2E8F0), + width: 1, + ), + ), + ), + child: Text( + label, + style: TextStyle( + fontSize: 14, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + ), + ), + ); + } + + /// Build notifications list + Widget _buildNotificationsList( + BuildContext context, + WidgetRef ref, + List notifications, + ValueNotifier selectedCategory, + ) { + if (notifications.isEmpty) { + return _buildEmptyState(); + } + + return RefreshIndicator( + onRefresh: () async { + ref.invalidate(filteredNotificationsProvider(selectedCategory.value)); + }, + child: ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + itemCount: notifications.length, + itemBuilder: (context, index) { + final notification = notifications[index]; + return NotificationCard( + notification: notification, + onTap: () { + // TODO: Handle notification tap (mark as read, navigate to related entity) + _handleNotificationTap(context, ref, notification); + }, + ); + }, + ), + ); + } + + /// Build empty state + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.notifications_none, + size: 64, + color: AppColors.grey500.withValues(alpha: 0.5), + ), + const SizedBox(height: 16), + const Text( + 'Không có thông báo', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.grey500, + ), + ), + const SizedBox(height: 8), + const Text( + 'Bạn chưa có thông báo nào', + style: TextStyle(fontSize: 14, color: AppColors.grey500), + ), + ], + ), + ); + } + + /// Build error state + Widget _buildErrorState( + WidgetRef ref, + ValueNotifier selectedCategory, + ) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 64, + color: AppColors.danger.withValues(alpha: 0.5), + ), + const SizedBox(height: 16), + const Text( + 'Không thể tải thông báo', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.grey500, + ), + ), + const SizedBox(height: 16), + ElevatedButton.icon( + onPressed: () { + ref.invalidate( + filteredNotificationsProvider(selectedCategory.value), + ); + }, + icon: const Icon(Icons.refresh), + label: const Text('Thử lại'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primaryBlue, + foregroundColor: Colors.white, + ), + ), + ], + ), + ); + } + + /// Handle notification tap + void _handleNotificationTap( + BuildContext context, + WidgetRef ref, + Notification notification, + ) { + // Mark as read if unread + if (notification.isUnread) { + // TODO: Implement mark as read + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Đã đánh dấu đã đọc'), + duration: Duration(seconds: 1), + ), + ); + } + + // Navigate to related entity if exists + // TODO: Implement navigation based on notification type + } +} diff --git a/lib/features/notifications/presentation/providers/notifications_provider.dart b/lib/features/notifications/presentation/providers/notifications_provider.dart new file mode 100644 index 0000000..70c4647 --- /dev/null +++ b/lib/features/notifications/presentation/providers/notifications_provider.dart @@ -0,0 +1,42 @@ +/// Notifications Providers +/// +/// Riverpod providers for managing notification state. +library; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:worker/features/notifications/data/datasources/notification_local_datasource.dart'; +import 'package:worker/features/notifications/data/repositories/notification_repository_impl.dart'; +import 'package:worker/features/notifications/domain/entities/notification.dart'; +import 'package:worker/features/notifications/domain/repositories/notification_repository.dart'; + +/// Notification Local Data Source Provider +final notificationLocalDataSourceProvider = + Provider((ref) { + return NotificationLocalDataSource(); + }); + +/// Notification Repository Provider +final notificationRepositoryProvider = Provider((ref) { + final localDataSource = ref.watch(notificationLocalDataSourceProvider); + return NotificationRepositoryImpl(localDataSource: localDataSource); +}); + +/// All Notifications Provider +final notificationsProvider = FutureProvider>((ref) async { + final repository = ref.watch(notificationRepositoryProvider); + return await repository.getAllNotifications(); +}); + +/// Filtered Notifications Provider (by category parameter) +/// Use .family to pass category as parameter from UI +final filteredNotificationsProvider = + FutureProvider.family, String>((ref, category) async { + final repository = ref.watch(notificationRepositoryProvider); + return await repository.getNotificationsByCategory(category); + }); + +/// Unread Count Provider +final unreadNotificationCountProvider = FutureProvider((ref) async { + final repository = ref.watch(notificationRepositoryProvider); + return await repository.getUnreadCount(); +}); diff --git a/lib/features/notifications/presentation/widgets/notification_card.dart b/lib/features/notifications/presentation/widgets/notification_card.dart new file mode 100644 index 0000000..1ab204c --- /dev/null +++ b/lib/features/notifications/presentation/widgets/notification_card.dart @@ -0,0 +1,162 @@ +/// Notification Card Widget +/// +/// Displays a single notification item with icon, title, message, and timestamp. +/// Shows unread indicator with blue left border for unread notifications. +library; + +import 'package:flutter/material.dart'; +import 'package:worker/core/constants/ui_constants.dart'; +import 'package:worker/core/theme/colors.dart'; +import 'package:worker/features/notifications/domain/entities/notification.dart' + as entity; + +/// Notification Card +/// +/// Features: +/// - Icon with color based on notification type +/// - Title and message text +/// - Time ago timestamp +/// - Blue left border for unread notifications +/// - Tap handler +class NotificationCard extends StatelessWidget { + /// Notification to display + final entity.Notification notification; + + /// Tap callback + final VoidCallback? onTap; + + /// Constructor + const NotificationCard({super.key, required this.notification, this.onTap}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: notification.isUnread + ? const Border( + left: BorderSide(color: AppColors.primaryBlue, width: 3), + ) + : null, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(AppSpacing.md), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title with icon + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(_getIcon(), size: 18, color: _getIconColor()), + const SizedBox(width: 8), + Expanded( + child: Text( + notification.title, + style: TextStyle( + fontSize: 14, + fontWeight: notification.isUnread + ? FontWeight.w600 + : FontWeight.w500, + color: const Color(0xFF212121), + ), + ), + ), + ], + ), + + const SizedBox(height: 4), + + // Message + Padding( + padding: const EdgeInsets.only(left: 26), + child: Text( + notification.message, + style: const TextStyle( + fontSize: 13, + color: Color(0xFF64748B), + ), + ), + ), + + const SizedBox(height: 8), + + // Time ago + Padding( + padding: const EdgeInsets.only(left: 26), + child: Text( + notification.formattedTimeAgo, + style: const TextStyle( + fontSize: 11, + color: Color(0xFF94A3B8), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + + /// Get icon based on notification type + IconData _getIcon() { + final type = notification.type.toLowerCase(); + + if (type.contains('points') || type.contains('loyalty')) { + return Icons.card_giftcard; + } else if (type.contains('promotion') || type.contains('sale')) { + return Icons.local_offer; + } else if (type.contains('shipping')) { + return Icons.local_shipping; + } else if (type.contains('tier') || type.contains('upgrade')) { + return Icons.workspace_premium; + } else if (type.contains('event')) { + return Icons.event; + } else if (type.contains('confirmed')) { + return Icons.check_circle; + } else if (type.contains('birthday')) { + return Icons.cake; + } else { + return Icons.notifications; + } + } + + /// Get icon color based on notification type + Color _getIconColor() { + final type = notification.type.toLowerCase(); + + if (type.contains('points') || type.contains('loyalty')) { + return AppColors.primaryBlue; // Blue for points/loyalty + } else if (type.contains('promotion') || type.contains('sale')) { + return AppColors.warning; // Orange/yellow for promotions + } else if (type.contains('shipping')) { + return AppColors.success; // Green for shipping + } else if (type.contains('tier') || type.contains('upgrade')) { + return AppColors.warning; // Gold/yellow for tier + } else if (type.contains('event')) { + return AppColors.primaryBlue; // Blue for events + } else if (type.contains('confirmed')) { + return AppColors.success; // Green for confirmations + } else if (type.contains('birthday')) { + return const Color(0xFFFF69B4); // Pink for birthday + } else { + return AppColors.grey500; // Gray for system + } + } +} diff --git a/lib/hive_registrar.g.dart b/lib/hive_registrar.g.dart deleted file mode 100644 index eb94e1b..0000000 --- a/lib/hive_registrar.g.dart +++ /dev/null @@ -1,148 +0,0 @@ -// Generated by Hive CE -// Do not modify -// Check in to version control - -import 'package:hive_ce/hive.dart'; -import 'package:worker/core/database/models/cached_data.dart'; -import 'package:worker/core/database/models/enums.dart'; -import 'package:worker/features/account/data/models/audit_log_model.dart'; -import 'package:worker/features/account/data/models/payment_reminder_model.dart'; -import 'package:worker/features/auth/data/models/user_model.dart'; -import 'package:worker/features/auth/data/models/user_session_model.dart'; -import 'package:worker/features/cart/data/models/cart_item_model.dart'; -import 'package:worker/features/cart/data/models/cart_model.dart'; -import 'package:worker/features/chat/data/models/chat_room_model.dart'; -import 'package:worker/features/chat/data/models/message_model.dart'; -import 'package:worker/features/favorites/data/models/favorite_model.dart'; -import 'package:worker/features/home/data/models/member_card_model.dart'; -import 'package:worker/features/home/data/models/promotion_model.dart'; -import 'package:worker/features/loyalty/data/models/gift_catalog_model.dart'; -import 'package:worker/features/loyalty/data/models/loyalty_point_entry_model.dart'; -import 'package:worker/features/loyalty/data/models/points_record_model.dart'; -import 'package:worker/features/loyalty/data/models/redeemed_gift_model.dart'; -import 'package:worker/features/notifications/data/models/notification_model.dart'; -import 'package:worker/features/orders/data/models/invoice_model.dart'; -import 'package:worker/features/orders/data/models/order_item_model.dart'; -import 'package:worker/features/orders/data/models/order_model.dart'; -import 'package:worker/features/orders/data/models/payment_line_model.dart'; -import 'package:worker/features/products/data/models/category_model.dart'; -import 'package:worker/features/products/data/models/product_model.dart'; -import 'package:worker/features/products/data/models/stock_level_model.dart'; -import 'package:worker/features/projects/data/models/design_request_model.dart'; -import 'package:worker/features/projects/data/models/project_submission_model.dart'; -import 'package:worker/features/quotes/data/models/quote_item_model.dart'; -import 'package:worker/features/quotes/data/models/quote_model.dart'; -import 'package:worker/features/showrooms/data/models/showroom_model.dart'; -import 'package:worker/features/showrooms/data/models/showroom_product_model.dart'; - -extension HiveRegistrar on HiveInterface { - void registerAdapters() { - registerAdapter(AuditLogModelAdapter()); - registerAdapter(CachedDataAdapter()); - registerAdapter(CartItemModelAdapter()); - registerAdapter(CartModelAdapter()); - registerAdapter(CategoryModelAdapter()); - registerAdapter(ChatRoomModelAdapter()); - registerAdapter(ComplaintStatusAdapter()); - registerAdapter(ContentTypeAdapter()); - registerAdapter(DesignRequestModelAdapter()); - registerAdapter(DesignStatusAdapter()); - registerAdapter(EntrySourceAdapter()); - registerAdapter(EntryTypeAdapter()); - registerAdapter(FavoriteModelAdapter()); - registerAdapter(GiftCatalogModelAdapter()); - registerAdapter(GiftCategoryAdapter()); - registerAdapter(GiftStatusAdapter()); - registerAdapter(InvoiceModelAdapter()); - registerAdapter(InvoiceStatusAdapter()); - registerAdapter(InvoiceTypeAdapter()); - registerAdapter(LoyaltyPointEntryModelAdapter()); - registerAdapter(LoyaltyTierAdapter()); - registerAdapter(MemberCardModelAdapter()); - registerAdapter(MessageModelAdapter()); - registerAdapter(NotificationModelAdapter()); - registerAdapter(OrderItemModelAdapter()); - registerAdapter(OrderModelAdapter()); - registerAdapter(OrderStatusAdapter()); - registerAdapter(PaymentLineModelAdapter()); - registerAdapter(PaymentMethodAdapter()); - registerAdapter(PaymentReminderModelAdapter()); - registerAdapter(PaymentStatusAdapter()); - registerAdapter(PointsRecordModelAdapter()); - registerAdapter(PointsStatusAdapter()); - registerAdapter(ProductModelAdapter()); - registerAdapter(ProjectSubmissionModelAdapter()); - registerAdapter(ProjectTypeAdapter()); - registerAdapter(PromotionModelAdapter()); - registerAdapter(QuoteItemModelAdapter()); - registerAdapter(QuoteModelAdapter()); - registerAdapter(QuoteStatusAdapter()); - registerAdapter(RedeemedGiftModelAdapter()); - registerAdapter(ReminderTypeAdapter()); - registerAdapter(RoomTypeAdapter()); - registerAdapter(ShowroomModelAdapter()); - registerAdapter(ShowroomProductModelAdapter()); - registerAdapter(StockLevelModelAdapter()); - registerAdapter(SubmissionStatusAdapter()); - registerAdapter(UserModelAdapter()); - registerAdapter(UserRoleAdapter()); - registerAdapter(UserSessionModelAdapter()); - registerAdapter(UserStatusAdapter()); - } -} - -extension IsolatedHiveRegistrar on IsolatedHiveInterface { - void registerAdapters() { - registerAdapter(AuditLogModelAdapter()); - registerAdapter(CachedDataAdapter()); - registerAdapter(CartItemModelAdapter()); - registerAdapter(CartModelAdapter()); - registerAdapter(CategoryModelAdapter()); - registerAdapter(ChatRoomModelAdapter()); - registerAdapter(ComplaintStatusAdapter()); - registerAdapter(ContentTypeAdapter()); - registerAdapter(DesignRequestModelAdapter()); - registerAdapter(DesignStatusAdapter()); - registerAdapter(EntrySourceAdapter()); - registerAdapter(EntryTypeAdapter()); - registerAdapter(FavoriteModelAdapter()); - registerAdapter(GiftCatalogModelAdapter()); - registerAdapter(GiftCategoryAdapter()); - registerAdapter(GiftStatusAdapter()); - registerAdapter(InvoiceModelAdapter()); - registerAdapter(InvoiceStatusAdapter()); - registerAdapter(InvoiceTypeAdapter()); - registerAdapter(LoyaltyPointEntryModelAdapter()); - registerAdapter(LoyaltyTierAdapter()); - registerAdapter(MemberCardModelAdapter()); - registerAdapter(MessageModelAdapter()); - registerAdapter(NotificationModelAdapter()); - registerAdapter(OrderItemModelAdapter()); - registerAdapter(OrderModelAdapter()); - registerAdapter(OrderStatusAdapter()); - registerAdapter(PaymentLineModelAdapter()); - registerAdapter(PaymentMethodAdapter()); - registerAdapter(PaymentReminderModelAdapter()); - registerAdapter(PaymentStatusAdapter()); - registerAdapter(PointsRecordModelAdapter()); - registerAdapter(PointsStatusAdapter()); - registerAdapter(ProductModelAdapter()); - registerAdapter(ProjectSubmissionModelAdapter()); - registerAdapter(ProjectTypeAdapter()); - registerAdapter(PromotionModelAdapter()); - registerAdapter(QuoteItemModelAdapter()); - registerAdapter(QuoteModelAdapter()); - registerAdapter(QuoteStatusAdapter()); - registerAdapter(RedeemedGiftModelAdapter()); - registerAdapter(ReminderTypeAdapter()); - registerAdapter(RoomTypeAdapter()); - registerAdapter(ShowroomModelAdapter()); - registerAdapter(ShowroomProductModelAdapter()); - registerAdapter(StockLevelModelAdapter()); - registerAdapter(SubmissionStatusAdapter()); - registerAdapter(UserModelAdapter()); - registerAdapter(UserRoleAdapter()); - registerAdapter(UserSessionModelAdapter()); - registerAdapter(UserStatusAdapter()); - } -} diff --git a/pubspec.lock b/pubspec.lock index c9153cc..f9eb0e5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -435,6 +435,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_hooks: + dependency: "direct main" + description: + name: flutter_hooks + sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" + url: "https://pub.dev" + source: hosted + version: "0.21.3+1" flutter_lints: dependency: "direct dev" description: @@ -559,6 +567,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.3" + hooks_riverpod: + dependency: "direct main" + description: + name: hooks_riverpod + sha256: ae4a2f6d82dd895379f9b95457e090ac2d2fef9446f9325f8d31b9c86cadc131 + url: "https://pub.dev" + source: hosted + version: "3.0.3" hotreloader: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 050fbc7..373f4d0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: # State Management - Riverpod 3.0 flutter_riverpod: ^3.0.0 + hooks_riverpod: ^3.0.0 + flutter_hooks: ^0.21.3+1 riverpod_annotation: ^3.0.0 # Local Database