feat: add sound and haptic feedback throughout onboarding and setup flow
This commit is contained in:
parent
3f2a804a31
commit
a3db70ffc6
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -51,5 +51,13 @@
|
|||
<string>PozoApp requires photo gallery access to upload documents and shop facade images.</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>PozoApp requires contacts access to let you search and auto-fill mobile numbers during registration and billing.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>PozoApp needs your location to automatically fetch and verify your shop's address.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>PozoApp needs your location to automatically fetch and verify your shop's address.</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>tel</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -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<void> 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<void> _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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'feedback_helper.dart';
|
||||
|
||||
class FadeSlidePageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget child;
|
||||
|
|
@ -33,7 +34,9 @@ class FadeSlidePageRoute<T> extends PageRouteBuilder<T> {
|
|||
},
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 200),
|
||||
);
|
||||
) {
|
||||
AppFeedback.subtleHaptic();
|
||||
}
|
||||
}
|
||||
|
||||
void showTopAlert(
|
||||
|
|
|
|||
|
|
@ -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<BillingDashboardScreen> {
|
|||
}
|
||||
|
||||
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<BillingDashboardScreen> {
|
|||
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<BillingDashboardScreen> {
|
|||
}
|
||||
|
||||
void _closeReceiptAndReset() {
|
||||
AppFeedback.click();
|
||||
setState(() {
|
||||
_printingInvoice = false;
|
||||
_billingList.clear();
|
||||
|
|
@ -206,7 +210,10 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
|
|||
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<BillingDashboardScreen> {
|
|||
InteractiveScale(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
showTopAlert(
|
||||
context,
|
||||
'Thermal Printed: $_invoiceNumber successfully!',
|
||||
|
|
|
|||
|
|
@ -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<BusinessSetupScreen> {
|
||||
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<String> _selectedDocs = {'GSTIN', 'FSSAI'};
|
||||
final Map<String, String> _uploadedDocs = {};
|
||||
final Map<String, XFile> _uploadedDocs = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -30,12 +40,30 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
|||
_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<BusinessSetupScreen> {
|
|||
];
|
||||
|
||||
Future<void> _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<bool>(
|
||||
|
|
@ -64,11 +98,17 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
|||
),
|
||||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
if (!mounted) return;
|
||||
if (file != null) {
|
||||
setState(() {
|
||||
_uploadedDocs[docName] = file.name;
|
||||
_uploadedDocs[docName] = file;
|
||||
});
|
||||
showTopAlert(
|
||||
context,
|
||||
|
|
@ -113,6 +158,110 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
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<void> _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<Placemark> placemarks = await placemarkFromCoordinates(
|
||||
position.latitude, position.longitude);
|
||||
|
||||
if (placemarks.isNotEmpty) {
|
||||
Placemark place = placemarks[0];
|
||||
List<String> 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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
return InteractiveScale(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
AppFeedback.select();
|
||||
setState(() {
|
||||
_selectedCategory = cat['id'];
|
||||
});
|
||||
|
|
@ -612,8 +811,14 @@ class _BusinessSetupScreenState extends State<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
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<BusinessSetupScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
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<BusinessSetupScreen> {
|
|||
),
|
||||
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<BusinessSetupScreen> {
|
|||
child: ElevatedButton(
|
||||
onPressed: _isNameValid
|
||||
? () {
|
||||
AppFeedback.click();
|
||||
Navigator.push(
|
||||
context,
|
||||
FadeSlidePageRoute(
|
||||
|
|
|
|||
|
|
@ -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<CustomizeSetupScreen> {
|
|||
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<CustomizeSetupScreen> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
Navigator.push(
|
||||
context,
|
||||
FadeSlidePageRoute(
|
||||
|
|
@ -194,6 +199,7 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
|
|||
child: InteractiveScale(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
Navigator.push(
|
||||
context,
|
||||
FadeSlidePageRoute(
|
||||
|
|
@ -235,8 +241,13 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
|
|||
ValueChanged<bool> 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<CustomizeSetupScreen> {
|
|||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
onChanged: wrappedOnChanged,
|
||||
activeColor: Colors.white,
|
||||
activeTrackColor: theme.colorScheme.primary,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<MobileLoginScreen> createState() => _MobileLoginScreenState();
|
||||
}
|
||||
|
||||
class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
||||
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<MobileLoginScreen> {
|
|||
}
|
||||
|
||||
Future<void> _pickContact() async {
|
||||
AppFeedback.click();
|
||||
const permission = Permission.contacts;
|
||||
var status = await permission.status;
|
||||
if (!mounted) return;
|
||||
|
|
@ -195,6 +202,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
|||
),
|
||||
),
|
||||
onTap: () {
|
||||
AppFeedback.click();
|
||||
_selectContactNumber(phone);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
|
|
@ -207,7 +215,10 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
|||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('Close'),
|
||||
)
|
||||
],
|
||||
|
|
@ -227,7 +238,10 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
|||
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<MobileLoginScreen> {
|
|||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
_controller.text = '9876543210';
|
||||
setState(() {
|
||||
_isValid = true;
|
||||
|
|
@ -605,6 +620,7 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
|||
child: ElevatedButton(
|
||||
onPressed: _isValid
|
||||
? () {
|
||||
AppFeedback.click();
|
||||
Navigator.push(
|
||||
context,
|
||||
FadeSlidePageRoute(
|
||||
|
|
@ -630,12 +646,28 @@ class _MobileLoginScreenState extends State<MobileLoginScreen> {
|
|||
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),
|
||||
|
|
|
|||
|
|
@ -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<OtpVerificationScreen> {
|
|||
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<OtpVerificationScreen> {
|
|||
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<OtpVerificationScreen> {
|
|||
),
|
||||
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<OtpVerificationScreen> {
|
|||
),
|
||||
),
|
||||
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<OtpVerificationScreen> {
|
|||
child: InteractiveScale(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
showTopAlert(
|
||||
context,
|
||||
'WhatsApp verification request sent!',
|
||||
|
|
@ -370,14 +400,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
|
|||
}),
|
||||
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<OtpVerificationScreen> {
|
|||
|
||||
Widget _buildFooterAction(IconData icon, String label, VoidCallback onTap) {
|
||||
return InteractiveScale(
|
||||
onTap: onTap,
|
||||
onTap: () {
|
||||
AppFeedback.click();
|
||||
onTap();
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -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<SetupCompleteScreen> with SingleTi
|
|||
);
|
||||
|
||||
_controller.forward();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
AppFeedback.success();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -384,6 +388,7 @@ class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTi
|
|||
child: InteractiveScale(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
showTopAlert(
|
||||
context,
|
||||
'Launching Register Dashboard... Enjoy billing!',
|
||||
|
|
|
|||
|
|
@ -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<WelcomeScreen> with SingleTickerProvider
|
|||
late Animation<double> _contentFadeAnimation;
|
||||
late Animation<Offset> _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<WelcomeScreen> with SingleTickerProvider
|
|||
_controller.forward();
|
||||
}
|
||||
|
||||
Future<void> _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<WelcomeScreen> with SingleTickerProvider
|
|||
InteractiveScale(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
AppFeedback.click();
|
||||
Navigator.push(
|
||||
context,
|
||||
FadeSlidePageRoute(child: const MobileLoginScreen()),
|
||||
FadeSlidePageRoute(child: MobileLoginScreen(autoFilledMobileNumber: _detectedMobileNumber)),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
Loading…
Reference in New Issue