feat(ui): add Android navigation shell, Web POS workstation, and bilingual Taglish/English localization
This commit is contained in:
1 parent
e8623577cd
commit
960b9a512e
10 files changed
+1308
-85
No files matched your search
Vendored
+8
-2
@@ -7,7 +7,10 @@
|
||||
"type": "dart",
|
||||
"deviceId": "all",
|
||||
"cwd": "${workspaceFolder}/frontend",
|
||||
"program": "lib/main.dart"
|
||||
"program": "lib/main.dart",
|
||||
"env": {
|
||||
"GRADLE_USER_HOME": "F:\\.gradle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TindaGrid: Android (ADB Device)",
|
||||
@@ -15,7 +18,10 @@
|
||||
"type": "dart",
|
||||
"deviceId": "android",
|
||||
"cwd": "${workspaceFolder}/frontend",
|
||||
"program": "lib/main.dart"
|
||||
"program": "lib/main.dart",
|
||||
"env": {
|
||||
"GRADLE_USER_HOME": "F:\\.gradle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TindaGrid: Chrome (Web PWA)",
|
||||
|
||||
Vendored
+5
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"options": {
|
||||
"env": {
|
||||
"GRADLE_USER_HOME": "F:\\.gradle"
|
||||
}
|
||||
},
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Flutter Run: All Devices",
|
||||
|
||||
@@ -4,3 +4,5 @@ android.useAndroidX=true
|
||||
android.newDsl=false
|
||||
# This builtInKotlin flag was added by the Flutter template
|
||||
android.builtInKotlin=false
|
||||
kotlin.incremental=false
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
enum AppLanguage {
|
||||
taglish,
|
||||
english,
|
||||
}
|
||||
|
||||
class AppStrings {
|
||||
final AppLanguage language;
|
||||
const AppStrings(this.language);
|
||||
|
||||
bool get isTaglish => language == AppLanguage.taglish;
|
||||
|
||||
// Global & Status
|
||||
String get appName => 'TindaGrid';
|
||||
String get offlineSafe => isTaglish ? 'Ligtas (Offline)' : 'Safe (Offline)';
|
||||
String get onlineSynced => isTaglish ? 'Naka-sync' : 'Cloud Synced';
|
||||
String get switchLang => isTaglish ? 'English' : 'Taglish';
|
||||
|
||||
// Navigation
|
||||
String get navPos => isTaglish ? 'POS' : 'POS';
|
||||
String get navInventory => isTaglish ? 'Paninda' : 'Inventory';
|
||||
String get navDebts => isTaglish ? 'Utang' : 'Debts';
|
||||
String get navGcash => isTaglish ? 'GCash' : 'GCash';
|
||||
String get navSettings => isTaglish ? 'Settings' : 'Settings';
|
||||
|
||||
// POS Screen
|
||||
String get searchPlaceholder =>
|
||||
isTaglish ? 'I-type ang paninda o barcode...' : 'Search product or barcode...';
|
||||
String get scanButton => 'SCAN';
|
||||
String get quickTingiTitle =>
|
||||
isTaglish ? 'MABILISANG TINGI (Walang Barcode — Pindutin Agad):' : 'QUICK TINGI (No Barcode — Quick Tap):';
|
||||
String get recentCartTitle =>
|
||||
isTaglish ? 'HULING NAIDAGDAG SA KART:' : 'RECENTLY ADDED TO CART:';
|
||||
String get cartLabel => isTaglish ? 'KART' : 'CART';
|
||||
String get itemsCountLabel => isTaglish ? 'Bagay' : 'Items';
|
||||
String get totalLabel => isTaglish ? 'KABUUAN' : 'TOTAL';
|
||||
String get checkoutButton => isTaglish ? 'BAYARAN / MANINGIL' : 'COLLECT / PAY';
|
||||
String get eachPrice => isTaglish ? 'bawat isa' : 'each';
|
||||
String get liveTicketTitle =>
|
||||
isTaglish ? 'KASALUKUYANG KART (Live Ticket)' : 'CURRENT CART (Live Ticket)';
|
||||
String get emptyCartPrompt =>
|
||||
isTaglish ? 'Walang laman ang kart. Pumili sa paninda.' : 'Cart is empty. Select products.';
|
||||
String get estProfit => isTaglish ? 'Tinatayang Tubo (Kita):' : 'Estimated Profit:';
|
||||
|
||||
// Checkout Modal
|
||||
String get checkoutModalTitle => isTaglish ? 'Bayaran at Sukli' : 'Payment & Change';
|
||||
String get totalPayable => isTaglish ? 'KABUUANG BABAYARAN:' : 'TOTAL AMOUNT DUE:';
|
||||
String get cashTendered =>
|
||||
isTaglish ? 'PERANG INABOT NG MAMIMILI:' : 'CASH RECEIVED FROM CUSTOMER:';
|
||||
String get exactBill => isTaglish ? 'Eksakto' : 'Exact';
|
||||
String get customerChange =>
|
||||
isTaglish ? '🟢 SUKLI SA MAMIMILI:' : '🟢 CHANGE DUE TO CUSTOMER:';
|
||||
String get returnToCustomer => isTaglish ? 'Ibalik sa Tao' : 'Return to Customer';
|
||||
String get insufficientCash =>
|
||||
isTaglish ? '⚠️ KULANG PA NG PERA:' : '⚠️ INSUFFICIENT CASH:';
|
||||
String get completeSale =>
|
||||
isTaglish ? 'TAPOS NA ANG BENTA' : 'CONFIRM & COMPLETE SALE';
|
||||
String get saleRecorded =>
|
||||
isTaglish ? '✅ Benta naitala! Ligtas sa lokal database.' : '✅ Sale recorded! Saved to local database.';
|
||||
|
||||
// Inventory Screen
|
||||
String get inventoryTitle => isTaglish ? 'Talaan ng mga Paninda' : 'Product Inventory';
|
||||
String get totalProducts => isTaglish ? 'Kabuuang Paninda' : 'Total Products';
|
||||
String get lowStockWarning => isTaglish ? 'Kulang sa Stock' : 'Low Stock Alert';
|
||||
String get addProduct => isTaglish ? 'Magdagdag ng Paninda' : 'Add Product';
|
||||
String get inStock => isTaglish ? 'piraso natitira' : 'items left';
|
||||
String get costPrice => isTaglish ? 'Puhunan' : 'Cost';
|
||||
String get sellingPrice => isTaglish ? 'Presyo' : 'Price';
|
||||
|
||||
// Debts Screen
|
||||
String get debtsTitle => isTaglish ? 'Kwaderno ng Pautang' : 'Customer Debt Ledger';
|
||||
String get totalCollectibles => isTaglish ? 'Kabuuang Sisingilin' : 'Total Collectibles';
|
||||
String get activeDebtors => isTaglish ? 'Mga May Utang' : 'Active Borrowers';
|
||||
String get recordNewDebt => isTaglish ? 'Maglista ng Bagong Utang' : 'Record New Debt';
|
||||
String get dueDate => isTaglish ? 'Petsa ng Bayaran' : 'Due Date';
|
||||
String get settleDebt => isTaglish ? 'Maningil / Magbayad' : 'Settle Payment';
|
||||
|
||||
// GCash Screen
|
||||
String get gcashTitle => isTaglish ? 'GCash Float & Transaksyon' : 'GCash Float & Ledger';
|
||||
String get ewalletFloat => isTaglish ? 'GCash E-Wallet Balance' : 'GCash E-Wallet Balance';
|
||||
String get cashBoxDrawer => isTaglish ? 'Pera sa Kahon (Cash Box)' : 'Cash Drawer (Physical Cash)';
|
||||
String get cashInAction => isTaglish ? 'Cash-In (Pasa Pera)' : 'Cash-In (Deposit/Send)';
|
||||
String get cashOutAction => isTaglish ? 'Cash-Out (Pera Palit)' : 'Cash-Out (Withdraw)';
|
||||
String get feeSchedule => isTaglish ? 'Talaan ng Patong (Fee Brackets)' : 'Fee Schedule';
|
||||
|
||||
// Settings Screen
|
||||
String get settingsTitle => isTaglish ? 'Pag-aayos ng Tindahan' : 'Store Settings';
|
||||
String get languageSection => isTaglish ? 'Piliin ang Wika' : 'Display Language';
|
||||
String get cloudBackupTitle => isTaglish ? 'Cloud Backup & Sync' : 'Cloud Backup & Sync';
|
||||
String get cloudBackupSubtitle => isTaglish
|
||||
? 'Awtomatikong backup sa Neon Cloud tuwing may internet'
|
||||
: 'Automatic backup to Neon Cloud when online';
|
||||
String get syncNowButton => isTaglish ? 'I-sync Ngayon' : 'Sync Now';
|
||||
String get securitySection => isTaglish ? 'Seguridad at PIN' : 'Security & Store PIN';
|
||||
String get storeProfile => isTaglish ? 'Detalye ng Tindahan' : 'Store Profile';
|
||||
String get aboutTindaGrid =>
|
||||
isTaglish ? 'Tungkol sa TindaGrid' : 'About TindaGrid';
|
||||
String get systemStatusSubtitle => isTaglish
|
||||
? '100% Offline-First | Walang Bayad Kada Buwan (\$0/mo)'
|
||||
: '100% Offline-First | \$0/mo Free Cloud Architecture';
|
||||
}
|
||||
|
||||
class LanguageNotifier extends Notifier<AppLanguage> {
|
||||
@override
|
||||
AppLanguage build() => AppLanguage.taglish;
|
||||
|
||||
void toggleLanguage() {
|
||||
state = state == AppLanguage.taglish ? AppLanguage.english : AppLanguage.taglish;
|
||||
}
|
||||
|
||||
void setLanguage(AppLanguage language) {
|
||||
state = language;
|
||||
}
|
||||
}
|
||||
|
||||
final languageProvider = NotifierProvider<LanguageNotifier, AppLanguage>(() {
|
||||
return LanguageNotifier();
|
||||
});
|
||||
|
||||
final stringsProvider = Provider<AppStrings>((ref) {
|
||||
final lang = ref.watch(languageProvider);
|
||||
return AppStrings(lang);
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/constants/app_colors.dart';
|
||||
import '../../core/localization/language_provider.dart';
|
||||
|
||||
class DebtsScreen extends ConsumerWidget {
|
||||
const DebtsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tr = ref.watch(stringsProvider);
|
||||
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||
|
||||
final sampleDebts = [
|
||||
{'name': 'Aling Marites (Kapitbahay)', 'phone': '0917-123-4567', 'amount': 385.00, 'date': 'Oct 18, 2026', 'items': 'Bear Brand, Bigas 2kg, Itlog'},
|
||||
{'name': 'Mang Boy Tricycle Driver', 'phone': '0928-888-9999', 'amount': 150.00, 'date': 'Oct 19, 2026', 'items': 'Marlboro Red, Cobra Energy'},
|
||||
{'name': 'Kuya Jun (Tubero)', 'phone': '0939-555-1234', 'amount': 520.00, 'date': 'Oct 15, 2026', 'items': 'San Miguel Pale, Yosi, Chicharon'},
|
||||
{'name': 'Ate Gina (Sari-Sari sukli)', 'phone': '0945-333-7777', 'amount': 45.00, 'date': 'Oct 20, 2026', 'items': 'Kopiko Blanca, SkyFlakes'},
|
||||
];
|
||||
|
||||
final double totalCollectibles = sampleDebts.fold(0.0, (sum, d) => sum + (d['amount'] as double));
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
tr.debtsTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: tr.recordNewDebt,
|
||||
icon: const Icon(Icons.person_add_alt_1_rounded, color: AppColors.primary, size: 26),
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? 'Magbubukas ang form para sa bagong pautang...' : 'Opening new debt recording form...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Total Collectibles Banner
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColors.warning, AppColors.warning.withAlpha(200)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.warning.withAlpha(80),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
tr.totalCollectibles.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.menu_book_rounded, color: Colors.white, size: 24),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'₱ ${totalCollectibles.toStringAsFixed(2)}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 34,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${sampleDebts.length} ${tr.activeDebtors}',
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Search bar
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search, color: AppColors.primary),
|
||||
hintText: tr.isTaglish ? 'Hanapin ang pangalan ng may utang...' : 'Search customer name...',
|
||||
filled: true,
|
||||
fillColor: isLight ? AppColors.surfaceLight : AppColors.surfaceDark,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Debt Customer Cards
|
||||
...sampleDebts.map((debt) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
debt['name'] as String,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
'₱ ${(debt['amount'] as double).toStringAsFixed(2)}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
color: AppColors.alert,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'📞 ${debt['phone']} • ${debt['date']}',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Paninda: ${debt['items']}',
|
||||
style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? 'Sisingilin si ${debt['name']}...' : 'Sending SMS reminder to ${debt['name']}...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.send_rounded, size: 16),
|
||||
label: Text(tr.isTaglish ? 'I-text Paalala' : 'Send Reminder'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? 'Naitalang bayad ang utang!' : 'Payment marked as settled!'),
|
||||
backgroundColor: AppColors.primary,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.check, size: 16),
|
||||
label: Text(tr.settleDebt),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/constants/app_colors.dart';
|
||||
import '../../core/localization/language_provider.dart';
|
||||
|
||||
class GcashScreen extends ConsumerWidget {
|
||||
const GcashScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tr = ref.watch(stringsProvider);
|
||||
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||
|
||||
final feeBrackets = [
|
||||
{'range': '₱ 1 - ₱ 500', 'fee': '₱ 10.00'},
|
||||
{'range': '₱ 501 - ₱ 1,000', 'fee': '₱ 15.00'},
|
||||
{'range': '₱ 1,001 - ₱ 1,500', 'fee': '₱ 20.00'},
|
||||
{'range': '₱ 1,501 - ₱ 2,000', 'fee': '₱ 25.00'},
|
||||
{'range': '₱ 2,001 - ₱ 3,000', 'fee': '₱ 35.00'},
|
||||
{'range': '₱ 3,001 - ₱ 5,000', 'fee': '₱ 50.00'},
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
tr.gcashTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Float balances (Dual Balances: GCash vs Cash Box)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildBalanceCard(
|
||||
context,
|
||||
title: tr.ewalletFloat,
|
||||
amount: '₱ 8,450.00',
|
||||
icon: Icons.account_balance_wallet_rounded,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildBalanceCard(
|
||||
context,
|
||||
title: tr.cashBoxDrawer,
|
||||
amount: '₱ 4,120.00',
|
||||
icon: Icons.payments_rounded,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Quick Action Buttons
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_showGcashActionModal(context, tr, isCashIn: true);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_upward_rounded),
|
||||
label: Text(tr.cashInAction),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade700,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_showGcashActionModal(context, tr, isCashIn: false);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_downward_rounded),
|
||||
label: Text(tr.cashOutAction),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Fee Scheduler Table Card
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
tr.feeSchedule,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
const Icon(Icons.receipt_long_rounded, color: Colors.blue, size: 22),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Divider(),
|
||||
Table(
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
tr.isTaglish ? 'Halaga ng Transaksyon' : 'Transaction Range',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
tr.isTaglish ? 'Patong / Tubo' : 'Fee / Profit',
|
||||
textAlign: TextAlign.end,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
...feeBrackets.map((f) {
|
||||
return TableRow(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
f['range']!,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
f['fee']!,
|
||||
textAlign: TextAlign.end,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showGcashActionModal(BuildContext context, AppStrings tr, {required bool isCashIn}) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (ctx) {
|
||||
final isLight = Theme.of(ctx).brightness == Brightness.light;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: isLight ? Colors.white : AppColors.surfaceDark,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
isCashIn ? tr.cashInAction : tr.cashOutAction,
|
||||
style: Theme.of(ctx).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
keyboardType: TextInputType.phone,
|
||||
decoration: InputDecoration(
|
||||
labelText: tr.isTaglish ? 'Numero ng GCash (09xx...)' : 'GCash Phone Number',
|
||||
prefixIcon: const Icon(Icons.phone_android_rounded),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: InputDecoration(
|
||||
labelText: tr.isTaglish ? 'Halaga (Halimbawa: 500)' : 'Amount (e.g. 500)',
|
||||
prefixIcon: const Icon(Icons.attach_money_rounded),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
tr.isTaglish ? 'Awtomatikong Patong (Fee):' : 'Calculated Convenience Fee:',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, color: AppColors.primary),
|
||||
),
|
||||
const Text(
|
||||
'₱ 10.00',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: AppColors.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? '✅ GCash transaksyon naitala!' : '✅ GCash transaction recorded!'),
|
||||
backgroundColor: AppColors.primary,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(tr.isTaglish ? 'I-REHISTRO ANG TRANSAKSYON' : 'RECORD TRANSACTION'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBalanceCard(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String amount,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isLight ? Colors.white : AppColors.surfaceDark,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: color.withAlpha(60)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: color.withAlpha(30),
|
||||
radius: 18,
|
||||
child: Icon(icon, color: color, size: 20),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 11, color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
amount,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: color),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/constants/app_colors.dart';
|
||||
import '../../core/localization/language_provider.dart';
|
||||
|
||||
class InventoryScreen extends ConsumerWidget {
|
||||
const InventoryScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tr = ref.watch(stringsProvider);
|
||||
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||
|
||||
// Sample inventory items
|
||||
final sampleProducts = [
|
||||
{'name': 'Coca-Cola Mismo 300ml', 'barcode': '4800016644021', 'stock': 24, 'cost': 15.0, 'price': 20.0},
|
||||
{'name': 'SkyFlakes Crackers Single', 'barcode': '4800016644038', 'stock': 48, 'cost': 6.5, 'price': 9.0},
|
||||
{'name': 'Kopiko Blanca 30g Sachet', 'barcode': '8996001414002', 'stock': 32, 'cost': 10.5, 'price': 14.0},
|
||||
{'name': 'Bear Brand Fortified 33g', 'barcode': '4800361280011', 'stock': 4, 'cost': 12.0, 'price': 16.0},
|
||||
{'name': 'Lucky Me! Pancit Canton Kalamansi', 'barcode': '4800361300023', 'stock': 18, 'cost': 13.0, 'price': 17.0},
|
||||
{'name': 'Safeguard Pure White 60g', 'barcode': '4902430730030', 'stock': 6, 'cost': 22.0, 'price': 28.0},
|
||||
{'name': 'Silver Swan Sukang Puti 200ml', 'barcode': '4800123000045', 'stock': 12, 'cost': 9.0, 'price': 12.0},
|
||||
{'name': 'Datu Puti Toyo 200ml', 'barcode': '4800123000052', 'stock': 15, 'cost': 10.0, 'price': 13.0},
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
tr.inventoryTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: tr.addProduct,
|
||||
icon: const Icon(Icons.add_circle, color: AppColors.primary, size: 28),
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? 'Bubuksan ang Barcode Scanner para mag-add...' : 'Opening Barcode Scanner to add item...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Inventory Summary Cards
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
context,
|
||||
title: tr.totalProducts,
|
||||
value: '${sampleProducts.length}',
|
||||
icon: Icons.inventory_2_rounded,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
context,
|
||||
title: tr.lowStockWarning,
|
||||
value: '2',
|
||||
icon: Icons.warning_amber_rounded,
|
||||
color: AppColors.alert,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Search bar
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search, color: AppColors.primary),
|
||||
hintText: tr.searchPlaceholder,
|
||||
filled: true,
|
||||
fillColor: isLight ? AppColors.surfaceLight : AppColors.surfaceDark,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Product List
|
||||
...sampleProducts.map((p) {
|
||||
final int stock = p['stock'] as int;
|
||||
final isLow = stock <= 6;
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: isLow ? AppColors.alertLight : AppColors.primaryLight,
|
||||
child: Icon(
|
||||
Icons.shopping_bag_outlined,
|
||||
color: isLow ? AppColors.alert : AppColors.primary,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
p['name'] as String,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
'${p['barcode']} • ${tr.costPrice}: ₱${(p['cost'] as double).toStringAsFixed(2)}',
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
trailing: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'₱${(p['price'] as double).toStringAsFixed(2)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: isLow ? AppColors.alertLight : Colors.grey.withAlpha(40),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'$stock ${tr.inStock}',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isLow ? AppColors.alert : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryCard(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String value,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isLight ? Colors.white : AppColors.surfaceDark,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: color.withAlpha(60)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: color.withAlpha(30),
|
||||
radius: 20,
|
||||
child: Icon(icon, color: color, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 11, color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: color),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/constants/app_colors.dart';
|
||||
import '../../core/localization/language_provider.dart';
|
||||
|
||||
class SettingsScreen extends ConsumerWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tr = ref.watch(stringsProvider);
|
||||
final currentLang = ref.watch(languageProvider);
|
||||
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
tr.settingsTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Language Switcher Section (Taglish / English)
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.language_rounded, color: AppColors.primary, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
tr.languageSection,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: currentLang == AppLanguage.taglish
|
||||
? AppColors.primary
|
||||
: Colors.transparent,
|
||||
foregroundColor: currentLang == AppLanguage.taglish
|
||||
? Colors.white
|
||||
: (isLight ? Colors.black87 : Colors.white70),
|
||||
side: BorderSide(
|
||||
color: currentLang == AppLanguage.taglish
|
||||
? AppColors.primary
|
||||
: Colors.grey.withAlpha(80),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () {
|
||||
ref.read(languageProvider.notifier).setLanguage(AppLanguage.taglish);
|
||||
},
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('🇵🇭 ', style: TextStyle(fontSize: 16)),
|
||||
Text('Taglish', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: currentLang == AppLanguage.english
|
||||
? AppColors.primary
|
||||
: Colors.transparent,
|
||||
foregroundColor: currentLang == AppLanguage.english
|
||||
? Colors.white
|
||||
: (isLight ? Colors.black87 : Colors.white70),
|
||||
side: BorderSide(
|
||||
color: currentLang == AppLanguage.english
|
||||
? AppColors.primary
|
||||
: Colors.grey.withAlpha(80),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () {
|
||||
ref.read(languageProvider.notifier).setLanguage(AppLanguage.english);
|
||||
},
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('🇺🇸 ', style: TextStyle(fontSize: 16)),
|
||||
Text('English', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Cloud Sync Card
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.cloud_sync_rounded, color: AppColors.primary, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
tr.cloudBackupTitle,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
tr.cloudBackupSubtitle,
|
||||
style: const TextStyle(fontSize: 13, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? '☁️ Matagumpay na nai-sync sa Neon Cloud!' : '☁️ Successfully synced to Neon Cloud!'),
|
||||
backgroundColor: AppColors.primary,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.sync_rounded),
|
||||
label: Text(tr.syncNowButton),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Security & PIN Card
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: AppColors.primaryLight,
|
||||
child: Icon(Icons.lock_outline_rounded, color: AppColors.primary),
|
||||
),
|
||||
title: Text(
|
||||
tr.securitySection,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(
|
||||
tr.isTaglish ? '4-6 digit Store PIN & Fingerprint' : '4-6 digit Store PIN & Biometrics',
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? 'Ligtas ang iyong PIN gamit ang Flutter Secure Storage' : 'PIN safely protected by Flutter Secure Storage'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// System Status & Architecture Card
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.info_outline_rounded, color: Colors.grey, size: 22),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
tr.aboutTindaGrid,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
tr.systemStatusSubtitle,
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'SQLite Drift (Local) + Neon PostgreSQL (Cloud) + Cloudflare R2',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+193
-80
@@ -1,9 +1,15 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'core/constants/app_colors.dart';
|
||||
import 'core/localization/language_provider.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
import 'core/theme/responsive_layout.dart';
|
||||
import 'core/utils/web_storage_helper.dart';
|
||||
import 'features/debts/debts_screen.dart';
|
||||
import 'features/gcash/gcash_screen.dart';
|
||||
import 'features/inventory/inventory_screen.dart';
|
||||
import 'features/settings/settings_screen.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -29,17 +35,88 @@ class TindaGridApp extends StatelessWidget {
|
||||
theme: AppTheme.lightTheme,
|
||||
darkTheme: AppTheme.darkTheme,
|
||||
themeMode: ThemeMode.system,
|
||||
home: const PosDashboardScreen(),
|
||||
// Web is strictly POS counter terminal; Android/Mobile includes full navigation
|
||||
home: kIsWeb ? const PosDashboardScreen() : const MainNavigationShell(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POS Demo Dashboard (All-Ages Touch-Driven Interface)
|
||||
// Main Navigation Shell for Android / Mobile
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class MainNavigationShell extends ConsumerStatefulWidget {
|
||||
const MainNavigationShell({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<MainNavigationShell> createState() => _MainNavigationShellState();
|
||||
}
|
||||
|
||||
class _MainNavigationShellState extends ConsumerState<MainNavigationShell> {
|
||||
int _currentIndex = 0;
|
||||
|
||||
final List<Widget> _screens = const [
|
||||
PosDashboardScreen(isEmbeddedInNav: true),
|
||||
InventoryScreen(),
|
||||
DebtsScreen(),
|
||||
GcashScreen(),
|
||||
SettingsScreen(),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tr = ref.watch(stringsProvider);
|
||||
|
||||
return Scaffold(
|
||||
body: _screens[_currentIndex],
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: _currentIndex,
|
||||
onDestinationSelected: (index) {
|
||||
setState(() => _currentIndex = index);
|
||||
},
|
||||
destinations: [
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.point_of_sale_outlined),
|
||||
selectedIcon: const Icon(Icons.point_of_sale_rounded, color: AppColors.primary),
|
||||
label: tr.navPos,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.inventory_2_outlined),
|
||||
selectedIcon: const Icon(Icons.inventory_2_rounded, color: AppColors.primary),
|
||||
label: tr.navInventory,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.menu_book_outlined),
|
||||
selectedIcon: const Icon(Icons.menu_book_rounded, color: AppColors.primary),
|
||||
label: tr.navDebts,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.payments_outlined),
|
||||
selectedIcon: const Icon(Icons.payments_rounded, color: AppColors.primary),
|
||||
label: tr.navGcash,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
selectedIcon: const Icon(Icons.settings_rounded, color: AppColors.primary),
|
||||
label: tr.navSettings,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POS Dashboard Screen (All-Ages Touch-Driven Counter Interface)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class PosDashboardScreen extends ConsumerStatefulWidget {
|
||||
const PosDashboardScreen({super.key});
|
||||
final bool isEmbeddedInNav;
|
||||
|
||||
const PosDashboardScreen({
|
||||
super.key,
|
||||
this.isEmbeddedInNav = false,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<PosDashboardScreen> createState() => _PosDashboardScreenState();
|
||||
@@ -69,7 +146,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
void _showCheckoutDialog() {
|
||||
void _showCheckoutDialog(AppStrings tr) {
|
||||
double tendered = 100.0;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -96,7 +173,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Bayaran at Sukli',
|
||||
tr.checkoutModalTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
IconButton(
|
||||
@@ -107,7 +184,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'KABUUANG BABAYARAN:',
|
||||
tr.totalPayable,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -120,7 +197,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'PERANG INABOT NG MAMIMILI:',
|
||||
tr.cashTendered,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -130,7 +207,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildQuickBillChip('Eksakto', _totalPrice, tendered, (val) {
|
||||
_buildQuickBillChip(tr.exactBill, _totalPrice, tendered, (val) {
|
||||
setModalState(() => tendered = val);
|
||||
}),
|
||||
_buildQuickBillChip('₱50', 50.0, tendered, (val) {
|
||||
@@ -169,8 +246,8 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
children: [
|
||||
Text(
|
||||
sukli >= 0
|
||||
? '🟢 SUKLI SA MAMIMILI:'
|
||||
: '⚠️ KULANG PA NG PERA:',
|
||||
? tr.customerChange
|
||||
: tr.insufficientCash,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -201,13 +278,13 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
setState(() => _cart.clear());
|
||||
Navigator.pop(ctx);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('✅ Benta naitala! Ligtas sa lokal database.'),
|
||||
SnackBar(
|
||||
content: Text(tr.saleRecorded),
|
||||
backgroundColor: AppColors.primary,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('TAPOS NA ANG BENTA'),
|
||||
child: Text(tr.completeSale),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -239,38 +316,67 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tr = ref.watch(stringsProvider);
|
||||
final currentLang = ref.watch(languageProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(Icons.storefront, color: AppColors.primary, size: 28),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'TindaGrid',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
titleSpacing: 0,
|
||||
leading: const Icon(Icons.storefront, color: AppColors.primary, size: 24),
|
||||
title: Text(
|
||||
tr.appName,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
),
|
||||
actions: [
|
||||
// 1-Tap Language Switcher Button
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () {
|
||||
ref.read(languageProvider.notifier).toggleLanguage();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(currentLang == AppLanguage.taglish ? '🇵🇭' : '🇺🇸', style: const TextStyle(fontSize: 12)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
currentLang == AppLanguage.taglish ? 'Taglish' : 'English',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
|
||||
// Offline Status Badge
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryLight,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.check_circle, color: AppColors.primary, size: 16),
|
||||
SizedBox(width: 4),
|
||||
const Icon(Icons.check_circle, color: AppColors.primary, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Ligtas (Offline)',
|
||||
style: TextStyle(
|
||||
tr.offlineSafe,
|
||||
style: const TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -280,36 +386,36 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
],
|
||||
),
|
||||
body: ResponsiveLayout(
|
||||
mobile: _buildMobileLayout(),
|
||||
desktop: _buildDesktopLayout(),
|
||||
mobile: _buildMobileLayout(tr),
|
||||
desktop: _buildDesktopLayout(tr),
|
||||
),
|
||||
bottomNavigationBar: ResponsiveLayout.isDesktop(context)
|
||||
? null
|
||||
: _buildMobileCartBottomBar(),
|
||||
: _buildMobileCartBottomBar(tr),
|
||||
);
|
||||
}
|
||||
|
||||
// Mobile View
|
||||
Widget _buildMobileLayout() {
|
||||
Widget _buildMobileLayout(AppStrings tr) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildSearchBar(),
|
||||
_buildSearchBar(tr),
|
||||
const SizedBox(height: 20),
|
||||
_buildTingiGridHeader(),
|
||||
_buildTingiGridHeader(tr),
|
||||
const SizedBox(height: 12),
|
||||
_buildTingiTiles(),
|
||||
const SizedBox(height: 24),
|
||||
_buildRecentCartList(),
|
||||
_buildRecentCartList(tr),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Desktop View (Split-Screen 2-Column POS Station)
|
||||
Widget _buildDesktopLayout() {
|
||||
Widget _buildDesktopLayout(AppStrings tr) {
|
||||
return Row(
|
||||
children: [
|
||||
// Left Column (Catalog & Fast Tingi Grid)
|
||||
@@ -320,9 +426,9 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildSearchBar(),
|
||||
_buildSearchBar(tr),
|
||||
const SizedBox(height: 20),
|
||||
_buildTingiGridHeader(),
|
||||
_buildTingiGridHeader(tr),
|
||||
const SizedBox(height: 12),
|
||||
_buildTingiTiles(),
|
||||
],
|
||||
@@ -331,7 +437,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
),
|
||||
// Right Column (Sticky Live Ticket & Checkout Panel)
|
||||
Container(
|
||||
width: 320,
|
||||
width: 340,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
border: const Border(
|
||||
@@ -347,22 +453,22 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'🛒 Kasalukuyang Kart',
|
||||
'🛒 ${tr.liveTicketTitle}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text('$_totalItems item',
|
||||
Text('$_totalItems ${tr.itemsCountLabel}',
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
),
|
||||
const Divider(height: 24),
|
||||
Expanded(child: _buildCartItemsList()),
|
||||
Expanded(child: _buildCartItemsList(tr)),
|
||||
const Divider(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('KABUUAN:', style: Theme.of(context).textTheme.titleMedium),
|
||||
Text('${tr.totalLabel}:', style: Theme.of(context).textTheme.titleMedium),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'₱ ${_totalPrice.toStringAsFixed(2)}',
|
||||
@@ -377,9 +483,9 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _cart.isEmpty ? null : _showCheckoutDialog,
|
||||
onPressed: _cart.isEmpty ? null : () => _showCheckoutDialog(tr),
|
||||
icon: const Icon(Icons.point_of_sale, size: 24),
|
||||
label: const Text('BAYARAN / MANINGIL'),
|
||||
label: Text(tr.checkoutButton),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -388,16 +494,18 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchBar() {
|
||||
Widget _buildSearchBar(AppStrings tr) {
|
||||
return TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'I-type ang paninda o i-scan ang barcode...',
|
||||
hintText: tr.searchPlaceholder,
|
||||
prefixIcon: const Icon(Icons.search, size: 24),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.qr_code_scanner, color: AppColors.primary),
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('📷 Binubuksan ang scanner...')),
|
||||
SnackBar(
|
||||
content: Text(tr.isTaglish ? '📷 Binubuksan ang scanner...' : '📷 Opening camera scanner...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -405,14 +513,14 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTingiGridHeader() {
|
||||
Widget _buildTingiGridHeader(AppStrings tr) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Mabilisan Tingi Grid',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
tr.quickTingiTitle,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontSize: 13, fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
@@ -485,45 +593,60 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecentCartList() {
|
||||
Widget _buildRecentCartList(AppStrings tr) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Mga Bagay sa Kart',
|
||||
tr.recentCartTitle,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildCartItemsList(),
|
||||
_buildCartItemsList(tr),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCartItemsList() {
|
||||
Widget _buildCartItemsList([AppStrings? tr]) {
|
||||
if (_cart.isEmpty) {
|
||||
return const Center(
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(32),
|
||||
child: Text('Walang laman ang kart. Pindutin ang paninda sa itaas.'),
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: Text(
|
||||
tr?.emptyCartPrompt ?? 'Walang laman ang kart.',
|
||||
style: const TextStyle(color: Colors.grey, fontStyle: FontStyle.italic),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: _cart.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final item = _cart[index];
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
title: Text(item.name, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: Text('₱ ${item.price.toStringAsFixed(2)} bawat isa'),
|
||||
subtitle: Text(
|
||||
'${item.quantity}x @ ₱${item.price.toStringAsFixed(2)} ${tr?.eachPrice ?? ''}',
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'₱ ${(item.price * item.quantity).toStringAsFixed(2)}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove_circle_outline, size: 20),
|
||||
icon: const Icon(Icons.remove_circle_outline, color: AppColors.alert, size: 20),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (item.quantity > 1) {
|
||||
@@ -534,16 +657,6 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
'${item.quantity}',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add_circle_outline, size: 20),
|
||||
onPressed: () {
|
||||
setState(() => item.quantity++);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -552,7 +665,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMobileCartBottomBar() {
|
||||
Widget _buildMobileCartBottomBar(AppStrings tr) {
|
||||
if (_cart.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
@@ -576,7 +689,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$_totalItems bagay sa kart',
|
||||
'$_totalItems ${tr.itemsCountLabel} sa ${tr.cartLabel.toLowerCase()}',
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
@@ -593,8 +706,8 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: _showCheckoutDialog,
|
||||
child: const Text('MANINGIL'),
|
||||
onPressed: () => _showCheckoutDialog(tr),
|
||||
child: Text(tr.checkoutButton),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,18 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:tindagrid/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('TindaGrid app smoke test', (WidgetTester tester) async {
|
||||
testWidgets('TindaGrid navigation and bilingual test', (WidgetTester tester) async {
|
||||
// Provide a standard phone screen size
|
||||
tester.view.physicalSize = const Size(1080, 2400);
|
||||
tester.view.devicePixelRatio = 2.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: TindaGridApp(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify TindaGrid brand name is present
|
||||
// 1. Verify Brand name and default Taglish state on POS
|
||||
expect(find.text('TindaGrid'), findsOneWidget);
|
||||
// Verify offline safe badge is present
|
||||
expect(find.text('Ligtas (Offline)'), findsOneWidget);
|
||||
|
||||
// 2. Verify Android Navigation Bar destinations are present in Taglish
|
||||
expect(find.text('POS'), findsOneWidget);
|
||||
expect(find.text('Paninda'), findsOneWidget);
|
||||
expect(find.text('Utang'), findsOneWidget);
|
||||
expect(find.text('GCash'), findsOneWidget);
|
||||
expect(find.text('Settings'), findsOneWidget);
|
||||
|
||||
// 3. Test 1-Tap Language Toggle in AppBar (Taglish -> English)
|
||||
final langToggle = find.text('Taglish');
|
||||
expect(langToggle, findsOneWidget);
|
||||
await tester.tap(langToggle);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify UI switched to English
|
||||
expect(find.text('Safe (Offline)'), findsOneWidget);
|
||||
expect(find.text('English'), findsOneWidget);
|
||||
expect(find.text('Inventory'), findsOneWidget);
|
||||
expect(find.text('Debts'), findsOneWidget);
|
||||
|
||||
// 4. Test Navigation Bar Switch to Settings Screen
|
||||
await tester.tap(find.text('Settings'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Settings screen should be visible with English labels
|
||||
expect(find.text('Store Settings'), findsOneWidget);
|
||||
expect(find.text('Display Language'), findsOneWidget);
|
||||
expect(find.text('Cloud Backup & Sync'), findsOneWidget);
|
||||
|
||||
// 5. Test Switching back to Taglish via Settings button
|
||||
await tester.tap(find.text('Taglish'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Settings screen should update to Taglish labels
|
||||
expect(find.text('Pag-aayos ng Tindahan'), findsOneWidget);
|
||||
expect(find.text('Piliin ang Wika'), findsOneWidget);
|
||||
expect(find.text('Paninda'), findsOneWidget);
|
||||
expect(find.text('Utang'), findsOneWidget);
|
||||
|
||||
// 6. Navigate back to POS
|
||||
await tester.tap(find.text('POS'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Ligtas (Offline)'), findsOneWidget);
|
||||
expect(find.text('MABILISANG TINGI (Walang Barcode — Pindutin Agad):'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user