diff --git a/flutter_app/assets/sounds/click.mp3 b/flutter_app/assets/sounds/click.mp3
new file mode 100644
index 0000000..44b2df6
Binary files /dev/null and b/flutter_app/assets/sounds/click.mp3 differ
diff --git a/flutter_app/assets/sounds/error.mp3 b/flutter_app/assets/sounds/error.mp3
new file mode 100644
index 0000000..3fe0de5
Binary files /dev/null and b/flutter_app/assets/sounds/error.mp3 differ
diff --git a/flutter_app/assets/sounds/select.mp3 b/flutter_app/assets/sounds/select.mp3
new file mode 100644
index 0000000..88c535b
Binary files /dev/null and b/flutter_app/assets/sounds/select.mp3 differ
diff --git a/flutter_app/assets/sounds/success.mp3 b/flutter_app/assets/sounds/success.mp3
new file mode 100644
index 0000000..629e4b6
Binary files /dev/null and b/flutter_app/assets/sounds/success.mp3 differ
diff --git a/flutter_app/ios/Runner/Info.plist b/flutter_app/ios/Runner/Info.plist
index 0b8b011..8c9cbf0 100644
--- a/flutter_app/ios/Runner/Info.plist
+++ b/flutter_app/ios/Runner/Info.plist
@@ -51,5 +51,13 @@
PozoApp requires photo gallery access to upload documents and shop facade images.
NSContactsUsageDescription
PozoApp requires contacts access to let you search and auto-fill mobile numbers during registration and billing.
+ NSLocationWhenInUseUsageDescription
+ PozoApp needs your location to automatically fetch and verify your shop's address.
+ NSLocationAlwaysUsageDescription
+ PozoApp needs your location to automatically fetch and verify your shop's address.
+ LSApplicationQueriesSchemes
+
+ tel
+
diff --git a/flutter_app/lib/feedback_helper.dart b/flutter_app/lib/feedback_helper.dart
new file mode 100644
index 0000000..a70a6ac
--- /dev/null
+++ b/flutter_app/lib/feedback_helper.dart
@@ -0,0 +1,79 @@
+import 'package:flutter/services.dart';
+import 'package:audioplayers/audioplayers.dart';
+import 'package:vibration/vibration.dart';
+import 'package:flutter/foundation.dart' show kIsWeb, debugPrint;
+
+class AppFeedback {
+ static final AudioPlayer _player = AudioPlayer();
+
+ /// Play a sound from assets/sounds/
+ static Future playSound(String fileName) async {
+ try {
+ // AudioPlayer play from assets/sounds/fileName
+ await _player.play(AssetSource('sounds/$fileName'));
+ } catch (e) {
+ // Web might throw security exception if player triggers before user interaction
+ // or other runtime issues. Catching prevents app crashes.
+ debugPrint('Audio playback error for $fileName: $e');
+ }
+ }
+
+ /// Light haptic feedback + subtle click sound
+ static void click() {
+ HapticFeedback.lightImpact();
+ playSound('click.mp3');
+ }
+
+ /// Medium haptic + selection sound
+ static void select() {
+ HapticFeedback.mediumImpact();
+ playSound('select.mp3');
+ }
+
+ /// Celebration sound + strong haptic vibration
+ static void success() {
+ HapticFeedback.heavyImpact();
+ playSound('success.mp3');
+ }
+
+ /// Wrong input -> error haptic (double vibrate) + error sound
+ static void error() {
+ _doubleVibrate();
+ playSound('error.mp3');
+ }
+
+ /// Light haptic feedback (e.g. chip selections)
+ static void lightHaptic() {
+ HapticFeedback.lightImpact();
+ }
+
+ /// Medium haptic feedback
+ static void mediumHaptic() {
+ HapticFeedback.mediumImpact();
+ }
+
+ /// Heavy haptic feedback
+ static void heavyHaptic() {
+ HapticFeedback.heavyImpact();
+ }
+
+ /// Subtle haptic feedback (e.g. screen navigation)
+ static void subtleHaptic() {
+ HapticFeedback.selectionClick();
+ }
+
+ /// Double vibrate pattern using vibration package
+ static Future _doubleVibrate() async {
+ try {
+ if (!kIsWeb && await Vibration.hasVibrator() == true) {
+ // Double vibrate pattern: wait 0, vibrate 100ms, wait 100ms, vibrate 100ms
+ Vibration.vibrate(pattern: [0, 100, 100, 100]);
+ } else {
+ // Fallback to native HapticFeedback
+ HapticFeedback.mediumImpact();
+ }
+ } catch (e) {
+ HapticFeedback.mediumImpact();
+ }
+ }
+}
diff --git a/flutter_app/lib/route_transitions.dart b/flutter_app/lib/route_transitions.dart
index 6a59a28..8056224 100644
--- a/flutter_app/lib/route_transitions.dart
+++ b/flutter_app/lib/route_transitions.dart
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
+import 'feedback_helper.dart';
class FadeSlidePageRoute extends PageRouteBuilder {
final Widget child;
@@ -33,7 +34,9 @@ class FadeSlidePageRoute extends PageRouteBuilder {
},
transitionDuration: const Duration(milliseconds: 300),
reverseTransitionDuration: const Duration(milliseconds: 200),
- );
+ ) {
+ AppFeedback.subtleHaptic();
+ }
}
void showTopAlert(
diff --git a/flutter_app/lib/screens/billing_dashboard_screen.dart b/flutter_app/lib/screens/billing_dashboard_screen.dart
index 3207a67..b4ca602 100644
--- a/flutter_app/lib/screens/billing_dashboard_screen.dart
+++ b/flutter_app/lib/screens/billing_dashboard_screen.dart
@@ -1,6 +1,7 @@
import 'dart:math';
import 'package:flutter/material.dart';
import '../route_transitions.dart';
+import '../feedback_helper.dart';
class ProductItem {
final String id;
@@ -136,6 +137,7 @@ class _BillingDashboardScreenState extends State {
}
void _simulateBarcodeScan() {
+ AppFeedback.click();
if (!widget.barcodeScanner) return;
final random = Random();
final randomProduct = _products[random.nextInt(_products.length)];
@@ -177,6 +179,7 @@ class _BillingDashboardScreenState extends State {
double get _total => _subtotal + _cgst + _sgst;
void _handleReviewAndGenerate() {
+ AppFeedback.success();
final random = Random();
final invoiceVal = 'POZO-${random.nextInt(90000) + 10000}';
setState(() {
@@ -186,6 +189,7 @@ class _BillingDashboardScreenState extends State {
}
void _closeReceiptAndReset() {
+ AppFeedback.click();
setState(() {
_printingInvoice = false;
_billingList.clear();
@@ -206,7 +210,10 @@ class _BillingDashboardScreenState extends State {
elevation: 4,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
- onPressed: () => Navigator.pop(context),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -964,6 +971,7 @@ class _BillingDashboardScreenState extends State {
InteractiveScale(
child: ElevatedButton.icon(
onPressed: () {
+ AppFeedback.click();
showTopAlert(
context,
'Thermal Printed: $_invoiceNumber successfully!',
diff --git a/flutter_app/lib/screens/business_setup_screen.dart b/flutter_app/lib/screens/business_setup_screen.dart
index 6654fd3..c00a3d0 100644
--- a/flutter_app/lib/screens/business_setup_screen.dart
+++ b/flutter_app/lib/screens/business_setup_screen.dart
@@ -2,9 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:permission_handler/permission_handler.dart';
import 'package:image_picker/image_picker.dart';
+import 'package:geolocator/geolocator.dart';
+import 'package:geocoding/geocoding.dart';
import 'dart:ui';
+import 'dart:io';
import '../theme.dart';
import '../route_transitions.dart';
+import '../feedback_helper.dart';
import 'customize_setup_screen.dart';
class BusinessSetupScreen extends StatefulWidget {
@@ -17,10 +21,16 @@ class BusinessSetupScreen extends StatefulWidget {
class _BusinessSetupScreenState extends State {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _gstinController = TextEditingController();
+ final TextEditingController _addressController = TextEditingController();
+
String _selectedCategory = 'supermarket';
bool _isNameValid = false;
+
+ bool _isGstinValid = false;
+ bool _hasGstinInput = false;
+
final Set _selectedDocs = {'GSTIN', 'FSSAI'};
- final Map _uploadedDocs = {};
+ final Map _uploadedDocs = {};
@override
void initState() {
@@ -30,12 +40,30 @@ class _BusinessSetupScreenState extends State {
_isNameValid = _nameController.text.trim().isNotEmpty;
});
});
+ _gstinController.addListener(_validateGstin);
+ }
+
+ void _validateGstin() {
+ final text = _gstinController.text.trim();
+ if (text.isEmpty) {
+ setState(() {
+ _hasGstinInput = false;
+ _isGstinValid = false;
+ });
+ return;
+ }
+ final RegExp gstinRegex = RegExp(r'^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[0-9]{1}Z[A-Z0-9]{1}$');
+ setState(() {
+ _hasGstinInput = true;
+ _isGstinValid = gstinRegex.hasMatch(text);
+ });
}
@override
void dispose() {
_nameController.dispose();
_gstinController.dispose();
+ _addressController.dispose();
super.dispose();
}
@@ -49,10 +77,16 @@ class _BusinessSetupScreenState extends State {
];
Future _handleImagePick(String docName, ImageSource source) async {
+ AppFeedback.click();
final permission = source == ImageSource.camera ? Permission.camera : Permission.photos;
var status = await permission.status;
if (!mounted) return;
+ if (status.isPermanentlyDenied) {
+ _showSettingsDialog(source == ImageSource.camera ? 'Camera' : 'Photos');
+ return;
+ }
+
if (status.isDenied && !kIsWeb) {
final String sourceName = source == ImageSource.camera ? 'Camera' : 'Photos/Gallery';
final bool? proceed = await showDialog(
@@ -64,11 +98,17 @@ class _BusinessSetupScreenState extends State {
),
actions: [
TextButton(
- onPressed: () => Navigator.pop(context, false),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context, false);
+ },
child: const Text('Cancel'),
),
TextButton(
- onPressed: () => Navigator.pop(context, true),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context, true);
+ },
child: const Text('Continue'),
),
],
@@ -79,6 +119,11 @@ class _BusinessSetupScreenState extends State {
if (proceed != true) return;
status = await permission.request();
if (!mounted) return;
+
+ if (status.isPermanentlyDenied) {
+ _showSettingsDialog(sourceName);
+ return;
+ }
}
if (status.isGranted || kIsWeb) {
@@ -88,7 +133,7 @@ class _BusinessSetupScreenState extends State {
if (!mounted) return;
if (file != null) {
setState(() {
- _uploadedDocs[docName] = file.name;
+ _uploadedDocs[docName] = file;
});
showTopAlert(
context,
@@ -113,6 +158,110 @@ class _BusinessSetupScreenState extends State {
}
}
+ void _showSettingsDialog(String permissionName) {
+ showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: Text('$permissionName Permission Disabled'),
+ content: Text('Please enable $permissionName access in system settings to proceed.'),
+ actions: [
+ TextButton(
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ openAppSettings();
+ },
+ child: const Text('Open Settings'),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Future _fetchLocationAndAddress() async {
+ AppFeedback.click();
+ bool serviceEnabled;
+ LocationPermission permission;
+
+ serviceEnabled = await Geolocator.isLocationServiceEnabled();
+ if (!serviceEnabled) {
+ if (mounted) {
+ showTopAlert(context, 'Location services are disabled.', isError: true);
+ }
+ return;
+ }
+
+ permission = await Geolocator.checkPermission();
+ if (permission == LocationPermission.denied) {
+ permission = await Geolocator.requestPermission();
+ if (permission == LocationPermission.denied) {
+ if (mounted) {
+ showTopAlert(context, 'Location permissions are denied', isError: true);
+ }
+ return;
+ }
+ }
+
+ if (permission == LocationPermission.deniedForever) {
+ if (mounted) {
+ _showSettingsDialog('Location');
+ }
+ return;
+ }
+
+ if (mounted) {
+ showTopAlert(context, 'Fetching location...', icon: Icons.gps_fixed);
+ }
+
+ try {
+ Position position = await Geolocator.getCurrentPosition(
+ desiredAccuracy: LocationAccuracy.high);
+
+ if (kIsWeb) {
+ // Reverse geocoding might have CORS issues on web without a backend proxy or proper keys depending on provider,
+ // Mocking for web demonstration unless specifically supported
+ setState(() {
+ _addressController.text = 'Web Mock Address: Lat ${position.latitude.toStringAsFixed(4)}, Lng ${position.longitude.toStringAsFixed(4)}\nCity, State, Country';
+ });
+ if (mounted) {
+ showTopAlert(context, 'Location detected (Web Mock)', icon: Icons.my_location_outlined);
+ }
+ } else {
+ List placemarks = await placemarkFromCoordinates(
+ position.latitude, position.longitude);
+
+ if (placemarks.isNotEmpty) {
+ Placemark place = placemarks[0];
+ List parts = [];
+ if (place.name != null && place.name!.isNotEmpty) parts.add(place.name!);
+ if (place.subLocality != null && place.subLocality!.isNotEmpty) parts.add(place.subLocality!);
+ if (place.locality != null && place.locality!.isNotEmpty) parts.add(place.locality!);
+ if (place.administrativeArea != null && place.administrativeArea!.isNotEmpty) parts.add(place.administrativeArea!);
+ if (place.postalCode != null && place.postalCode!.isNotEmpty) parts.add(place.postalCode!);
+
+ setState(() {
+ _addressController.text = parts.join(', ');
+ });
+
+ if (mounted) {
+ showTopAlert(context, 'Address auto-filled successfully!', icon: Icons.my_location_outlined);
+ }
+ }
+ }
+ } catch (e) {
+ if (mounted) {
+ showTopAlert(context, 'Failed to fetch address: $e', isError: true);
+ }
+ }
+ }
+
void _showFacadePhotoOptions() {
showModalBottomSheet(
context: context,
@@ -124,6 +273,7 @@ class _BusinessSetupScreenState extends State {
leading: const Icon(Icons.camera_alt, color: Color(0xFFAF101A)),
title: const Text('Take Photo'),
onTap: () {
+ AppFeedback.click();
Navigator.pop(context);
_handleImagePick('facade_photo', ImageSource.camera);
},
@@ -132,6 +282,7 @@ class _BusinessSetupScreenState extends State {
leading: const Icon(Icons.photo_library, color: Color(0xFFAF101A)),
title: const Text('Choose from Gallery'),
onTap: () {
+ AppFeedback.click();
Navigator.pop(context);
_handleImagePick('facade_photo', ImageSource.gallery);
},
@@ -149,13 +300,16 @@ class _BusinessSetupScreenState extends State {
return InteractiveScale(
child: InkWell(
onTap: () {
- setState(() {
- if (isSelected) {
+ AppFeedback.lightHaptic();
+ if (!isSelected) {
+ _selectedDocs.add(label);
+ _showFacadePhotoOptionsForDoc(label);
+ } else {
+ setState(() {
_selectedDocs.remove(label);
- } else {
- _selectedDocs.add(label);
- }
- });
+ _uploadedDocs.remove(label);
+ });
+ }
},
borderRadius: BorderRadius.circular(8),
child: Container(
@@ -193,6 +347,38 @@ class _BusinessSetupScreenState extends State {
);
}
+ void _showFacadePhotoOptionsForDoc(String docName) {
+ showModalBottomSheet(
+ context: context,
+ builder: (context) {
+ return SafeArea(
+ child: Wrap(
+ children: [
+ ListTile(
+ leading: const Icon(Icons.camera_alt, color: Color(0xFFAF101A)),
+ title: const Text('Take Photo'),
+ onTap: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ _handleImagePick(docName, ImageSource.camera);
+ },
+ ),
+ ListTile(
+ leading: const Icon(Icons.photo_library, color: Color(0xFFAF101A)),
+ title: const Text('Choose from Gallery'),
+ onTap: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ _handleImagePick(docName, ImageSource.gallery);
+ },
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
Widget _buildUploadSection(String docName, IconData icon) {
final theme = Theme.of(context);
final isUploaded = _uploadedDocs.containsKey(docName);
@@ -231,6 +417,7 @@ class _BusinessSetupScreenState extends State {
IconButton(
icon: const Icon(Icons.close, size: 18, color: Colors.grey),
onPressed: () {
+ AppFeedback.click();
setState(() {
_selectedDocs.remove(docName);
});
@@ -258,13 +445,20 @@ class _BusinessSetupScreenState extends State {
color: Colors.red[50],
borderRadius: BorderRadius.circular(8),
),
- child: const Center(
- child: Icon(
- Icons.insert_drive_file,
- color: Color(0xFFAF101A),
- size: 24,
- ),
- ),
+ clipBehavior: Clip.antiAlias,
+ child: kIsWeb
+ ? Image.network(
+ _uploadedDocs[docName]!.path,
+ fit: BoxFit.cover,
+ errorBuilder: (context, error, stackTrace) =>
+ const Icon(Icons.broken_image, color: Color(0xFFAF101A)),
+ )
+ : Image.file(
+ File(_uploadedDocs[docName]!.path),
+ fit: BoxFit.cover,
+ errorBuilder: (context, error, stackTrace) =>
+ const Icon(Icons.broken_image, color: Color(0xFFAF101A)),
+ ),
),
const SizedBox(width: 12),
Expanded(
@@ -272,7 +466,7 @@ class _BusinessSetupScreenState extends State {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- _uploadedDocs[docName] ?? 'Attached_document.pdf',
+ _uploadedDocs[docName]?.name ?? 'Attached_document.jpg',
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
@@ -293,6 +487,7 @@ class _BusinessSetupScreenState extends State {
IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red),
onPressed: () {
+ AppFeedback.click();
setState(() {
_uploadedDocs.remove(docName);
});
@@ -392,7 +587,10 @@ class _BusinessSetupScreenState extends State {
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFFBA1A20)),
- onPressed: () => Navigator.pop(context),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
),
title: Text(
'PozoApp',
@@ -523,6 +721,7 @@ class _BusinessSetupScreenState extends State {
return InteractiveScale(
child: InkWell(
onTap: () {
+ AppFeedback.select();
setState(() {
_selectedCategory = cat['id'];
});
@@ -612,8 +811,14 @@ class _BusinessSetupScreenState extends State {
child: TextField(
controller: _gstinController,
textCapitalization: TextCapitalization.characters,
- decoration: const InputDecoration(
+ decoration: InputDecoration(
hintText: '15-DIGIT GST NUMBER',
+ suffixIcon: _hasGstinInput
+ ? Icon(
+ _isGstinValid ? Icons.check_circle : Icons.error_outline,
+ color: _isGstinValid ? Colors.green : Colors.red,
+ )
+ : null,
),
style: const TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2),
),
@@ -622,11 +827,21 @@ class _BusinessSetupScreenState extends State {
InteractiveScale(
child: ElevatedButton(
onPressed: () {
- showTopAlert(
- context,
- 'GSTIN Verified Successfully!',
- icon: Icons.verified_outlined,
- );
+ if (_isGstinValid) {
+ AppFeedback.success();
+ showTopAlert(
+ context,
+ 'GSTIN Verified Successfully!',
+ icon: Icons.verified_outlined,
+ );
+ } else {
+ AppFeedback.error();
+ showTopAlert(
+ context,
+ 'Invalid GSTIN Format. Check 15-digit code.',
+ isError: true,
+ );
+ }
},
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 48),
@@ -667,13 +882,7 @@ class _BusinessSetupScreenState extends State {
const SizedBox(height: 8),
InteractiveScale(
child: InkWell(
- onTap: () {
- showTopAlert(
- context,
- 'Location detected: Sector 62, Noida, UP',
- icon: Icons.my_location_outlined,
- );
- },
+ onTap: _fetchLocationAndAddress,
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
@@ -731,6 +940,23 @@ class _BusinessSetupScreenState extends State {
),
),
),
+ const SizedBox(height: 16),
+ Text(
+ 'Shop Address *',
+ style: theme.textTheme.labelSmall?.copyWith(
+ color: theme.colorScheme.onSurface,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 8),
+ TextField(
+ controller: _addressController,
+ maxLines: 3,
+ decoration: const InputDecoration(
+ hintText: 'Enter complete shop address',
+ ),
+ style: const TextStyle(fontWeight: FontWeight.bold),
+ ),
],
),
),
@@ -828,7 +1054,7 @@ class _BusinessSetupScreenState extends State {
),
const SizedBox(height: 12),
Text(
- _uploadedDocs.containsKey('facade_photo') ? _uploadedDocs['facade_photo']! : 'Upload front view',
+ _uploadedDocs.containsKey('facade_photo') ? _uploadedDocs['facade_photo']!.name : 'Upload front view',
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface,
@@ -867,6 +1093,7 @@ class _BusinessSetupScreenState extends State {
child: ElevatedButton(
onPressed: _isNameValid
? () {
+ AppFeedback.click();
Navigator.push(
context,
FadeSlidePageRoute(
diff --git a/flutter_app/lib/screens/customize_setup_screen.dart b/flutter_app/lib/screens/customize_setup_screen.dart
index 6692948..5b144fe 100644
--- a/flutter_app/lib/screens/customize_setup_screen.dart
+++ b/flutter_app/lib/screens/customize_setup_screen.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import '../theme.dart';
import '../route_transitions.dart';
+import '../feedback_helper.dart';
import 'setup_complete_screen.dart';
class CustomizeSetupScreen extends StatefulWidget {
@@ -33,7 +34,10 @@ class _CustomizeSetupScreenState extends State {
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFFBA1A20)),
- onPressed: () => Navigator.pop(context),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
),
title: Row(
children: const [
@@ -52,6 +56,7 @@ class _CustomizeSetupScreenState extends State {
actions: [
TextButton(
onPressed: () {
+ AppFeedback.click();
Navigator.push(
context,
FadeSlidePageRoute(
@@ -194,6 +199,7 @@ class _CustomizeSetupScreenState extends State {
child: InteractiveScale(
child: ElevatedButton(
onPressed: () {
+ AppFeedback.click();
Navigator.push(
context,
FadeSlidePageRoute(
@@ -235,8 +241,13 @@ class _CustomizeSetupScreenState extends State {
ValueChanged onChanged,
ThemeData theme,
) {
+ void wrappedOnChanged(bool val) {
+ AppFeedback.click();
+ onChanged(val);
+ }
+
return InteractiveScale(
- onTap: () => onChanged(!value),
+ onTap: () => wrappedOnChanged(!value),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
@@ -290,7 +301,7 @@ class _CustomizeSetupScreenState extends State {
),
Switch(
value: value,
- onChanged: onChanged,
+ onChanged: wrappedOnChanged,
activeColor: Colors.white,
activeTrackColor: theme.colorScheme.primary,
),
diff --git a/flutter_app/lib/screens/mobile_login_screen.dart b/flutter_app/lib/screens/mobile_login_screen.dart
index a119abc..6263587 100644
--- a/flutter_app/lib/screens/mobile_login_screen.dart
+++ b/flutter_app/lib/screens/mobile_login_screen.dart
@@ -3,8 +3,10 @@ import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
+import 'package:url_launcher/url_launcher.dart';
import '../theme.dart';
import '../route_transitions.dart';
+import '../feedback_helper.dart';
import 'otp_verification_screen.dart';
class MockContact {
@@ -21,19 +23,23 @@ class MockPhone {
}
class MobileLoginScreen extends StatefulWidget {
- const MobileLoginScreen({super.key});
+ final String? autoFilledMobileNumber;
+
+ const MobileLoginScreen({super.key, this.autoFilledMobileNumber});
@override
State createState() => _MobileLoginScreenState();
}
class _MobileLoginScreenState extends State {
- final TextEditingController _controller = TextEditingController();
+ late final TextEditingController _controller;
bool _isValid = false;
@override
void initState() {
super.initState();
+ _controller = TextEditingController(text: widget.autoFilledMobileNumber ?? '');
+ _isValid = _controller.text.length == 10;
_controller.addListener(_validateInput);
}
@@ -50,6 +56,7 @@ class _MobileLoginScreenState extends State {
}
Future _pickContact() async {
+ AppFeedback.click();
const permission = Permission.contacts;
var status = await permission.status;
if (!mounted) return;
@@ -195,6 +202,7 @@ class _MobileLoginScreenState extends State {
),
),
onTap: () {
+ AppFeedback.click();
_selectContactNumber(phone);
Navigator.pop(context);
},
@@ -207,7 +215,10 @@ class _MobileLoginScreenState extends State {
),
actions: [
TextButton(
- onPressed: () => Navigator.pop(context),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
child: const Text('Close'),
)
],
@@ -227,7 +238,10 @@ class _MobileLoginScreenState extends State {
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFFAF101A)),
- onPressed: () => Navigator.pop(context),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
),
title: Text(
'PozoApp',
@@ -409,6 +423,7 @@ class _MobileLoginScreenState extends State {
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () {
+ AppFeedback.click();
_controller.text = '9876543210';
setState(() {
_isValid = true;
@@ -605,6 +620,7 @@ class _MobileLoginScreenState extends State {
child: ElevatedButton(
onPressed: _isValid
? () {
+ AppFeedback.click();
Navigator.push(
context,
FadeSlidePageRoute(
@@ -630,12 +646,28 @@ class _MobileLoginScreenState extends State {
SizedBox(height: gap / 2),
InteractiveScale(
child: OutlinedButton(
- onPressed: () {
- showTopAlert(
- context,
- 'Connecting with Pozo Support... Please wait.',
- icon: Icons.support_agent_outlined,
- );
+ onPressed: () async {
+ AppFeedback.click();
+ final Uri uri = Uri.parse('tel:7324000011');
+ try {
+ if (await canLaunchUrl(uri)) {
+ await launchUrl(uri);
+ } else {
+ if (!context.mounted) return;
+ showTopAlert(
+ context,
+ 'Could not launch dialer. Call 7324000011',
+ isError: true,
+ );
+ }
+ } catch (e) {
+ if (!context.mounted) return;
+ showTopAlert(
+ context,
+ 'Dialer not available in this environment. Call 7324000011',
+ isError: true,
+ );
+ }
},
style: OutlinedButton.styleFrom(
minimumSize: const Size.fromHeight(56),
diff --git a/flutter_app/lib/screens/otp_verification_screen.dart b/flutter_app/lib/screens/otp_verification_screen.dart
index 53283b4..f7f2d4a 100644
--- a/flutter_app/lib/screens/otp_verification_screen.dart
+++ b/flutter_app/lib/screens/otp_verification_screen.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import '../theme.dart';
import '../route_transitions.dart';
+import '../feedback_helper.dart';
import 'business_setup_screen.dart';
class OtpVerificationScreen extends StatefulWidget {
@@ -50,6 +51,21 @@ class _OtpVerificationScreenState extends State {
super.dispose();
}
+ void _verifyOtp() {
+ final code = _controllers.map((c) => c.text.trim()).join();
+ if (code.length < 6) {
+ AppFeedback.error();
+ showTopAlert(context, 'Please enter a valid 6-digit OTP code.', isError: true);
+ return;
+ }
+ AppFeedback.success();
+ showTopAlert(context, 'OTP Verified Successfully!');
+ Navigator.pushReplacement(
+ context,
+ FadeSlidePageRoute(child: const BusinessSetupScreen()),
+ );
+ }
+
String _getFormattedTime() {
return '00:${_timeLeft.toString().padLeft(2, '0')}';
}
@@ -67,7 +83,10 @@ class _OtpVerificationScreenState extends State {
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFFBA1A20)),
- onPressed: () => Navigator.pop(context),
+ onPressed: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
),
title: Text(
'PozoApp',
@@ -182,7 +201,10 @@ class _OtpVerificationScreenState extends State {
),
const SizedBox(width: 8),
InteractiveScale(
- onTap: () => Navigator.pop(context),
+ onTap: () {
+ AppFeedback.click();
+ Navigator.pop(context);
+ },
child: Text(
'Edit',
style: TextStyle(
@@ -233,8 +255,15 @@ class _OtpVerificationScreenState extends State {
),
),
onChanged: (value) {
- if (value.isNotEmpty && fieldIndex < 5) {
- _focusNodes[fieldIndex + 1].requestFocus();
+ AppFeedback.subtleHaptic();
+ if (value.isNotEmpty) {
+ if (fieldIndex < 5) {
+ _focusNodes[fieldIndex + 1].requestFocus();
+ } else {
+ _verifyOtp();
+ }
+ } else if (value.isEmpty && fieldIndex > 0) {
+ _focusNodes[fieldIndex - 1].requestFocus();
}
},
),
@@ -304,6 +333,7 @@ class _OtpVerificationScreenState extends State {
child: InteractiveScale(
child: OutlinedButton.icon(
onPressed: () {
+ AppFeedback.click();
showTopAlert(
context,
'WhatsApp verification request sent!',
@@ -370,14 +400,7 @@ class _OtpVerificationScreenState extends State {
}),
InteractiveScale(
child: TextButton.icon(
- onPressed: () {
- Navigator.push(
- context,
- FadeSlidePageRoute(
- child: const BusinessSetupScreen(),
- ),
- );
- },
+ onPressed: _verifyOtp,
icon: const Text(
'Verify Manually',
style: TextStyle(fontWeight: FontWeight.bold),
@@ -407,7 +430,10 @@ class _OtpVerificationScreenState extends State {
Widget _buildFooterAction(IconData icon, String label, VoidCallback onTap) {
return InteractiveScale(
- onTap: onTap,
+ onTap: () {
+ AppFeedback.click();
+ onTap();
+ },
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
diff --git a/flutter_app/lib/screens/setup_complete_screen.dart b/flutter_app/lib/screens/setup_complete_screen.dart
index 4e11171..2de5573 100644
--- a/flutter_app/lib/screens/setup_complete_screen.dart
+++ b/flutter_app/lib/screens/setup_complete_screen.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import '../theme.dart';
import '../route_transitions.dart';
+import '../feedback_helper.dart';
import 'billing_dashboard_screen.dart';
class SetupCompleteScreen extends StatefulWidget {
@@ -77,6 +78,9 @@ class _SetupCompleteScreenState extends State with SingleTi
);
_controller.forward();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ AppFeedback.success();
+ });
}
@override
@@ -384,6 +388,7 @@ class _SetupCompleteScreenState extends State with SingleTi
child: InteractiveScale(
child: ElevatedButton(
onPressed: () {
+ AppFeedback.click();
showTopAlert(
context,
'Launching Register Dashboard... Enjoy billing!',
diff --git a/flutter_app/lib/screens/welcome_screen.dart b/flutter_app/lib/screens/welcome_screen.dart
index f185777..0d2e116 100644
--- a/flutter_app/lib/screens/welcome_screen.dart
+++ b/flutter_app/lib/screens/welcome_screen.dart
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
+import 'package:flutter/foundation.dart';
+import 'package:mobile_number/mobile_number.dart';
import '../theme.dart';
import '../route_transitions.dart';
+import '../feedback_helper.dart';
import 'mobile_login_screen.dart';
class WelcomeScreen extends StatefulWidget {
@@ -18,9 +21,12 @@ class _WelcomeScreenState extends State with SingleTickerProvider
late Animation _contentFadeAnimation;
late Animation _contentSlideAnimation;
+ String? _detectedMobileNumber;
+
@override
void initState() {
super.initState();
+ _initMobileNumberDetection();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1400),
@@ -72,6 +78,30 @@ class _WelcomeScreenState extends State with SingleTickerProvider
_controller.forward();
}
+ Future _initMobileNumberDetection() async {
+ try {
+ if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
+ if (await MobileNumber.hasPhonePermission) {
+ final String? mobileNumber = await MobileNumber.mobileNumber;
+ if (mobileNumber != null && mobileNumber.isNotEmpty) {
+ // Extract 10 digits if possible, else just use the number
+ final cleaned = mobileNumber.replaceAll(RegExp(r'\D'), '');
+ if (cleaned.length >= 10) {
+ _detectedMobileNumber = cleaned.substring(cleaned.length - 10);
+ } else {
+ _detectedMobileNumber = mobileNumber;
+ }
+ return;
+ }
+ }
+ }
+ } catch (e) {
+ debugPrint('Error getting mobile number: $e');
+ }
+ // Fallback to mock number
+ _detectedMobileNumber = '9988776655';
+ }
+
@override
void dispose() {
_controller.dispose();
@@ -493,9 +523,10 @@ class _WelcomeScreenState extends State with SingleTickerProvider
InteractiveScale(
child: ElevatedButton(
onPressed: () {
+ AppFeedback.click();
Navigator.push(
context,
- FadeSlidePageRoute(child: const MobileLoginScreen()),
+ FadeSlidePageRoute(child: MobileLoginScreen(autoFilledMobileNumber: _detectedMobileNumber)),
);
},
style: ElevatedButton.styleFrom(
diff --git a/flutter_app/pubspec.lock b/flutter_app/pubspec.lock
index 53d805e..c82a998 100644
--- a/flutter_app/pubspec.lock
+++ b/flutter_app/pubspec.lock
@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.7.0"
async:
dependency: transitive
description:
@@ -9,6 +17,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.0"
+ audioplayers:
+ dependency: "direct main"
+ description:
+ name: audioplayers
+ sha256: c05c6147124cd63e725e861335a8b4d57300b80e6e92cea7c145c739223bbaef
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.2.1"
+ audioplayers_android:
+ dependency: transitive
+ description:
+ name: audioplayers_android
+ sha256: b00e1a0e11365d88576320ec2d8c192bc21f1afb6c0e5995d1c57ae63156acb5
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.3"
+ audioplayers_darwin:
+ dependency: transitive
+ description:
+ name: audioplayers_darwin
+ sha256: "3034e99a6df8d101da0f5082dcca0a2a99db62ab1d4ddb3277bed3f6f81afe08"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.2"
+ audioplayers_linux:
+ dependency: transitive
+ description:
+ name: audioplayers_linux
+ sha256: "60787e73fefc4d2e0b9c02c69885402177e818e4e27ef087074cf27c02246c9e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ audioplayers_platform_interface:
+ dependency: transitive
+ description:
+ name: audioplayers_platform_interface
+ sha256: "365c547f1bb9e77d94dd1687903a668d8f7ac3409e48e6e6a3668a1ac2982adb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.0"
+ audioplayers_web:
+ dependency: transitive
+ description:
+ name: audioplayers_web
+ sha256: "22cd0173e54d92bd9b2c80b1204eb1eb159ece87475ab58c9788a70ec43c2a62"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.0"
+ audioplayers_windows:
+ dependency: transitive
+ description:
+ name: audioplayers_windows
+ sha256: "9536812c9103563644ada2ef45ae523806b0745f7a78e89d1b5fb1951de90e1a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
boolean_selector:
dependency: transitive
description:
@@ -65,6 +129,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
+ dbus:
+ dependency: transitive
+ description:
+ name: dbus
+ sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.14"
+ device_info_plus:
+ dependency: transitive
+ description:
+ name: device_info_plus
+ sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.5.0"
+ device_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: device_info_plus_platform_interface
+ sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.3"
fake_async:
dependency: transitive
description:
@@ -81,6 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.0"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
file_selector_linux:
dependency: transitive
description:
@@ -113,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
@@ -126,6 +230,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.9+2"
+ flutter_haptic_feedback:
+ dependency: "direct main"
+ description:
+ name: flutter_haptic_feedback
+ sha256: e6a1ac6b5119c9b0964e4de2eaa6bfb66facedc6e33bfe2a902da3f6f9666499
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
flutter_lints:
dependency: "direct dev"
description:
@@ -152,6 +264,102 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ geoclue:
+ dependency: transitive
+ description:
+ name: geoclue
+ sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.1"
+ geocoding:
+ dependency: "direct main"
+ description:
+ name: geocoding
+ sha256: "606be036287842d779d7ec4e2f6c9435fc29bbbd3c6da6589710f981d8852895"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.0"
+ geocoding_android:
+ dependency: transitive
+ description:
+ name: geocoding_android
+ sha256: ba810da90d6633cbb82bbab630e5b4a3b7d23503263c00ae7f1ef0316dcae5b9
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.1"
+ geocoding_ios:
+ dependency: transitive
+ description:
+ name: geocoding_ios
+ sha256: "18ab1c8369e2b0dcb3a8ccc907319334f35ee8cf4cfef4d9c8e23b13c65cb825"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ geocoding_platform_interface:
+ dependency: transitive
+ description:
+ name: geocoding_platform_interface
+ sha256: "8c2c8226e5c276594c2e18bfe88b19110ed770aeb7c1ab50ede570be8b92229b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.0"
+ geolocator:
+ dependency: "direct main"
+ description:
+ name: geolocator
+ sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516"
+ url: "https://pub.dev"
+ source: hosted
+ version: "14.0.2"
+ geolocator_android:
+ dependency: transitive
+ description:
+ name: geolocator_android
+ sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.2"
+ geolocator_apple:
+ dependency: transitive
+ description:
+ name: geolocator_apple
+ sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.13"
+ geolocator_linux:
+ dependency: transitive
+ description:
+ name: geolocator_linux
+ sha256: d64112a205931926f4363bb6bd48f14cb38e7326833041d170615586cd143797
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.4"
+ geolocator_platform_interface:
+ dependency: transitive
+ description:
+ name: geolocator_platform_interface
+ sha256: dde05dae7d584db6e82feb87dd9fb0b4b4c83ed68065667b4bef637be38e13a7
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.7"
+ geolocator_web:
+ dependency: transitive
+ description:
+ name: geolocator_web
+ sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.3"
+ geolocator_windows:
+ dependency: transitive
+ description:
+ name: geolocator_windows
+ sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.5"
google_fonts:
dependency: "direct main"
description:
@@ -160,6 +368,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.0"
+ gsettings:
+ dependency: transitive
+ description:
+ name: gsettings
+ sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.8"
http:
dependency: transitive
description:
@@ -240,6 +456,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.2"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.7"
leak_tracker:
dependency: transitive
description:
@@ -304,6 +528,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
+ mobile_number:
+ dependency: "direct main"
+ description:
+ name: mobile_number
+ sha256: "8dc5b7a19193f7a41fa07aa933c8db8085033e6201b20323f20e4d283ea2a9a8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ package_info_plus:
+ dependency: transitive
+ description:
+ name: package_info_plus
+ sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20"
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.0.1"
+ package_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: package_info_plus_platform_interface
+ sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.1"
path:
dependency: transitive
description:
@@ -364,18 +612,18 @@ packages:
dependency: "direct main"
description:
name: permission_handler
- sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
+ sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6
url: "https://pub.dev"
source: hosted
- version: "11.4.0"
+ version: "12.0.3"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
- sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
+ sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.dev"
source: hosted
- version: "12.1.0"
+ version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
@@ -408,6 +656,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.1"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.2"
platform:
dependency: transitive
description:
@@ -461,6 +717,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.0"
term_glyph:
dependency: transitive
description:
@@ -485,6 +749,78 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.2"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.20"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.4"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.2"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.3"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.5"
+ uuid:
+ dependency: transitive
+ description:
+ name: uuid
+ sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.3"
vector_math:
dependency: transitive
description:
@@ -493,6 +829,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
+ vibration:
+ dependency: "direct main"
+ description:
+ name: vibration
+ sha256: "06588a845a4ebc73ab7ff7da555c2b3dbcd9676164b5856a38bf0b2287f1045d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.0"
+ vibration_platform_interface:
+ dependency: transitive
+ description:
+ name: vibration_platform_interface
+ sha256: "6ffeee63547562a6fef53c05a41d4fdcae2c0595b83ef59a4813b0612cd2bc36"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.3"
vm_service:
dependency: transitive
description:
@@ -509,6 +861,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.15.0"
+ win32_registry:
+ dependency: transitive
+ description:
+ name: win32_registry
+ sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
xdg_directories:
dependency: transitive
description:
@@ -517,6 +885,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.6.1"
sdks:
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.32.0"
diff --git a/flutter_app/pubspec.yaml b/flutter_app/pubspec.yaml
index 4f76fa0..ab87a0c 100644
--- a/flutter_app/pubspec.yaml
+++ b/flutter_app/pubspec.yaml
@@ -10,9 +10,16 @@ dependencies:
sdk: flutter
cupertino_icons: ^1.0.5
google_fonts: ^5.1.0
- image_picker: ^1.0.4
- permission_handler: ^11.0.1
+ image_picker: ^1.2.1
+ permission_handler: ^12.0.3
flutter_contacts: ^1.1.9
+ geolocator: ^14.0.2
+ geocoding: ^4.0.0
+ url_launcher: ^6.3.2
+ mobile_number: ^3.0.0
+ vibration: ^1.8.4
+ audioplayers: ^5.2.1
+ flutter_haptic_feedback: ^1.0.1
dev_dependencies:
flutter_test:
@@ -23,3 +30,4 @@ flutter:
uses-material-design: true
assets:
- assets/logo/
+ - assets/sounds/