update keep alive filter
This commit is contained in:
@@ -6,8 +6,8 @@ library;
|
|||||||
import 'package:curl_logger_dio_interceptor/curl_logger_dio_interceptor.dart';
|
import 'package:curl_logger_dio_interceptor/curl_logger_dio_interceptor.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:worker/core/constants/api_constants.dart';
|
||||||
import 'package:worker/features/auth/data/datasources/auth_remote_datasource.dart';
|
import 'package:worker/features/auth/data/datasources/auth_remote_datasource.dart';
|
||||||
import 'package:worker/features/auth/data/models/auth_session_model.dart';
|
|
||||||
|
|
||||||
part 'session_provider.g.dart';
|
part 'session_provider.g.dart';
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ part 'session_provider.g.dart';
|
|||||||
Dio dio(Ref ref) {
|
Dio dio(Ref ref) {
|
||||||
final dio = Dio(
|
final dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl: 'https://land.dbiz.com',
|
baseUrl: ApiConstants.baseUrl,
|
||||||
connectTimeout: const Duration(seconds: 30),
|
connectTimeout: const Duration(seconds: 30),
|
||||||
receiveTimeout: const Duration(seconds: 30),
|
receiveTimeout: const Duration(seconds: 30),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ final class DioProvider extends $FunctionalProvider<Dio, Dio, Dio>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$dioHash() => r'2bc10725a1b646cfaabd88c722e5101c06837c75';
|
String _$dioHash() => r'3c682dd2b6f7ca8e39e1c26713a9160c2e69d894';
|
||||||
|
|
||||||
/// Provider for AuthRemoteDataSource
|
/// Provider for AuthRemoteDataSource
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ class NewsRemoteDataSource {
|
|||||||
'published_on',
|
'published_on',
|
||||||
'blogger',
|
'blogger',
|
||||||
'blog_intro',
|
'blog_intro',
|
||||||
'content',
|
|
||||||
'meta_image',
|
'meta_image',
|
||||||
'meta_description',
|
'meta_description',
|
||||||
'blog_category',
|
'blog_category',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:worker/core/constants/api_constants.dart';
|
||||||
import 'package:worker/features/news/domain/entities/news_article.dart';
|
import 'package:worker/features/news/domain/entities/news_article.dart';
|
||||||
|
|
||||||
part 'blog_post_model.g.dart';
|
part 'blog_post_model.g.dart';
|
||||||
@@ -97,11 +98,11 @@ class BlogPostModel {
|
|||||||
if (metaImage != null && metaImage!.isNotEmpty) {
|
if (metaImage != null && metaImage!.isNotEmpty) {
|
||||||
// If meta_image starts with /, prepend the base URL
|
// If meta_image starts with /, prepend the base URL
|
||||||
if (metaImage!.startsWith('/')) {
|
if (metaImage!.startsWith('/')) {
|
||||||
imageUrl = 'https://land.dbiz.com$metaImage';
|
imageUrl = '${ApiConstants.baseUrl}$metaImage';
|
||||||
} else if (metaImage!.startsWith('http')) {
|
} else if (metaImage!.startsWith('http')) {
|
||||||
imageUrl = metaImage!;
|
imageUrl = metaImage!;
|
||||||
} else {
|
} else {
|
||||||
imageUrl = 'https://land.dbiz.com/$metaImage';
|
imageUrl = '${ApiConstants.baseUrl}/$metaImage';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
imageUrl = 'https://via.placeholder.com/400x300?text=${Uri.encodeComponent(title)}';
|
imageUrl = 'https://via.placeholder.com/400x300?text=${Uri.encodeComponent(title)}';
|
||||||
|
|||||||
@@ -154,33 +154,8 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
// Excerpt
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF8FAFC),
|
|
||||||
border: const Border(
|
|
||||||
left: BorderSide(color: AppColors.primaryBlue, width: 4),
|
|
||||||
),
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topRight: Radius.circular(8),
|
|
||||||
bottomRight: Radius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
article.excerpt,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Color(0xFF64748B),
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Article Body - Render HTML content
|
// Article Body - Render HTML content
|
||||||
if (article.content != null && article.content!.isNotEmpty)
|
if (article.content != null && article.content!.isNotEmpty)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:hive_ce/hive.dart';
|
import 'package:hive_ce/hive.dart';
|
||||||
|
|
||||||
|
import 'package:worker/core/constants/api_constants.dart';
|
||||||
import 'package:worker/core/constants/storage_constants.dart';
|
import 'package:worker/core/constants/storage_constants.dart';
|
||||||
import 'package:worker/features/products/domain/entities/product.dart';
|
import 'package:worker/features/products/domain/entities/product.dart';
|
||||||
|
|
||||||
@@ -141,61 +142,101 @@ class ProductModel extends HiveObject {
|
|||||||
///
|
///
|
||||||
/// Maps Frappe Item doctype fields to our ProductModel structure.
|
/// Maps Frappe Item doctype fields to our ProductModel structure.
|
||||||
/// Frappe fields:
|
/// Frappe fields:
|
||||||
/// - name: Item code (e.g., "GIB20 G02")
|
/// - name: Item code (e.g., "CHG S01P")
|
||||||
/// - item_name: Display name
|
/// - item_name: Display name
|
||||||
/// - description: Product description
|
/// - description: Product description
|
||||||
/// - standard_rate: Price
|
/// - price: Price (from product detail API)
|
||||||
|
/// - standard_rate: Price (from product list API)
|
||||||
/// - stock_uom: Unit of measurement
|
/// - stock_uom: Unit of measurement
|
||||||
/// - image: Image path
|
/// - thumbnail: Thumbnail image URL
|
||||||
/// - thumbnail: Thumbnail image path
|
/// - image_list: Array of images with image_url
|
||||||
/// - brand: Brand name
|
/// - brand: Brand name
|
||||||
/// - item_group: Category/group
|
/// - item_group_name: Category/group name
|
||||||
/// - custom_link_360: Custom 360 view link
|
/// - custom_link_360: Custom 360 view link
|
||||||
|
/// - attributes: Array of product attributes (Size, Color, Surface, etc.)
|
||||||
factory ProductModel.fromFrappeJson(Map<String, dynamic> json) {
|
factory ProductModel.fromFrappeJson(Map<String, dynamic> json) {
|
||||||
// Handle image - prepend base URL if needed
|
|
||||||
String? imageUrl;
|
|
||||||
if (json['image'] != null && (json['image'] as String).isNotEmpty) {
|
|
||||||
final imagePath = json['image'] as String;
|
|
||||||
if (imagePath.startsWith('/')) {
|
|
||||||
imageUrl = 'https://land.dbiz.com$imagePath';
|
|
||||||
} else if (imagePath.startsWith('http')) {
|
|
||||||
imageUrl = imagePath;
|
|
||||||
} else {
|
|
||||||
imageUrl = 'https://land.dbiz.com/$imagePath';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle thumbnail - prepend base URL if needed
|
// Handle thumbnail - prepend base URL if needed
|
||||||
String? thumbnailUrl;
|
String? thumbnailUrl;
|
||||||
if (json['thumbnail'] != null && (json['thumbnail'] as String).isNotEmpty) {
|
if (json['thumbnail'] != null && (json['thumbnail'] as String).isNotEmpty) {
|
||||||
final thumbnailPath = json['thumbnail'] as String;
|
final thumbnailPath = json['thumbnail'] as String;
|
||||||
if (thumbnailPath.startsWith('/')) {
|
if (thumbnailPath.startsWith('/')) {
|
||||||
thumbnailUrl = 'https://land.dbiz.com$thumbnailPath';
|
thumbnailUrl = '${ApiConstants.baseUrl}$thumbnailPath';
|
||||||
} else if (thumbnailPath.startsWith('http')) {
|
} else if (thumbnailPath.startsWith('http')) {
|
||||||
thumbnailUrl = thumbnailPath;
|
thumbnailUrl = thumbnailPath;
|
||||||
} else {
|
} else {
|
||||||
thumbnailUrl = 'https://land.dbiz.com/$thumbnailPath';
|
thumbnailUrl = '${ApiConstants.baseUrl}/$thumbnailPath';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert single image to list format
|
// Handle image_list array (from product detail API)
|
||||||
final imagesList = imageUrl != null ? [imageUrl] : [];
|
final List<String> imagesList = [];
|
||||||
|
final Map<String, String> imageCaptionsMap = {};
|
||||||
|
|
||||||
|
if (json['image_list'] != null && json['image_list'] is List) {
|
||||||
|
final imageListData = json['image_list'] as List;
|
||||||
|
for (final imgData in imageListData) {
|
||||||
|
if (imgData is Map<String, dynamic> && imgData['image_url'] != null) {
|
||||||
|
final imageUrl = imgData['image_url'] as String;
|
||||||
|
imagesList.add(imageUrl);
|
||||||
|
|
||||||
|
// Store image caption (image_name: A, B, C, etc.)
|
||||||
|
if (imgData['image_name'] != null) {
|
||||||
|
imageCaptionsMap[imageUrl] = imgData['image_name'] as String;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to single image field (from product list API)
|
||||||
|
else if (json['image'] != null && (json['image'] as String).isNotEmpty) {
|
||||||
|
final imagePath = json['image'] as String;
|
||||||
|
String imageUrl;
|
||||||
|
if (imagePath.startsWith('/')) {
|
||||||
|
imageUrl = '${ApiConstants.baseUrl}$imagePath';
|
||||||
|
} else if (imagePath.startsWith('http')) {
|
||||||
|
imageUrl = imagePath;
|
||||||
|
} else {
|
||||||
|
imageUrl = '${ApiConstants.baseUrl}/$imagePath';
|
||||||
|
}
|
||||||
|
imagesList.add(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse attributes array into specifications map
|
||||||
|
final Map<String, dynamic> specificationsMap = {};
|
||||||
|
if (json['attributes'] != null && json['attributes'] is List) {
|
||||||
|
final attributesData = json['attributes'] as List;
|
||||||
|
for (final attr in attributesData) {
|
||||||
|
if (attr is Map<String, dynamic> &&
|
||||||
|
attr['attribute_name'] != null &&
|
||||||
|
attr['attribute_value'] != null) {
|
||||||
|
specificationsMap[attr['attribute_name'] as String] =
|
||||||
|
attr['attribute_value'] as String;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
// Handle price from both product detail (price) and product list (standard_rate)
|
||||||
|
final price = (json['price'] as num?)?.toDouble() ??
|
||||||
|
(json['standard_rate'] as num?)?.toDouble() ??
|
||||||
|
0.0;
|
||||||
|
|
||||||
return ProductModel(
|
return ProductModel(
|
||||||
productId: json['name'] as String, // Item code
|
productId: json['name'] as String, // Item code
|
||||||
name: json['item_name'] as String? ?? json['name'] as String,
|
name: json['item_name'] as String? ?? json['name'] as String,
|
||||||
description: json['description'] as String?,
|
description: json['description'] as String?,
|
||||||
basePrice: (json['standard_rate'] as num?)?.toDouble() ?? 0.0,
|
basePrice: price,
|
||||||
images: imagesList.isNotEmpty ? jsonEncode(imagesList) : null,
|
images: imagesList.isNotEmpty ? jsonEncode(imagesList) : null,
|
||||||
thumbnail: thumbnailUrl,
|
thumbnail: thumbnailUrl,
|
||||||
imageCaptions: null, // Not provided by API
|
imageCaptions: imageCaptionsMap.isNotEmpty
|
||||||
customLink360: json['custom_link_360'] as String?,
|
? jsonEncode(imageCaptionsMap)
|
||||||
specifications: json['specifications'] != null
|
|
||||||
? jsonEncode(json['specifications'])
|
|
||||||
: null,
|
: null,
|
||||||
category: json['item_group'] as String?, // Frappe uses item_group
|
customLink360: json['custom_link_360'] as String?,
|
||||||
|
specifications: specificationsMap.isNotEmpty
|
||||||
|
? jsonEncode(specificationsMap)
|
||||||
|
: null,
|
||||||
|
category: json['item_group_name'] as String? ??
|
||||||
|
json['item_group'] as String?, // Try item_group_name first, fallback to item_group
|
||||||
brand: json['brand'] as String?,
|
brand: json['brand'] as String?,
|
||||||
unit: json['stock_uom'] as String? ?? 'm²',
|
unit: json['stock_uom'] as String? ?? 'm²',
|
||||||
isActive: (json['disabled'] as int?) == 0, // Frappe uses 'disabled' field
|
isActive: (json['disabled'] as int?) == 0, // Frappe uses 'disabled' field
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:worker/core/router/app_router.dart';
|
|||||||
import 'package:worker/core/theme/colors.dart';
|
import 'package:worker/core/theme/colors.dart';
|
||||||
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
||||||
import 'package:worker/features/products/presentation/providers/categories_provider.dart';
|
import 'package:worker/features/products/presentation/providers/categories_provider.dart';
|
||||||
|
import 'package:worker/features/products/presentation/providers/product_filter_options_provider.dart';
|
||||||
import 'package:worker/features/products/presentation/providers/products_provider.dart';
|
import 'package:worker/features/products/presentation/providers/products_provider.dart';
|
||||||
import 'package:worker/features/products/presentation/widgets/category_filter_chips.dart';
|
import 'package:worker/features/products/presentation/widgets/category_filter_chips.dart';
|
||||||
import 'package:worker/features/products/presentation/widgets/product_filter_drawer.dart';
|
import 'package:worker/features/products/presentation/widgets/product_filter_drawer.dart';
|
||||||
@@ -31,11 +32,14 @@ class ProductsPage extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context);
|
||||||
final categoriesAsync = ref.watch(categoriesProvider);
|
final categoriesAsync = ref.watch(categoriesProvider);
|
||||||
final productsAsync = ref.watch(productsProvider);
|
final productsAsync = ref.watch(productsProvider);
|
||||||
final cartItemCount = ref.watch(cartItemCountProvider);
|
final cartItemCount = ref.watch(cartItemCountProvider);
|
||||||
|
|
||||||
|
// Preload filter options for better UX when opening filter drawer
|
||||||
|
ref.watch(productFilterOptionsProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFF4F6F8), // Match HTML background
|
backgroundColor: const Color(0xFFF4F6F8), // Match HTML background
|
||||||
endDrawer: const ProductFilterDrawer(),
|
endDrawer: const ProductFilterDrawer(),
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ class FilterOption {
|
|||||||
/// 2. Product Brands
|
/// 2. Product Brands
|
||||||
/// 3. Product Attributes
|
/// 3. Product Attributes
|
||||||
///
|
///
|
||||||
|
/// Memory footprint: ~5-15 KB (negligible)
|
||||||
|
/// Cache strategy: Keep alive for session duration
|
||||||
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
||||||
@@ -100,7 +103,7 @@ class FilterOption {
|
|||||||
/// error: (error, stack) => ErrorWidget(error),
|
/// error: (error, stack) => ErrorWidget(error),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
@riverpod
|
@Riverpod(keepAlive: true)
|
||||||
Future<ProductFilterOptions> productFilterOptions(Ref ref) async {
|
Future<ProductFilterOptions> productFilterOptions(Ref ref) async {
|
||||||
try {
|
try {
|
||||||
// Get remote datasource
|
// Get remote datasource
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ part of 'product_filter_options_provider.dart';
|
|||||||
/// 2. Product Brands
|
/// 2. Product Brands
|
||||||
/// 3. Product Attributes
|
/// 3. Product Attributes
|
||||||
///
|
///
|
||||||
|
/// Memory footprint: ~5-15 KB (negligible)
|
||||||
|
/// Cache strategy: Keep alive for session duration
|
||||||
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
||||||
@@ -36,6 +39,9 @@ const productFilterOptionsProvider = ProductFilterOptionsProvider._();
|
|||||||
/// 2. Product Brands
|
/// 2. Product Brands
|
||||||
/// 3. Product Attributes
|
/// 3. Product Attributes
|
||||||
///
|
///
|
||||||
|
/// Memory footprint: ~5-15 KB (negligible)
|
||||||
|
/// Cache strategy: Keep alive for session duration
|
||||||
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
||||||
@@ -64,6 +70,9 @@ final class ProductFilterOptionsProvider
|
|||||||
/// 2. Product Brands
|
/// 2. Product Brands
|
||||||
/// 3. Product Attributes
|
/// 3. Product Attributes
|
||||||
///
|
///
|
||||||
|
/// Memory footprint: ~5-15 KB (negligible)
|
||||||
|
/// Cache strategy: Keep alive for session duration
|
||||||
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
/// final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
||||||
@@ -80,7 +89,7 @@ final class ProductFilterOptionsProvider
|
|||||||
argument: null,
|
argument: null,
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'productFilterOptionsProvider',
|
name: r'productFilterOptionsProvider',
|
||||||
isAutoDispose: true,
|
isAutoDispose: false,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
@@ -101,4 +110,4 @@ final class ProductFilterOptionsProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$productFilterOptionsHash() =>
|
String _$productFilterOptionsHash() =>
|
||||||
r'394f47113bc2afeea8a0a4548df826900884644b';
|
r'253586215f05ca2fd1ccae7922b5925150614af0';
|
||||||
|
|||||||
Reference in New Issue
Block a user