update
This commit is contained in:
@@ -28,12 +28,12 @@ android {
|
|||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_20
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_20
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '20'
|
jvmTarget = '17'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.2.1'
|
classpath 'com.android.tools.build:gradle:8.6.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.2.1" apply false
|
id "com.android.application" version "8.9.1" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
id "org.jetbrains.kotlin.android" version "2.2.21" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.9.1" apply false
|
id("com.android.application") version "8.9.1" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
id("org.jetbrains.kotlin.android") version "2.2.21" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
|
|
||||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
||||||
|
|
||||||
|
|||||||
@@ -125,8 +125,9 @@ class AppRouter {
|
|||||||
|
|
||||||
/// Product Detail Route
|
/// Product Detail Route
|
||||||
/// Path: /product-detail
|
/// Path: /product-detail
|
||||||
/// Takes warehouseId, productId, and warehouseName as extra parameter
|
/// Takes warehouseId, productId, warehouseName, and optional stageId as extra parameter
|
||||||
/// Shows detailed information for a specific product
|
/// Shows detailed information for a specific product
|
||||||
|
/// If stageId is provided, only that stage is shown, otherwise all stages are shown
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/product-detail',
|
path: '/product-detail',
|
||||||
name: 'product-detail',
|
name: 'product-detail',
|
||||||
@@ -147,6 +148,8 @@ class AppRouter {
|
|||||||
final warehouseId = params['warehouseId'] as int?;
|
final warehouseId = params['warehouseId'] as int?;
|
||||||
final productId = params['productId'] as int?;
|
final productId = params['productId'] as int?;
|
||||||
final warehouseName = params['warehouseName'] as String?;
|
final warehouseName = params['warehouseName'] as String?;
|
||||||
|
// Extract optional stageId
|
||||||
|
final stageId = params['stageId'] as int?;
|
||||||
|
|
||||||
// Validate parameters
|
// Validate parameters
|
||||||
if (warehouseId == null || productId == null || warehouseName == null) {
|
if (warehouseId == null || productId == null || warehouseName == null) {
|
||||||
@@ -162,6 +165,7 @@ class AppRouter {
|
|||||||
warehouseId: warehouseId,
|
warehouseId: warehouseId,
|
||||||
productId: productId,
|
productId: productId,
|
||||||
warehouseName: warehouseName,
|
warehouseName: warehouseName,
|
||||||
|
stageId: stageId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -375,10 +379,12 @@ extension AppRouterExtension on BuildContext {
|
|||||||
/// [warehouseId] - ID of the warehouse
|
/// [warehouseId] - ID of the warehouse
|
||||||
/// [productId] - ID of the product to view
|
/// [productId] - ID of the product to view
|
||||||
/// [warehouseName] - Name of the warehouse (for display)
|
/// [warehouseName] - Name of the warehouse (for display)
|
||||||
|
/// [stageId] - Optional ID of specific stage to show (if null, show all stages)
|
||||||
void goToProductDetail({
|
void goToProductDetail({
|
||||||
required int warehouseId,
|
required int warehouseId,
|
||||||
required int productId,
|
required int productId,
|
||||||
required String warehouseName,
|
required String warehouseName,
|
||||||
|
int? stageId,
|
||||||
}) {
|
}) {
|
||||||
push(
|
push(
|
||||||
'/product-detail',
|
'/product-detail',
|
||||||
@@ -386,6 +392,7 @@ extension AppRouterExtension on BuildContext {
|
|||||||
'warehouseId': warehouseId,
|
'warehouseId': warehouseId,
|
||||||
'productId': productId,
|
'productId': productId,
|
||||||
'warehouseName': warehouseName,
|
'warehouseName': warehouseName,
|
||||||
|
if (stageId != null) 'stageId': stageId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,19 @@ import '../../domain/entities/product_stage_entity.dart';
|
|||||||
|
|
||||||
/// Product detail page
|
/// Product detail page
|
||||||
/// Displays product stages as chips and shows selected stage information
|
/// Displays product stages as chips and shows selected stage information
|
||||||
|
/// If [stageId] is provided, only that stage is shown, otherwise all stages are shown
|
||||||
class ProductDetailPage extends ConsumerStatefulWidget {
|
class ProductDetailPage extends ConsumerStatefulWidget {
|
||||||
final int warehouseId;
|
final int warehouseId;
|
||||||
final int productId;
|
final int productId;
|
||||||
final String warehouseName;
|
final String warehouseName;
|
||||||
|
final int? stageId;
|
||||||
|
|
||||||
const ProductDetailPage({
|
const ProductDetailPage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.warehouseId,
|
required this.warehouseId,
|
||||||
required this.productId,
|
required this.productId,
|
||||||
required this.warehouseName,
|
required this.warehouseName,
|
||||||
|
this.stageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -31,11 +34,22 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
_providerKey = '${widget.warehouseId}_${widget.productId}';
|
_providerKey = '${widget.warehouseId}_${widget.productId}';
|
||||||
|
|
||||||
// Load product stages when page is initialized
|
// Load product stages when page is initialized
|
||||||
Future.microtask(() {
|
Future.microtask(() async {
|
||||||
ref.read(productDetailProvider(_providerKey).notifier).loadProductDetail(
|
await ref.read(productDetailProvider(_providerKey).notifier).loadProductDetail(
|
||||||
widget.warehouseId,
|
widget.warehouseId,
|
||||||
widget.productId,
|
widget.productId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If stageId is provided, auto-select that stage
|
||||||
|
if (widget.stageId != null) {
|
||||||
|
final stages = ref.read(productDetailProvider(_providerKey)).stages;
|
||||||
|
final stageIndex = stages.indexWhere(
|
||||||
|
(stage) => stage.productStageId == widget.stageId,
|
||||||
|
);
|
||||||
|
if (stageIndex != -1) {
|
||||||
|
ref.read(productDetailProvider(_providerKey).notifier).selectStage(stageIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,12 +180,58 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter stages if stageId is provided
|
||||||
|
final displayStages = widget.stageId != null
|
||||||
|
? stages.where((stage) => stage.productStageId == widget.stageId).toList()
|
||||||
|
: stages;
|
||||||
|
|
||||||
|
// When stageId is provided but no matching stage found
|
||||||
|
if (widget.stageId != null && displayStages.isEmpty && stages.isNotEmpty) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.search_off,
|
||||||
|
size: 64,
|
||||||
|
color: theme.colorScheme.error,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Stage Not Found',
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
color: theme.colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Stage with ID ${widget.stageId} was not found in this product.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
label: const Text('Go Back'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: _onRefresh,
|
onRefresh: _onRefresh,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Stage chips section
|
// Stage chips section
|
||||||
|
if (displayStages.isNotEmpty)
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -185,29 +245,61 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Production Stages (${stages.length})',
|
widget.stageId != null
|
||||||
|
? 'Selected Stage'
|
||||||
|
: 'Production Stages (${displayStages.length})',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.stageId != null) ...[
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'ID: ${widget.stageId}',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: List.generate(stages.length, (index) {
|
children: List.generate(displayStages.length, (index) {
|
||||||
final stage = stages[index];
|
final stage = displayStages[index];
|
||||||
final isSelected = index == selectedIndex;
|
// When stageId is provided, check if this stage matches the stageId
|
||||||
|
// Otherwise, check if the index matches the selected index
|
||||||
|
final isSelected = widget.stageId != null
|
||||||
|
? stage.productStageId == widget.stageId
|
||||||
|
: stages.indexOf(stage) == selectedIndex;
|
||||||
|
|
||||||
return FilterChip(
|
return FilterChip(
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
label: Text(stage.displayName),
|
label: Text(stage.displayName),
|
||||||
onSelected: (_) {
|
onSelected: widget.stageId == null
|
||||||
|
? (_) {
|
||||||
ref
|
ref
|
||||||
.read(productDetailProvider(_providerKey).notifier)
|
.read(productDetailProvider(_providerKey).notifier)
|
||||||
.selectStage(index);
|
.selectStage(stages.indexOf(stage));
|
||||||
},
|
}
|
||||||
|
: null, // Disable selection when specific stage is shown
|
||||||
backgroundColor: theme.colorScheme.surface,
|
backgroundColor: theme.colorScheme.surface,
|
||||||
selectedColor: theme.colorScheme.primaryContainer,
|
selectedColor: theme.colorScheme.primaryContainer,
|
||||||
checkmarkColor: theme.colorScheme.primary,
|
checkmarkColor: theme.colorScheme.primary,
|
||||||
@@ -226,16 +318,24 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
|
|
||||||
// Stage details section
|
// Stage details section
|
||||||
Expanded(
|
Expanded(
|
||||||
child: selectedStage == null
|
child: () {
|
||||||
? const Center(child: Text('No stage selected'))
|
// When stageId is provided, use the filtered stage
|
||||||
: SingleChildScrollView(
|
final stageToShow = widget.stageId != null && displayStages.isNotEmpty
|
||||||
|
? displayStages.first
|
||||||
|
: selectedStage;
|
||||||
|
|
||||||
|
if (stageToShow == null) {
|
||||||
|
return const Center(child: Text('No stage selected'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Stage header
|
// Stage header
|
||||||
_buildStageHeader(selectedStage, theme),
|
_buildStageHeader(stageToShow, theme),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Quantity information
|
// Quantity information
|
||||||
@@ -244,16 +344,16 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
title: 'Quantities',
|
title: 'Quantities',
|
||||||
icon: Icons.inventory_outlined,
|
icon: Icons.inventory_outlined,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow('Passed Quantity', '${selectedStage.passedQuantity}'),
|
_buildInfoRow('Passed Quantity', '${stageToShow.passedQuantity}'),
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
'Passed Weight',
|
'Passed Weight',
|
||||||
'${selectedStage.passedQuantityWeight.toStringAsFixed(2)} kg',
|
'${stageToShow.passedQuantityWeight.toStringAsFixed(2)} kg',
|
||||||
),
|
),
|
||||||
const Divider(height: 24),
|
const Divider(height: 24),
|
||||||
_buildInfoRow('Issued Quantity', '${selectedStage.issuedQuantity}'),
|
_buildInfoRow('Issued Quantity', '${stageToShow.issuedQuantity}'),
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
'Issued Weight',
|
'Issued Weight',
|
||||||
'${selectedStage.issuedQuantityWeight.toStringAsFixed(2)} kg',
|
'${stageToShow.issuedQuantityWeight.toStringAsFixed(2)} kg',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -265,21 +365,22 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
title: 'Stage Information',
|
title: 'Stage Information',
|
||||||
icon: Icons.info_outlined,
|
icon: Icons.info_outlined,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow('Product ID', '${selectedStage.productId}'),
|
_buildInfoRow('Product ID', '${stageToShow.productId}'),
|
||||||
if (selectedStage.productStageId != null)
|
if (stageToShow.productStageId != null)
|
||||||
_buildInfoRow('Stage ID', '${selectedStage.productStageId}'),
|
_buildInfoRow('Stage ID', '${stageToShow.productStageId}'),
|
||||||
if (selectedStage.actionTypeId != null)
|
if (stageToShow.actionTypeId != null)
|
||||||
_buildInfoRow('Action Type ID', '${selectedStage.actionTypeId}'),
|
_buildInfoRow('Action Type ID', '${stageToShow.actionTypeId}'),
|
||||||
_buildInfoRow('Stage Name', selectedStage.displayName),
|
_buildInfoRow('Stage Name', stageToShow.displayName),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Status indicators
|
// Status indicators
|
||||||
_buildStatusCards(selectedStage, theme),
|
_buildStatusCards(stageToShow, theme),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
}(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
|
||||||
import '../../../../core/di/providers.dart';
|
import '../../../../core/di/providers.dart';
|
||||||
import '../../../../core/router/app_router.dart';
|
import '../../../../core/router/app_router.dart';
|
||||||
@@ -41,6 +42,141 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
await ref.read(productsProvider.notifier).refreshProducts();
|
await ref.read(productsProvider.notifier).refreshProducts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showBarcodeScanner() {
|
||||||
|
final controller = MobileScannerController(
|
||||||
|
formats: const [BarcodeFormat.code128],
|
||||||
|
facing: CameraFacing.back,
|
||||||
|
);
|
||||||
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (context) => Container(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.7,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.black,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade900,
|
||||||
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
top: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.qr_code_scanner,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Scan Barcode',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, color: Colors.white),
|
||||||
|
onPressed: () {
|
||||||
|
controller.dispose();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Scanner
|
||||||
|
Expanded(
|
||||||
|
child: MobileScanner(
|
||||||
|
controller: controller,
|
||||||
|
onDetect: (capture) {
|
||||||
|
final List<Barcode> barcodes = capture.barcodes;
|
||||||
|
if (barcodes.isNotEmpty) {
|
||||||
|
final barcode = barcodes.first.rawValue;
|
||||||
|
if (barcode != null) {
|
||||||
|
controller.dispose();
|
||||||
|
Navigator.pop(context);
|
||||||
|
_handleScannedBarcode(barcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Instructions
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
color: Colors.grey.shade900,
|
||||||
|
child: const Text(
|
||||||
|
'Position the Code 128 barcode within the frame to scan',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).whenComplete(() => controller.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleScannedBarcode(String barcode) {
|
||||||
|
// Parse barcode to extract productId and optional stageId
|
||||||
|
// Format 1: "123" (only productId)
|
||||||
|
// Format 2: "123-456" (productId-stageId)
|
||||||
|
|
||||||
|
int? productId;
|
||||||
|
int? stageId;
|
||||||
|
|
||||||
|
if (barcode.contains('-')) {
|
||||||
|
// Format: productId-stageId
|
||||||
|
final parts = barcode.split('-');
|
||||||
|
if (parts.length == 2) {
|
||||||
|
productId = int.tryParse(parts[0]);
|
||||||
|
stageId = int.tryParse(parts[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Format: productId only
|
||||||
|
productId = int.tryParse(barcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productId == null) {
|
||||||
|
// Invalid barcode format
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Invalid barcode format: "$barcode"'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: 'OK',
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to product detail with productId and optional stageId
|
||||||
|
context.goToProductDetail(
|
||||||
|
warehouseId: widget.warehouseId,
|
||||||
|
productId: productId,
|
||||||
|
warehouseName: widget.warehouseName,
|
||||||
|
stageId: stageId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@@ -83,6 +219,13 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
products: products,
|
products: products,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
),
|
),
|
||||||
|
floatingActionButton: products.isNotEmpty
|
||||||
|
? FloatingActionButton(
|
||||||
|
onPressed: _showBarcodeScanner,
|
||||||
|
tooltip: 'Scan Barcode',
|
||||||
|
child: const Icon(Icons.qr_code_scanner),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user