first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

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

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

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

View 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"),
),
],
),
),
),
),
],
);
}
}

View File

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

View 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();
}
}

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

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

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

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

View 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');
}
}
}