first commit
This commit is contained in:
150
lib/GlobalComponents/bar_code_scaner_widget.dart
Normal file
150
lib/GlobalComponents/bar_code_scaner_widget.dart
Normal file
@@ -0,0 +1,150 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class BarcodeScannerWidget extends StatefulWidget {
|
||||
final Function(String) onBarcodeFound;
|
||||
|
||||
const BarcodeScannerWidget({super.key, required this.onBarcodeFound});
|
||||
|
||||
@override
|
||||
_BarcodeScannerWidgetState createState() => _BarcodeScannerWidgetState();
|
||||
}
|
||||
|
||||
class _BarcodeScannerWidgetState extends State<BarcodeScannerWidget> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _animation;
|
||||
final MobileScannerController controller = MobileScannerController(
|
||||
torchEnabled: false,
|
||||
returnImage: false,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Red Line Animation (Moves Up and Down)
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 2),
|
||||
)..repeat(reverse: true);
|
||||
|
||||
_animation = Tween<double>(begin: 50, end: 250).animate(_animationController);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Container(
|
||||
width: 320,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Title and Close Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"Scan Barcode",
|
||||
style: TextStyle(color: Colors.white, fontSize: 18),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Scanner Box
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Camera Scanner
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: MobileScanner(
|
||||
fit: BoxFit.cover,
|
||||
controller: controller,
|
||||
onDetect: (capture) {
|
||||
final List<Barcode> barcodes = capture.barcodes;
|
||||
|
||||
if (barcodes.isNotEmpty) {
|
||||
final Barcode barcode = barcodes.first;
|
||||
debugPrint('Barcode found: ${barcode.rawValue}');
|
||||
|
||||
// Call the callback function with the barcode value
|
||||
widget.onBarcodeFound(barcode.rawValue!);
|
||||
|
||||
// Close the scanner
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Animated Red Line
|
||||
AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) {
|
||||
return Positioned(
|
||||
top: _animation.value,
|
||||
left: 10,
|
||||
right: 10,
|
||||
child: Container(
|
||||
height: 2,
|
||||
width: 280,
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BarCodeButton extends StatelessWidget {
|
||||
const BarCodeButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 48.0,
|
||||
width: 100.0,
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
border: Border.all(color: const Color(0xffD8D8D8)),
|
||||
),
|
||||
child: const Image(
|
||||
image: AssetImage('images/barcode.png'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
129
lib/GlobalComponents/button_global.dart
Normal file
129
lib/GlobalComponents/button_global.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_pos/constant.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ButtonGlobal extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
var iconWidget;
|
||||
final String buttontext;
|
||||
final Color iconColor;
|
||||
final Decoration? buttonDecoration;
|
||||
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
var onPressed;
|
||||
|
||||
// ignore: use_key_in_widget_constructors
|
||||
ButtonGlobal({required this.iconWidget, required this.buttontext, required this.iconColor, this.buttonDecoration, required this.onPressed});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
decoration: buttonDecoration ?? BoxDecoration(borderRadius: BorderRadius.circular(8), color: kMainColor),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
buttontext,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Colors.white),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Icon(
|
||||
iconWidget,
|
||||
color: iconColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ButtonGlobalWithoutIcon extends StatelessWidget {
|
||||
final String buttontext;
|
||||
final Decoration buttonDecoration;
|
||||
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
var onPressed;
|
||||
final Color buttonTextColor;
|
||||
|
||||
// ignore: use_key_in_widget_constructors
|
||||
ButtonGlobalWithoutIcon({required this.buttontext, required this.buttonDecoration, required this.onPressed, required this.buttonTextColor});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
child: Container(
|
||||
height: 50,
|
||||
alignment: Alignment.center,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
decoration: buttonDecoration,
|
||||
child: Text(
|
||||
buttontext,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(color: buttonTextColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///-----------------------name with logo------------------
|
||||
class NameWithLogo extends StatelessWidget {
|
||||
const NameWithLogo({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 75,
|
||||
width: 66,
|
||||
decoration: const BoxDecoration(image: DecorationImage(image: AssetImage(logo))),
|
||||
),
|
||||
const Text(
|
||||
appsName,
|
||||
style: TextStyle(color: kTitleColor, fontWeight: FontWeight.bold, fontSize: 28),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///-------------------update button--------------------------------
|
||||
|
||||
class UpdateButton extends StatelessWidget {
|
||||
const UpdateButton({Key? key, required this.text, required this.onpressed}) : super(key: key);
|
||||
final String text;
|
||||
final VoidCallback onpressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return GestureDetector(
|
||||
onTap: onpressed,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: kMainColor,
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: theme.textTheme.titleMedium?.copyWith(color: kWhite),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
48
lib/GlobalComponents/check_subscription.dart
Normal file
48
lib/GlobalComponents/check_subscription.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
// import 'package:flutter/material.dart';
|
||||
// import '../../model/business_info_model.dart' as business;
|
||||
// import '../Screens/subscription/purchase_premium_plan_screen.dart';
|
||||
//
|
||||
// Future<void> checkSubscriptionAndNavigate(
|
||||
// BuildContext context,
|
||||
// String? subscriptionDate,
|
||||
// String expireDate,
|
||||
// business.EnrolledPlan? enrolledPlan,
|
||||
// ) async {
|
||||
// print('Subscription plan: Expire date : $expireDate');
|
||||
// DateTime expireDate2 = DateTime.parse(expireDate);
|
||||
// if (DateTime.now().isAfter(expireDate2)) {
|
||||
// await navigateToPurchasePremiumPlanScreen(context, true, expireDate, enrolledPlan);
|
||||
// }
|
||||
// if (subscriptionDate == null || enrolledPlan == null) {
|
||||
// await navigateToPurchasePremiumPlanScreen(context, true, expireDate, enrolledPlan);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// DateTime parsedSubscriptionDate = DateTime.parse(subscriptionDate);
|
||||
// num duration = enrolledPlan.duration ?? 0;
|
||||
// DateTime expirationDate = parsedSubscriptionDate.add(Duration(days: duration.toInt()));
|
||||
// num daysLeft = expirationDate.difference(DateTime.now()).inDays;
|
||||
//
|
||||
// if (daysLeft < 0) {
|
||||
// await navigateToPurchasePremiumPlanScreen(context, true, expireDate, enrolledPlan);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> navigateToPurchasePremiumPlanScreen(
|
||||
// BuildContext context,
|
||||
// bool isExpired,
|
||||
// String expireDate,
|
||||
// business.EnrolledPlan? enrolledPlan,
|
||||
// ) async {
|
||||
// await Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => PurchasePremiumPlanScreen(
|
||||
// isExpired: true,
|
||||
// isCameBack: true,
|
||||
// enrolledPlan: enrolledPlan,
|
||||
// willExpire: expireDate,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
54
lib/GlobalComponents/glonal_popup.dart
Normal file
54
lib/GlobalComponents/glonal_popup.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../constant.dart';
|
||||
import 'internet_connection_notifier.dart';
|
||||
|
||||
class GlobalPopup extends ConsumerStatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const GlobalPopup({super.key, required this.child});
|
||||
|
||||
@override
|
||||
ConsumerState<GlobalPopup> createState() => _GlobalPopupState();
|
||||
}
|
||||
|
||||
class _GlobalPopupState extends ConsumerState<GlobalPopup> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final internetStatus = ref.watch(internetConnectionProvider);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
widget.child,
|
||||
if (!internetStatus.isConnected && internetStatus.appLifecycleState == AppLifecycleState.resumed)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
color: Colors.white,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.wifi_off, color: kMainColor, size: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
'No Internet Connection',
|
||||
style: TextStyle(color: kTitleColor, fontSize: 24),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final notifier = ref.read(internetConnectionProvider);
|
||||
await notifier.checkConnection();
|
||||
},
|
||||
child: const Text("Try Again"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../Screens/subscription/package_screen.dart';
|
||||
import '../constant.dart';
|
||||
import '../generated/l10n.dart' as lang;
|
||||
import '../model/business_info_model.dart';
|
||||
|
||||
Widget goToPackagePagePopup({required BuildContext context, required EnrolledPlan? enrolledPlan}) {
|
||||
return AlertDialog(
|
||||
backgroundColor: kWhite,
|
||||
surfaceTintColor: kWhite,
|
||||
elevation: 0.0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
titlePadding: const EdgeInsets.all(0),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: kGreyTextColor,
|
||||
)),
|
||||
],
|
||||
),
|
||||
SvgPicture.asset(
|
||||
'assets/upgradePlan.svg',
|
||||
height: 198,
|
||||
width: 238,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
// lang.S.of(context).endYourFreePlan,
|
||||
textAlign: TextAlign.center,
|
||||
enrolledPlan?.plan?.subscriptionName != null ? 'End your ${enrolledPlan?.plan?.subscriptionName} plan?' : "No active plan!",
|
||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kTitleColor),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
enrolledPlan?.plan?.subscriptionName != null
|
||||
? 'Your ${enrolledPlan?.plan?.subscriptionName} plan is almost done, buy your next plan Thanks.'
|
||||
: 'You don’t have an active plan! buy your next plan now, Thanks',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: kGreyTextColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
child: Text(lang.S.of(context).upgradeNow),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
55
lib/GlobalComponents/internet_connection_notifier.dart
Normal file
55
lib/GlobalComponents/internet_connection_notifier.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
|
||||
|
||||
final internetConnectionProvider = ChangeNotifierProvider<InternetConnectionNotifier>((ref) {
|
||||
return InternetConnectionNotifier();
|
||||
});
|
||||
|
||||
class InternetConnectionNotifier extends ChangeNotifier with WidgetsBindingObserver {
|
||||
bool _isConnected = true;
|
||||
AppLifecycleState appLifecycleState = AppLifecycleState.resumed;
|
||||
late final StreamSubscription<InternetStatus> _subscription;
|
||||
|
||||
bool get isConnected => _isConnected;
|
||||
|
||||
InternetConnectionNotifier() {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_init();
|
||||
}
|
||||
|
||||
void _init() {
|
||||
checkConnection();
|
||||
_subscription = InternetConnection().onStatusChange.listen((status) {
|
||||
print('Internet connection status: $status');
|
||||
if (appLifecycleState != AppLifecycleState.paused) {
|
||||
final wasConnected = _isConnected;
|
||||
_isConnected = status == InternetStatus.connected;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> checkConnection() async {
|
||||
final previous = _isConnected;
|
||||
_isConnected = await InternetConnection().hasInternetAccess;
|
||||
if (_isConnected != previous) notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
appLifecycleState = state;
|
||||
notifyListeners();
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
checkConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_subscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
44
lib/GlobalComponents/license_verifier.dart
Normal file
44
lib/GlobalComponents/license_verifier.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showLicense({required BuildContext context}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 180.0,
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
),
|
||||
child: const Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Please Check Your Purchase Code',
|
||||
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(
|
||||
height: 10.0,
|
||||
),
|
||||
Text(
|
||||
'Your purchase code is not valid. Please buy our product from envato to get a new purchase code',
|
||||
maxLines: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
31
lib/GlobalComponents/returned_tag_widget.dart
Normal file
31
lib/GlobalComponents/returned_tag_widget.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||
|
||||
class ReturnedTagWidget extends StatelessWidget {
|
||||
const ReturnedTagWidget({super.key, required this.show});
|
||||
|
||||
final bool show;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Visibility(
|
||||
visible: show,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withOpacity(0.2),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
lang.S.of(context).returned,
|
||||
style: const TextStyle(color: Colors.orange),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
387
lib/GlobalComponents/sales_transaction_widget.dart
Normal file
387
lib/GlobalComponents/sales_transaction_widget.dart
Normal file
@@ -0,0 +1,387 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hugeicons/hugeicons.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobile_pos/GlobalComponents/returned_tag_widget.dart';
|
||||
import 'package:mobile_pos/model/sale_transaction_model.dart';
|
||||
import 'package:nb_utils/nb_utils.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as l;
|
||||
|
||||
import '../PDF Invoice/sales_invoice_pdf.dart';
|
||||
import '../Provider/profile_provider.dart';
|
||||
import '../Screens/Loss_Profit/single_loss_profit_screen.dart';
|
||||
import '../Screens/Sales/add_sales.dart';
|
||||
import '../Screens/Sales/provider/sales_cart_provider.dart';
|
||||
import '../Screens/invoice return/invoice_return_screen.dart';
|
||||
import '../Screens/invoice_details/sales_invoice_details_screen.dart';
|
||||
import '../constant.dart';
|
||||
import '../core/theme/_app_colors.dart';
|
||||
import '../currency.dart';
|
||||
import '../generated/l10n.dart' as lang;
|
||||
import '../model/business_info_model.dart' as bInfo;
|
||||
import '../service/check_actions_when_no_branch.dart';
|
||||
import '../thermal priting invoices/provider/print_thermal_invoice_provider.dart';
|
||||
|
||||
Widget salesTransactionWidget({
|
||||
required BuildContext context,
|
||||
required SalesTransactionModel sale,
|
||||
required bInfo.BusinessInformationModel businessInfo,
|
||||
required WidgetRef ref,
|
||||
bool? showProductQTY,
|
||||
required bool advancePermission,
|
||||
bool? fromLossProfit,
|
||||
num? returnAmount,
|
||||
bool? isFromSaleList,
|
||||
}) {
|
||||
final theme = Theme.of(context);
|
||||
final _lang = l.S.of(context);
|
||||
final printerData = ref.watch(thermalPrinterProvider);
|
||||
return Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (fromLossProfit ?? false) {
|
||||
SingleLossProfitScreen(
|
||||
transactionModel: sale,
|
||||
).launch(context);
|
||||
} else {
|
||||
SalesInvoiceDetails(
|
||||
saleTransaction: sale,
|
||||
businessInfo: businessInfo,
|
||||
).launch(context);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
(showProductQTY ?? false)
|
||||
? "${lang.S.of(context).totalProduct} : ${sale.salesDetails?.length.toString()}"
|
||||
: sale.party?.name ?? '',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'#${sale.invoiceNumber}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
///_____Payment_Sttus________________________________________
|
||||
getPaymentStatusBadge(
|
||||
context: context, dueAmount: sale.dueAmount!, totalAmount: sale.totalAmount!),
|
||||
|
||||
///________Return_tag_________________________________________
|
||||
ReturnedTagWidget(show: sale.salesReturns?.isNotEmpty ?? false),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
DateFormat('dd MMM, yyyy').format(DateTime.parse(sale.saleDate ?? '')),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: kPeragrapColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${lang.S.of(context).total} : $currency${formatPointNumber(sale.totalAmount ?? 0)}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
color: kPeraColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
if (sale.dueAmount!.toInt() != 0)
|
||||
Text(
|
||||
'${lang.S.of(context).paid} : $currency${formatPointNumber(
|
||||
(sale.totalAmount!.toDouble() - sale.dueAmount!.toDouble()),
|
||||
)}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
color: kPeraColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (fromLossProfit ?? false) ...{
|
||||
Flexible(
|
||||
child: Text(
|
||||
'${lang.S.of(context).profit} : $currency ${formatPointNumber(sale.detailsSumLossProfit ?? 0)}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
color: Colors.green,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
).visible(!sale.detailsSumLossProfit!.isNegative),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'${lang.S.of(context).loss}: $currency ${formatPointNumber(sale.detailsSumLossProfit!.abs())}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
color: Colors.redAccent,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
).visible(sale.detailsSumLossProfit!.isNegative),
|
||||
),
|
||||
} else ...{
|
||||
if (sale.dueAmount!.toInt() == 0)
|
||||
Flexible(
|
||||
child: Text(
|
||||
(returnAmount != null)
|
||||
? '${_lang.returnedAmount}: $currency${formatPointNumber(returnAmount)}'
|
||||
: '${lang.S.of(context).paid} : $currency${formatPointNumber((sale.totalAmount!.toDouble() - sale.dueAmount!.toDouble()))}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
if (sale.dueAmount!.toInt() != 0)
|
||||
Flexible(
|
||||
child: Text(
|
||||
(returnAmount != null)
|
||||
? '${_lang.returnedAmount}: $currency${formatPointNumber(returnAmount)}'
|
||||
: '${lang.S.of(context).due}: $currency${formatPointNumber(sale.dueAmount ?? 0)}',
|
||||
maxLines: 2,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 6),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: () =>
|
||||
SalesInvoicePdf.generateSaleDocument(sale, businessInfo, context, showPreview: true),
|
||||
icon: HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedPdf02,
|
||||
size: 22,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: () async {
|
||||
// PrintSalesTransactionModel model = PrintSalesTransactionModel(transitionModel: sale, personalInformationModel: businessInfo);
|
||||
// await printerData.printSalesThermalInvoiceNow(
|
||||
// transaction: model,
|
||||
// productList: model.transitionModel!.salesDetails,
|
||||
// context: context,
|
||||
// );
|
||||
SalesInvoiceDetails(
|
||||
saleTransaction: sale,
|
||||
businessInfo: businessInfo,
|
||||
).launch(context);
|
||||
},
|
||||
icon: const Icon(
|
||||
FeatherIcons.printer,
|
||||
color: kPeraColor,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: () => SalesInvoiceExcel.generateSaleDocument(sale, businessInfo, context),
|
||||
icon: HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedXls02,
|
||||
size: 22,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: () =>
|
||||
SalesInvoicePdf.generateSaleDocument(sale, businessInfo, context, download: true),
|
||||
icon: HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedDownload01,
|
||||
size: 22,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: () =>
|
||||
SalesInvoicePdf.generateSaleDocument(sale, businessInfo, context, share: true),
|
||||
icon: HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedShare08,
|
||||
size: 22,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
///________Sales_return_____________________________
|
||||
if (isFromSaleList == true)
|
||||
if (advancePermission)
|
||||
PopupMenuButton(
|
||||
offset: const Offset(0, 30),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (BuildContext bc) => [
|
||||
///________Sale Return___________________________________
|
||||
PopupMenuItem(
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
bool result = await checkActionWhenNoBranch(ref: ref, context: context);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => InvoiceReturnScreen(saleTransactionModel: sale),
|
||||
),
|
||||
);
|
||||
Navigator.pop(bc);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.keyboard_return_outlined,
|
||||
color: kGreyTextColor,
|
||||
),
|
||||
SizedBox(width: 10.0),
|
||||
Text(
|
||||
_lang.saleReturn,
|
||||
style: TextStyle(color: kGreyTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
ref.refresh(cartNotifier);
|
||||
AddSalesScreen(
|
||||
transitionModel: sale,
|
||||
customerModel: null,
|
||||
).launch(context);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FeatherIcons.edit,
|
||||
color: kGreyTextColor,
|
||||
),
|
||||
SizedBox(width: 10.0),
|
||||
Text(
|
||||
_lang.saleEdit,
|
||||
style: TextStyle(color: kGreyTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
// child:
|
||||
//
|
||||
// ///_________Sales_edit___________________________
|
||||
// Visibility(
|
||||
// visible: !(sale.salesReturns?.isNotEmpty ?? false),
|
||||
// child: const Icon(
|
||||
// FeatherIcons.edit,
|
||||
// color: Colors.grey,
|
||||
// ),
|
||||
// ),
|
||||
),
|
||||
],
|
||||
onSelected: (value) {
|
||||
Navigator.pushNamed(context, '$value');
|
||||
},
|
||||
child: const Icon(
|
||||
FeatherIcons.moreVertical,
|
||||
color: kPeraColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(height: 1, color: kLineColor),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget getPaymentStatusBadge({required num dueAmount, required num totalAmount, required BuildContext context}) {
|
||||
String status;
|
||||
Color textColor;
|
||||
Color bgColor;
|
||||
|
||||
if (dueAmount <= 0) {
|
||||
status = lang.S.of(context).paid;
|
||||
textColor = const Color(0xff0dbf7d);
|
||||
bgColor = const Color(0xff0dbf7d).withOpacity(0.1);
|
||||
} else if (dueAmount >= totalAmount) {
|
||||
status = lang.S.of(context).unPaid;
|
||||
textColor = const Color(0xFFED1A3B);
|
||||
bgColor = const Color(0xFFED1A3B).withOpacity(0.1);
|
||||
} else {
|
||||
status = lang.S.of(context).partialPaid;
|
||||
textColor = const Color(0xFFFFA500);
|
||||
bgColor = const Color(0xFFFFA500).withOpacity(0.1);
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
),
|
||||
child: Text(
|
||||
status,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
122
lib/GlobalComponents/tab_buttons.dart
Normal file
122
lib/GlobalComponents/tab_buttons.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class TabButton extends StatelessWidget {
|
||||
TabButton({
|
||||
required this.title,
|
||||
required this.text,
|
||||
required this.background,
|
||||
required this.press,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
final Color background;
|
||||
final Color text;
|
||||
final String title;
|
||||
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
var press;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 40.0,
|
||||
width: 100.0,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
color: background,
|
||||
),
|
||||
child: Center(
|
||||
child: TextButton(
|
||||
onPressed: press,
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: text,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class TabButtonSmall extends StatelessWidget {
|
||||
TabButtonSmall({
|
||||
required this.title,
|
||||
required this.text,
|
||||
required this.background,
|
||||
required this.press,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
final Color background;
|
||||
final Color text;
|
||||
final String title;
|
||||
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
var press;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
height: 40.0,
|
||||
width: 90.0,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
color: background,
|
||||
),
|
||||
child: Center(
|
||||
child: TextButton(
|
||||
onPressed: press,
|
||||
child: Text(
|
||||
title,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: text,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class TabButtonBig extends StatelessWidget {
|
||||
TabButtonBig({
|
||||
required this.title,
|
||||
required this.text,
|
||||
required this.background,
|
||||
required this.press,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
final Color background;
|
||||
final Color text;
|
||||
final String title;
|
||||
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
var press;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
height: 40.0,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
color: background,
|
||||
),
|
||||
child: Center(
|
||||
child: TextButton(
|
||||
onPressed: press,
|
||||
child: Text(
|
||||
title,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: text,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
lib/GlobalComponents/url_lanuncer.dart
Normal file
32
lib/GlobalComponents/url_lanuncer.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class UrlLauncher {
|
||||
static Future<void> handleLaunchURL(BuildContext context, String url, bool isEmail) async {
|
||||
try {
|
||||
final parsedUrl = Uri.tryParse(url);
|
||||
if (parsedUrl == null || !parsedUrl.hasScheme) {
|
||||
throw const FormatException('Invalid URL format');
|
||||
}
|
||||
|
||||
final launched = await launchUrl(
|
||||
parsedUrl,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
|
||||
if (!launched && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Could not launch ${isEmail ? 'Email' : 'Sms'}')),
|
||||
);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Could not launch the ${isEmail ? 'Email' : 'Sms'}')),
|
||||
);
|
||||
}
|
||||
// Consider logging the error for debugging
|
||||
debugPrint('URL Launch Error: $e\n$stackTrace');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user