Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Manager/lib/db/database_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class DatabaseHelper {

return await openDatabase(
path,
version: 4, // Incremented version
version: 5, // Incremented version for search fields
onCreate: _createDB,
onUpgrade: _upgradeDB,
);
Expand All @@ -46,7 +46,9 @@ class DatabaseHelper {
last_updated INTEGER NOT NULL,
brand TEXT,
size TEXT,
type TEXT
type TEXT,
pinyin TEXT,
initials TEXT
)
''');

Expand Down Expand Up @@ -79,6 +81,10 @@ class DatabaseHelper {
if (oldVersion < 4) {
await db.execute('ALTER TABLE kiosks ADD COLUMN device_id TEXT');
}
if (oldVersion < 5) {
await db.execute('ALTER TABLE products ADD COLUMN pinyin TEXT');
await db.execute('ALTER TABLE products ADD COLUMN initials TEXT');
}
}

// Product Methods
Expand Down
1 change: 1 addition & 0 deletions Manager/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"settingsSaved": "Settings saved",
"productApiUrlLabel": "Product API URL",
"productApiUrlHint": "https://barcode100.market.alicloudapi.com/getBarcode?Code=",
"searchProducts": "Search Products",
"cancel": "Cancel",
"remove": "Remove",
"connectKioskToEdit": "Connect to a kiosk to add or edit products.",
Expand Down
6 changes: 6 additions & 0 deletions Manager/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,12 @@ abstract class AppLocalizations {
/// **'https://barcode100.market.alicloudapi.com/getBarcode?Code='**
String get productApiUrlHint;

/// No description provided for @searchProducts.
///
/// In en, this message translates to:
/// **'Search Products'**
String get searchProducts;

/// No description provided for @cancel.
///
/// In en, this message translates to:
Expand Down
3 changes: 3 additions & 0 deletions Manager/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ class AppLocalizationsEn extends AppLocalizations {
String get productApiUrlHint =>
'https://barcode100.market.alicloudapi.com/getBarcode?Code=';

@override
String get searchProducts => 'Search Products';

@override
String get cancel => 'Cancel';

Expand Down
3 changes: 3 additions & 0 deletions Manager/lib/l10n/app_localizations_zh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ class AppLocalizationsZh extends AppLocalizations {
String get productApiUrlHint =>
'https://barcode100.market.alicloudapi.com/getBarcode?Code=';

@override
String get searchProducts => '搜索商品';

@override
String get cancel => '取消';

Expand Down
1 change: 1 addition & 0 deletions Manager/lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"settingsSaved": "设置已保存",
"productApiUrlLabel": "商品API地址",
"productApiUrlHint": "https://barcode100.market.alicloudapi.com/getBarcode?Code=",
"searchProducts": "搜索商品",
"cancel": "取消",
"remove": "移除",
"connectKioskToEdit": "请先连接自助终端后再添加或编辑商品。",
Expand Down
10 changes: 10 additions & 0 deletions Manager/lib/models/product.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class Product {
final String? size;
@HiveField(6)
final String? type;
@HiveField(7)
final String? pinyin;
@HiveField(8)
final String? initials;

Product({
required this.barcode,
Expand All @@ -27,6 +31,8 @@ class Product {
this.brand,
this.size,
this.type,
this.pinyin,
this.initials,
});

factory Product.fromJson(Map<String, dynamic> json) {
Expand All @@ -38,6 +44,8 @@ class Product {
brand: json['brand'],
size: json['size'],
type: json['type'],
pinyin: json['pinyin'],
initials: json['initials'],
);
}

Expand All @@ -50,6 +58,8 @@ class Product {
'brand': brand,
'size': size,
'type': type,
'pinyin': pinyin,
'initials': initials,
};
}
}
20 changes: 18 additions & 2 deletions Manager/lib/screens/product_form_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import 'package:manager/services/api_service.dart';
import 'package:manager/db/database_helper.dart';
import 'package:manager/services/kiosk_connection_service.dart';
import 'package:manager/services/kiosk_client/kiosk_client.dart';
import 'package:manager/l10n/app_localizations.dart'; // Re-added
import 'package:manager/l10n/app_localizations.dart';
import 'package:lpinyin/lpinyin.dart'; // Proper import placement

class ProductFormScreen extends StatefulWidget {
final String? initialBarcode;
Expand Down Expand Up @@ -178,11 +179,26 @@ class _ProductFormScreenState extends State<ProductFormScreen> {
? _barcodeController.text
: await DatabaseHelper.instance.getNextNoBarcodeId())
: _barcodeController.text.trim();

final name = _nameController.text;
// Generate Pinyin and Initials
String? pinyin;
String? initials;
if (name.isNotEmpty) {
// PinyinHelper.getPinyin returns full pinyin with separator
// e.g. "ke kou ke le"
pinyin = PinyinHelper.getPinyin(name, separator: ' ', format: PinyinFormat.WITHOUT_TONE).toLowerCase();
// Initials: "kkkl"
initials = PinyinHelper.getShortPinyin(name).toLowerCase();
}

final product = Product(
barcode: barcode,
name: _nameController.text,
name: name,
price: double.parse(_priceController.text.trim()),
lastUpdated: DateTime.now().millisecondsSinceEpoch,
pinyin: pinyin,
initials: initials,
);

try {
Expand Down
110 changes: 79 additions & 31 deletions Manager/lib/screens/product_list_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,48 @@ class ProductListScreen extends StatefulWidget {

class _ProductListScreenState extends State<ProductListScreen> {
List<Product> _products = [];
bool _isLoading = true;
bool _isLoading = false;
final KioskConnectionService _connectionService = KioskConnectionService();
final KioskClientService _kioskService = KioskClientService();
final TextEditingController _searchController = TextEditingController();
List<Product> _filteredProducts = [];

@override
void initState() {
super.initState();
_connectionService.addListener(_onConnectionChange);
_searchController.addListener(_onSearchChanged);
_loadProducts();
}

@override
void dispose() {
_connectionService.removeListener(_onConnectionChange);
_searchController.dispose();
super.dispose();
}

void _onSearchChanged() {
final query = _searchController.text.trim().toLowerCase();
if (query.isEmpty) {
setState(() => _filteredProducts = _products);
return;
}
setState(() {
_filteredProducts = _products.where((p) {
final name = p.name.toLowerCase();
final barcode = p.barcode.toLowerCase();
final pinyin = p.pinyin?.toLowerCase() ?? '';
final initials = p.initials?.toLowerCase() ?? '';

return name.contains(query) ||
barcode.contains(query) ||
pinyin.contains(query) ||
initials.contains(query);
}).toList();
});
}

void _onConnectionChange() {
if (mounted) setState(() {});
}
Expand All @@ -45,8 +70,13 @@ class _ProductListScreenState extends State<ProductListScreen> {
if (!mounted) return;
setState(() {
_products = products;
_filteredProducts = products;
_isLoading = false;
});
// Re-apply search if exists
if (_searchController.text.isNotEmpty) {
_onSearchChanged();
}
} catch (_) {
if (!mounted) return;
setState(() => _isLoading = false);
Expand Down Expand Up @@ -147,36 +177,51 @@ class _ProductListScreenState extends State<ProductListScreen> {
appBar: AppBar(title: Text(l10n.addProduct)), // Reuse "Add Product" label or "Products"
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _products.isEmpty
? Center(child: Text(l10n.noProductsFound))
: ListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
final tile = ListTile(
key: ValueKey(product.barcode),
title: Text(product.name),
subtitle: Text(product.barcode),
trailing: Text(currencyFormat.format(product.price)),
onTap: isConnected ? () => _navigateToAddEdit(barcode: product.barcode) : null,
enabled: isConnected,
);

if (!isConnected) {
return tile;
}

return Dismissible(
key: ValueKey('dismiss_${product.barcode}'),
direction: DismissDirection.endToStart,
confirmDismiss: (_) => _confirmDelete(product),
onDismissed: (_) {
setState(() {
_products.removeAt(index);
});
_deleteProduct(product);
},
background: const SizedBox.shrink(),
: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
labelText: l10n.searchProducts,
prefixIcon: const Icon(Icons.search),
border: const OutlineInputBorder(),
),
),
),
Expanded(
child: _filteredProducts.isEmpty
? Center(child: Text(l10n.noProductsFound))
: ListView.builder(
itemCount: _filteredProducts.length,
itemBuilder: (context, index) {
final product = _filteredProducts[index];
final tile = ListTile(
key: ValueKey(product.barcode),
title: Text(product.name),
subtitle: Text(product.barcode),
trailing: Text(currencyFormat.format(product.price)),
onTap: isConnected ? () => _navigateToAddEdit(barcode: product.barcode) : null,
enabled: isConnected,
);

if (!isConnected) {
return tile;
}

return Dismissible(
key: ValueKey('dismiss_${product.barcode}'),
direction: DismissDirection.endToStart,
confirmDismiss: (_) => _confirmDelete(product),
onDismissed: (_) {
setState(() {
_products.remove(product); // Remove from main list
_filteredProducts.removeAt(index);
});
_deleteProduct(product);
},
background: const SizedBox.shrink(),
secondaryBackground: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(horizontal: 20),
Expand All @@ -190,6 +235,9 @@ class _ProductListScreenState extends State<ProductListScreen> {
);
},
),
),
],
),
floatingActionButton: isConnected
? FloatingActionButton(
onPressed: () => _navigateToAddEdit(),
Expand Down
8 changes: 8 additions & 0 deletions Manager/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
lpinyin:
dependency: "direct main"
description:
name: lpinyin
sha256: "0bb843363f1f65170efd09fbdfc760c7ec34fc6354f9fcb2f89e74866a0d814a"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
matcher:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions Manager/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies:
http_server: ^1.0.0
image_picker: ^1.2.1
intl: ^0.20.2
lpinyin: ^2.0.3
mobile_scanner: ^7.1.4
network_info_plus: ^7.0.0
path: ^1.9.1
Expand Down