first commit
This commit is contained in:
192
lib/controllers/product/product_controller.dart
Normal file
192
lib/controllers/product/product_controller.dart
Normal file
@@ -0,0 +1,192 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../../domain/provider/product/all_products.dart';
|
||||
import '../../modules/product/product.dart';
|
||||
|
||||
class ProductsController extends GetxController {
|
||||
|
||||
|
||||
|
||||
final ProductsProvider provider = ProductsProvider();
|
||||
var isConnected = true.obs;
|
||||
var isLoading = false.obs;
|
||||
var productResponse = Rxn<ProductResponse>();
|
||||
var selectedIndex = 0.obs;
|
||||
var searchQuery = ''.obs;
|
||||
var isSearching = false.obs;
|
||||
|
||||
|
||||
|
||||
List<Product> get allProducts {
|
||||
final response = productResponse.value;
|
||||
if (response == null) return <Product>[];
|
||||
|
||||
final details = response.data?.details ?? <Detail>[];
|
||||
|
||||
return details
|
||||
.expand<Product>((d) => d.products ?? <Product>[])
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
/// In-memory cache: key is "categoryId_tenantId"
|
||||
final Map<String, ProductResponse> _cache = {};
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
selectedIndex.value = 0;
|
||||
// Listen for connectivity changes
|
||||
Connectivity().onConnectivityChanged.listen((status) {
|
||||
isConnected.value = (status != ConnectivityResult.none);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
Get.delete<ProductsController>();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<bool> hasInternet() async {
|
||||
try {
|
||||
final response = await http.get(Uri.parse('https://www.google.com'))
|
||||
.timeout(const Duration(seconds: 5));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<void> fetchProducts(int categoryId, int tenantId, int locationId) async {
|
||||
final cacheKey = '${categoryId}_${tenantId}_$locationId'; // ✅ Include locationId in cache key
|
||||
|
||||
// 1️⃣ Use cache if available
|
||||
if (_cache.containsKey(cacheKey)) {
|
||||
productResponse.value = _cache[cacheKey];
|
||||
selectedIndex.value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
bool connected = await hasInternet();
|
||||
if (!connected) {
|
||||
isLoading.value = false;
|
||||
isConnected = false.obs;
|
||||
return; // Stop fetching
|
||||
}
|
||||
|
||||
// 2️⃣ Otherwise fetch from API
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
final response = await provider.getProductsBySubCategory(
|
||||
categoryId: categoryId,
|
||||
tenantId: tenantId,
|
||||
locationId: locationId, // ✅ Pass locationId to API
|
||||
);
|
||||
|
||||
productResponse.value = response;
|
||||
|
||||
selectedIndex.value = 0;
|
||||
// 3️⃣ Save in cache
|
||||
_cache[cacheKey] = response!;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Force refresh API and update cache
|
||||
Future<void> refreshProducts(int categoryId, int tenantId, int locationId) async {
|
||||
final cacheKey = '${categoryId}_${tenantId}_$locationId'; // ✅ Include locationId
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
final response = await provider.getProductsBySubCategory(
|
||||
categoryId: categoryId,
|
||||
tenantId: tenantId,
|
||||
locationId: locationId, // ✅ Pass locationId to API
|
||||
);
|
||||
|
||||
productResponse.value = response;
|
||||
selectedIndex.value = 0;
|
||||
|
||||
// ✅ Update cache with new key
|
||||
_cache[cacheKey] = response!;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Returns products depending on search query and selected subcategory
|
||||
List<Product> get filteredProducts {
|
||||
// Check if nested data exists (main API)
|
||||
final details = productResponse.value?.data?.details;
|
||||
if (details != null && details.isNotEmpty) {
|
||||
if (searchQuery.value.isEmpty) {
|
||||
final selectedDetail = details[selectedIndex.value];
|
||||
return selectedDetail.products ?? [];
|
||||
}
|
||||
|
||||
List<Product> allProducts = [];
|
||||
for (var detail in details) {
|
||||
allProducts.addAll(detail.products ?? []);
|
||||
}
|
||||
return allProducts
|
||||
.where((p) =>
|
||||
(p.productname ?? '')
|
||||
.toLowerCase()
|
||||
.contains(searchQuery.value.toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// If flat details exist (variants API)
|
||||
final variantDetails = productResponse.value?.details ?? [];
|
||||
if (variantDetails.isNotEmpty) {
|
||||
if (searchQuery.value.isEmpty) return variantDetails;
|
||||
|
||||
return variantDetails
|
||||
.where((p) =>
|
||||
(p.productname ?? '')
|
||||
.toLowerCase()
|
||||
.contains(searchQuery.value.toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
// NEW: Dedicated method for subcategory-specific screen
|
||||
List<Product> getProductsBySubcategory(String subCategoryName) {
|
||||
final details = productResponse.value?.data?.details ?? [];
|
||||
|
||||
if (details.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Find matching subcategory (case-insensitive, trimmed for safety)
|
||||
final matchingDetail = details.firstWhere(
|
||||
(detail) =>
|
||||
(detail.subcategoryname ?? '').trim().toLowerCase() ==
|
||||
subCategoryName.trim().toLowerCase(),
|
||||
orElse: () => Detail(), // fallback - make sure Detail() is valid in your modules
|
||||
);
|
||||
|
||||
// Return the products of that subcategory (or empty if no match)
|
||||
return matchingDetail.products ?? [];
|
||||
}
|
||||
}
|
||||
117
lib/controllers/product/variant_controller.dart
Normal file
117
lib/controllers/product/variant_controller.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../domain/provider/varient/varient_pro.dart';
|
||||
import '../../domain/repository/varient/varient_repo.dart';
|
||||
import '../../modules/product/product.dart';
|
||||
|
||||
class ProductVariantController extends GetxController {
|
||||
final ProductVariantRepository provider;
|
||||
|
||||
ProductVariantController({required this.provider});
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Reactive state
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
final RxBool isLoading = false.obs;
|
||||
final RxList<Product> productVariants = <Product>[].obs;
|
||||
|
||||
/// Selected variant ID (null = nothing selected)
|
||||
final RxnInt selectedProductId = RxnInt();
|
||||
|
||||
/// Quantity per variant (default = 1)
|
||||
final RxMap<int, int> variantQuantities = <int, int>{}.obs;
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Public helpers
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
/// **Always call this before opening a new product’s variants**
|
||||
void clearVariantState() {
|
||||
productVariants.clear();
|
||||
selectedProductId.value = null;
|
||||
variantQuantities.clear();
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
void selectVariant(int productId) {
|
||||
if (selectedProductId.value == productId) {
|
||||
selectedProductId.value = null; // allow deselection
|
||||
} else {
|
||||
selectedProductId.value = productId;
|
||||
// Initialise quantity = 1 if not present
|
||||
variantQuantities.putIfAbsent(productId, () => 1);
|
||||
}
|
||||
}
|
||||
|
||||
void increaseQuantity(int productId) {
|
||||
final current = variantQuantities[productId] ?? 0;
|
||||
variantQuantities[productId] = current + 1;
|
||||
}
|
||||
|
||||
void decreaseQuantity(int productId) {
|
||||
final current = variantQuantities[productId] ?? 0;
|
||||
if (current > 1) {
|
||||
variantQuantities[productId] = current - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// FETCH VARIANTS – **reset + safe init**
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
Future<void> fetchVariants({
|
||||
required int tenantId,
|
||||
required int variantId,
|
||||
}) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
// 1. ALWAYS start fresh
|
||||
clearVariantState();
|
||||
|
||||
final variants = await provider.getProductVariant(
|
||||
tenantId: tenantId,
|
||||
variantId: variantId,
|
||||
);
|
||||
|
||||
if (variants != null && variants.isNotEmpty) {
|
||||
productVariants.assignAll(variants);
|
||||
|
||||
// 2. Initialise quantity = 1 for every variant
|
||||
for (final v in variants) {
|
||||
final pid = v.productid ?? 0;
|
||||
if (pid != 0) {
|
||||
variantQuantities[pid] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Auto-select first variant
|
||||
final first = variants.first;
|
||||
if (first.productid != null) {
|
||||
selectedProductId.value = first.productid;
|
||||
}
|
||||
} else {
|
||||
productVariants.clear();
|
||||
variantQuantities.clear();
|
||||
}
|
||||
} catch (e, s) {
|
||||
debugPrint('fetchVariants error: $e\n$s');
|
||||
// Get.snackbar(
|
||||
// 'Error',
|
||||
// 'Failed to load variants',
|
||||
// snackPosition: SnackPosition.TOP,
|
||||
// backgroundColor: Colors.redAccent,
|
||||
// colorText: Colors.white,
|
||||
// );
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Lifecycle
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
@override
|
||||
void onClose() {
|
||||
clearVariantState();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user