Fixed web loading screen and setup related issues

This commit is contained in:
2026-09-01 07:38:45 +05:30
parent a0e7a898fa
commit bfe0c1efbd
14 changed files with 268 additions and 131 deletions

View File

@@ -1,4 +1,7 @@
flutter_icons:
android: true
ios: true
web:
generate: true
image_path: "assets/icon.png"
image_path: "assets/icon.png"

View File

@@ -26,6 +26,8 @@ class DesktopSidebar extends ConsumerWidget {
final isDark = theme.brightness == Brightness.dark;
final isBusinessMode = ref.watch(businessModeProvider);
final businessProfile = ref.watch(businessProfileProvider).asData?.value;
final nature = (businessProfile?.natureOfBusiness ?? businessProfile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
final ratesAsync = ref.watch(commodityRatesProvider);
return Container(
@@ -51,22 +53,23 @@ class DesktopSidebar extends ConsumerWidget {
width: 40,
height: 40,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFD4AF37), Color(0xFFAA771C)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: const Color(0xFFD4AF37).withValues(alpha: 0.3),
blurRadius: 10,
offset: const Offset(0, 4),
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: const Center(
child: Icon(LucideIcons.gem, color: Colors.white, size: 22),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/icon.png',
width: 40,
height: 40,
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
@@ -276,8 +279,9 @@ class DesktopSidebar extends ConsumerWidget {
),
),
// 4. Live Metal Rate Widget in Sidebar
ratesAsync.when(
// 4. Live Metal Rate Widget in Sidebar (Jewellery only)
if (isJewellery)
ratesAsync.when(
data: (rates) {
if (rates.isEmpty) return const SizedBox.shrink();
final goldRate = rates.firstWhere(

View File

@@ -131,19 +131,28 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
),
child: Center(
child: Image.asset(
'assets/logo.png',
width: 80,
height: 80,
fit: BoxFit.contain,
Center(
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 16,
offset: const Offset(0, 4),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.asset(
'assets/icon.png',
width: 80,
height: 80,
fit: BoxFit.cover,
),
),
),
),

View File

@@ -12,8 +12,7 @@ class SetupWizardScreen extends StatefulWidget {
State<SetupWizardScreen> createState() => _SetupWizardScreenState();
}
class _SetupWizardScreenState extends State<SetupWizardScreen>
with SingleTickerProviderStateMixin {
class _SetupWizardScreenState extends State<SetupWizardScreen> {
bool _isLoading = false;
int _currentStep = 0;
String _accountType = 'INDIVIDUAL';
@@ -40,6 +39,19 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
int get _totalSteps => _accountType == 'BUSINESS' ? 4 : 3;
@override
void dispose() {
_nameController.dispose();
_usernameController.dispose();
_businessNameController.dispose();
_addressController.dispose();
_emailController.dispose();
_gstController.dispose();
_panController.dispose();
_msmeController.dispose();
super.dispose();
}
Future<void> _checkUsername(String username) async {
if (username.length < 3) return;
setState(() => _checkingUsername = true);
@@ -111,6 +123,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
}
void _nextStep() {
FocusScope.of(context).unfocus();
if (_currentStep == 1) {
if (_nameController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -150,6 +163,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
}
void _prevStep() {
FocusScope.of(context).unfocus();
if (_currentStep > 0) {
setState(() => _currentStep--);
}
@@ -209,7 +223,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep0AccountType() {
return Column(
key: const ValueKey(0),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
@@ -324,7 +337,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep1Personal() {
return Column(
key: const ValueKey(1),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
@@ -354,6 +366,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
duration: const Duration(milliseconds: 200),
child: _checkingUsername
? const SizedBox(
key: ValueKey('user_check_spinner'),
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
@@ -362,7 +375,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
_isUsernameAvailable
? LucideIcons.checkCircle2
: LucideIcons.xCircle,
key: ValueKey(_isUsernameAvailable),
key: ValueKey('user_avail_${_isUsernameAvailable}'),
color: _usernameController.text.isEmpty
? Colors.transparent
: (_isUsernameAvailable
@@ -388,7 +401,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep2Business() {
return Column(
key: const ValueKey(2),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
@@ -410,6 +422,10 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
value: 'JEWELLERY',
child: Text('Jewellery', style: TextStyle(color: Colors.black87)),
),
DropdownMenuItem(
value: 'ECOMMERCE',
child: Text('E-Commerce Seller', style: TextStyle(color: Colors.black87)),
),
DropdownMenuItem(
value: 'PROJECT_MANAGEMENT',
child: Text(
@@ -474,7 +490,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep3Consent() {
return Column(
key: const ValueKey(3),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
@@ -556,12 +571,11 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
);
}
Widget _buildCurrentStep() {
if (_currentStep == 0) return _buildStep0AccountType();
if (_currentStep == 1) return _buildStep1Personal();
if (_accountType == 'BUSINESS' && _currentStep == 2)
return _buildStep2Business();
return _buildStep3Consent(); // Step 2 (Individual) or Step 3 (Business)
int get _stackIndex {
if (_accountType == 'INDIVIDUAL' && _currentStep == 2) {
return 3;
}
return _currentStep;
}
@override
@@ -587,28 +601,38 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
transitionBuilder: (child, animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.05, 0),
end: Offset.zero,
).animate(animation),
child: FadeTransition(opacity: animation, child: child),
);
},
child: SingleChildScrollView(
key: ValueKey(_currentStep),
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
child: IndexedStack(
index: _stackIndex,
children: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
),
child: _buildStep0AccountType(),
),
child: _buildCurrentStep(),
),
SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
),
child: _buildStep1Personal(),
),
SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
),
child: _buildStep2Business(),
),
SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
),
child: _buildStep3Consent(),
),
],
),
),
),

View File

@@ -13,6 +13,8 @@ class BusinessProfile {
final String? emailId;
final String? panNumber;
final String? gstin;
final String? natureOfBusiness;
final String? msmeNumber;
BusinessProfile({
this.id,
@@ -29,13 +31,15 @@ class BusinessProfile {
this.emailId,
this.panNumber,
this.gstin,
this.natureOfBusiness,
this.msmeNumber,
});
factory BusinessProfile.fromJson(Map<String, dynamic> json) {
return BusinessProfile(
id: json['id'],
userId: json['userId'],
businessName: json['businessName'],
businessName: json['businessName'] ?? '',
industry: json['industry'],
taxNumber: json['taxNumber'],
currency: json['currency'],
@@ -47,6 +51,8 @@ class BusinessProfile {
emailId: json['emailId'],
panNumber: json['panNumber'],
gstin: json['gstin'],
natureOfBusiness: json['natureOfBusiness'] ?? json['industry'],
msmeNumber: json['msmeNumber'],
);
}
@@ -66,6 +72,8 @@ class BusinessProfile {
'emailId': emailId,
'panNumber': panNumber,
'gstin': gstin,
'natureOfBusiness': natureOfBusiness,
'msmeNumber': msmeNumber,
};
}
@@ -82,6 +90,8 @@ class BusinessProfile {
String? emailId,
String? panNumber,
String? gstin,
String? natureOfBusiness,
String? msmeNumber,
}) {
return BusinessProfile(
id: id,
@@ -98,6 +108,8 @@ class BusinessProfile {
emailId: emailId ?? this.emailId,
panNumber: panNumber ?? this.panNumber,
gstin: gstin ?? this.gstin,
natureOfBusiness: natureOfBusiness ?? this.natureOfBusiness,
msmeNumber: msmeNumber ?? this.msmeNumber,
);
}
}

View File

@@ -34,6 +34,8 @@ class BusinessHubScreen extends ConsumerWidget {
final bool showInventory = featureState?.inventoryManagement ?? false;
final bool showSales = featureState?.salesManagement ?? false;
final bool showPurchase = featureState?.purchaseManagement ?? false;
final nature = (profile?.natureOfBusiness ?? profile?.industry ?? 'JEWELLERY').toUpperCase();
final bool isJewellery = nature == 'JEWELLERY';
return RefreshIndicator(
onRefresh: () async {
@@ -200,7 +202,7 @@ class BusinessHubScreen extends ConsumerWidget {
),
isDesktop: isDesktop,
),
if (showInventory)
if (showInventory && isJewellery)
_buildActionCard(
context,
'Daily Rates',

View File

@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/product_categories_provider.dart';
import '../../../core/theme/nature_colors.dart';
import '../../business/providers/business_mode_provider.dart';
import '../../business/providers/business_provider.dart';
import '../../../core/theme/app_theme.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'dart:ui';
@@ -99,6 +100,18 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
final children = allCategories.where((c) => c.parentCategoryId == category.id).toList();
final isLeaf = children.isEmpty && !category.hasChild;
final hasCommodity = category.commodityCode != null && category.commodityCode != 'NONE' && category.commodityCode!.isNotEmpty;
final hasHsn = category.defaultHsn != null && category.defaultHsn!.isNotEmpty;
String? subtitleText;
if (hasCommodity && hasHsn) {
subtitleText = '${category.commodityCode} • HSN: ${category.defaultHsn}';
} else if (hasCommodity) {
subtitleText = category.commodityCode;
} else if (hasHsn) {
subtitleText = 'HSN: ${category.defaultHsn}';
}
// A nice frosted glass card
return Container(
margin: EdgeInsets.only(left: depth * 16.0, bottom: 8),
@@ -136,8 +149,8 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
child: const Icon(LucideIcons.tag, size: 20, color: Colors.blue),
),
title: Text(category.name, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: category.commodityCode != null
? Text('${category.commodityCode} • HSN: ${category.defaultHsn ?? "N/A"}',
subtitle: subtitleText != null
? Text(subtitleText,
style: TextStyle(fontSize: 12, color: Colors.grey.shade600))
: null,
trailing: Row(
@@ -186,35 +199,77 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
Widget _buildCategoryPopupMenu(ProductCategory category, bool isLeaf) {
return PopupMenuButton<String>(
icon: const Icon(LucideIcons.moreVertical, color: Colors.grey),
onSelected: (value) {
if (value == 'edit') {
icon: const Icon(LucideIcons.ellipsisVertical, size: 18, color: Colors.grey),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
onSelected: (val) {
if (val == 'edit') {
_showAddCategorySheet(context, category, ref, isEdit: true);
} else if (value == 'add_sub') {
} else if (val == 'addSub') {
_showAddCategorySheet(context, category, ref, isEdit: false);
} else if (value == 'delete') {
if (category.id != null) {
ref.read(productCategoriesProvider.notifier).deleteCategory(category.id!);
}
} else if (val == 'delete') {
_confirmDelete(category);
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'edit',
child: Row(children: [Icon(LucideIcons.edit, size: 18), SizedBox(width: 8), Text('Edit')]),
),
if (!isLeaf)
const PopupMenuItem(
value: 'add_sub',
child: Row(children: [Icon(LucideIcons.plus, size: 18), SizedBox(width: 8), Text('Add Subcategory')]),
value: 'addSub',
child: Row(
children: [
Icon(LucideIcons.folderPlus, size: 18, color: Colors.blue),
SizedBox(width: 12),
Text('Add Subcategory'),
],
),
),
const PopupMenuItem(
value: 'edit',
child: Row(
children: [
Icon(LucideIcons.pencil, size: 18, color: Colors.orange),
SizedBox(width: 12),
Text('Edit Details'),
],
),
),
const PopupMenuItem(
value: 'delete',
child: Row(children: [Icon(LucideIcons.trash2, size: 18, color: Colors.red), SizedBox(width: 8), Text('Delete', style: TextStyle(color: Colors.red))]),
child: Row(
children: [
Icon(LucideIcons.trash2, size: 18, color: Colors.red),
SizedBox(width: 12),
Text('Delete Category', style: TextStyle(color: Colors.red)),
],
),
),
],
);
}
void _confirmDelete(ProductCategory category) {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Text('Delete Category?'),
content: Text('Are you sure you want to delete "${category.name}"? If it has subcategories or products, they will also be affected.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white),
onPressed: () {
ref.read(productCategoriesProvider.notifier).deleteCategory(category.id!);
Navigator.pop(context);
},
child: const Text('Delete'),
),
],
),
);
}
}
void _showAddCategorySheet(BuildContext context, ProductCategory? parentOrCategory, WidgetRef ref, {bool isEdit = false}) {
@@ -251,7 +306,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
bool _huidRequired = false;
String _commodityCode = 'XAU';
String _makingChargeType = 'PER_GRAM';
String _baseUnit = 'g';
String _baseUnit = 'pcs';
final List<Map<String, String>> _commodities = [
{'code': 'XAU', 'label': 'XAU - Gold'},
@@ -264,6 +319,13 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
@override
void initState() {
super.initState();
final profile = ref.read(businessProfileProvider).value;
final nature = (profile?.natureOfBusiness ?? profile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
_baseUnit = isJewellery ? 'g' : 'pcs';
_commodityCode = isJewellery ? 'XAU' : 'NONE';
if (widget.categoryToEdit != null) {
final c = widget.categoryToEdit!;
_selectedParentId = c.parentCategoryId;
@@ -275,14 +337,13 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
_makingChargeController.text = c.defaultMakingCharge?.toString() ?? '';
_purityFactorController.text = formatPurity(c.purityFactor);
_huidRequired = c.huidRequired;
_commodityCode = c.commodityCode ?? 'XAU';
_commodityCode = c.commodityCode ?? (isJewellery ? 'XAU' : 'NONE');
// Handle legacy or varying enum string values
_makingChargeType = c.makingChargeType ?? 'PER_GRAM';
if (_makingChargeType == 'PER_GM') _makingChargeType = 'PER_GRAM';
_baseUnit = c.baseUnit ?? 'g';
_baseUnit = c.baseUnit ?? (isJewellery ? 'g' : 'pcs');
if (_baseUnit == 'gm') _baseUnit = 'g';
}
} else if (widget.parent != null) {
@@ -302,18 +363,22 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
void _submit() {
if (_formKey.currentState!.validate()) {
final profile = ref.read(businessProfileProvider).value;
final nature = (profile?.natureOfBusiness ?? profile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
final newCategory = ProductCategory(
id: widget.categoryToEdit?.id,
name: _nameController.text.trim(),
parentCategoryId: _selectedParentId,
hasChild: !_isLeaf,
commodityCode: _isLeaf ? _commodityCode : null,
purityFactor: _isLeaf ? normalizePurity(double.tryParse(_purityFactorController.text)) : 1.0,
commodityCode: _isLeaf ? (isJewellery ? _commodityCode : 'NONE') : null,
purityFactor: _isLeaf ? (isJewellery ? normalizePurity(double.tryParse(_purityFactorController.text)) : 1.0) : 1.0,
defaultHsn: _isLeaf ? _hsnController.text.trim() : null,
defaultGst: _isLeaf ? double.tryParse(_gstController.text) : null,
huidRequired: _isLeaf ? _huidRequired : false,
defaultMakingCharge: _isLeaf ? double.tryParse(_makingChargeController.text) : null,
makingChargeType: _isLeaf ? _makingChargeType : null,
huidRequired: _isLeaf ? (isJewellery ? _huidRequired : false) : false,
defaultMakingCharge: _isLeaf && isJewellery ? double.tryParse(_makingChargeController.text) : null,
makingChargeType: _isLeaf && isJewellery ? _makingChargeType : null,
baseUnit: _isLeaf ? _baseUnit : 'pcs',
);
@@ -330,6 +395,9 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final isBusinessMode = ref.watch(businessModeProvider);
final businessProfile = ref.watch(businessProfileProvider).value;
final nature = (businessProfile?.natureOfBusiness ?? businessProfile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
return Container(
height: MediaQuery.of(context).size.height * 0.85,
@@ -461,7 +529,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
const Text('Product Defaults', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
if (isBusinessMode) ...[
if (isJewellery && isBusinessMode) ...[
Row(
children: [
Expanded(
@@ -516,51 +584,66 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('HUID Required'),
subtitle: const Text('Is Hallmark Unique Identification mandatory?'),
value: _huidRequired,
onChanged: (val) => setState(() => _huidRequired = val),
contentPadding: EdgeInsets.zero,
),
const SizedBox(height: 16),
if (isJewellery) ...[
SwitchListTile(
title: const Text('HUID Required'),
subtitle: const Text('Is Hallmark Unique Identification mandatory?'),
value: _huidRequired,
onChanged: (val) => setState(() => _huidRequired = val),
contentPadding: EdgeInsets.zero,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
value: _makingChargeType,
decoration: InputDecoration(
labelText: 'Making Charge Type',
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
value: _makingChargeType,
decoration: const InputDecoration(
labelText: 'Making Charge Type',
),
items: const [
DropdownMenuItem(value: 'PER_GRAM', child: Text('Per Gram')),
DropdownMenuItem(value: 'PER_PIECE', child: Text('Per Piece')),
DropdownMenuItem(value: 'PERCENTAGE', child: Text('Percentage')),
],
onChanged: (val) => setState(() => _makingChargeType = val!),
),
items: const [
DropdownMenuItem(value: 'PER_GRAM', child: Text('Per Gram')),
DropdownMenuItem(value: 'PER_PIECE', child: Text('Per Piece')),
DropdownMenuItem(value: 'PERCENTAGE', child: Text('Percentage')),
],
onChanged: (val) => setState(() => _makingChargeType = val!),
),
),
const SizedBox(width: 16),
Expanded(
child: _buildTextField(
controller: _makingChargeController,
label: 'Default Charge',
keyboardType: const TextInputType.numberWithOptions(decimal: true),
const SizedBox(width: 16),
Expanded(
child: _buildTextField(
controller: _makingChargeController,
label: 'Default Charge',
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
),
),
],
),
const SizedBox(height: 16),
],
),
const SizedBox(height: 16),
],
DropdownButtonFormField<String>(
value: _baseUnit,
decoration: InputDecoration(
decoration: const InputDecoration(
labelText: 'Base Unit',
),
items: const [
DropdownMenuItem(value: 'g', child: Text('Grams (g)')),
DropdownMenuItem(value: 'kg', child: Text('Kilograms (kg)')),
DropdownMenuItem(value: 'pcs', child: Text('Pieces (pcs)')),
items: [
if (isJewellery) ...const [
DropdownMenuItem(value: 'g', child: Text('Grams (g)')),
DropdownMenuItem(value: 'kg', child: Text('Kilograms (kg)')),
DropdownMenuItem(value: 'pcs', child: Text('Pieces (pcs)')),
] else ...const [
DropdownMenuItem(value: 'pcs', child: Text('Pieces (pcs)')),
DropdownMenuItem(value: 'unit', child: Text('Units (unit)')),
DropdownMenuItem(value: 'box', child: Text('Boxes (box)')),
DropdownMenuItem(value: 'set', child: Text('Sets (set)')),
DropdownMenuItem(value: 'pair', child: Text('Pairs (pair)')),
DropdownMenuItem(value: 'g', child: Text('Grams (g)')),
DropdownMenuItem(value: 'kg', child: Text('Kilograms (kg)')),
DropdownMenuItem(value: 'm', child: Text('Meters (m)')),
DropdownMenuItem(value: 'l', child: Text('Liters (l)')),
],
],
onChanged: (val) => setState(() => _baseUnit = val!),
),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 161 KiB

View File

@@ -5,7 +5,7 @@
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="description" content="KIFI - Modern Financial Ledger & Jewellery Business Suite">
<meta name="description" content="KIFI - Modern Financial Ledger & Business ERP">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
@@ -16,7 +16,7 @@
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>KIFI - Financial Ledger & Jewellery ERP</title>
<title>KIFI - Financial Ledger & Business ERP</title>
<link rel="manifest" href="manifest.json">
<style>
body {
@@ -85,9 +85,9 @@
</head>
<body>
<div class="splash-container">
<div class="splash-logo">💎</div>
<img src="icons/Icon-192.png" alt="KIFI" style="width: 64px; height: 64px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); animation: pulse 2s infinite ease-in-out;">
<div class="splash-title">KIFI</div>
<div class="splash-sub">Financial Ledger & Jewellery ERP</div>
<div class="splash-sub">Financial Ledger & Business ERP</div>
<div class="spinner"></div>
</div>
<script src="flutter_bootstrap.js" async></script>