Refactor contacts using flutter_contacts, unify InteractiveScale animations and 16px corner radius across welcome and setup complete screens, and compile final release APK
This commit is contained in:
parent
ac1294a150
commit
3f2a804a31
|
|
@ -11,6 +11,28 @@ rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
subprojects {
|
subprojects {
|
||||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
if (extensions.findByName("android") != null) {
|
||||||
|
val android = extensions.findByName("android") as com.android.build.gradle.BaseExtension
|
||||||
|
if (android.namespace == null) {
|
||||||
|
var manifestPackage: String? = null
|
||||||
|
val manifestFile = project.projectDir.resolve("src/main/AndroidManifest.xml")
|
||||||
|
if (manifestFile.exists()) {
|
||||||
|
val content = manifestFile.readText()
|
||||||
|
val match = Regex("package=\"([^\"]+)\"").find(content)
|
||||||
|
if (match != null) {
|
||||||
|
manifestPackage = match.groupValues[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (manifestPackage != null) {
|
||||||
|
android.namespace = manifestPackage
|
||||||
|
} else {
|
||||||
|
android.namespace = "co.gittouch." + project.name.replace("-", "_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
subprojects {
|
subprojects {
|
||||||
project.evaluationDependsOn(":app")
|
project.evaluationDependsOn(":app")
|
||||||
|
|
|
||||||
|
|
@ -186,3 +186,119 @@ class _TopAlertWidgetState extends State<_TopAlertWidget> with SingleTickerProvi
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SlideFadeEntrance extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final Duration delay;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
const SlideFadeEntrance({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
this.delay = Duration.zero,
|
||||||
|
this.duration = const Duration(milliseconds: 500),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SlideFadeEntrance> createState() => _SlideFadeEntranceState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SlideFadeEntranceState extends State<SlideFadeEntrance> with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _opacityAnimation;
|
||||||
|
late Animation<double> _slideAnimation;
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: widget.duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
|
||||||
|
);
|
||||||
|
|
||||||
|
_slideAnimation = Tween<double>(begin: 12.0, end: 0.0).animate(
|
||||||
|
CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (widget.delay == Duration.zero) {
|
||||||
|
_controller.forward();
|
||||||
|
} else {
|
||||||
|
_timer = Timer(widget.delay, () {
|
||||||
|
if (mounted) {
|
||||||
|
_controller.forward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _controller,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Opacity(
|
||||||
|
opacity: _opacityAnimation.value,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(0.0, _slideAnimation.value),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InteractiveScale extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
final double hoverScale;
|
||||||
|
final double tapScale;
|
||||||
|
|
||||||
|
const InteractiveScale({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
this.onTap,
|
||||||
|
this.hoverScale = 1.03,
|
||||||
|
this.tapScale = 0.96,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InteractiveScale> createState() => _InteractiveScaleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InteractiveScaleState extends State<InteractiveScale> {
|
||||||
|
double _scale = 1.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _scale = widget.hoverScale),
|
||||||
|
onExit: (_) => setState(() => _scale = 1.0),
|
||||||
|
child: AnimatedScale(
|
||||||
|
scale: _scale,
|
||||||
|
duration: const Duration(milliseconds: 120),
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTapDown: (_) => setState(() => _scale = widget.tapScale),
|
||||||
|
onTapUp: (_) => setState(() => _scale = widget.hoverScale),
|
||||||
|
onTapCancel: () => setState(() => _scale = 1.0),
|
||||||
|
onTap: widget.onTap,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -248,21 +248,23 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.rtl,
|
textDirection: TextDirection.rtl,
|
||||||
child: ElevatedButton.icon(
|
child: InteractiveScale(
|
||||||
onPressed: _simulateBarcodeScan,
|
child: ElevatedButton.icon(
|
||||||
style: ElevatedButton.styleFrom(
|
onPressed: _simulateBarcodeScan,
|
||||||
minimumSize: const Size(0, 32),
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: theme.colorScheme.primary,
|
minimumSize: const Size(0, 32),
|
||||||
foregroundColor: Colors.white,
|
backgroundColor: theme.colorScheme.primary,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
foregroundColor: Colors.white,
|
||||||
shape: RoundedRectangleBorder(
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
borderRadius: BorderRadius.circular(12),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.qr_code_scanner, size: 16),
|
||||||
|
label: const Text(
|
||||||
|
'Scan Item',
|
||||||
|
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
icon: const Icon(Icons.qr_code_scanner, size: 16),
|
|
||||||
label: const Text(
|
|
||||||
'Scan Item',
|
|
||||||
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -407,95 +409,103 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
|
||||||
final isLowStock = widget.inventoryAlerts && remainingStock <= 3;
|
final isLowStock = widget.inventoryAlerts && remainingStock <= 3;
|
||||||
final isOutOfStock = widget.inventoryAlerts && remainingStock <= 0;
|
final isOutOfStock = widget.inventoryAlerts && remainingStock <= 0;
|
||||||
|
|
||||||
return InkWell(
|
return SlideFadeEntrance(
|
||||||
onTap: isOutOfStock ? null : () => _handleAddItem(prod),
|
delay: Duration(milliseconds: index * 40),
|
||||||
borderRadius: BorderRadius.circular(16),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: Container(
|
child: InteractiveScale(
|
||||||
padding: const EdgeInsets.all(10),
|
tapScale: isOutOfStock ? 1.0 : 0.95,
|
||||||
decoration: BoxDecoration(
|
hoverScale: isOutOfStock ? 1.0 : 1.03,
|
||||||
color: countInCart > 0
|
child: InkWell(
|
||||||
? const Color(0xFF1E1B4B) // Deep indigo accent
|
onTap: isOutOfStock ? null : () => _handleAddItem(prod),
|
||||||
: const Color(0xFF1E293B).withOpacity(0.8),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
child: Container(
|
||||||
color: countInCart > 0 ? theme.colorScheme.primary : const Color(0xFF334155),
|
padding: const EdgeInsets.all(10),
|
||||||
width: countInCart > 0 ? 1.5 : 1.0,
|
decoration: BoxDecoration(
|
||||||
),
|
color: countInCart > 0
|
||||||
),
|
? const Color(0xFF1E1B4B) // Deep indigo accent
|
||||||
child: Column(
|
: const Color(0xFF1E293B).withOpacity(0.8),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
borderRadius: BorderRadius.circular(16),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
border: Border.all(
|
||||||
children: [
|
color: countInCart > 0 ? theme.colorScheme.primary : const Color(0xFF334155),
|
||||||
Expanded(
|
width: countInCart > 0 ? 1.5 : 1.0,
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
prod.name,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'₹${prod.price.toStringAsFixed(0)}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
if (widget.inventoryAlerts)
|
Expanded(
|
||||||
Container(
|
child: Column(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
color: isLowStock
|
Text(
|
||||||
? Colors.red.withOpacity(0.15)
|
prod.name,
|
||||||
: const Color(0xFF334155),
|
maxLines: 2,
|
||||||
borderRadius: BorderRadius.circular(6),
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
style: const TextStyle(
|
||||||
child: Text(
|
color: Colors.white,
|
||||||
isOutOfStock ? 'Out of stock' : '$remainingStock Stock',
|
fontWeight: FontWeight.bold,
|
||||||
style: TextStyle(
|
fontSize: 11,
|
||||||
color: isLowStock ? Colors.redAccent : Colors.grey,
|
),
|
||||||
fontSize: 8,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
)
|
Text(
|
||||||
else
|
'₹${prod.price.toStringAsFixed(0)}',
|
||||||
const SizedBox(),
|
style: TextStyle(
|
||||||
if (countInCart > 0)
|
color: theme.colorScheme.primary,
|
||||||
Container(
|
fontWeight: FontWeight.w800,
|
||||||
width: 20,
|
fontSize: 13,
|
||||||
height: 20,
|
),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
'$countInCart',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
)
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
if (widget.inventoryAlerts)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isLowStock
|
||||||
|
? Colors.red.withOpacity(0.15)
|
||||||
|
: const Color(0xFF334155),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
isOutOfStock ? 'Out of stock' : '$remainingStock Stock',
|
||||||
|
style: TextStyle(
|
||||||
|
color: isLowStock ? Colors.redAccent : Colors.grey,
|
||||||
|
fontSize: 8,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const SizedBox(),
|
||||||
|
if (countInCart > 0)
|
||||||
|
Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'$countInCart',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -655,20 +665,27 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
|
||||||
const Divider(color: Color(0xFF334155), height: 16),
|
const Divider(color: Color(0xFF334155), height: 16),
|
||||||
_buildComputationCalculations(theme),
|
_buildComputationCalculations(theme),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
ElevatedButton(
|
InteractiveScale(
|
||||||
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
|
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
|
||||||
style: ElevatedButton.styleFrom(
|
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
|
||||||
backgroundColor: theme.colorScheme.primary,
|
child: ElevatedButton(
|
||||||
disabledBackgroundColor: const Color(0xFF1E293B),
|
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
|
||||||
minimumSize: const Size.fromHeight(48),
|
style: ElevatedButton.styleFrom(
|
||||||
),
|
backgroundColor: theme.colorScheme.primary,
|
||||||
child: const Row(
|
disabledBackgroundColor: const Color(0xFF1E293B),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
minimumSize: const Size.fromHeight(48),
|
||||||
children: [
|
shape: RoundedRectangleBorder(
|
||||||
Icon(Icons.receipt, size: 16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
SizedBox(width: 8),
|
),
|
||||||
Text('Generate Invoice'),
|
),
|
||||||
],
|
child: const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.receipt, size: 16),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Generate Invoice'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
@ -751,19 +768,26 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
InteractiveScale(
|
||||||
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
|
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
|
||||||
style: ElevatedButton.styleFrom(
|
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
child: ElevatedButton(
|
||||||
minimumSize: const Size(180, 44),
|
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
|
||||||
),
|
style: ElevatedButton.styleFrom(
|
||||||
child: const Row(
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
mainAxisSize: MainAxisSize.min,
|
minimumSize: const Size(180, 44),
|
||||||
children: [
|
shape: RoundedRectangleBorder(
|
||||||
Icon(Icons.print, size: 16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
SizedBox(width: 8),
|
),
|
||||||
Text('Review & Invoice', style: TextStyle(fontSize: 12)),
|
),
|
||||||
],
|
child: const Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.print, size: 16),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Review & Invoice', style: TextStyle(fontSize: 12)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
@ -937,30 +961,40 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
InteractiveScale(
|
||||||
onPressed: () {
|
child: ElevatedButton.icon(
|
||||||
showTopAlert(
|
onPressed: () {
|
||||||
context,
|
showTopAlert(
|
||||||
'Thermal Printed: $_invoiceNumber successfully!',
|
context,
|
||||||
icon: Icons.print,
|
'Thermal Printed: $_invoiceNumber successfully!',
|
||||||
);
|
icon: Icons.print,
|
||||||
},
|
);
|
||||||
icon: const Icon(Icons.print, size: 16),
|
},
|
||||||
label: const Text('Simulate Print Receipt', style: TextStyle(fontSize: 12)),
|
icon: const Icon(Icons.print, size: 16),
|
||||||
style: ElevatedButton.styleFrom(
|
label: const Text('Simulate Print Receipt', style: TextStyle(fontSize: 12)),
|
||||||
backgroundColor: Colors.black,
|
style: ElevatedButton.styleFrom(
|
||||||
foregroundColor: Colors.white,
|
backgroundColor: Colors.black,
|
||||||
minimumSize: const Size.fromHeight(42),
|
foregroundColor: Colors.white,
|
||||||
|
minimumSize: const Size.fromHeight(42),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
ElevatedButton(
|
InteractiveScale(
|
||||||
onPressed: _closeReceiptAndReset,
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
onPressed: _closeReceiptAndReset,
|
||||||
backgroundColor: theme.colorScheme.primary,
|
style: ElevatedButton.styleFrom(
|
||||||
minimumSize: const Size.fromHeight(42),
|
backgroundColor: theme.colorScheme.primary,
|
||||||
|
minimumSize: const Size.fromHeight(42),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('New Ledger Sale', style: TextStyle(fontSize: 12)),
|
||||||
),
|
),
|
||||||
child: const Text('New Ledger Sale', style: TextStyle(fontSize: 12)),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -146,46 +146,48 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
||||||
Widget _buildDocChip(String label, IconData icon) {
|
Widget _buildDocChip(String label, IconData icon) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final isSelected = _selectedDocs.contains(label);
|
final isSelected = _selectedDocs.contains(label);
|
||||||
return InkWell(
|
return InteractiveScale(
|
||||||
onTap: () {
|
child: InkWell(
|
||||||
setState(() {
|
onTap: () {
|
||||||
if (isSelected) {
|
setState(() {
|
||||||
_selectedDocs.remove(label);
|
if (isSelected) {
|
||||||
} else {
|
_selectedDocs.remove(label);
|
||||||
_selectedDocs.add(label);
|
} else {
|
||||||
}
|
_selectedDocs.add(label);
|
||||||
});
|
}
|
||||||
},
|
});
|
||||||
borderRadius: BorderRadius.circular(8),
|
},
|
||||||
child: Container(
|
borderRadius: BorderRadius.circular(8),
|
||||||
constraints: const BoxConstraints(minHeight: 48),
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
constraints: const BoxConstraints(minHeight: 48),
|
||||||
decoration: BoxDecoration(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
color: isSelected ? const Color(0xFFFAF0F0) : Colors.white,
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: isSelected ? const Color(0xFFFAF0F0) : Colors.white,
|
||||||
border: Border.all(
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: isSelected ? const Color(0xFFAF101A) : theme.colorScheme.outlineVariant,
|
border: Border.all(
|
||||||
width: isSelected ? 1.5 : 1,
|
color: isSelected ? const Color(0xFFAF101A) : theme.colorScheme.outlineVariant,
|
||||||
|
width: isSelected ? 1.5 : 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Row(
|
||||||
child: Row(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
Icon(
|
||||||
Icon(
|
icon,
|
||||||
icon,
|
size: 16,
|
||||||
size: 16,
|
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600],
|
||||||
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600],
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
],
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -337,31 +339,35 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton.icon(
|
InteractiveScale(
|
||||||
onPressed: () => _handleImagePick(docName, ImageSource.camera),
|
child: ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.camera_alt, size: 14),
|
onPressed: () => _handleImagePick(docName, ImageSource.camera),
|
||||||
label: const Text('Take Photo'),
|
icon: const Icon(Icons.camera_alt, size: 14),
|
||||||
style: ElevatedButton.styleFrom(
|
label: const Text('Take Photo'),
|
||||||
minimumSize: const Size(0, 36),
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFAF101A),
|
minimumSize: const Size(0, 36),
|
||||||
foregroundColor: Colors.white,
|
backgroundColor: const Color(0xFFAF101A),
|
||||||
elevation: 1,
|
foregroundColor: Colors.white,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
elevation: 1,
|
||||||
shape: RoundedRectangleBorder(
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
OutlinedButton.icon(
|
InteractiveScale(
|
||||||
onPressed: () => _handleImagePick(docName, ImageSource.gallery),
|
child: OutlinedButton.icon(
|
||||||
icon: const Icon(Icons.photo_library, size: 14),
|
onPressed: () => _handleImagePick(docName, ImageSource.gallery),
|
||||||
label: const Text('From Gallery'),
|
icon: const Icon(Icons.photo_library, size: 14),
|
||||||
style: OutlinedButton.styleFrom(
|
label: const Text('From Gallery'),
|
||||||
foregroundColor: const Color(0xFFAF101A),
|
style: OutlinedButton.styleFrom(
|
||||||
side: const BorderSide(color: Color(0xFFAF101A)),
|
foregroundColor: const Color(0xFFAF101A),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
side: const BorderSide(color: Color(0xFFAF101A)),
|
||||||
shape: RoundedRectangleBorder(
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -441,337 +447,407 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
SlideFadeEntrance(
|
||||||
'Complete verification',
|
delay: Duration.zero,
|
||||||
style: theme.textTheme.headlineLarge?.copyWith(
|
child: Column(
|
||||||
fontWeight: FontWeight.bold,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 6),
|
'Complete verification',
|
||||||
Text(
|
style: theme.textTheme.headlineLarge?.copyWith(
|
||||||
'Verify your business details to start selling.',
|
fontWeight: FontWeight.bold,
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Business/Shop Name *',
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
TextField(
|
|
||||||
controller: _nameController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'e.g. Sharma General Store',
|
|
||||||
),
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Business Type *',
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
|
|
||||||
GridView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
crossAxisSpacing: 16,
|
|
||||||
mainAxisSpacing: 16,
|
|
||||||
childAspectRatio: 2.2,
|
|
||||||
),
|
|
||||||
itemCount: _categories.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final cat = _categories[index];
|
|
||||||
final isSelected = _selectedCategory == cat['id'];
|
|
||||||
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_selectedCategory = cat['id'];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected
|
|
||||||
? const Color(0xFFAF101A).withOpacity(0.08)
|
|
||||||
: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2),
|
|
||||||
width: isSelected ? 2 : 1,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.01),
|
|
||||||
blurRadius: 4,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Column(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
const SizedBox(height: 6),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Text(
|
||||||
children: [
|
'Verify your business details to start selling.',
|
||||||
Center(
|
style: theme.textTheme.bodyMedium,
|
||||||
child: Icon(
|
),
|
||||||
cat['icon'],
|
],
|
||||||
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600],
|
),
|
||||||
size: 26,
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 100),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Business/Shop Name *',
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'e.g. Sharma General Store',
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 180),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Business Type *',
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: 16,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
childAspectRatio: 2.2,
|
||||||
|
),
|
||||||
|
itemCount: _categories.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final cat = _categories[index];
|
||||||
|
final isSelected = _selectedCategory == cat['id'];
|
||||||
|
|
||||||
|
return InteractiveScale(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selectedCategory = cat['id'];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? const Color(0xFFAF101A).withOpacity(0.08)
|
||||||
|
: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2),
|
||||||
|
width: isSelected ? 2 : 1,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.01),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Icon(
|
||||||
|
cat['icon'],
|
||||||
|
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600],
|
||||||
|
size: 26,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
cat['name'],
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
);
|
||||||
Center(
|
},
|
||||||
child: Text(
|
|
||||||
cat['name'],
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
Row(
|
SlideFadeEntrance(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
delay: const Duration(milliseconds: 260),
|
||||||
children: [
|
child: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
'GSTIN',
|
children: [
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
Row(
|
||||||
color: theme.colorScheme.onSurface,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
fontWeight: FontWeight.bold,
|
children: [
|
||||||
|
Text(
|
||||||
|
'GSTIN',
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'(Optional)',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.grey[500],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Row(
|
||||||
'(Optional)',
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
children: [
|
||||||
color: Colors.grey[500],
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _gstinController,
|
||||||
|
textCapitalization: TextCapitalization.characters,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '15-DIGIT GST NUMBER',
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
InteractiveScale(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
showTopAlert(
|
||||||
|
context,
|
||||||
|
'GSTIN Verified Successfully!',
|
||||||
|
icon: Icons.verified_outlined,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: const Size(0, 48),
|
||||||
|
backgroundColor: theme.colorScheme.primary,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Verify'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 6),
|
||||||
],
|
Text(
|
||||||
|
"Auto-fetches address & legal name on verification.",
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.grey[500]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 16),
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
SlideFadeEntrance(
|
||||||
children: [
|
delay: const Duration(milliseconds: 340),
|
||||||
Expanded(
|
child: Column(
|
||||||
child: TextField(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
controller: _gstinController,
|
children: [
|
||||||
textCapitalization: TextCapitalization.characters,
|
Text(
|
||||||
decoration: const InputDecoration(
|
'Shop Location *',
|
||||||
hintText: '15-DIGIT GST NUMBER',
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(width: 12),
|
InteractiveScale(
|
||||||
ElevatedButton(
|
child: InkWell(
|
||||||
onPressed: () {
|
onTap: () {
|
||||||
showTopAlert(
|
showTopAlert(
|
||||||
context,
|
context,
|
||||||
'GSTIN Verified Successfully!',
|
'Location detected: Sector 62, Noida, UP',
|
||||||
icon: Icons.verified_outlined,
|
icon: Icons.my_location_outlined,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
minimumSize: const Size(0, 48),
|
|
||||||
backgroundColor: theme.colorScheme.primary,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
child: Container(
|
||||||
),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||||
child: const Text('Verify'),
|
decoration: BoxDecoration(
|
||||||
),
|
color: Colors.white,
|
||||||
],
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
border: Border.all(
|
||||||
const SizedBox(height: 6),
|
color: theme.colorScheme.outlineVariant,
|
||||||
Text(
|
|
||||||
"Auto-fetches address & legal name on verification.",
|
|
||||||
style: TextStyle(fontSize: 11, color: Colors.grey[500]),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Shop Location *',
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
showTopAlert(
|
|
||||||
context,
|
|
||||||
'Location detected: Sector 62, Noida, UP',
|
|
||||||
icon: Icons.my_location_outlined,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: theme.colorScheme.outlineVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.primary.withOpacity(0.1),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.my_location,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 14),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Detect current location',
|
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 15,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
),
|
||||||
Text(
|
child: Row(
|
||||||
'Pin shop address automatically',
|
children: [
|
||||||
style: TextStyle(
|
Container(
|
||||||
fontSize: 11,
|
padding: const EdgeInsets.all(10),
|
||||||
color: Colors.grey[600],
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.1),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.my_location,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 14),
|
||||||
],
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Detect current location',
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'Pin shop address automatically',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(
|
|
||||||
Icons.chevron_right,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Identity / License Documents (Select all that apply)',
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: [
|
|
||||||
_buildDocChip('GSTIN', Icons.description),
|
|
||||||
_buildDocChip('FSSAI', Icons.restaurant),
|
|
||||||
_buildDocChip('Aadhar', Icons.badge_outlined),
|
|
||||||
_buildDocChip('Udhyog Aadhar', Icons.domain),
|
|
||||||
_buildDocChip('PAN', Icons.credit_card),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
..._selectedDocs.map((doc) {
|
|
||||||
IconData docIcon = Icons.description;
|
|
||||||
if (doc == 'GSTIN') docIcon = Icons.description;
|
|
||||||
if (doc == 'FSSAI') docIcon = Icons.restaurant;
|
|
||||||
if (doc == 'Aadhar') docIcon = Icons.badge_outlined;
|
|
||||||
if (doc == 'Udhyog Aadhar') docIcon = Icons.domain;
|
|
||||||
if (doc == 'PAN') docIcon = Icons.credit_card;
|
|
||||||
return _buildUploadSection(doc, docIcon);
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Shop Facade Photo *',
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
_showFacadePhotoOptions();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.outlineVariant,
|
|
||||||
width: _uploadedDocs.containsKey('facade_photo') ? 2.0 : 1.0,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
child: Column(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: [
|
const SizedBox(height: 16),
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
SlideFadeEntrance(
|
||||||
decoration: BoxDecoration(
|
delay: const Duration(milliseconds: 420),
|
||||||
color: _uploadedDocs.containsKey('facade_photo') ? Colors.green.withOpacity(0.1) : theme.colorScheme.primary.withOpacity(0.1),
|
child: Column(
|
||||||
shape: BoxShape.circle,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
child: Icon(
|
Text(
|
||||||
_uploadedDocs.containsKey('facade_photo') ? Icons.check_circle : Icons.add_a_photo_outlined,
|
'Identity / License Documents (Select all that apply)',
|
||||||
color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.primary,
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
size: 24,
|
color: theme.colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
_buildDocChip('GSTIN', Icons.description),
|
||||||
|
_buildDocChip('FSSAI', Icons.restaurant),
|
||||||
|
_buildDocChip('Aadhar', Icons.badge_outlined),
|
||||||
|
_buildDocChip('Udhyog Aadhar', Icons.domain),
|
||||||
|
_buildDocChip('PAN', Icons.credit_card),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 500),
|
||||||
|
child: Column(
|
||||||
|
children: _selectedDocs.map((doc) {
|
||||||
|
IconData docIcon = Icons.description;
|
||||||
|
if (doc == 'GSTIN') docIcon = Icons.description;
|
||||||
|
if (doc == 'FSSAI') docIcon = Icons.restaurant;
|
||||||
|
if (doc == 'Aadhar') docIcon = Icons.badge_outlined;
|
||||||
|
if (doc == 'Udhyog Aadhar') docIcon = Icons.domain;
|
||||||
|
if (doc == 'PAN') docIcon = Icons.credit_card;
|
||||||
|
return _buildUploadSection(doc, docIcon);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 580),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Shop Facade Photo *',
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
InteractiveScale(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
_showFacadePhotoOptions();
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.outlineVariant,
|
||||||
|
width: _uploadedDocs.containsKey('facade_photo') ? 2.0 : 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _uploadedDocs.containsKey('facade_photo') ? Colors.green.withOpacity(0.1) : theme.colorScheme.primary.withOpacity(0.1),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
_uploadedDocs.containsKey('facade_photo') ? Icons.check_circle : Icons.add_a_photo_outlined,
|
||||||
|
color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
_uploadedDocs.containsKey('facade_photo') ? _uploadedDocs['facade_photo']! : 'Upload front view',
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
_uploadedDocs.containsKey('facade_photo') ? 'Facade Photo Attached • Tap to change' : 'Clear photo showing shop board',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.grey[500],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
),
|
||||||
Text(
|
],
|
||||||
_uploadedDocs.containsKey('facade_photo') ? _uploadedDocs['facade_photo']! : 'Upload front view',
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
_uploadedDocs.containsKey('facade_photo') ? 'Facade Photo Attached • Tap to change' : 'Clear photo showing shop board',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: Colors.grey[500],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -781,30 +857,40 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Padding(
|
SlideFadeEntrance(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
delay: const Duration(milliseconds: 660),
|
||||||
child: ElevatedButton(
|
child: Padding(
|
||||||
onPressed: _isNameValid
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||||
? () {
|
child: InteractiveScale(
|
||||||
Navigator.push(
|
hoverScale: _isNameValid ? 1.03 : 1.0,
|
||||||
context,
|
tapScale: _isNameValid ? 0.96 : 1.0,
|
||||||
FadeSlidePageRoute(
|
child: ElevatedButton(
|
||||||
child: CustomizeSetupScreen(
|
onPressed: _isNameValid
|
||||||
businessName: _nameController.text.trim(),
|
? () {
|
||||||
businessType: _selectedCategory,
|
Navigator.push(
|
||||||
gstin: _gstinController.text.trim(),
|
context,
|
||||||
),
|
FadeSlidePageRoute(
|
||||||
),
|
child: CustomizeSetupScreen(
|
||||||
);
|
businessName: _nameController.text.trim(),
|
||||||
}
|
businessType: _selectedCategory,
|
||||||
: null,
|
gstin: _gstinController.text.trim(),
|
||||||
style: ElevatedButton.styleFrom(
|
),
|
||||||
minimumSize: const Size.fromHeight(54),
|
),
|
||||||
backgroundColor: _isNameValid ? theme.colorScheme.primary : Colors.grey[200],
|
);
|
||||||
foregroundColor: _isNameValid ? Colors.white : Colors.grey[400],
|
}
|
||||||
elevation: _isNameValid ? 2 : 0,
|
: null,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: const Size.fromHeight(54),
|
||||||
|
backgroundColor: _isNameValid ? theme.colorScheme.primary : Colors.grey[200],
|
||||||
|
foregroundColor: _isNameValid ? Colors.white : Colors.grey[400],
|
||||||
|
elevation: _isNameValid ? 2 : 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Continue to Verification'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: const Text('Continue to Verification'),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -119,49 +119,66 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
SlideFadeEntrance(
|
||||||
'Customize Your Setup',
|
delay: Duration.zero,
|
||||||
style: theme.textTheme.headlineLarge?.copyWith(
|
child: Column(
|
||||||
fontWeight: FontWeight.bold,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Customize Your Setup',
|
||||||
|
style: theme.textTheme.headlineLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Turn on the features you need for your shop. You can always change these later in settings.',
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 20),
|
||||||
Text(
|
|
||||||
'Turn on the features you need for your shop. You can always change these later in settings.',
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Toggle 1: Barcode
|
// Toggle 1: Barcode
|
||||||
_buildToggleCard(
|
SlideFadeEntrance(
|
||||||
Icons.qr_code_scanner,
|
delay: const Duration(milliseconds: 100),
|
||||||
'Barcode Scanner',
|
child: _buildToggleCard(
|
||||||
'Do you use a Barcode Scanner?',
|
Icons.qr_code_scanner,
|
||||||
_barcode,
|
'Barcode Scanner',
|
||||||
(val) => setState(() => _barcode = val),
|
'Do you use a Barcode Scanner?',
|
||||||
theme,
|
_barcode,
|
||||||
|
(val) => setState(() => _barcode = val),
|
||||||
|
theme,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Toggle 2: KOT
|
// Toggle 2: KOT
|
||||||
_buildToggleCard(
|
SlideFadeEntrance(
|
||||||
Icons.receipt_long,
|
delay: const Duration(milliseconds: 180),
|
||||||
'KOT Management',
|
child: _buildToggleCard(
|
||||||
'Do you need Kitchen Order Ticket management?',
|
Icons.receipt_long,
|
||||||
_kot,
|
'KOT Management',
|
||||||
(val) => setState(() => _kot = val),
|
'Do you need Kitchen Order Ticket management?',
|
||||||
theme,
|
_kot,
|
||||||
|
(val) => setState(() => _kot = val),
|
||||||
|
theme,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Toggle 3: Inventory stock tracking
|
// Toggle 3: Inventory stock tracking
|
||||||
_buildToggleCard(
|
SlideFadeEntrance(
|
||||||
Icons.inventory_2,
|
delay: const Duration(milliseconds: 260),
|
||||||
'Inventory Alerts',
|
child: _buildToggleCard(
|
||||||
'Get notified when stock is running low.',
|
Icons.inventory_2,
|
||||||
_inventory,
|
'Inventory Alerts',
|
||||||
(val) => setState(() => _inventory = val),
|
'Get notified when stock is running low.',
|
||||||
theme,
|
_inventory,
|
||||||
|
(val) => setState(() => _inventory = val),
|
||||||
|
theme,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -170,28 +187,36 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
|
||||||
),
|
),
|
||||||
|
|
||||||
// Pinned action keys
|
// Pinned action keys
|
||||||
Padding(
|
SlideFadeEntrance(
|
||||||
padding: EdgeInsets.all(padding),
|
delay: const Duration(milliseconds: 340),
|
||||||
child: ElevatedButton(
|
child: Padding(
|
||||||
onPressed: () {
|
padding: EdgeInsets.all(padding),
|
||||||
Navigator.push(
|
child: InteractiveScale(
|
||||||
context,
|
child: ElevatedButton(
|
||||||
FadeSlidePageRoute(
|
onPressed: () {
|
||||||
child: SetupCompleteScreen(
|
Navigator.push(
|
||||||
businessName: widget.businessName,
|
context,
|
||||||
businessType: widget.businessType,
|
FadeSlidePageRoute(
|
||||||
gstin: widget.gstin,
|
child: SetupCompleteScreen(
|
||||||
barcodeScanner: _barcode,
|
businessName: widget.businessName,
|
||||||
kotManagement: _kot,
|
businessType: widget.businessType,
|
||||||
inventoryAlerts: _inventory,
|
gstin: widget.gstin,
|
||||||
|
barcodeScanner: _barcode,
|
||||||
|
kotManagement: _kot,
|
||||||
|
inventoryAlerts: _inventory,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: const Size.fromHeight(54),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
child: const Text('Save & Continue'),
|
||||||
},
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
minimumSize: const Size.fromHeight(54),
|
|
||||||
),
|
),
|
||||||
child: const Text('Save & Continue'),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
@ -210,64 +235,67 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
|
||||||
ValueChanged<bool> onChanged,
|
ValueChanged<bool> onChanged,
|
||||||
ThemeData theme,
|
ThemeData theme,
|
||||||
) {
|
) {
|
||||||
return Container(
|
return InteractiveScale(
|
||||||
padding: const EdgeInsets.all(16),
|
onTap: () => onChanged(!value),
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
color: Colors.white,
|
padding: const EdgeInsets.all(16),
|
||||||
borderRadius: BorderRadius.circular(16),
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: Colors.grey[200]!),
|
color: Colors.white,
|
||||||
boxShadow: [
|
borderRadius: BorderRadius.circular(16),
|
||||||
BoxShadow(
|
border: Border.all(color: Colors.grey[200]!),
|
||||||
color: Colors.black.withOpacity(0.02),
|
boxShadow: [
|
||||||
blurRadius: 10,
|
BoxShadow(
|
||||||
offset: const Offset(0, 4),
|
color: Colors.black.withOpacity(0.02),
|
||||||
)
|
blurRadius: 10,
|
||||||
],
|
offset: const Offset(0, 4),
|
||||||
),
|
)
|
||||||
child: Row(
|
],
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
),
|
||||||
children: [
|
child: Row(
|
||||||
Container(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
width: 44,
|
children: [
|
||||||
height: 44,
|
Container(
|
||||||
decoration: BoxDecoration(
|
width: 44,
|
||||||
color: Colors.grey[100],
|
height: 44,
|
||||||
shape: BoxShape.circle,
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: Colors.grey[600], size: 22),
|
||||||
),
|
),
|
||||||
child: Icon(icon, color: Colors.grey[600], size: 22),
|
const SizedBox(width: 14),
|
||||||
),
|
Expanded(
|
||||||
const SizedBox(width: 14),
|
child: Column(
|
||||||
Expanded(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
Text(
|
||||||
children: [
|
title,
|
||||||
Text(
|
style: const TextStyle(
|
||||||
title,
|
fontSize: 15,
|
||||||
style: const TextStyle(
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 15,
|
color: Colors.black87,
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
color: Colors.black87,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
const SizedBox(height: 4),
|
Text(
|
||||||
Text(
|
subtitle,
|
||||||
subtitle,
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 11,
|
||||||
fontSize: 11,
|
color: Colors.grey[500],
|
||||||
color: Colors.grey[500],
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
Switch(
|
||||||
Switch(
|
value: value,
|
||||||
value: value,
|
onChanged: onChanged,
|
||||||
onChanged: onChanged,
|
activeColor: Colors.white,
|
||||||
activeColor: Colors.white,
|
activeTrackColor: theme.colorScheme.primary,
|
||||||
activeTrackColor: theme.colorScheme.primary,
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:contacts_service/contacts_service.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import '../theme.dart';
|
import '../theme.dart';
|
||||||
import '../route_transitions.dart';
|
import '../route_transitions.dart';
|
||||||
import 'otp_verification_screen.dart';
|
import 'otp_verification_screen.dart';
|
||||||
|
|
@ -16,8 +16,8 @@ class MockContact {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockPhone {
|
class MockPhone {
|
||||||
final String? value;
|
final String number;
|
||||||
MockPhone(this.value);
|
MockPhone(this.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MobileLoginScreen extends StatefulWidget {
|
class MobileLoginScreen extends StatefulWidget {
|
||||||
|
|
@ -103,7 +103,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
final List<Contact> list = await ContactsService.getContacts(withThumbnails: false);
|
final List<Contact> list = await FlutterContacts.getContacts(withProperties: true, withPhoto: false);
|
||||||
return list;
|
return list;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -166,7 +166,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
||||||
setDialogState(() {
|
setDialogState(() {
|
||||||
filteredContacts = allContacts.where((contact) {
|
filteredContacts = allContacts.where((contact) {
|
||||||
final name = (contact.displayName ?? '').toLowerCase();
|
final name = (contact.displayName ?? '').toLowerCase();
|
||||||
final phone = (contact.phones.isNotEmpty ? contact.phones.first.value ?? '' : '').toLowerCase();
|
final phone = (contact.phones.isNotEmpty ? contact.phones.first.number : '').toLowerCase();
|
||||||
return name.contains(query.toLowerCase()) || phone.contains(query.toLowerCase());
|
return name.contains(query.toLowerCase()) || phone.contains(query.toLowerCase());
|
||||||
}).toList();
|
}).toList();
|
||||||
});
|
});
|
||||||
|
|
@ -183,7 +183,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final contact = filteredContacts[index];
|
final contact = filteredContacts[index];
|
||||||
final name = contact.displayName ?? 'Unknown';
|
final name = contact.displayName ?? 'Unknown';
|
||||||
final phone = contact.phones.isNotEmpty ? contact.phones.first.value : 'No number';
|
final phone = contact.phones.isNotEmpty ? contact.phones.first.number : 'No number';
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(name),
|
title: Text(name),
|
||||||
subtitle: Text(phone),
|
subtitle: Text(phone),
|
||||||
|
|
@ -256,318 +256,335 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
SlideFadeEntrance(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
delay: Duration.zero,
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Step 2 of 5',
|
|
||||||
style: theme.textTheme.labelMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Mobile Login',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'Plus Jakarta Sans',
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFFAF101A),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: List.generate(5, (index) {
|
|
||||||
return Expanded(
|
|
||||||
child: Container(
|
|
||||||
height: 6,
|
|
||||||
margin: EdgeInsets.only(
|
|
||||||
left: index == 0 ? 0 : 3.0,
|
|
||||||
right: index == 4 ? 0 : 3.0,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: index <= 1 ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2),
|
|
||||||
borderRadius: BorderRadius.circular(100),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Welcome Back',
|
|
||||||
style: theme.textTheme.headlineLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Enter your mobile number to sign in or create a new account.',
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFFAFFD1).withOpacity(0.8),
|
|
||||||
borderRadius: BorderRadius.circular(100),
|
|
||||||
border: Border.all(color: const Color(0xFF016619).withOpacity(0.3)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.lock, size: 14, color: Color(0xFF016619)),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
'Secure OTP Verification',
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: const Color(0xFF016619),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF3F3F3),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(color: const Color(0xFFE4BEBA).withOpacity(0.5)),
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Text(
|
||||||
width: 40,
|
'Step 2 of 5',
|
||||||
height: 40,
|
style: theme.textTheme.labelMedium?.copyWith(
|
||||||
decoration: const BoxDecoration(
|
fontWeight: FontWeight.bold,
|
||||||
color: Color(0xFF005FAF),
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.verified_user,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 24,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const Text(
|
||||||
Expanded(
|
'Mobile Login',
|
||||||
child: Column(
|
style: TextStyle(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
fontFamily: 'Plus Jakarta Sans',
|
||||||
children: [
|
fontSize: 14,
|
||||||
Text(
|
fontWeight: FontWeight.bold,
|
||||||
'Verify instantly with',
|
color: Color(0xFFAF101A),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Truecaller',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'Plus Jakarta Sans',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 8),
|
||||||
const Text(
|
Row(
|
||||||
'+91 98765 43210',
|
children: List.generate(5, (index) {
|
||||||
style: TextStyle(
|
return Expanded(
|
||||||
fontFamily: 'Plus Jakarta Sans',
|
child: Container(
|
||||||
fontSize: 24,
|
height: 6,
|
||||||
fontWeight: FontWeight.bold,
|
margin: EdgeInsets.only(
|
||||||
color: Colors.black,
|
left: index == 0 ? 0 : 3.0,
|
||||||
),
|
right: index == 4 ? 0 : 3.0,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
decoration: BoxDecoration(
|
||||||
ElevatedButton.icon(
|
color: index <= 1 ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2),
|
||||||
onPressed: () {
|
borderRadius: BorderRadius.circular(100),
|
||||||
_controller.text = '9876543210';
|
|
||||||
setState(() {
|
|
||||||
_isValid = true;
|
|
||||||
});
|
|
||||||
showTopAlert(
|
|
||||||
context,
|
|
||||||
'Phone verified automatically with Truecaller! Joining PozoApp...',
|
|
||||||
icon: Icons.verified_user_outlined,
|
|
||||||
);
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
FadeSlidePageRoute(
|
|
||||||
child: const OtpVerificationScreen(
|
|
||||||
mobileNumber: '9876543210',
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
icon: const Icon(Icons.flash_on, size: 18, color: Colors.white),
|
),
|
||||||
label: const Text(
|
const SizedBox(height: 16),
|
||||||
'1-Tap Verification',
|
Text(
|
||||||
style: TextStyle(
|
'Welcome Back',
|
||||||
fontFamily: 'Inter',
|
style: theme.textTheme.headlineLarge?.copyWith(
|
||||||
fontSize: 16,
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Enter your mobile number to sign in or create a new account.',
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 100),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFAFFD1).withOpacity(0.8),
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
border: Border.all(color: const Color(0xFF016619).withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.lock, size: 14, color: Color(0xFF016619)),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Secure OTP Verification',
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: const Color(0xFF016619),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
],
|
||||||
backgroundColor: const Color(0xFF005FAF),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
minimumSize: const Size.fromHeight(56),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
elevation: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
'No OTP required. Your phone will be verified automatically.',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
height: 1.4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
SizedBox(height: gap),
|
|
||||||
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Expanded(child: Divider(color: Color(0xFFE4BEBA))),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Text(
|
|
||||||
'Or enter mobile number manually',
|
|
||||||
style: theme.textTheme.labelMedium?.copyWith(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Expanded(child: Divider(color: Color(0xFFE4BEBA))),
|
),
|
||||||
],
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 200),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF3F3F3),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: const Color(0xFFE4BEBA).withOpacity(0.5)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF005FAF),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.verified_user,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Verify instantly with',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Truecaller',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Plus Jakarta Sans',
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text(
|
||||||
|
'+91 98765 43210',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Plus Jakarta Sans',
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_controller.text = '9876543210';
|
||||||
|
setState(() {
|
||||||
|
_isValid = true;
|
||||||
|
});
|
||||||
|
showTopAlert(
|
||||||
|
context,
|
||||||
|
'Phone verified automatically with Truecaller! Joining PozoApp...',
|
||||||
|
icon: Icons.verified_user_outlined,
|
||||||
|
);
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
FadeSlidePageRoute(
|
||||||
|
child: const OtpVerificationScreen(
|
||||||
|
mobileNumber: '9876543210',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.flash_on, size: 18, color: Colors.white),
|
||||||
|
label: const Text(
|
||||||
|
'1-Tap Verification',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF005FAF),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
minimumSize: const Size.fromHeight(56),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
elevation: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'No OTP required. Your phone will be verified automatically.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: gap),
|
SizedBox(height: gap),
|
||||||
|
|
||||||
Text(
|
SlideFadeEntrance(
|
||||||
'Mobile Number',
|
delay: const Duration(milliseconds: 300),
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
child: Column(
|
||||||
color: theme.colorScheme.onSurface,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
Container(
|
|
||||||
height: 56,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.02),
|
|
||||||
blurRadius: 4,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Row(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
children: [
|
||||||
decoration: BoxDecoration(
|
const Expanded(child: Divider(color: Color(0xFFE4BEBA))),
|
||||||
border: Border(
|
Padding(
|
||||||
right: BorderSide(color: theme.colorScheme.outlineVariant),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text(
|
||||||
|
'Or enter mobile number manually',
|
||||||
|
style: theme.textTheme.labelMedium?.copyWith(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
const Expanded(child: Divider(color: Color(0xFFE4BEBA))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: gap),
|
||||||
|
Text(
|
||||||
|
'Mobile Number',
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.02),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 22,
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
height: 14,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: Colors.grey[300]!, width: 0.5),
|
border: Border(
|
||||||
|
right: BorderSide(color: theme.colorScheme.outlineVariant),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(height: 4, color: const Color(0xFFFF9933)),
|
|
||||||
Container(
|
Container(
|
||||||
height: 4,
|
width: 22,
|
||||||
color: Colors.white,
|
height: 14,
|
||||||
child: Center(
|
decoration: BoxDecoration(
|
||||||
child: Container(
|
border: Border.all(color: Colors.grey[300]!, width: 0.5),
|
||||||
width: 2,
|
),
|
||||||
height: 2,
|
child: Column(
|
||||||
decoration: const BoxDecoration(
|
children: [
|
||||||
color: Color(0xFF000080),
|
Container(height: 4, color: const Color(0xFFFF9933)),
|
||||||
shape: BoxShape.circle,
|
Container(
|
||||||
|
height: 4,
|
||||||
|
color: Colors.white,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 2,
|
||||||
|
height: 2,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF000080),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Container(height: 4, color: const Color(0xFF128807)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(height: 4, color: const Color(0xFF128807)),
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'+91',
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
Expanded(
|
||||||
Text(
|
child: TextField(
|
||||||
'+91',
|
controller: _controller,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
keyboardType: TextInputType.phone,
|
||||||
fontWeight: FontWeight.bold,
|
inputFormatters: [
|
||||||
color: theme.colorScheme.onSurface,
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
|
LengthLimitingTextInputFormatter(10),
|
||||||
|
],
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '00000 00000',
|
||||||
|
border: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
filled: false,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.contact_phone, color: Color(0xFFAF101A)),
|
||||||
|
onPressed: _pickContact,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: _controller,
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
LengthLimitingTextInputFormatter(10),
|
|
||||||
],
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: '00000 00000',
|
|
||||||
border: InputBorder.none,
|
|
||||||
enabledBorder: InputBorder.none,
|
|
||||||
focusedBorder: InputBorder.none,
|
|
||||||
filled: false,
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
),
|
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.contact_phone, color: Color(0xFFAF101A)),
|
|
||||||
onPressed: _pickContact,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -577,66 +594,75 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Column(
|
SlideFadeEntrance(
|
||||||
mainAxisSize: MainAxisSize.min,
|
delay: const Duration(milliseconds: 400),
|
||||||
children: [
|
child: Column(
|
||||||
ElevatedButton(
|
mainAxisSize: MainAxisSize.min,
|
||||||
onPressed: _isValid
|
children: [
|
||||||
? () {
|
InteractiveScale(
|
||||||
Navigator.push(
|
hoverScale: _isValid ? 1.03 : 1.0,
|
||||||
context,
|
tapScale: _isValid ? 0.96 : 1.0,
|
||||||
FadeSlidePageRoute(
|
child: ElevatedButton(
|
||||||
child: OtpVerificationScreen(
|
onPressed: _isValid
|
||||||
mobileNumber: _controller.text,
|
? () {
|
||||||
),
|
Navigator.push(
|
||||||
),
|
context,
|
||||||
);
|
FadeSlidePageRoute(
|
||||||
}
|
child: OtpVerificationScreen(
|
||||||
: null,
|
mobileNumber: _controller.text,
|
||||||
style: ElevatedButton.styleFrom(
|
),
|
||||||
minimumSize: const Size.fromHeight(56),
|
),
|
||||||
backgroundColor: _isValid ? const Color(0xFFAF101A) : Colors.grey[200],
|
);
|
||||||
foregroundColor: _isValid ? Colors.white : Colors.grey[400],
|
}
|
||||||
elevation: _isValid ? 2 : 0,
|
: null,
|
||||||
shape: RoundedRectangleBorder(
|
style: ElevatedButton.styleFrom(
|
||||||
borderRadius: BorderRadius.circular(8),
|
minimumSize: const Size.fromHeight(56),
|
||||||
),
|
backgroundColor: _isValid ? const Color(0xFFAF101A) : Colors.grey[200],
|
||||||
),
|
foregroundColor: _isValid ? Colors.white : Colors.grey[400],
|
||||||
child: const Text('Send OTP'),
|
elevation: _isValid ? 2 : 0,
|
||||||
),
|
shape: RoundedRectangleBorder(
|
||||||
SizedBox(height: gap / 2),
|
borderRadius: BorderRadius.circular(16),
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () {
|
|
||||||
showTopAlert(
|
|
||||||
context,
|
|
||||||
'Connecting with Pozo Support... Please wait.',
|
|
||||||
icon: Icons.support_agent_outlined,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
minimumSize: const Size.fromHeight(56),
|
|
||||||
side: const BorderSide(color: Color(0xFFAF101A), width: 2),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.forum, color: Color(0xFFAF101A), size: 18),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text(
|
|
||||||
'Help / Talk to Support',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFFAF101A),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
child: const Text('Send OTP'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: gap / 2),
|
||||||
],
|
InteractiveScale(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () {
|
||||||
|
showTopAlert(
|
||||||
|
context,
|
||||||
|
'Connecting with Pozo Support... Please wait.',
|
||||||
|
icon: Icons.support_agent_outlined,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
minimumSize: const Size.fromHeight(56),
|
||||||
|
side: const BorderSide(color: Color(0xFFAF101A), width: 2),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.forum, color: Color(0xFFAF101A), size: 18),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Text(
|
||||||
|
'Help / Talk to Support',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFAF101A),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -123,184 +123,212 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// Alert envelope pulse symbol
|
// Alert envelope pulse symbol with SlideFadeEntrance
|
||||||
Center(
|
SlideFadeEntrance(
|
||||||
child: Container(
|
delay: Duration.zero,
|
||||||
width: 96,
|
child: Center(
|
||||||
height: 96,
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
width: 96,
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
height: 96,
|
||||||
shape: BoxShape.circle,
|
decoration: BoxDecoration(
|
||||||
),
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
child: Center(
|
shape: BoxShape.circle,
|
||||||
child: Container(
|
|
||||||
width: 72,
|
|
||||||
height: 72,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.06),
|
|
||||||
blurRadius: 10,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.sms,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
size: 38,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
child: Center(
|
||||||
),
|
child: Container(
|
||||||
),
|
width: 72,
|
||||||
const SizedBox(height: 16),
|
height: 72,
|
||||||
Text(
|
decoration: BoxDecoration(
|
||||||
'Verifying your number',
|
color: Colors.white,
|
||||||
textAlign: TextAlign.center,
|
borderRadius: BorderRadius.circular(16),
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
boxShadow: [
|
||||||
fontWeight: FontWeight.bold,
|
BoxShadow(
|
||||||
),
|
color: Colors.black.withOpacity(0.06),
|
||||||
),
|
blurRadius: 10,
|
||||||
const SizedBox(height: 16),
|
)
|
||||||
Row(
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Sent code to $displayMobile",
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => Navigator.pop(context),
|
|
||||||
child: Text(
|
|
||||||
'Edit',
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// 6 Custom Inputs Fields
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: List.generate(11, (index) {
|
|
||||||
if (index.isOdd) {
|
|
||||||
return const SizedBox(width: 8);
|
|
||||||
}
|
|
||||||
final fieldIndex = index ~/ 2;
|
|
||||||
return SizedBox(
|
|
||||||
width: 44,
|
|
||||||
child: TextField(
|
|
||||||
controller: _controllers[fieldIndex],
|
|
||||||
focusNode: _focusNodes[fieldIndex],
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
maxLength: 1,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
counterText: '',
|
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
borderSide: BorderSide(color: theme.colorScheme.outlineVariant),
|
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
child: Icon(
|
||||||
borderRadius: BorderRadius.circular(8),
|
Icons.sms,
|
||||||
borderSide: BorderSide(color: theme.colorScheme.primary, width: 2),
|
color: theme.colorScheme.primary,
|
||||||
|
size: 38,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
|
||||||
if (value.isNotEmpty && fieldIndex < 5) {
|
|
||||||
_focusNodes[fieldIndex + 1].requestFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Auto detect loader
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.surfaceContainerLow,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 100),
|
||||||
|
child: Text(
|
||||||
|
'Verifying your number',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 150),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(
|
Text(
|
||||||
'Auto-detecting OTP...',
|
"Sent code to $displayMobile",
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodyMedium,
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
InteractiveScale(
|
||||||
|
onTap: () => Navigator.pop(context),
|
||||||
|
child: Text(
|
||||||
|
'Edit',
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Resending timer action column
|
// 6 Custom Inputs Fields with SlideFadeEntrance
|
||||||
Row(
|
SlideFadeEntrance(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
delay: const Duration(milliseconds: 200),
|
||||||
children: [
|
child: Row(
|
||||||
const Icon(Icons.schedule, size: 16, color: Colors.grey),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
const SizedBox(width: 6),
|
children: List.generate(11, (index) {
|
||||||
Text(
|
if (index.isOdd) {
|
||||||
_timeLeft > 0
|
return const SizedBox(width: 8);
|
||||||
? 'Resend code in ${_getFormattedTime()}'
|
}
|
||||||
: 'Resend Code via SMS',
|
final fieldIndex = index ~/ 2;
|
||||||
style: theme.textTheme.bodyMedium,
|
return SizedBox(
|
||||||
|
width: 44,
|
||||||
|
child: TextField(
|
||||||
|
controller: _controllers[fieldIndex],
|
||||||
|
focusNode: _focusNodes[fieldIndex],
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLength: 1,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
counterText: '',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(color: theme.colorScheme.outlineVariant),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(color: theme.colorScheme.primary, width: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value.isNotEmpty && fieldIndex < 5) {
|
||||||
|
_focusNodes[fieldIndex + 1].requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Auto detect loader card with SlideFadeEntrance
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 250),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.surfaceContainerLow,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
],
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Auto-detecting OTP...',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Resending timer action row
|
||||||
|
SlideFadeEntrance(
|
||||||
|
delay: const Duration(milliseconds: 300),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.schedule, size: 16, color: Colors.grey),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
_timeLeft > 0
|
||||||
|
? 'Resend code in ${_getFormattedTime()}'
|
||||||
|
: 'Resend Code via SMS',
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// WhatsApp custom CTA button
|
// WhatsApp custom CTA button with InteractiveScale
|
||||||
ElevatedButton(
|
SlideFadeEntrance(
|
||||||
onPressed: () {},
|
delay: const Duration(milliseconds: 350),
|
||||||
style: ElevatedButton.styleFrom(
|
child: InteractiveScale(
|
||||||
backgroundColor: Colors.white,
|
child: OutlinedButton.icon(
|
||||||
foregroundColor: Colors.black87,
|
onPressed: () {
|
||||||
side: const BorderSide(color: Color(0xFFE2E2E2)),
|
showTopAlert(
|
||||||
elevation: 1,
|
context,
|
||||||
minimumSize: const Size(200, 48),
|
'WhatsApp verification request sent!',
|
||||||
),
|
icon: Icons.chat_bubble_outline,
|
||||||
child: Row(
|
);
|
||||||
mainAxisSize: MainAxisSize.min,
|
},
|
||||||
children: const [
|
icon: const Icon(Icons.forum, color: Colors.green, size: 18),
|
||||||
Text(
|
label: const Text(
|
||||||
'Resend via WhatsApp',
|
'Resend via WhatsApp',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
style: OutlinedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
side: const BorderSide(color: Color(0xFFE2E2E2)),
|
||||||
|
elevation: 1,
|
||||||
|
minimumSize: const Size(220, 48),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
@ -326,28 +354,42 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildFooterAction(Icons.help_outline, 'Help'),
|
_buildFooterAction(Icons.help_outline, 'Help', () {
|
||||||
_buildFooterAction(Icons.save, 'Save Draft'),
|
showTopAlert(
|
||||||
TextButton.icon(
|
context,
|
||||||
onPressed: () {
|
'Support agent contacted. We will call you shortly.',
|
||||||
Navigator.push(
|
icon: Icons.support_agent_outlined,
|
||||||
context,
|
);
|
||||||
FadeSlidePageRoute(
|
}),
|
||||||
child: const BusinessSetupScreen(),
|
_buildFooterAction(Icons.save, 'Save Draft', () {
|
||||||
|
showTopAlert(
|
||||||
|
context,
|
||||||
|
'Onboarding draft saved successfully!',
|
||||||
|
icon: Icons.save_outlined,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
InteractiveScale(
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
FadeSlidePageRoute(
|
||||||
|
child: const BusinessSetupScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Text(
|
||||||
|
'Verify Manually',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
label: const Icon(Icons.arrow_forward, size: 16),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: theme.colorScheme.primary,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Text(
|
|
||||||
'Verify Manually',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
label: const Icon(Icons.arrow_forward, size: 16),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
backgroundColor: theme.colorScheme.primary,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -363,21 +405,24 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFooterAction(IconData icon, String label) {
|
Widget _buildFooterAction(IconData icon, String label, VoidCallback onTap) {
|
||||||
return Column(
|
return InteractiveScale(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
onTap: onTap,
|
||||||
children: [
|
child: Column(
|
||||||
Icon(icon, color: Colors.grey[600], size: 22),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
const SizedBox(height: 4),
|
children: [
|
||||||
Text(
|
Icon(icon, color: Colors.grey[600], size: 22),
|
||||||
label,
|
const SizedBox(height: 4),
|
||||||
style: TextStyle(
|
Text(
|
||||||
fontSize: 10,
|
label,
|
||||||
fontWeight: FontWeight.bold,
|
style: TextStyle(
|
||||||
color: Colors.grey[600],
|
fontSize: 10,
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
)
|
color: Colors.grey[600],
|
||||||
],
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTi
|
||||||
late Animation<double> _cardOpacityAnimation;
|
late Animation<double> _cardOpacityAnimation;
|
||||||
late Animation<double> _btnSlideAnimation;
|
late Animation<double> _btnSlideAnimation;
|
||||||
late Animation<double> _btnOpacityAnimation;
|
late Animation<double> _btnOpacityAnimation;
|
||||||
double _btnScale = 1.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -382,53 +381,42 @@ class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTi
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: InteractiveScale(
|
||||||
onEnter: (_) => setState(() => _btnScale = 1.03),
|
child: ElevatedButton(
|
||||||
onExit: (_) => setState(() => _btnScale = 1.0),
|
onPressed: () {
|
||||||
child: AnimatedScale(
|
showTopAlert(
|
||||||
scale: _btnScale,
|
context,
|
||||||
duration: const Duration(milliseconds: 150),
|
'Launching Register Dashboard... Enjoy billing!',
|
||||||
child: GestureDetector(
|
icon: Icons.rocket_launch_outlined,
|
||||||
onTapDown: (_) => setState(() => _btnScale = 0.96),
|
);
|
||||||
onTapUp: (_) => setState(() => _btnScale = 1.03),
|
Navigator.push(
|
||||||
onTapCancel: () => setState(() => _btnScale = 1.0),
|
context,
|
||||||
child: ElevatedButton(
|
FadeSlidePageRoute(
|
||||||
onPressed: () {
|
child: BillingDashboardScreen(
|
||||||
showTopAlert(
|
businessName: widget.businessName,
|
||||||
context,
|
businessType: widget.businessType,
|
||||||
'Launching Register Dashboard... Enjoy billing!',
|
gstin: widget.gstin,
|
||||||
icon: Icons.rocket_launch_outlined,
|
barcodeScanner: widget.barcodeScanner,
|
||||||
);
|
kotManagement: widget.kotManagement,
|
||||||
Navigator.push(
|
inventoryAlerts: widget.inventoryAlerts,
|
||||||
context,
|
|
||||||
FadeSlidePageRoute(
|
|
||||||
child: BillingDashboardScreen(
|
|
||||||
businessName: widget.businessName,
|
|
||||||
businessType: widget.businessType,
|
|
||||||
gstin: widget.gstin,
|
|
||||||
barcodeScanner: widget.barcodeScanner,
|
|
||||||
kotManagement: widget.kotManagement,
|
|
||||||
inventoryAlerts: widget.inventoryAlerts,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
minimumSize: const Size.fromHeight(56),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
);
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
},
|
||||||
children: const [
|
style: ElevatedButton.styleFrom(
|
||||||
Icon(Icons.receipt, size: 20),
|
minimumSize: const Size.fromHeight(56),
|
||||||
SizedBox(width: 8),
|
shape: RoundedRectangleBorder(
|
||||||
Text('Create Your First Bill'),
|
borderRadius: BorderRadius.circular(16),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.receipt, size: 20),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Create Your First Bill'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ class _WelcomeScreenState extends State<WelcomeScreen> with SingleTickerProvider
|
||||||
late Animation<double> _splashContainerOpacityAnimation;
|
late Animation<double> _splashContainerOpacityAnimation;
|
||||||
late Animation<double> _contentFadeAnimation;
|
late Animation<double> _contentFadeAnimation;
|
||||||
late Animation<Offset> _contentSlideAnimation;
|
late Animation<Offset> _contentSlideAnimation;
|
||||||
double _btnScale = 1.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -491,37 +490,25 @@ class _WelcomeScreenState extends State<WelcomeScreen> with SingleTickerProvider
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: context.isMobile ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
crossAxisAlignment: context.isMobile ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
MouseRegion(
|
InteractiveScale(
|
||||||
onEnter: (_) => setState(() => _btnScale = 1.03),
|
child: ElevatedButton(
|
||||||
onExit: (_) => setState(() => _btnScale = 1.0),
|
onPressed: () {
|
||||||
child: GestureDetector(
|
Navigator.push(
|
||||||
onTapDown: (_) => setState(() => _btnScale = 0.95),
|
context,
|
||||||
onTapUp: (_) => setState(() => _btnScale = 1.03),
|
FadeSlidePageRoute(child: const MobileLoginScreen()),
|
||||||
onTapCancel: () => setState(() => _btnScale = 1.0),
|
);
|
||||||
child: AnimatedScale(
|
},
|
||||||
scale: _btnScale,
|
style: ElevatedButton.styleFrom(
|
||||||
duration: const Duration(milliseconds: 150),
|
minimumSize: const Size(260, 54),
|
||||||
curve: Curves.easeOut,
|
),
|
||||||
child: ElevatedButton(
|
child: Row(
|
||||||
onPressed: () {
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Navigator.push(
|
mainAxisSize: MainAxisSize.min,
|
||||||
context,
|
children: const [
|
||||||
FadeSlidePageRoute(child: const MobileLoginScreen()),
|
Text('Get Started'),
|
||||||
);
|
SizedBox(width: 8),
|
||||||
},
|
Icon(Icons.arrow_forward, size: 18),
|
||||||
style: ElevatedButton.styleFrom(
|
],
|
||||||
minimumSize: const Size(260, 54),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: const [
|
|
||||||
Text('Get Started'),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Icon(Icons.arrow_forward, size: 18),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ class PozoTheme {
|
||||||
minimumSize: const Size.fromHeight(54),
|
minimumSize: const Size.fromHeight(54),
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
textStyle: GoogleFonts.plusJakartaSans(
|
textStyle: GoogleFonts.plusJakartaSans(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
|
||||||
|
|
@ -41,14 +41,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
contacts_service:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: contacts_service
|
|
||||||
sha256: f6d5ea33b31dfcdcd2e65d8abdc836502e04ddb0f66a96aa726fa9891ea9671e
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.3"
|
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -126,6 +118,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_contacts:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_contacts
|
||||||
|
sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.9+2"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|
@ -424,14 +424,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
quiver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: quiver
|
|
||||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.2.2"
|
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ dependencies:
|
||||||
google_fonts: ^5.1.0
|
google_fonts: ^5.1.0
|
||||||
image_picker: ^1.0.4
|
image_picker: ^1.0.4
|
||||||
permission_handler: ^11.0.1
|
permission_handler: ^11.0.1
|
||||||
contacts_service: ^0.6.3
|
flutter_contacts: ^1.1.9
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue