Compare commits

..

17 Commits

Author SHA1 Message Date
Sridhar R 1fed631f1b Regenerate launcher icons using user-provided high-resolution source logo text to fix pixelation and align styling 2026-06-10 19:21:06 +05:30
Sridhar R 2a7d83cf41 Update adaptive launcher icon to use a transparent foreground image to resolve cropping and rendering issues on modern Android devices 2026-06-10 19:15:51 +05:30
Sridhar R 28cf436dad Generate padded launcher icon to fit inside Android adaptive safe zone and prevent text cropping 2026-06-10 19:08:58 +05:30
Sridhar R 0a80b8d817 Configure splash screen background color #d2242e and adaptive launcher icon background to remove white masking border on launch 2026-06-10 18:59:05 +05:30
Sridhar R 44b0f47648 Change app name to PozoApp and configure custom launcher icons using the Pozo logo 2026-06-10 18:49:34 +05:30
Sridhar R c630dda016 Implement interactive Retail Universe orbit animation on welcome screen and add feedback sound/haptic toggles 2026-06-10 18:40:58 +05:30
Sridhar R 4b35077fba Add location permissions to AndroidManifest.xml and manual text input fields for legal documents (FSSAI, GSTIN, Udhyog, PAN) 2026-06-10 18:24:02 +05:30
Sridhar R 8096cad270 Optimize sound latency with preloading, add startup welcome chime, and improve Android vibration haptics 2026-06-10 18:19:47 +05:30
Sridhar R bf87873181 fix: upgrade vibration package to 3.1.8 for android gradle 8 compatibility 2026-06-10 18:08:33 +05:30
Sridhar R a3db70ffc6 feat: add sound and haptic feedback throughout onboarding and setup flow 2026-06-10 17:43:21 +05:30
Sridhar R 3f2a804a31 Refactor contacts using flutter_contacts, unify InteractiveScale animations and 16px corner radius across welcome and setup complete screens, and compile final release APK 2026-06-10 14:47:50 +05:30
Sridhar R ac1294a150 Add premium logo zoom/scale-down splash entrance on app load 2026-06-10 14:14:11 +05:30
Sridhar R 45a3e381c0 Animate SetupCompleteScreen elements sequentially and add button scaling 2026-06-10 14:08:47 +05:30
Sridhar R 67142977c0 Replace SnackBars with custom iOS-style top alerts with icons 2026-06-10 14:05:58 +05:30
Sridhar R 4acfdb9c4a Configure native Android and iOS permission usage declarations 2026-06-10 13:55:34 +05:30
Sridhar R ca87000e87 Add and integrate actual PozoApp brand logo 2026-06-10 13:53:42 +05:30
Sridhar R 6f11b367b6 Enhance Flutter app with animations, contacts access, image picker, and permissions handler 2026-06-10 13:48:06 +05:30
61 changed files with 4285 additions and 1863 deletions

View File

@ -1,8 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:label="pozo_app"
android:label="PozoApp"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<item android:drawable="@color/splash_bg" />
<!-- You can insert your own image assets here -->
<!-- <item>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<item android:drawable="@color/splash_bg" />
<!-- You can insert your own image assets here -->
<!-- <item>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -5,6 +5,7 @@
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowSplashScreenBackground">@color/splash_bg</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_bg">#d2242e</color>
<color name="ic_launcher_background">#d2242e</color>
</resources>

View File

@ -5,6 +5,7 @@
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowSplashScreenBackground">@color/splash_bg</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@ -11,6 +11,28 @@ rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
afterEvaluate {
if (extensions.findByName("android") != null) {
val android = extensions.findByName("android") as com.android.build.gradle.BaseExtension
if (android.namespace == null) {
var manifestPackage: String? = null
val manifestFile = project.projectDir.resolve("src/main/AndroidManifest.xml")
if (manifestFile.exists()) {
val content = manifestFile.readText()
val match = Regex("package=\"([^\"]+)\"").find(content)
if (match != null) {
manifestPackage = match.groupValues[1]
}
}
if (manifestPackage != null) {
android.namespace = manifestPackage
} else {
android.namespace = "co.gittouch." + project.name.replace("-", "_")
}
}
}
}
}
subprojects {
project.evaluationDependsOn(":app")

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Pozo App</string>
<string>PozoApp</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>pozo_app</string>
<string>PozoApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@ -45,5 +45,19 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>PozoApp requires camera access to capture shop facade photos and document images for business verification.</string>
<key>NSPhotoLibraryUsageDescription</key>
<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>

View File

@ -0,0 +1,151 @@
import 'package:flutter/services.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:vibration/vibration.dart';
import 'package:flutter/foundation.dart' show kIsWeb, debugPrint;
import 'dart:io' show Platform;
class AppFeedback {
// Global settings for sound and haptic vibration feedback
static bool isSoundEnabled = true;
static bool isHapticEnabled = true;
static final Map<String, AudioPlayer> _players = {
'click.mp3': AudioPlayer(),
'select.mp3': AudioPlayer(),
'success.mp3': AudioPlayer(),
'error.mp3': AudioPlayer(),
};
static bool _initialized = false;
/// Preload sounds to achieve zero latency
static void init() {
if (_initialized) return;
try {
_players.forEach((key, player) {
player.setReleaseMode(ReleaseMode.stop);
player.setSource(AssetSource('sounds/$key'));
});
_initialized = true;
} catch (e) {
debugPrint('Error initializing AppFeedback: $e');
}
}
/// Play a sound from assets/sounds/ with zero delay
static Future<void> playSound(String fileName) async {
if (!isSoundEnabled) return;
try {
if (!_initialized) {
init();
}
final player = _players[fileName];
if (player != null) {
// Stop/seek and resume for instant audio
await player.seek(Duration.zero);
await player.resume();
} else {
// Fallback for dynamically added files
final tempPlayer = AudioPlayer();
await tempPlayer.play(AssetSource('sounds/$fileName'));
}
} catch (e) {
debugPrint('Audio playback error for $fileName: $e');
}
}
/// Helper to check Android platform safely
static bool get _isAndroid => !kIsWeb && Platform.isAndroid;
/// Trigger native vibration for Android with customized duration (in milliseconds)
static void _triggerVibration(int duration) async {
if (!isHapticEnabled || kIsWeb) return;
try {
if (_isAndroid) {
if (await Vibration.hasVibrator() == true) {
Vibration.vibrate(duration: duration);
}
}
} catch (e) {
debugPrint('Vibration error: $e');
}
}
/// Light haptic feedback + subtle click sound
static void click() {
if (isHapticEnabled) {
HapticFeedback.lightImpact();
_triggerVibration(30);
}
playSound('click.mp3');
}
/// Medium haptic + selection sound
static void select() {
if (isHapticEnabled) {
HapticFeedback.mediumImpact();
_triggerVibration(60);
}
playSound('select.mp3');
}
/// Celebration sound + strong haptic vibration
static void success() {
if (isHapticEnabled) {
HapticFeedback.heavyImpact();
_doubleVibrate();
}
playSound('success.mp3');
}
/// Wrong input -> error haptic (double vibrate) + error sound
static void error() {
if (isHapticEnabled) {
_doubleVibrate();
}
playSound('error.mp3');
}
/// Light haptic feedback (e.g. chip selections)
static void lightHaptic() {
if (!isHapticEnabled) return;
HapticFeedback.lightImpact();
_triggerVibration(30);
}
/// Medium haptic feedback
static void mediumHaptic() {
if (!isHapticEnabled) return;
HapticFeedback.mediumImpact();
_triggerVibration(60);
}
/// Heavy haptic feedback
static void heavyHaptic() {
if (!isHapticEnabled) return;
HapticFeedback.heavyImpact();
_triggerVibration(100);
}
/// Subtle haptic feedback (e.g. screen navigation)
static void subtleHaptic() {
if (!isHapticEnabled) return;
HapticFeedback.selectionClick();
_triggerVibration(20);
}
/// Double vibrate pattern using vibration package
static Future<void> _doubleVibrate() async {
if (!isHapticEnabled) return;
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 {
HapticFeedback.mediumImpact();
}
} catch (e) {
HapticFeedback.mediumImpact();
}
}
}

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'theme.dart';
import 'screens/welcome_screen.dart';
import 'feedback_helper.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
AppFeedback.init();
runApp(const PozoApp());
}

View File

@ -0,0 +1,307 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'feedback_helper.dart';
class FadeSlidePageRoute<T> extends PageRouteBuilder<T> {
final Widget child;
FadeSlidePageRoute({required this.child})
: super(
pageBuilder: (context, animation, secondaryAnimation) => child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final fadeAnimation = CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
);
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.05),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
),
);
return FadeTransition(
opacity: fadeAnimation,
child: SlideTransition(
position: slideAnimation,
child: child,
),
);
},
transitionDuration: const Duration(milliseconds: 300),
reverseTransitionDuration: const Duration(milliseconds: 200),
) {
AppFeedback.subtleHaptic();
}
}
void showTopAlert(
BuildContext context,
String message, {
bool isError = false,
IconData? icon,
}) {
final overlayState = Overlay.of(context);
late OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(
builder: (context) => _TopAlertWidget(
message: message,
isError: isError,
icon: icon,
onDismiss: () {
overlayEntry.remove();
},
),
);
overlayState.insert(overlayEntry);
}
class _TopAlertWidget extends StatefulWidget {
final String message;
final bool isError;
final IconData? icon;
final VoidCallback onDismiss;
const _TopAlertWidget({
required this.message,
required this.isError,
this.icon,
required this.onDismiss,
});
@override
State<_TopAlertWidget> createState() => _TopAlertWidgetState();
}
class _TopAlertWidgetState extends State<_TopAlertWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _slideAnimation;
late Animation<double> _opacityAnimation;
Timer? _dismissTimer;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_slideAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeOutBack,
);
_opacityAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
_controller.forward();
_dismissTimer = Timer(const Duration(milliseconds: 2800), () {
if (mounted) {
_controller.reverse().then((_) {
widget.onDismiss();
});
}
});
}
@override
void dispose() {
_dismissTimer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final topPadding = mediaQuery.padding.top + 16.0;
IconData alertIcon = widget.icon ??
(widget.isError ? Icons.error_outline : Icons.check_circle_outline);
Color backgroundColor = widget.isError ? const Color(0xFFBA1A20) : const Color(0xFF2E7D32);
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Positioned(
top: topPadding * _slideAnimation.value - 80.0 * (1.0 - _slideAnimation.value),
left: 16.0,
right: 16.0,
child: Opacity(
opacity: _opacityAnimation.value,
child: Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 450),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.12),
blurRadius: 16,
offset: const Offset(0, 6),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
alertIcon,
color: Colors.white,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Text(
widget.message,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
),
),
),
),
);
},
);
}
}
class SlideFadeEntrance extends StatefulWidget {
final Widget child;
final Duration delay;
final Duration duration;
const SlideFadeEntrance({
super.key,
required this.child,
this.delay = Duration.zero,
this.duration = const Duration(milliseconds: 500),
});
@override
State<SlideFadeEntrance> createState() => _SlideFadeEntranceState();
}
class _SlideFadeEntranceState extends State<SlideFadeEntrance> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacityAnimation;
late Animation<double> _slideAnimation;
Timer? _timer;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.duration,
);
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
_slideAnimation = Tween<double>(begin: 12.0, end: 0.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
);
if (widget.delay == Duration.zero) {
_controller.forward();
} else {
_timer = Timer(widget.delay, () {
if (mounted) {
_controller.forward();
}
});
}
}
@override
void dispose() {
_timer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _opacityAnimation.value,
child: Transform.translate(
offset: Offset(0.0, _slideAnimation.value),
child: child,
),
);
},
child: widget.child,
);
}
}
class InteractiveScale extends StatefulWidget {
final Widget child;
final VoidCallback? onTap;
final double hoverScale;
final double tapScale;
const InteractiveScale({
super.key,
required this.child,
this.onTap,
this.hoverScale = 1.03,
this.tapScale = 0.96,
});
@override
State<InteractiveScale> createState() => _InteractiveScaleState();
}
class _InteractiveScaleState extends State<InteractiveScale> {
double _scale = 1.0;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => _scale = widget.hoverScale),
onExit: (_) => setState(() => _scale = 1.0),
child: AnimatedScale(
scale: _scale,
duration: const Duration(milliseconds: 120),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) => setState(() => _scale = widget.tapScale),
onTapUp: (_) => setState(() => _scale = widget.hoverScale),
onTapCancel: () => setState(() => _scale = 1.0),
onTap: widget.onTap,
child: widget.child,
),
),
);
}
}

View File

@ -1,5 +1,7 @@
import 'dart:math';
import 'package:flutter/material.dart';
import '../route_transitions.dart';
import '../feedback_helper.dart';
class ProductItem {
final String id;
@ -135,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)];
@ -160,13 +163,7 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
}
void _showErrorSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red[800],
behavior: SnackBarBehavior.floating,
),
);
showTopAlert(context, message, isError: true);
}
double get _subtotal {
@ -182,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(() {
@ -191,6 +189,7 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
}
void _closeReceiptAndReset() {
AppFeedback.click();
setState(() {
_printingInvoice = false;
_billingList.clear();
@ -211,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,
@ -253,21 +255,23 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
padding: const EdgeInsets.only(right: 8.0),
child: Directionality(
textDirection: TextDirection.rtl,
child: ElevatedButton.icon(
onPressed: _simulateBarcodeScan,
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 32),
backgroundColor: theme.colorScheme.primary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
child: InteractiveScale(
child: ElevatedButton.icon(
onPressed: _simulateBarcodeScan,
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 32),
backgroundColor: theme.colorScheme.primary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
icon: const Icon(Icons.qr_code_scanner, size: 16),
label: const Text(
'Scan Item',
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
),
),
icon: const Icon(Icons.qr_code_scanner, size: 16),
label: const Text(
'Scan Item',
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
),
),
),
@ -412,95 +416,103 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
final isLowStock = widget.inventoryAlerts && remainingStock <= 3;
final isOutOfStock = widget.inventoryAlerts && remainingStock <= 0;
return InkWell(
onTap: isOutOfStock ? null : () => _handleAddItem(prod),
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: countInCart > 0
? const Color(0xFF1E1B4B) // Deep indigo accent
: const Color(0xFF1E293B).withOpacity(0.8),
return SlideFadeEntrance(
delay: Duration(milliseconds: index * 40),
duration: const Duration(milliseconds: 300),
child: InteractiveScale(
tapScale: isOutOfStock ? 1.0 : 0.95,
hoverScale: isOutOfStock ? 1.0 : 1.03,
child: InkWell(
onTap: isOutOfStock ? null : () => _handleAddItem(prod),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: countInCart > 0 ? theme.colorScheme.primary : const Color(0xFF334155),
width: countInCart > 0 ? 1.5 : 1.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
prod.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 11,
),
),
const SizedBox(height: 4),
Text(
'${prod.price.toStringAsFixed(0)}',
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w800,
fontSize: 13,
),
),
],
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: countInCart > 0
? const Color(0xFF1E1B4B) // Deep indigo accent
: const Color(0xFF1E293B).withOpacity(0.8),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: countInCart > 0 ? theme.colorScheme.primary : const Color(0xFF334155),
width: countInCart > 0 ? 1.5 : 1.0,
),
),
Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.inventoryAlerts)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: isLowStock
? Colors.red.withOpacity(0.15)
: const Color(0xFF334155),
borderRadius: BorderRadius.circular(6),
),
child: Text(
isOutOfStock ? 'Out of stock' : '$remainingStock Stock',
style: TextStyle(
color: isLowStock ? Colors.redAccent : Colors.grey,
fontSize: 8,
fontWeight: FontWeight.bold,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
prod.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 11,
),
),
),
)
else
const SizedBox(),
if (countInCart > 0)
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: theme.colorScheme.primary,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
'$countInCart',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 10,
const SizedBox(height: 4),
Text(
'${prod.price.toStringAsFixed(0)}',
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w800,
fontSize: 13,
),
),
),
)
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.inventoryAlerts)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: isLowStock
? Colors.red.withOpacity(0.15)
: const Color(0xFF334155),
borderRadius: BorderRadius.circular(6),
),
child: Text(
isOutOfStock ? 'Out of stock' : '$remainingStock Stock',
style: TextStyle(
color: isLowStock ? Colors.redAccent : Colors.grey,
fontSize: 8,
fontWeight: FontWeight.bold,
),
),
)
else
const SizedBox(),
if (countInCart > 0)
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: theme.colorScheme.primary,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
'$countInCart',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
)
],
)
],
)
],
),
),
),
),
);
@ -660,20 +672,27 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
const Divider(color: Color(0xFF334155), height: 16),
_buildComputationCalculations(theme),
const SizedBox(height: 10),
ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
disabledBackgroundColor: const Color(0xFF1E293B),
minimumSize: const Size.fromHeight(48),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt, size: 16),
SizedBox(width: 8),
Text('Generate Invoice'),
],
InteractiveScale(
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
disabledBackgroundColor: const Color(0xFF1E293B),
minimumSize: const Size.fromHeight(48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt, size: 16),
SizedBox(width: 8),
Text('Generate Invoice'),
],
),
),
)
]
@ -756,19 +775,26 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
)
],
),
ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
minimumSize: const Size(180, 44),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.print, size: 16),
SizedBox(width: 8),
Text('Review & Invoice', style: TextStyle(fontSize: 12)),
],
InteractiveScale(
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
minimumSize: const Size(180, 44),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.print, size: 16),
SizedBox(width: 8),
Text('Review & Invoice', style: TextStyle(fontSize: 12)),
],
),
),
)
],
@ -942,32 +968,41 @@ class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('🧾 Thermal Printed: $_invoiceNumber successfully!'),
backgroundColor: const Color(0xFF10B981),
behavior: SnackBarBehavior.floating,
InteractiveScale(
child: ElevatedButton.icon(
onPressed: () {
AppFeedback.click();
showTopAlert(
context,
'Thermal Printed: $_invoiceNumber successfully!',
icon: Icons.print,
);
},
icon: const Icon(Icons.print, size: 16),
label: const Text('Simulate Print Receipt', style: TextStyle(fontSize: 12)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(42),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
);
},
icon: const Icon(Icons.print, size: 16),
label: const Text('Simulate Print Receipt', style: TextStyle(fontSize: 12)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(42),
),
),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _closeReceiptAndReset,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
minimumSize: const Size.fromHeight(42),
InteractiveScale(
child: ElevatedButton(
onPressed: _closeReceiptAndReset,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
minimumSize: const Size.fromHeight(42),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Text('New Ledger Sale', style: TextStyle(fontSize: 12)),
),
child: const Text('New Ledger Sale', style: TextStyle(fontSize: 12)),
),
],
),

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +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 {
@ -32,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 [
@ -51,10 +56,11 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
actions: [
TextButton(
onPressed: () {
AppFeedback.click();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SetupCompleteScreen(
FadeSlidePageRoute(
child: SetupCompleteScreen(
businessName: widget.businessName,
businessType: widget.businessType,
gstin: widget.gstin,
@ -118,49 +124,66 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Customize Your Setup',
style: theme.textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
SlideFadeEntrance(
delay: Duration.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Customize Your Setup',
style: theme.textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Text(
'Turn on the features you need for your shop. You can always change these later in settings.',
style: theme.textTheme.bodyMedium,
),
],
),
),
const SizedBox(height: 16),
Text(
'Turn on the features you need for your shop. You can always change these later in settings.',
style: theme.textTheme.bodyMedium,
),
const SizedBox(height: 16),
const SizedBox(height: 20),
// Toggle 1: Barcode
_buildToggleCard(
Icons.qr_code_scanner,
'Barcode Scanner',
'Do you use a Barcode Scanner?',
_barcode,
(val) => setState(() => _barcode = val),
theme,
SlideFadeEntrance(
delay: const Duration(milliseconds: 100),
child: _buildToggleCard(
Icons.qr_code_scanner,
'Barcode Scanner',
'Do you use a Barcode Scanner?',
_barcode,
(val) => setState(() => _barcode = val),
theme,
),
),
const SizedBox(height: 12),
// Toggle 2: KOT
_buildToggleCard(
Icons.receipt_long,
'KOT Management',
'Do you need Kitchen Order Ticket management?',
_kot,
(val) => setState(() => _kot = val),
theme,
SlideFadeEntrance(
delay: const Duration(milliseconds: 180),
child: _buildToggleCard(
Icons.receipt_long,
'KOT Management',
'Do you need Kitchen Order Ticket management?',
_kot,
(val) => setState(() => _kot = val),
theme,
),
),
const SizedBox(height: 12),
// Toggle 3: Inventory stock tracking
_buildToggleCard(
Icons.inventory_2,
'Inventory Alerts',
'Get notified when stock is running low.',
_inventory,
(val) => setState(() => _inventory = val),
theme,
SlideFadeEntrance(
delay: const Duration(milliseconds: 260),
child: _buildToggleCard(
Icons.inventory_2,
'Inventory Alerts',
'Get notified when stock is running low.',
_inventory,
(val) => setState(() => _inventory = val),
theme,
),
),
],
),
@ -169,28 +192,37 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
),
// Pinned action keys
Padding(
padding: EdgeInsets.all(padding),
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SetupCompleteScreen(
businessName: widget.businessName,
businessType: widget.businessType,
gstin: widget.gstin,
barcodeScanner: _barcode,
kotManagement: _kot,
inventoryAlerts: _inventory,
SlideFadeEntrance(
delay: const Duration(milliseconds: 340),
child: Padding(
padding: EdgeInsets.all(padding),
child: InteractiveScale(
child: ElevatedButton(
onPressed: () {
AppFeedback.click();
Navigator.push(
context,
FadeSlidePageRoute(
child: SetupCompleteScreen(
businessName: widget.businessName,
businessType: widget.businessType,
gstin: widget.gstin,
barcodeScanner: _barcode,
kotManagement: _kot,
inventoryAlerts: _inventory,
),
),
);
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(54),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
);
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(54),
child: const Text('Save & Continue'),
),
),
child: const Text('Save & Continue'),
),
)
],
@ -209,64 +241,72 @@ class _CustomizeSetupScreenState extends State<CustomizeSetupScreen> {
ValueChanged<bool> onChanged,
ThemeData theme,
) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[200]!),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.02),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
void wrappedOnChanged(bool val) {
AppFeedback.click();
onChanged(val);
}
return InteractiveScale(
onTap: () => wrappedOnChanged(!value),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[200]!),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.02),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: Icon(icon, color: Colors.grey[600], size: 22),
),
child: Icon(icon, color: Colors.grey[600], size: 22),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black87,
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 11,
color: Colors.grey[500],
),
)
],
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 11,
color: Colors.grey[500],
),
)
],
),
),
),
Switch(
value: value,
onChanged: onChanged,
activeColor: Colors.white,
activeTrackColor: theme.colorScheme.primary,
),
],
Switch(
value: value,
onChanged: wrappedOnChanged,
activeColor: Colors.white,
activeTrackColor: theme.colorScheme.primary,
),
],
),
),
);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
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 {
@ -49,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')}';
}
@ -66,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',
@ -122,184 +142,223 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Alert envelope pulse symbol
Center(
child: Container(
width: 96,
height: 96,
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHigh,
shape: BoxShape.circle,
),
child: Center(
child: Container(
width: 72,
height: 72,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 10,
)
],
),
child: Icon(
Icons.sms,
color: theme.colorScheme.primary,
size: 38,
),
// Alert envelope pulse symbol with SlideFadeEntrance
SlideFadeEntrance(
delay: Duration.zero,
child: Center(
child: Container(
width: 96,
height: 96,
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHigh,
shape: BoxShape.circle,
),
),
),
),
const SizedBox(height: 16),
Text(
'Verifying your number',
textAlign: TextAlign.center,
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Sent code to $displayMobile",
style: theme.textTheme.bodyMedium,
),
const SizedBox(width: 8),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Text(
'Edit',
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
const SizedBox(height: 16),
// 6 Custom Inputs Fields
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(11, (index) {
if (index.isOdd) {
return const SizedBox(width: 8);
}
final fieldIndex = index ~/ 2;
return SizedBox(
width: 44,
child: TextField(
controller: _controllers[fieldIndex],
focusNode: _focusNodes[fieldIndex],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 1,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
decoration: InputDecoration(
counterText: '',
contentPadding: const EdgeInsets.symmetric(vertical: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: theme.colorScheme.outlineVariant),
child: Center(
child: Container(
width: 72,
height: 72,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 10,
)
],
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: theme.colorScheme.primary, width: 2),
child: Icon(
Icons.sms,
color: theme.colorScheme.primary,
size: 38,
),
),
onChanged: (value) {
if (value.isNotEmpty && fieldIndex < 5) {
_focusNodes[fieldIndex + 1].requestFocus();
}
},
),
);
}),
),
const SizedBox(height: 16),
// Auto detect loader
Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(12),
),
),
),
const SizedBox(height: 16),
SlideFadeEntrance(
delay: const Duration(milliseconds: 100),
child: Text(
'Verifying your number',
textAlign: TextAlign.center,
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 16),
SlideFadeEntrance(
delay: const Duration(milliseconds: 150),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary),
),
),
const SizedBox(width: 12),
Text(
'Auto-detecting OTP...',
style: theme.textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.bold,
"Sent code to $displayMobile",
style: theme.textTheme.bodyMedium,
),
const SizedBox(width: 8),
InteractiveScale(
onTap: () {
AppFeedback.click();
Navigator.pop(context);
},
child: Text(
'Edit',
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
),
const SizedBox(height: 16),
const SizedBox(height: 20),
// Resending timer action column
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.schedule, size: 16, color: Colors.grey),
const SizedBox(width: 6),
Text(
_timeLeft > 0
? 'Resend code in ${_getFormattedTime()}'
: 'Resend Code via SMS',
style: theme.textTheme.bodyMedium,
// 6 Custom Inputs Fields with SlideFadeEntrance
SlideFadeEntrance(
delay: const Duration(milliseconds: 200),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(11, (index) {
if (index.isOdd) {
return const SizedBox(width: 8);
}
final fieldIndex = index ~/ 2;
return SizedBox(
width: 44,
child: TextField(
controller: _controllers[fieldIndex],
focusNode: _focusNodes[fieldIndex],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 1,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
decoration: InputDecoration(
counterText: '',
contentPadding: const EdgeInsets.symmetric(vertical: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: theme.colorScheme.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: theme.colorScheme.primary, width: 2),
),
),
onChanged: (value) {
AppFeedback.subtleHaptic();
if (value.isNotEmpty) {
if (fieldIndex < 5) {
_focusNodes[fieldIndex + 1].requestFocus();
} else {
_verifyOtp();
}
} else if (value.isEmpty && fieldIndex > 0) {
_focusNodes[fieldIndex - 1].requestFocus();
}
},
),
);
}),
),
),
const SizedBox(height: 24),
// Auto detect loader card with SlideFadeEntrance
SlideFadeEntrance(
delay: const Duration(milliseconds: 250),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(16),
),
],
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary),
),
),
const SizedBox(width: 12),
Text(
'Auto-detecting OTP...',
style: theme.textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
),
),
const SizedBox(height: 24),
// Resending timer action row
SlideFadeEntrance(
delay: const Duration(milliseconds: 300),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.schedule, size: 16, color: Colors.grey),
const SizedBox(width: 6),
Text(
_timeLeft > 0
? 'Resend code in ${_getFormattedTime()}'
: 'Resend Code via SMS',
style: theme.textTheme.bodyMedium,
),
],
),
),
const SizedBox(height: 16),
// WhatsApp custom CTA button
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
side: const BorderSide(color: Color(0xFFE2E2E2)),
elevation: 1,
minimumSize: const Size(200, 48),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text(
// WhatsApp custom CTA button with InteractiveScale
SlideFadeEntrance(
delay: const Duration(milliseconds: 350),
child: InteractiveScale(
child: OutlinedButton.icon(
onPressed: () {
AppFeedback.click();
showTopAlert(
context,
'WhatsApp verification request sent!',
icon: Icons.chat_bubble_outline,
);
},
icon: const Icon(Icons.forum, color: Colors.green, size: 18),
label: const Text(
'Resend via WhatsApp',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
style: OutlinedButton.styleFrom(
backgroundColor: Colors.white,
side: const BorderSide(color: Color(0xFFE2E2E2)),
elevation: 1,
minimumSize: const Size(220, 48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
)
],
@ -325,28 +384,35 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildFooterAction(Icons.help_outline, 'Help'),
_buildFooterAction(Icons.save, 'Save Draft'),
TextButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const BusinessSetupScreen(),
_buildFooterAction(Icons.help_outline, 'Help', () {
showTopAlert(
context,
'Support agent contacted. We will call you shortly.',
icon: Icons.support_agent_outlined,
);
}),
_buildFooterAction(Icons.save, 'Save Draft', () {
showTopAlert(
context,
'Onboarding draft saved successfully!',
icon: Icons.save_outlined,
);
}),
InteractiveScale(
child: TextButton.icon(
onPressed: _verifyOtp,
icon: const Text(
'Verify Manually',
style: TextStyle(fontWeight: FontWeight.bold),
),
label: const Icon(Icons.arrow_forward, size: 16),
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: theme.colorScheme.primary,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
);
},
icon: const Text(
'Verify Manually',
style: TextStyle(fontWeight: FontWeight.bold),
),
label: const Icon(Icons.arrow_forward, size: 16),
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: theme.colorScheme.primary,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
)
@ -362,21 +428,27 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
);
}
Widget _buildFooterAction(IconData icon, String label) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Colors.grey[600], size: 22),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
)
],
Widget _buildFooterAction(IconData icon, String label, VoidCallback onTap) {
return InteractiveScale(
onTap: () {
AppFeedback.click();
onTap();
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Colors.grey[600], size: 22),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
)
],
),
);
}
}

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import '../theme.dart';
import '../route_transitions.dart';
import '../feedback_helper.dart';
import 'billing_dashboard_screen.dart';
class SetupCompleteScreen extends StatelessWidget {
class SetupCompleteScreen extends StatefulWidget {
final String businessName;
final String businessType;
final String gstin;
@ -20,6 +22,73 @@ class SetupCompleteScreen extends StatelessWidget {
this.inventoryAlerts = true,
});
@override
State<SetupCompleteScreen> createState() => _SetupCompleteScreenState();
}
class _SetupCompleteScreenState extends State<SetupCompleteScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _checkScaleAnimation;
late Animation<double> _cardSlideAnimation;
late Animation<double> _cardOpacityAnimation;
late Animation<double> _btnSlideAnimation;
late Animation<double> _btnOpacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1400),
);
_checkScaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.45, curve: Curves.elasticOut),
),
);
_cardSlideAnimation = Tween<double>(begin: 32.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.25, 0.75, curve: Curves.easeOutCubic),
),
);
_cardOpacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.25, 0.65, curve: Curves.easeIn),
),
);
_btnSlideAnimation = Tween<double>(begin: 24.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.55, 1.0, curve: Curves.easeOutCubic),
),
);
_btnOpacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.55, 0.85, curve: Curves.easeIn),
),
);
_controller.forward();
WidgetsBinding.instance.addPostFrameCallback((_) {
AppFeedback.success();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
String _getCategoryDisplayName(String id) {
final cleanId = id.replaceAll('category/', '').trim().toLowerCase();
switch (cleanId) {
@ -40,7 +109,7 @@ class SetupCompleteScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final categoryText = _getCategoryDisplayName(businessType);
final categoryText = _getCategoryDisplayName(widget.businessType);
final gap = context.responsiveGap;
return Scaffold(
@ -81,262 +150,289 @@ class SetupCompleteScreen extends StatelessWidget {
children: [
const SizedBox(height: 24),
// Circular Green Check badge
Container(
width: 96,
height: 96,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 16,
offset: Offset(0, 8),
)
],
),
child: Center(
// Circular Green Check badge with Elastic Entrance Animation
ScaleTransition(
scale: _checkScaleAnimation,
child: Container(
width: 72,
height: 72,
width: 96,
height: 96,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
child: const Icon(
Icons.check,
color: Colors.white,
size: 40,
),
),
),
),
SizedBox(height: gap),
Text(
'Setup Complete! 🎉',
textAlign: TextAlign.center,
style: theme.textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Your billing dashboard is ready for action.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium,
),
SizedBox(height: gap * 1.5),
// Glassmorphism summary Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey[200]!),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 20,
offset: const Offset(0, 8),
)
],
),
child: Column(
children: [
// Row 1: Store name
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: const Icon(Icons.storefront, color: Colors.black87),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'BUSINESS NAME',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey,
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Text(
businessName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 16,
offset: Offset(0, 8),
)
],
),
// Divider line
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Divider(color: Colors.grey[200]),
),
// Row 2: Category type
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: const Icon(Icons.category, color: Colors.black87),
child: Center(
child: Container(
width: 72,
height: 72,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'BUSINESS TYPE',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey,
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Text(
categoryText,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
)
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Divider(color: Colors.grey[200]),
),
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: const Icon(Icons.settings, color: Colors.black87),
child: const Icon(
Icons.check,
color: Colors.white,
size: 40,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'ACTIVE CONFIGURATIONS',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey,
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Text(
[
if (barcodeScanner) 'Barcode Enabled',
if (kotManagement) 'KOT/Kitchen Order',
if (inventoryAlerts) 'Low Stock Warnings',
if (!barcodeScanner && !kotManagement && !inventoryAlerts) 'Standard Register',
].join(', '),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
)
],
)
],
),
),
const SizedBox(height: 24),
// Primary Launcher CTA
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('⚡ Launching Register Dashboard... Enjoy billing!'),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
),
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BillingDashboardScreen(
businessName: businessName,
businessType: businessType,
gstin: gstin,
barcodeScanner: barcodeScanner,
kotManagement: kotManagement,
inventoryAlerts: inventoryAlerts,
),
),
);
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(56),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.receipt, size: 20),
SizedBox(width: 8),
Text('Create Your First Bill'),
],
SizedBox(height: gap),
Text(
'Setup Complete! 🎉',
textAlign: TextAlign.center,
style: theme.textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 24),
],
const SizedBox(height: 8),
Text(
'Your billing dashboard is ready for action.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium,
),
SizedBox(height: gap * 1.5),
// Glassmorphism summary Card with Fade + Slide Transition
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: Offset(0.0, _cardSlideAnimation.value),
child: Opacity(
opacity: _cardOpacityAnimation.value,
child: child,
),
);
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey[200]!),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 20,
offset: const Offset(0, 8),
)
],
),
child: Column(
children: [
// Row 1: Store name
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: const Icon(Icons.storefront, color: Colors.black87),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'BUSINESS NAME',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey,
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Text(
widget.businessName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
)
],
),
// Divider line
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Divider(color: Colors.grey[200]),
),
// Row 2: Category type
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: const Icon(Icons.category, color: Colors.black87),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'BUSINESS TYPE',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey,
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Text(
categoryText,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
)
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Divider(color: Colors.grey[200]),
),
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: const Icon(Icons.settings, color: Colors.black87),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'ACTIVE CONFIGURATIONS',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey,
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Text(
[
if (widget.barcodeScanner) 'Barcode Enabled',
if (widget.kotManagement) 'KOT/Kitchen Order',
if (widget.inventoryAlerts) 'Low Stock Warnings',
if (!widget.barcodeScanner && !widget.kotManagement && !widget.inventoryAlerts) 'Standard Register',
].join(', '),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
)
],
)
],
),
),
),
const SizedBox(height: 24),
// Primary Launcher CTA with Slide + Fade + Hover/Tap Scale micro-animations
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: Offset(0.0, _btnSlideAnimation.value),
child: Opacity(
opacity: _btnOpacityAnimation.value,
child: child,
),
);
},
child: InteractiveScale(
child: ElevatedButton(
onPressed: () {
AppFeedback.click();
showTopAlert(
context,
'Launching Register Dashboard... Enjoy billing!',
icon: Icons.rocket_launch_outlined,
);
Navigator.push(
context,
FadeSlidePageRoute(
child: BillingDashboardScreen(
businessName: widget.businessName,
businessType: widget.businessType,
gstin: widget.gstin,
barcodeScanner: widget.barcodeScanner,
kotManagement: widget.kotManagement,
inventoryAlerts: widget.inventoryAlerts,
),
),
);
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(56),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.receipt, size: 20),
SizedBox(width: 8),
Text('Create Your First Bill'),
],
),
),
),
),
const SizedBox(height: 24),
],
),
),
),
),
),
),
),
);
}
);
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,6 +1,22 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
url: "https://pub.dev"
source: hosted
version: "4.0.9"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
@ -9,6 +25,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:
@ -25,6 +97,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
@ -41,6 +129,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
url: "https://pub.dev"
source: hosted
version: "0.3.5+2"
crypto:
dependency: transitive
description:
@ -57,6 +153,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:
@ -73,11 +193,83 @@ 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:
name: file_selector_linux
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
url: "https://pub.dev"
source: hosted
version: "0.9.4+4"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
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
source: sdk
version: "0.0.0"
flutter_contacts:
dependency: "direct main"
description:
name: flutter_contacts
sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae"
url: "https://pub.dev"
source: hosted
version: "1.1.9+2"
flutter_haptic_feedback:
dependency: "direct main"
description:
name: flutter_haptic_feedback
sha256: e6a1ac6b5119c9b0964e4de2eaa6bfb66facedc6e33bfe2a902da3f6f9666499
url: "https://pub.dev"
source: hosted
version: "1.0.1"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints:
dependency: "direct dev"
description:
@ -86,11 +278,120 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476
url: "https://pub.dev"
source: hosted
version: "2.0.31"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
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:
@ -99,6 +400,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:
@ -115,6 +424,94 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image:
dependency: transitive
description:
name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
url: "https://pub.dev"
source: hosted
version: "4.8.0"
image_picker:
dependency: "direct main"
description:
name: image_picker
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
url: "https://pub.dev"
source: hosted
version: "0.8.13+1"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
url: "https://pub.dev"
source: hosted
version: "0.8.13"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
url: "https://pub.dev"
source: hosted
version: "2.11.1"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
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"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
@ -171,6 +568,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.16.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
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:
@ -227,6 +656,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6
url: "https://pub.dev"
source: hosted
version: "12.0.3"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.dev"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e20daf680eef1ca62ffe8c8c526b778cc386d50137c77ac71c8ec9c88c13fb9d
url: "https://pub.dev"
source: hosted
version: "9.4.9"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
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:
@ -243,6 +728,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
posix:
dependency: transitive
description:
name: posix
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sky_engine:
dependency: transitive
description: flutter
@ -280,6 +773,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:
@ -304,6 +805,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:
@ -312,6 +885,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vibration:
dependency: "direct main"
description:
name: vibration
sha256: "9bb06614c69260f8bd11c80fe01ed7988905cf00e3417d656c2647e41f261d87"
url: "https://pub.dev"
source: hosted
version: "3.1.8"
vibration_platform_interface:
dependency: transitive
description:
name: vibration_platform_interface
sha256: "258c273268f8aa40c88d29741137c536874a738779b92ddb8aa32ed093721ec5"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
vm_service:
dependency: transitive
description:
@ -328,6 +917,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:
@ -336,6 +941,22 @@ 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"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.29.0"
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.32.0"

View File

@ -10,11 +10,33 @@ dependencies:
sdk: flutter
cupertino_icons: ^1.0.5
google_fonts: ^5.1.0
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: ^3.1.8
audioplayers: ^5.2.1
flutter_haptic_feedback: ^1.0.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter_launcher_icons: ^0.13.1
flutter_launcher_icons:
android: "launcher_icon"
ios: true
image_path: "assets/logo/pozo_logo_padded.png"
remove_alpha_ios: true
adaptive_icon_background: "#d2242e"
adaptive_icon_foreground: "assets/logo/pozo_logo_foreground.png"
flutter:
uses-material-design: true
assets:
- assets/logo/
- assets/sounds/