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:
Sridhar R 2026-06-10 14:47:50 +05:30
parent ac1294a150
commit 3f2a804a31
12 changed files with 1580 additions and 1256 deletions

View File

@ -11,6 +11,28 @@ rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
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 {
project.evaluationDependsOn(":app")

View File

@ -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,
),
),
);
}
}

View File

@ -248,6 +248,7 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
padding: const EdgeInsets.only(right: 8.0),
child: Directionality(
textDirection: TextDirection.rtl,
child: InteractiveScale(
child: ElevatedButton.icon(
onPressed: _simulateBarcodeScan,
style: ElevatedButton.styleFrom(
@ -267,6 +268,7 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
),
),
),
),
if (widget.kotManagement)
Container(
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 12),
@ -407,7 +409,13 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
final isLowStock = widget.inventoryAlerts && remainingStock <= 3;
final isOutOfStock = widget.inventoryAlerts && remainingStock <= 0;
return InkWell(
return SlideFadeEntrance(
delay: Duration(milliseconds: index * 40),
duration: const Duration(milliseconds: 300),
child: InteractiveScale(
tapScale: isOutOfStock ? 1.0 : 0.95,
hoverScale: isOutOfStock ? 1.0 : 1.03,
child: InkWell(
onTap: isOutOfStock ? null : () => _handleAddItem(prod),
borderRadius: BorderRadius.circular(16),
child: Container(
@ -498,6 +506,8 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
],
),
),
),
),
);
},
),
@ -655,12 +665,18 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
const Divider(color: Color(0xFF334155), height: 16),
_buildComputationCalculations(theme),
const SizedBox(height: 10),
ElevatedButton(
InteractiveScale(
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
disabledBackgroundColor: const Color(0xFF1E293B),
minimumSize: const Size.fromHeight(48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
@ -670,6 +686,7 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
Text('Generate Invoice'),
],
),
),
)
]
],
@ -751,11 +768,17 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
)
],
),
ElevatedButton(
InteractiveScale(
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
minimumSize: const Size(180, 44),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
@ -765,6 +788,7 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
Text('Review & Invoice', style: TextStyle(fontSize: 12)),
],
),
),
)
],
)
@ -937,7 +961,8 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
),
),
const SizedBox(height: 16),
ElevatedButton.icon(
InteractiveScale(
child: ElevatedButton.icon(
onPressed: () {
showTopAlert(
context,
@ -951,17 +976,26 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
backgroundColor: Colors.black,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(42),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
),
const SizedBox(height: 8),
ElevatedButton(
InteractiveScale(
child: ElevatedButton(
onPressed: _closeReceiptAndReset,
style: ElevatedButton.styleFrom(
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)),
),
),
],
),
),

View File

@ -146,7 +146,8 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
Widget _buildDocChip(String label, IconData icon) {
final theme = Theme.of(context);
final isSelected = _selectedDocs.contains(label);
return InkWell(
return InteractiveScale(
child: InkWell(
onTap: () {
setState(() {
if (isSelected) {
@ -188,6 +189,7 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
],
),
),
),
);
}
@ -337,7 +339,8 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
InteractiveScale(
child: ElevatedButton.icon(
onPressed: () => _handleImagePick(docName, ImageSource.camera),
icon: const Icon(Icons.camera_alt, size: 14),
label: const Text('Take Photo'),
@ -348,11 +351,13 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
elevation: 1,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(16)),
),
),
),
const SizedBox(width: 8),
OutlinedButton.icon(
InteractiveScale(
child: OutlinedButton.icon(
onPressed: () => _handleImagePick(docName, ImageSource.gallery),
icon: const Icon(Icons.photo_library, size: 14),
label: const Text('From Gallery'),
@ -361,7 +366,8 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
side: const BorderSide(color: Color(0xFFAF101A)),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(16)),
),
),
),
],
@ -438,6 +444,11 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SlideFadeEntrance(
delay: Duration.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -452,8 +463,16 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
'Verify your business details to start selling.',
style: theme.textTheme.bodyMedium,
),
],
),
),
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(
@ -469,8 +488,16 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
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(
@ -479,7 +506,6 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
),
const SizedBox(height: 10),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
@ -494,7 +520,8 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
final cat = _categories[index];
final isSelected = _selectedCategory == cat['id'];
return InkWell(
return InteractiveScale(
child: InkWell(
onTap: () {
setState(() {
_selectedCategory = cat['id'];
@ -545,11 +572,20 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
],
),
),
),
);
},
),
],
),
),
const SizedBox(height: 16),
SlideFadeEntrance(
delay: const Duration(milliseconds: 260),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -583,7 +619,8 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
),
const SizedBox(width: 12),
ElevatedButton(
InteractiveScale(
child: ElevatedButton(
onPressed: () {
showTopAlert(
context,
@ -602,6 +639,7 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
child: const Text('Verify'),
),
),
],
),
const SizedBox(height: 6),
@ -609,8 +647,16 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
"Auto-fetches address & legal name on verification.",
style: TextStyle(fontSize: 11, color: Colors.grey[500]),
),
],
),
),
const SizedBox(height: 16),
SlideFadeEntrance(
delay: const Duration(milliseconds: 340),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Shop Location *',
style: theme.textTheme.labelSmall?.copyWith(
@ -619,7 +665,8 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
),
const SizedBox(height: 8),
InkWell(
InteractiveScale(
child: InkWell(
onTap: () {
showTopAlert(
context,
@ -683,8 +730,17 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
),
),
),
],
),
),
const SizedBox(height: 16),
SlideFadeEntrance(
delay: const Duration(milliseconds: 420),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Identity / License Documents (Select all that apply)',
style: theme.textTheme.labelSmall?.copyWith(
@ -704,8 +760,15 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
_buildDocChip('PAN', Icons.credit_card),
],
),
],
),
),
const SizedBox(height: 8),
..._selectedDocs.map((doc) {
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;
@ -713,9 +776,16 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
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(
@ -724,10 +794,12 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
),
const SizedBox(height: 8),
InkWell(
InteractiveScale(
child: InkWell(
onTap: () {
_showFacadePhotoOptions();
},
borderRadius: BorderRadius.circular(16),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 24),
@ -774,6 +846,10 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
),
),
),
],
),
),
const SizedBox(height: 12),
],
),
@ -781,8 +857,13 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
),
),
Padding(
SlideFadeEntrance(
delay: const Duration(milliseconds: 660),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: InteractiveScale(
hoverScale: _isNameValid ? 1.03 : 1.0,
tapScale: _isNameValid ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _isNameValid
? () {
@ -803,9 +884,14 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
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'),
),
),
),
)
],
),

View File

@ -116,6 +116,11 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SlideFadeEntrance(
delay: Duration.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -130,10 +135,15 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
'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),
// Toggle 1: Barcode
_buildToggleCard(
SlideFadeEntrance(
delay: const Duration(milliseconds: 100),
child: _buildToggleCard(
Icons.qr_code_scanner,
'Barcode Scanner',
'Do you use a Barcode Scanner?',
@ -141,10 +151,13 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
(val) => setState(() => _barcode = val),
theme,
),
),
const SizedBox(height: 12),
// Toggle 2: KOT
_buildToggleCard(
SlideFadeEntrance(
delay: const Duration(milliseconds: 180),
child: _buildToggleCard(
Icons.receipt_long,
'KOT Management',
'Do you need Kitchen Order Ticket management?',
@ -152,10 +165,13 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
(val) => setState(() => _kot = val),
theme,
),
),
const SizedBox(height: 12),
// Toggle 3: Inventory stock tracking
_buildToggleCard(
SlideFadeEntrance(
delay: const Duration(milliseconds: 260),
child: _buildToggleCard(
Icons.inventory_2,
'Inventory Alerts',
'Get notified when stock is running low.',
@ -163,6 +179,7 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
(val) => setState(() => _inventory = val),
theme,
),
),
],
),
),
@ -170,8 +187,11 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
),
// Pinned action keys
Padding(
SlideFadeEntrance(
delay: const Duration(milliseconds: 340),
child: Padding(
padding: EdgeInsets.all(padding),
child: InteractiveScale(
child: ElevatedButton(
onPressed: () {
Navigator.push(
@ -190,9 +210,14 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(54),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Text('Save & Continue'),
),
),
),
)
],
),
@ -210,7 +235,9 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
ValueChanged<bool> onChanged,
ThemeData theme,
) {
return Container(
return InteractiveScale(
onTap: () => onChanged(!value),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
@ -269,6 +296,7 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
),
],
),
),
);
}
}

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
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 '../route_transitions.dart';
import 'otp_verification_screen.dart';
@ -16,8 +16,8 @@ class MockContact {
}
class MockPhone {
final String? value;
MockPhone(this.value);
final String number;
MockPhone(this.number);
}
class MobileLoginScreen extends StatefulWidget {
@ -103,7 +103,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
];
} else {
try {
final List<Contact> list = await ContactsService.getContacts(withThumbnails: false);
final List<Contact> list = await FlutterContacts.getContacts(withProperties: true, withPhoto: false);
return list;
} catch (e) {
return [];
@ -166,7 +166,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
setDialogState(() {
filteredContacts = allContacts.where((contact) {
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());
}).toList();
});
@ -183,7 +183,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
itemBuilder: (context, index) {
final contact = filteredContacts[index];
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(
title: Text(name),
subtitle: Text(phone),
@ -253,6 +253,11 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SlideFadeEntrance(
delay: Duration.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -296,7 +301,6 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
}),
),
const SizedBox(height: 16),
Text(
'Welcome Back',
style: theme.textTheme.headlineLarge?.copyWith(
@ -308,9 +312,14 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
'Enter your mobile number to sign in or create a new account.',
style: theme.textTheme.bodyMedium,
),
],
),
),
const SizedBox(height: 16),
Container(
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),
@ -332,10 +341,13 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
],
),
),
),
const SizedBox(height: 16),
Container(
SlideFadeEntrance(
delay: const Duration(milliseconds: 200),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFF3F3F3),
@ -446,9 +458,15 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
],
),
),
),
SizedBox(height: gap),
SlideFadeEntrance(
delay: const Duration(milliseconds: 300),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Expanded(child: Divider(color: Color(0xFFE4BEBA))),
@ -466,9 +484,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
const Expanded(child: Divider(color: Color(0xFFE4BEBA))),
],
),
SizedBox(height: gap),
Text(
'Mobile Number',
style: theme.textTheme.labelSmall?.copyWith(
@ -477,7 +493,6 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
),
),
const SizedBox(height: 8),
Container(
height: 56,
decoration: BoxDecoration(
@ -541,7 +556,6 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
],
),
),
Expanded(
child: TextField(
controller: _controller,
@ -571,16 +585,24 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
],
),
),
],
),
),
SizedBox(height: gap * 2),
],
),
),
),
Column(
SlideFadeEntrance(
delay: const Duration(milliseconds: 400),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
InteractiveScale(
hoverScale: _isValid ? 1.03 : 1.0,
tapScale: _isValid ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _isValid
? () {
Navigator.push(
@ -599,13 +621,15 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
foregroundColor: _isValid ? Colors.white : Colors.grey[400],
elevation: _isValid ? 2 : 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(16),
),
),
child: const Text('Send OTP'),
),
),
SizedBox(height: gap / 2),
OutlinedButton(
InteractiveScale(
child: OutlinedButton(
onPressed: () {
showTopAlert(
context,
@ -617,7 +641,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
minimumSize: const Size.fromHeight(56),
side: const BorderSide(color: Color(0xFFAF101A), width: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(16),
),
),
child: Row(
@ -636,8 +660,10 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
],
),
),
),
],
),
),
],
),
),

View File

@ -123,8 +123,10 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Alert envelope pulse symbol
Center(
// Alert envelope pulse symbol with SlideFadeEntrance
SlideFadeEntrance(
delay: Duration.zero,
child: Center(
child: Container(
width: 96,
height: 96,
@ -155,16 +157,22 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
),
),
),
),
const SizedBox(height: 16),
Text(
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),
Row(
SlideFadeEntrance(
delay: const Duration(milliseconds: 150),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@ -173,7 +181,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
style: theme.textTheme.bodyMedium,
),
const SizedBox(width: 8),
GestureDetector(
InteractiveScale(
onTap: () => Navigator.pop(context),
child: Text(
'Edit',
@ -186,11 +194,14 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
),
],
),
),
const SizedBox(height: 16),
const SizedBox(height: 20),
// 6 Custom Inputs Fields
Row(
// 6 Custom Inputs Fields with SlideFadeEntrance
SlideFadeEntrance(
delay: const Duration(milliseconds: 200),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(11, (index) {
if (index.isOdd) {
@ -213,11 +224,11 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
counterText: '',
contentPadding: const EdgeInsets.symmetric(vertical: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: theme.colorScheme.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: theme.colorScheme.primary, width: 2),
),
),
@ -230,15 +241,18 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
);
}),
),
),
const SizedBox(height: 16),
const SizedBox(height: 24),
// Auto detect loader
Container(
// 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(12),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
@ -261,11 +275,14 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
],
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 24),
// Resending timer action column
Row(
// 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),
@ -278,29 +295,40 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
),
],
),
),
const SizedBox(height: 16),
// WhatsApp custom CTA button
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
side: const BorderSide(color: Color(0xFFE2E2E2)),
elevation: 1,
minimumSize: const Size(200, 48),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text(
// WhatsApp custom CTA button with InteractiveScale
SlideFadeEntrance(
delay: const Duration(milliseconds: 350),
child: InteractiveScale(
child: OutlinedButton.icon(
onPressed: () {
showTopAlert(
context,
'WhatsApp verification request sent!',
icon: Icons.chat_bubble_outline,
);
},
icon: const Icon(Icons.forum, color: Colors.green, size: 18),
label: const Text(
'Resend via WhatsApp',
style: TextStyle(
fontSize: 13,
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,9 +354,22 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildFooterAction(Icons.help_outline, 'Help'),
_buildFooterAction(Icons.save, 'Save Draft'),
TextButton.icon(
_buildFooterAction(Icons.help_outline, 'Help', () {
showTopAlert(
context,
'Support agent contacted. We will call you shortly.',
icon: Icons.support_agent_outlined,
);
}),
_buildFooterAction(Icons.save, 'Save Draft', () {
showTopAlert(
context,
'Onboarding draft saved successfully!',
icon: Icons.save_outlined,
);
}),
InteractiveScale(
child: TextButton.icon(
onPressed: () {
Navigator.push(
context,
@ -347,7 +388,8 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
backgroundColor: theme.colorScheme.primary,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(16),
),
),
),
)
@ -363,8 +405,10 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
);
}
Widget _buildFooterAction(IconData icon, String label) {
return Column(
Widget _buildFooterAction(IconData icon, String label, VoidCallback onTap) {
return InteractiveScale(
onTap: onTap,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Colors.grey[600], size: 22),
@ -378,6 +422,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
),
)
],
),
);
}
}

View File

@ -32,7 +32,6 @@ class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTi
late Animation<double> _cardOpacityAnimation;
late Animation<double> _btnSlideAnimation;
late Animation<double> _btnOpacityAnimation;
double _btnScale = 1.0;
@override
void initState() {
@ -382,16 +381,7 @@ class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTi
),
);
},
child: MouseRegion(
onEnter: (_) => setState(() => _btnScale = 1.03),
onExit: (_) => setState(() => _btnScale = 1.0),
child: AnimatedScale(
scale: _btnScale,
duration: const Duration(milliseconds: 150),
child: GestureDetector(
onTapDown: (_) => setState(() => _btnScale = 0.96),
onTapUp: (_) => setState(() => _btnScale = 1.03),
onTapCancel: () => setState(() => _btnScale = 1.0),
child: InteractiveScale(
child: ElevatedButton(
onPressed: () {
showTopAlert(
@ -416,7 +406,7 @@ class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTi
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(56),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(16),
),
),
child: Row(
@ -430,8 +420,6 @@ class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTi
),
),
),
),
),
const SizedBox(height: 24),
],
),

View File

@ -17,7 +17,6 @@ class _WelcomeScreenState extends State<WelcomeScreen> with SingleTickerProvider
late Animation<double> _splashContainerOpacityAnimation;
late Animation<double> _contentFadeAnimation;
late Animation<Offset> _contentSlideAnimation;
double _btnScale = 1.0;
@override
void initState() {
@ -491,17 +490,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> with SingleTickerProvider
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: context.isMobile ? CrossAxisAlignment.center : CrossAxisAlignment.start,
children: [
MouseRegion(
onEnter: (_) => setState(() => _btnScale = 1.03),
onExit: (_) => setState(() => _btnScale = 1.0),
child: GestureDetector(
onTapDown: (_) => setState(() => _btnScale = 0.95),
onTapUp: (_) => setState(() => _btnScale = 1.03),
onTapCancel: () => setState(() => _btnScale = 1.0),
child: AnimatedScale(
scale: _btnScale,
duration: const Duration(milliseconds: 150),
curve: Curves.easeOut,
InteractiveScale(
child: ElevatedButton(
onPressed: () {
Navigator.push(
@ -523,8 +512,6 @@ class _WelcomeScreenState extends State<WelcomeScreen> with SingleTickerProvider
),
),
),
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: context.isMobile ? MainAxisAlignment.center : MainAxisAlignment.start,

View File

@ -122,7 +122,7 @@ class PozoTheme {
minimumSize: const Size.fromHeight(54),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(16),
),
textStyle: GoogleFonts.plusJakartaSans(
fontSize: 16,

View File

@ -41,14 +41,6 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -126,6 +118,14 @@ packages:
description: flutter
source: sdk
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:
dependency: "direct dev"
description:
@ -424,14 +424,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
quiver:
dependency: transitive
description:
name: quiver
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.dev"
source: hosted
version: "3.2.2"
sky_engine:
dependency: transitive
description: flutter

View File

@ -12,7 +12,7 @@ dependencies:
google_fonts: ^5.1.0
image_picker: ^1.0.4
permission_handler: ^11.0.1
contacts_service: ^0.6.3
flutter_contacts: ^1.1.9
dev_dependencies:
flutter_test: