Commit e714661c authored by Sai Srinivas's avatar Sai Srinivas
Browse files

Initial commit of Gen Service app

parent e276e49f
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gen_service/Notifiers/AuthProvider.dart';
import 'package:provider/provider.dart';
import '../../Utility/AdvancedSnackbar.dart';
import '../../Utility/CustomSnackbar.dart';
import '../../Utility/SharedpreferencesService.dart';
import '../HomeScreen.dart';
class OtpScreen extends StatefulWidget {
final String mob;
const OtpScreen({super.key, required this.mob});
@override
State<OtpScreen> createState() => _OtpScreenState();
}
class _OtpScreenState extends State<OtpScreen> {
final List<TextEditingController> _controllers =
List.generate(4, (_) => TextEditingController());
final List<FocusNode> _focusNodes = List.generate(4, (_) => FocusNode());
final prefs = SharedPreferencesService.instance;
bool _isValid = false;
bool _isVerifying = false;
bool _isResending = false;
int _secondsRemaining = 22;
Timer? _timer;
@override
void initState() {
super.initState();
_startTimer();
}
void _startTimer() {
_timer?.cancel();
_secondsRemaining = 22;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_secondsRemaining > 0) {
setState(() => _secondsRemaining--);
} else {
timer.cancel();
}
});
}
void _onOtpChange() {
final otp = _controllers.map((c) => c.text).join();
setState(() {
_isValid = otp.length == 4 && RegExp(r'^[0-9]{4}$').hasMatch(otp);
});
}
Future<void> _verifyOtp() async {
final authProvider = Provider.of<AuthProvider>(context, listen: false);
final enteredOtp = _controllers.map((c) => c.text).join();
if (enteredOtp.length != 4) {
CustomSnackBar.showWarning(
context: context,
title: "Incomplete OTP",
message: "Please enter all 4 digits of the OTP.",
);
return;
}
setState(() => _isVerifying = true);
try {
await authProvider.verifyOtp(widget.mob, enteredOtp);
if (authProvider.otpResponse != null) {
if (authProvider.otpResponse?.error == "0") {
if (!mounted) return;
AnimatedSnackBar.success(
context: context,
title: "Success",
message: authProvider.otpResponse?.message ?? "OTP Verified Successfully!",
);
// ✅ Save session and accId
if (authProvider.otpResponse?.accId != null) {
await prefs.saveString("accId", authProvider.otpResponse!.accId!);
await prefs.saveString("session_id", authProvider.otpResponse!.sessionId!);
}
// ✅ Navigate to Home Screen
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => HomeScreen(
accId: authProvider.otpResponse!.accId!,
sessionId: authProvider.otpResponse!.sessionId.toString(),
),
transitionsBuilder: (_, animation, __, child) {
return FadeTransition(opacity: animation, child: child);
},
transitionDuration: const Duration(milliseconds: 800),
),
);
} else {
CustomSnackBar.showError(
context: context,
title: "Error",
message: authProvider.otpResponse?.message ?? "Invalid OTP. Please try again.",
);
}
} else {
CustomSnackBar.showError(
context: context,
title: "Error",
message: "Failed to verify OTP. Please try again.",
);
}
} catch (e) {
CustomSnackBar.showError(
context: context,
title: "Network Error",
message: e.toString(),
);
} finally {
if (mounted) setState(() => _isVerifying = false);
}
}
Future<void> _resendOtp(BuildContext context) async {
setState(() => _isResending = true);
try {
final authProvider = Provider.of<AuthProvider>(context, listen: false);
await authProvider.fetchMobile(widget.mob);
if (authProvider.mobileResponse != null && authProvider.mobileResponse?.error == "0") {
AnimatedSnackBar.success(
context: context,
title: "OTP Sent",
message: authProvider.mobileResponse?.message ?? "OTP sent successfully.",
);
setState(() {
_secondsRemaining = 22;
});
for (var controller in _controllers) {
controller.clear();
}
setState(() {
_isValid = false;
});
_startTimer();
} else {
CustomSnackBar.showWarning(
context: context,
title: "Error",
message: authProvider.mobileResponse?.message ?? "Failed to resend OTP",
);
}
} catch (e) {
CustomSnackBar.showError(
context: context,
title: "Network Error",
message: e.toString(),
);
} finally {
if (mounted) setState(() => _isResending = false);
}
}
Widget _buildOtpBox(int index) {
return SizedBox(
width: 55,
height: 55,
child: TextField(
controller: _controllers[index],
focusNode: _focusNodes[index],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 1,
style: const TextStyle(
fontSize: 22,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
color: Colors.black,
),
decoration: InputDecoration(
counterText: "",
filled: true,
fillColor: const Color(0xFFD7F0FF),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30),
borderSide: BorderSide(color: Colors.grey.shade300, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30),
borderSide: const BorderSide(color: Color(0xFF008CDE), width: 2),
),
),
onChanged: (value) {
_onOtpChange();
if (value.isNotEmpty && index < 3) {
FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
} else if (value.isEmpty && index > 0) {
FocusScope.of(context).requestFocus(_focusNodes[index - 1]);
}
},
),
);
}
@override
void dispose() {
for (var controller in _controllers) {
controller.dispose();
}
for (var node in _focusNodes) {
node.dispose();
}
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
resizeToAvoidBottomInset: true,
body: Stack(
children: [
/// 🔹 Background
Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/background_png.png"),
fit: BoxFit.cover,
),
),
),
SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(height: 80),
/// 🔹 Logo
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(width: 30),
SvgPicture.asset(
"assets/svg/genesis_logo_2io.svg",
height: 50,
color: Colors.white,
),
],
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 6,),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Login to",
style: TextStyle(
fontSize: 48,
height: 1,
fontFamily: "PoppinsExtraLight",
fontWeight: FontWeight.w200,
color: Colors.white,
),
),
Text(
"continue",
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
),
],
),
SizedBox(height: 20,),
/// 🔹 Bottom sheet area
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 30),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Enter the OTP, sent to +91 ${widget.mob}",
style: const TextStyle(
color: Colors.black87,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(4, (i) => _buildOtpBox(i)),
),
const SizedBox(height: 16),
/// 🔹 Resend OTP
TextButton(
onPressed: _secondsRemaining == 0 && !_isResending
? () => _resendOtp(context)
: null,
child: _isResending
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.blue,
),
)
: Text(
_secondsRemaining == 0
? "Resend OTP"
: "Resend OTP in $_secondsRemaining s",
style: TextStyle(
color: _secondsRemaining == 0
? const Color(0xFF008CDE)
: Colors.black45,
fontSize: 14,
decorationColor: const Color(0xFF008CDE),
decoration: _secondsRemaining == 0
? TextDecoration.underline
: TextDecoration.none,
),
),
),
const SizedBox(height: 4),
/// 🔹 Continue Button
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
onPressed: _isVerifying ? null : _verifyOtp,
child: _isVerifying
? const CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
)
: const Text(
"Continue",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 12),
],
),
),
],
),
),
),
);
},
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _mobileController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.blue,
body: Stack(
children: [
/// 🔹 Background image
Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/background_png.png"),
fit: BoxFit.cover,
),
),
),
/// 🔹 Main content (scrollable & keyboard-safe)
SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom, // moves up with keyboard
),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(height: 80),
/// 🔹 Logo
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 20,),
SvgPicture.asset(
"assets/svg/genesis_logo_2io.svg",
height: 48,
color: Colors.white,
),
]
),
const SizedBox(height: 12),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Login to",
style: TextStyle(
fontSize: 48,
fontFamily: "PoppinsLight",
fontWeight: FontWeight.w100,
color: Colors.white,
),
),
Text(
"continue",
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
),
],
),
const SizedBox(height: 20),
/// 🔹 Bottom Sheet style area
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Enter Registered Mobile No.",
style: TextStyle(
fontSize: 14,
color: Colors.black54,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 10),
/// 🔹 Mobile Field
TextFormField(
controller: _mobileController,
keyboardType: TextInputType.phone,
maxLength: 10,
decoration: InputDecoration(
hintText: "Enter Mobile No.",
counterText: "",
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(50),
borderSide: const BorderSide(color: Colors.blue, width: 1.2),
),
filled: true,
fillColor: Colors.grey.shade100,
),
validator: (value) {
if (value == null || value.isEmpty) {
return "Please enter mobile number";
} else if (value.length != 10) {
return "Enter valid 10-digit number";
}
return null;
},
),
const SizedBox(height: 20),
/// 🔹 Continue Button (Always visible)
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0086F1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: () {
if (_formKey.currentState!.validate()) {
FocusScope.of(context).unfocus();
// TODO: Add API call here
}
},
child: const Text(
"Continue",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
),
],
),
),
),
);
},
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gen_service/Screens/ProfileScreen.dart';
import 'package:gen_service/Screens/TransactionListScreen.dart';
import 'package:provider/provider.dart';
import '../Notifiers/DashboardProvider.dart';
import '../Utility/AppColors.dart';
class HomeScreen extends StatefulWidget {
final String accId;
final String sessionId;
const HomeScreen({
Key? key,
required this.accId,
required this.sessionId,
}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _stretch = true;
@override
void initState() {
super.initState();
Future.microtask(() {
final dashboardProvider =
Provider.of<DashboardProvider>(context, listen: false);
dashboardProvider.fetchDashboard(widget.accId, widget.sessionId);
});
}
@override
Widget build(BuildContext context) {
final dashboardProvider = Provider.of<DashboardProvider>(context);
final isLoading = dashboardProvider.isLoading;
final error = dashboardProvider.errorMessage;
final data = dashboardProvider.dashboardData;
if (isLoading) {
return const Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: CircularProgressIndicator(),
),
);
}
if (error != null) {
return Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: Text(
error,
style: const TextStyle(color: Colors.red, fontSize: 16),
),
),
);
}
if (data == null) {
return const Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Center(
child: Text("No data found."),
),
);
}
return Scaffold(
backgroundColor: Color(0xFF4076FF),
body: CustomScrollView(
physics: ClampingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
leading: Container(),
stretch: _stretch,
backgroundColor: Color(0xFF4076FF),
onStretchTrigger: () async {
// Refresh data when pulled down
final dashboardProvider =
Provider.of<DashboardProvider>(context, listen: false);
dashboardProvider.fetchDashboard(widget.accId, widget.sessionId);
},
stretchTriggerOffset: 300.0,
expandedHeight: 280.0,
flexibleSpace: LayoutBuilder(
builder: (context, constraints) {
final top = constraints.biggest.height;
return FlexibleSpaceBar(
stretchModes: const [
StretchMode.zoomBackground,
StretchMode.blurBackground,
],
background: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfileScreen(
accId: widget.accId,
sessionId: widget.sessionId,
),
),
);
},
child: Container(
width: double.infinity,
decoration: const BoxDecoration(gradient: AppColors.backgroundGradient),
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.only(top: 60, bottom: 60, left: 20, right: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Profile Image
Container(
height: 80,
width: 80,
decoration: const BoxDecoration(
color: Color(0xFFE6F6FF),
shape: BoxShape.circle,
),
clipBehavior: Clip.antiAlias,
child: (data.userProfile?.isNotEmpty == true)
? Image.network(
data.userProfile.toString(),
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
const Icon(Icons.person,
color: Color(0xFF2d2d2d), size: 40),
)
: CircleAvatar(
radius: 40,
backgroundColor: const Color(0xFFE0F4FF),
child: SvgPicture.asset(
height: 40,
"assets/svg/person_ic.svg",
fit: BoxFit.contain,
),
),
),
const SizedBox(height: 16),
Flexible(
child: Text(
data.userName.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(height: 8),
Text(
'+91 ${data.mobNum}',
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
],
),
),
),
),
),
);
},
),
),
// Body content
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.only(top: 1, bottom: 0),
color: Colors.transparent,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 0),
decoration: const BoxDecoration(
color: AppColors.backgroundRegular,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
// Orders Section
_buildOrdersSection(data),
const SizedBox(height: 4),
// Transactions Card
_buildTransactionsCard(data),
const SizedBox(height: 18),
const Divider(),
// Complaints Section
_buildComplaintsSection(data),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFD7F0FF),
borderRadius: BorderRadius.circular(16),
border: Border.all(width: 1.5, color: AppColors.buttonColor),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Facing Issues?',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.amountText)),
Text(
'Raise a ticket to resolve your issues.',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: AppColors.subtitleText),
),
],
),
SvgPicture.asset("assets/svg/requirements.svg",
height: 32, width: 32),
],
),
),
),
const SizedBox(height: 20),
// Get in Touch Card
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: BoxDecoration(
color: AppColors.amountText,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Get in touch With Us',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
),
const SizedBox(height: 4),
Text(
'Please feel free to reach out to us anytime',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Color(0xAAFFFFFF)),
),
],
),
),
const Icon(Icons.arrow_circle_right,
color: Color(0xFFFFFFFF), size: 30),
],
),
)
), // Add bottom padding
],
),
),
),
),
],
),
);
}
// ORDERS SECTION
Widget _buildOrdersSection(dashboardData) {
final orders = dashboardData.orders ?? [];
if (orders.isEmpty) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 14),
child: Center(
child: Text("No Orders Found"),
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
...orders.map<Widget>((order) {
return Column(
children: [
_buildOrderItem(
assetId: "#${order.hashId ?? ''} | Engine : ${order.engine ?? ''}",
description: order.prodName ?? '',
amc: (order.amc?.toString() ?? ''),
warranty: (order.warranty?.toString() ?? ''), // Fixed: was using amc instead of warranty
pImage: order.productImage ?? '',
date: order.schedule?.isNotEmpty == true
? order.schedule!.first
: null,
serviceText: order.schedule?.isNotEmpty == true
? 'Upcoming Service Scheduled'
: null,
),
const SizedBox(height: 12),
],
);
}).toList(),
// See All Button
],
);
}
Widget _buildOrderItem({
required String assetId,
required String description,
required String amc,
required String warranty,
required String pImage,
String? date,
String? serviceText,
}) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
assetId,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: AppColors.amountText,
),
),
const SizedBox(height: 4),
Text(
description,
style: TextStyle(
fontSize: 14,
color: AppColors.normalText,
height: 1.4,
),
),
const SizedBox(height: 8),
// Status Badge with checkmark for AMC Protected
if (amc == "1" || amc == "2")
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
gradient: amc == "1"
? AppColors.greenStripGradient
: AppColors.fadeGradient,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
SvgPicture.asset(
"assets/svg/tick_ic.svg",
height: 14,
color: amc == "1"
? AppColors.greenICBg
: AppColors.subtitleText,
),
const SizedBox(width: 4),
Text(
"AMC ",
style: TextStyle(
fontSize: 11,
fontFamily: "PoppinsBold",
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w700,
color: amc == "1"
? AppColors.greenICBg
: AppColors.subtitleText,
),
),
Text(
"Protected",
style: TextStyle(
fontSize: 11,
fontFamily: "PoppinsBold",
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w700,
color: amc == "1"
? AppColors.normalText
: AppColors.subtitleText,
),
),
if (amc == "1")
const Icon(Icons.info_outline, color: Colors.red, size: 12,)
],
),
],
),
),
const SizedBox(height: 2),
// for warranty
if (warranty == "1" || warranty == "2")
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
gradient: warranty == "1"
? AppColors.yellowStripGradient
: AppColors.fadeGradient,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
SvgPicture.asset(
"assets/svg/tick2_ic.svg",
height: 14,
color: warranty == "1"
? AppColors.warning
: AppColors.subtitleText,
),
const SizedBox(width: 4),
Text(
"Warranty",
style: TextStyle(
fontSize: 11,
fontFamily: "PoppinsBold",
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w700,
color: warranty == "1"
? AppColors.normalText
: AppColors.subtitleText,
),
),
],
),
],
),
),
],
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xffF2F2F2),
borderRadius: BorderRadius.circular(12),
),
child: Image.network(
pImage.isNotEmpty ? pImage : "https://erp.gengroup.in/assets/upload/inventory_add_genesis_product_pic/_1761047459_6425.png",
height: 50,
width: 50,
fit: BoxFit.contain,
errorBuilder: (context, error, stack) =>
Image.asset('assets/images/dashboard_gen.png',
height: 40,
width: 40),
),
),
],
),
// Date and Service Text for first item
if (date != null && serviceText != null) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xffF2F2F2),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.asset(
height: 30,
"assets/svg/checked_ic.svg",
fit: BoxFit.contain,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
date,
style: TextStyle(
fontSize: 12,
color: AppColors.normalText,
fontWeight: FontWeight.w500,
),
),
Text(
serviceText,
style: TextStyle(
fontSize: 12,
color: AppColors.subtitleText,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
),
],
],
),
);
}
// TRANSACTIONS CARD
Widget _buildTransactionsCard(dashboardData) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Transactions',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
InkResponse(
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => TransactionListScreen(accId: widget.accId, sessionId: widget.sessionId))
);
},
child: const Text(
'See All',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.amountText,
),
),
),
],
),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(22),
decoration: BoxDecoration(
gradient: AppColors.balanceCardGradient,
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹",
style: TextStyle(
color: Colors.white,
fontSize: 20,
height: 2,
fontWeight: FontWeight.w500,
),
),
Text(
dashboardData?.balanceAmount?.toString() ?? "0",
style: TextStyle(
color: Colors.white,
fontSize: 34,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.info_outline,
color: Colors.white, size: 16),
const SizedBox(width: 6),
Text(
dashboardData.balanceType ?? 'Pending Balance',
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
],
),
],
),
Column(
children: [
Text(
"*Make sure to pay before\n you incur any fines.",
maxLines: 2,
style: TextStyle(
color: const Color(0xAAFFFFFF),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 10),
InkResponse(
onTap: () {
// Add pay now logic
},
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: const Text(
" Pay Now ",
style: TextStyle(
color: Colors.blue,
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
],
),
),
],
),
);
}
// COMPLAINTS SECTION
Widget _buildComplaintsSection(dashboardData) {
final complaints = dashboardData.complaints ?? [];
if (complaints.isEmpty) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: Text(
"No Complaints Found",
style: TextStyle(fontSize: 14, color: Colors.grey),
),
),
);
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Complaints",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
Text(
'See All',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.amountText,
),
),
],
),
const SizedBox(height: 8),
...complaints.map<Widget>((c) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// 🔹 Top row — Complaint name and status
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Complaint info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"#${c.hashId ?? ''} | ${c.complaintName ?? ''}",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.amountText,
),
),
const SizedBox(height: 4),
Text(
c.registredDate ?? '',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
// Status badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: (c.openStatus?.toLowerCase() == 'open')
? AppColors.successBG
: AppColors.warningBg2,
borderRadius: BorderRadius.circular(10),
),
child: Text(
c.openStatus ?? '',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: (c.openStatus?.toLowerCase() == 'open')
? AppColors.success
: AppColors.warningText,
),
),
),
],
),
const SizedBox(height: 12),
const Divider(),
/// 🔹 Product Info Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Product details
Text(
c.productName ?? "Unknown Product",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
// Product ID
Row(
children: [
Text(
"#${c.id ?? ''} | ",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: AppColors.subtitleText,
),
),
if ((c.modelName ?? '').isNotEmpty)
Text(
"Engine: ${c.modelName}",
style: TextStyle(
fontSize: 12,
color: AppColors.subtitleText,
),
),
],
),
],
),
],
),
);
}).toList(),
],
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import '../Notifiers/DashboardProvider.dart';
import '../Utility/AppColors.dart';
class ProfileScreen extends StatefulWidget {
final String accId;
final String sessionId;
const ProfileScreen({
Key? key,
required this.accId,
required this.sessionId,
}) : super(key: key);
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
@override
void initState() {
super.initState();
Future.microtask(() {
final dashboardProvider =
Provider.of<DashboardProvider>(context, listen: false);
dashboardProvider.fetchDashboard(widget.accId, widget.sessionId);
});
}
@override
Widget build(BuildContext context) {
final dashboardProvider = Provider.of<DashboardProvider>(context);
final isLoading = dashboardProvider.isLoading;
final error = dashboardProvider.errorMessage;
final data = dashboardProvider.dashboardData;
return Scaffold(
backgroundColor: AppColors.backgroundRegular,
body: Stack(
children: [
if (isLoading)
const Center(
child: CircularProgressIndicator(),
)
else if (error != null)
Center(
child: Text(
error,
style: const TextStyle(color: Colors.red, fontSize: 16),
),
)
else if (data == null)
const Center(
child: Text("No data found."),
)
else
Stack(
children: [
/// 🔹 Profile Section
Positioned(
top: 0,
left: 0,
right: 0,
child: _buildProfileSection(
data.userProfile ?? "",
data.userName ?? "User",
data.mobNum ?? "",
),
),
/// 🔹 Scrollable Content
SafeArea(
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const SizedBox(height: 200),
/// Bottom Sheet style container
Container(
padding: const EdgeInsets.symmetric(horizontal: 14),
width: double.infinity,
decoration: const BoxDecoration(
color: AppColors.backgroundRegular,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
// 🔹 Orders Section
_buildOrdersSection(data),
const SizedBox(height: 4),
// 🔹 Transactions Card
_buildTransactionsCard(data),
const SizedBox(height: 18),
const Divider(),
// 🔹 Complaints Section
_buildComplaintsSection(data),
const SizedBox(height: 20),
_buildFixIssuesCard(),
const SizedBox(height: 20),
// _buildGetInTouchCard(),
// const SizedBox(height: 30),
],
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: BoxDecoration(
color: AppColors.amountText,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Get in touch With Us',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
),
Text(
'Please feel free to reach out to us anytime',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Color(0xAAFFFFFF)),
),
],
),
Icon(Icons.arrow_circle_right_rounded,
color: Color(0xFFFFFFFF), size: 30),
],
),
)
),
],
),
),
),
],
),
],
),
);
}
// PROFILE SECTION
// ===============================
Widget _buildProfileSection(
String userProfile, String userName, String userContact) {
return Container(
width: double.infinity,
height: 275,
padding: const EdgeInsets.only(top: 60, bottom: 60, left: 20, right: 20),
decoration: const BoxDecoration(gradient: AppColors.backgroundGradient),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Profile Image
Container(
height: 80,
width: 80,
decoration: const BoxDecoration(
color: Color(0xFFE6F6FF),
shape: BoxShape.circle,
),
clipBehavior: Clip.antiAlias,
child: (userProfile.isNotEmpty)
? Image.network(
userProfile,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
const Icon(Icons.person,
color: Color(0xFF2d2d2d), size: 100),
)
: CircleAvatar(
radius: 80,
backgroundColor: const Color(0xFFE0F4FF),
child: SvgPicture.asset(
height: 80,
"assets/svg/person_ic.svg",
fit: BoxFit.contain,
),
),
),
const SizedBox(height: 16),
Text(
userName,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
'+91 $userContact',
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
),
)
],
),
);
}
// ORDERS SECTION
// ===============================
Widget _buildOrdersSection(dashboardData) {
final orders = dashboardData.orders ?? [];
if (orders.isEmpty) {
return const Center(
child: Text("No Orders Found"),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: orders.map<Widget>((order) {
return Column(
children: [
_buildOrderItem(
assetId: "#${order.hashId ?? ''} | Engine : ${order.engine ?? ''}",
description: order.prodName ?? '',
status: (order.amc?.isNotEmpty ?? false)
? 'AMC Protected'
: (order.warranty?.isNotEmpty ?? false)
? 'WARRANTY'
: 'Unknown',
statusColor: (order.amc?.isNotEmpty ?? false)
? Colors.green
: (order.warranty?.isNotEmpty ?? false)
? Colors.orange
: Colors.grey,
p_imagae: order.productImage ?? '',
date: order.schedule?.isNotEmpty == true
? order.schedule!.first
: null,
serviceText: order.schedule?.isNotEmpty == true
? 'Upcoming Service Scheduled'
: null,
),
const SizedBox(height: 12),
],
);
}).toList(),
);
}
Widget _buildOrderItem({
required String assetId,
required String description,
required String status,
required Color statusColor,
required String p_imagae,
String? date,
String? serviceText,
}) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
assetId,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: AppColors.amountText,
),
),
const SizedBox(height: 4),
Text(
description,
style: TextStyle(
fontSize: 14,
color: AppColors.normalText,
height: 1.4,
),
),
const SizedBox(height: 8),
// Status Badge with checkmark for AMC Protected
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: statusColor.withOpacity(0.3),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (status == 'AMC Protected')
const Icon(
Icons.check_circle,
color: Colors.green,
size: 12,
),
if (status == 'AMC Protected') const SizedBox(width: 4),
Text(
status,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: statusColor,
),
),
],
),
),
],
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xffF2F2F2),
borderRadius: BorderRadius.circular(12),
),
child: Image.network(
p_imagae ?? "https://erp.gengroup.in/assets/upload/inventory_add_genesis_product_pic/_1761047459_6425.png",
height: 50,
width: 50,
fit: BoxFit.contain,
errorBuilder: (context, error, stack) =>
Image.asset('assets/images/dashboard_gen.png',
height: 40,
width: 40),
),
),
],
),
// Date and Service Text for first item
if (date != null && serviceText != null) ...[
const SizedBox(height: 12),
Container(
padding: EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xffF2F2F2),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.asset(
height: 30,
"assets/svg/checked_ic.svg",
fit: BoxFit.contain,
),
SizedBox(width: 10,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
date,
style: TextStyle(
fontSize: 12,
color: AppColors.normalText,
fontWeight: FontWeight.w500,
),
),
Text(
serviceText,
style: TextStyle(
fontSize: 12,
color: AppColors.subtitleText,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
),
],
],
),
);
}
// TRANSACTIONS CARD
// ===============================
Widget _buildTransactionsCard(dashboardData) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Transactions',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(22),
decoration: BoxDecoration(
gradient: AppColors.balanceCardGradient,
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹",
style: TextStyle(
color: Colors.white,
fontSize: 20,
height: 2,
fontWeight: FontWeight.w500,
),
),
Text(
dashboardData?.balanceAmount?.toString() ?? "0",
style: TextStyle(
color: Colors.white,
fontSize: 34,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.info_outline,
color: Colors.white, size: 16),
const SizedBox(width: 6),
Text(
dashboardData.balanceType ?? 'Pending Balance',
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
],
),
],
),
Column(
children: [
Text(
"*Make sure to pay before\n you incur any fines.",
maxLines: 2,
style: TextStyle(
color: Color(0xAAFFFFFF),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 10,),
InkResponse(
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: const Text(
" Pay Now ",
style: TextStyle(
color: Colors.blue,
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
],
),
),
],
);
}
// COMPLAINTS SECTION
// ===============================
Widget _buildComplaintsSection(dashboardData) {
final complaints = dashboardData.complaints ?? [];
if (complaints.isEmpty) {
return const Center(
child: Text(
"No Complaints Found",
style: TextStyle(fontSize: 14, color: Colors.grey),
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
const Text(
"Complaints",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 8),
...complaints.map<Widget>((c) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// 🔹 Top row — Complaint name and status
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Complaint info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"#${c.hashId ?? ''} | ${c.complaintName ?? ''}",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.amountText,
),
),
const SizedBox(height: 4),
Text(
c.registredDate ?? '',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
// Status badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: (c.openStatus?.toLowerCase() == 'open')
? AppColors.successBG
: AppColors.warningBg2,
borderRadius: BorderRadius.circular(10),
),
child: Text(
c.openStatus ?? '',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: (c.openStatus?.toLowerCase() == 'open')
? AppColors.success
: AppColors.warningText,
),
),
),
],
),
const SizedBox(height: 12),
const Divider(),
/// 🔹 Product Info Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Product details
Text(
c.productName ?? "Unknown Product",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
// Product ID
Row(
children: [
Text(
"#${c.id ?? ''} | ",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: AppColors.subtitleText,
),
),
if ((c.modelName ?? '').isNotEmpty)
Text(
"Engine: ${c.modelName}",
style: TextStyle(
fontSize: 12,
color: AppColors.subtitleText,
),
),
],
),
],
),
],
),
);
}).toList(),
],
);
}
// ===============================
// The remaining helper cards (no changes)
// ===============================
Widget _buildFixIssuesCard() => // same as before
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xFFD7F0FF),
borderRadius: BorderRadius.circular(16),
border: Border.all(width: 1.5, color: AppColors.buttonColor),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Facing Issues?',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.amountText)),
Text(
'Raise a ticket to resolve your issues.',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: AppColors.subtitleText),
),
],
),
SvgPicture.asset("assets/svg/requirements.svg",
height: 22, width: 22),
],
),
);
Widget _buildGetInTouchCard() => // same as before
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: const Row(
children: [
Icon(Icons.support_agent,
color: Color(0xFF1487C9), size: 24),
SizedBox(width: 16),
Expanded(
child: Text(
'Get in touch With Us',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.black87),
),
),
],
),
);
}
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:gen_service/Screens/HomeScreen.dart';
import '../Utility/CustomSnackbar.dart';
import '../Utility/SharedpreferencesService.dart';
import 'authScreen/LoginScreen.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _slideAnimation;
late Animation<double> _rotationAnimation;
late Animation<Color?> _gradientAnimation;
late Animation<double> _particleAnimation;
final prefs = SharedPreferencesService.instance;
Timer? _connectivityTimer;
bool _progressCheckCompleted = false;
bool _hasInternet = true;
@override
void initState() {
super.initState();
// Initialize connectivity check
_initConnectivity();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
);
// Enhanced animations
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.8, curve: Curves.easeInOut),
),
);
_scaleAnimation = Tween<double>(begin: 0.3, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.1, 0.7, curve: Curves.elasticOut),
),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 1.0),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.2, 0.8, curve: Curves.easeOutBack),
),
);
_rotationAnimation = Tween<double>(begin: -0.5, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.5, curve: Curves.easeInOut),
),
);
_gradientAnimation = ColorTween(
begin: const Color(0xFF1487C9),
end: const Color(0xFF26BAE7),
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 1.0, curve: Curves.easeInOut),
),
);
_particleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 1.0, curve: Curves.easeInOut),
),
);
// Start animations
_controller.forward();
// Navigate after delay
Timer(const Duration(seconds: 3), () {
_checkAccountAndNavigate();
});
}
Future<void> _checkAccountAndNavigate() async {
try {
final String? savedAccId = await prefs.getString("accId");
if (savedAccId != null && savedAccId.isNotEmpty) {
_navigateToDashboard();
} else {
_navigateToLogin();
}
} catch (e) {
debugPrint("Error checking account ID: $e");
_navigateToLogin();
}
}
Future<void> _navigateToDashboard() async {
final String? savedAccId = await prefs.getString("accId");
final String? savedSessionId = await prefs.getString("session_id");
if (savedAccId != null && savedAccId.isNotEmpty) {
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => HomeScreen(
accId: savedAccId,
sessionId: savedSessionId.toString(),
),
transitionsBuilder: (_, animation, __, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
transitionDuration: const Duration(milliseconds: 800),
),
);
} else {
_navigateToLogin();
}
}
void _navigateToLogin() {
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => const LoginScreen(),
transitionsBuilder: (_, animation, __, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
transitionDuration: const Duration(milliseconds: 800),
),
);
}
Future<void> _initConnectivity() async {
try {
_checkConnectivity();
_connectivityTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
_checkConnectivity();
});
} catch (e) {
debugPrint("Connectivity initialization error: $e");
_updateConnectionStatus(false);
}
}
Future<void> _checkConnectivity() async {
try {
final connectivity = Connectivity();
final results = await connectivity.checkConnectivity();
final hasInternet = results.any((element) =>
element == ConnectivityResult.wifi ||
element == ConnectivityResult.mobile);
if (hasInternet) {
try {
final result = await InternetAddress.lookup('google.com');
final socketCheck = result.isNotEmpty && result[0].rawAddress.isNotEmpty;
_updateConnectionStatus(socketCheck);
} catch (e) {
_updateConnectionStatus(false);
}
} else {
_updateConnectionStatus(false);
}
} catch (e) {
debugPrint("Connectivity check error: $e");
_updateConnectionStatus(false);
}
}
void _updateConnectionStatus(bool hasInternet) {
if (mounted) {
setState(() {
_hasInternet = hasInternet;
});
}
if (!hasInternet) {
_showNoInternetSnackbar();
} else {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
}
}
void _showNoInternetSnackbar() {
if (mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
CustomSnackBar.showError(
context: context,
message: "No internet connection! Please connect to the internet.",
action: SnackBarAction(
label: "RETRY",
onPressed: _checkConnectivity,
),
);
});
}
}
@override
void dispose() {
_controller.dispose();
_connectivityTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedBuilder(
animation: _gradientAnimation,
builder: (context, child) {
return Container(
decoration: BoxDecoration(
gradient: RadialGradient(
center: Alignment.topLeft,
radius: 1.5,
colors: [
_gradientAnimation.value!,
_gradientAnimation.value!.withOpacity(0.9),
const Color(0xFF1487C9),
],
stops: const [0.0, 0.6, 1.0],
),
),
child: Stack(
children: [
// Animated background particles
..._buildParticles(),
// Floating geometric shapes
Positioned(
top: 100,
right: 50,
child: AnimatedBuilder(
animation: _particleAnimation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 20 * _particleAnimation.value),
child: Opacity(
opacity: 0.1,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 2,
),
),
),
),
);
},
),
),
Positioned(
bottom: 150,
left: 40,
child: AnimatedBuilder(
animation: _particleAnimation,
builder: (context, child) {
return Transform.rotate(
angle: _particleAnimation.value * 2 * 3.14159,
child: Opacity(
opacity: 0.08,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.white,
width: 1.5,
),
),
),
),
);
},
),
),
// Main content
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo container with enhanced styling
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: _slideAnimation.value * 200,
child: Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _fadeAnimation.value,
child: Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.white.withOpacity(0.15),
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.3),
blurRadius: 20,
spreadRadius: 2,
offset: const Offset(0, 10),
),
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 1,
),
),
child: Stack(
children: [
// Background glow
Container(
width: 140,
height: 140,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
Colors.white.withOpacity(0.3),
Colors.transparent,
],
),
),
),
// Logo
RotationTransition(
turns: _rotationAnimation,
child: SizedBox(
height: 170,
width: 170,
child: SvgPicture.asset(
"assets/svg/genesis_logo_2io.svg",
color: Color(0xFF4076FF),
),
),
),
],
),
),
),
),
);
},
),
const SizedBox(height: 40),
// App name with enhanced typography
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: _slideAnimation.value * 100,
child: Opacity(
opacity: _fadeAnimation.value,
child: Column(
children: [
Text(
"Gen Services",
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w900,
color: Colors.white,
letterSpacing: 2.0,
shadows: [
Shadow(
blurRadius: 10,
color: Colors.black.withOpacity(0.3),
offset: const Offset(2, 2),
),
],
),
),
const SizedBox(height: 8),
Text(
"Premium Service Solutions",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w300,
color: Colors.white.withOpacity(0.8),
letterSpacing: 3.0,
),
),
],
),
),
);
},
),
const SizedBox(height: 60),
// Enhanced loading indicator
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Column(
children: [
// Dots loading animation
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 500),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(
_controller.value > (index + 1) * 0.25 ? 1.0 : 0.3,
),
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.5),
blurRadius: 8,
spreadRadius: 1,
),
],
),
);
}),
),
const SizedBox(height: 20),
Text(
"Loading...",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white.withOpacity(0.7),
letterSpacing: 1.0,
),
),
],
),
);
},
),
const SizedBox(height: 80),
// Powered by section
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Transform.translate(
offset: _slideAnimation.value * 50,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Colors.white.withOpacity(0.2),
),
),
child: Column(
children: [
Text(
"Powered by",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.white.withOpacity(0.7),
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Text(
"Avantect Web Grid",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 1.0,
),
),
],
),
),
),
);
},
),
],
),
),
// Bottom wave with enhanced design
Positioned(
bottom: 0,
left: 0,
right: 0,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 50 * (1 - _controller.value)),
child: Opacity(
opacity: _fadeAnimation.value,
child: CustomPaint(
size: Size(MediaQuery.of(context).size.width, 120),
painter: _EnhancedWavePainter(),
),
),
);
},
),
),
],
),
);
},
),
);
}
List<Widget> _buildParticles() {
return List.generate(8, (index) {
return Positioned(
left: (index * 50.0) % MediaQuery.of(context).size.width,
top: (index * 70.0) % MediaQuery.of(context).size.height,
child: AnimatedBuilder(
animation: _particleAnimation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 30 * _particleAnimation.value * (index.isEven ? 1 : -1)),
child: Opacity(
opacity: 0.1 + (0.05 * (index % 3)),
child: Container(
width: 8 + (index % 3) * 4,
height: 8 + (index % 3) * 4,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.5),
blurRadius: 10,
spreadRadius: 2,
),
],
),
),
),
);
},
),
);
});
}
}
class _EnhancedWavePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.15)
..style = PaintingStyle.fill;
final path = Path();
// Create multiple waves for depth
path.moveTo(0, size.height * 0.6);
// First wave
path.quadraticBezierTo(
size.width * 0.25, size.height * 0.4,
size.width * 0.5, size.height * 0.6,
);
path.quadraticBezierTo(
size.width * 0.75, size.height * 0.8,
size.width, size.height * 0.6,
);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
canvas.drawPath(path, paint);
// Second smaller wave for depth
final secondPaint = Paint()
..color = Colors.white.withOpacity(0.08)
..style = PaintingStyle.fill;
final secondPath = Path();
secondPath.moveTo(0, size.height * 0.7);
secondPath.quadraticBezierTo(
size.width * 0.3, size.height * 0.5,
size.width * 0.6, size.height * 0.7,
);
secondPath.quadraticBezierTo(
size.width * 0.8, size.height * 0.85,
size.width, size.height * 0.7,
);
secondPath.lineTo(size.width, size.height);
secondPath.lineTo(0, size.height);
secondPath.close();
canvas.drawPath(secondPath, secondPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import '../Notifiers/TransactionsProvider.dart';
class TransactionListScreen extends StatefulWidget {
final String accId;
final String sessionId;
const TransactionListScreen({
required this.accId,
required this.sessionId,
super.key,
});
@override
State<TransactionListScreen> createState() => _TransactionScreenState();
}
class _TransactionScreenState extends State<TransactionListScreen> {
bool _stretch = true;
@override
void initState() {
super.initState();
Future.microtask(() {
Provider.of<TransactionsProvider>(context, listen: false)
.fetchTransactions(widget.accId, widget.sessionId);
});
}
@override
Widget build(BuildContext context) {
final provider = Provider.of<TransactionsProvider>(context);
final balance = provider.transactionList?.balanceAmount ?? "0";
final balanceType = provider.transactionList?.balanceType ?? "Pending";
final totalCredit = provider.transactionList?.totalCredit ?? "0";
final totalDebit = provider.transactionList?.totalDebit ?? "0";
final transactions = provider.transactionList?.transactions ?? {};
return Scaffold(
backgroundColor: const Color(0xFFF6F6F6),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
/// Top SliverAppBar (balance card)
SliverAppBar(
stretch: _stretch,
pinned: true,
expandedHeight: 245,
backgroundColor: const Color(0xFFFF5722),
elevation: 0, // Remove shadow
leading: Container(), // Remove back button
toolbarHeight: 0, // Remove toolbar space
collapsedHeight: 0, // Completely collapse to 0 height
flexibleSpace: FlexibleSpaceBar(
stretchModes: const [StretchMode.zoomBackground],
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFFF5722), Color(0xFFFF7043)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 20),
const Text(
"Transactions",
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
Text(
"₹$balance",
style: const TextStyle(
color: Colors.white,
fontSize: 36,
fontWeight: FontWeight.w600,
),
),
Text(
"$balanceType",
style: const TextStyle(color: Colors.white70, fontSize: 16),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.deepOrange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text("Pay Now"),
),
],
),
),
),
),
),
),
/// Summary Row (Paid / Pending)
SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildSummaryItem("₹$totalCredit", "Bill Paid", Colors.green),
Container(height: 30, width: 1, color: Colors.grey.shade300),
_buildSummaryItem("₹$totalDebit", "Bill Pending", Colors.red),
],
),
),
),
/// Transaction List
if (provider.isLoading)
const SliverFillRemaining(
child: Center(child: CircularProgressIndicator()),
)
else if (provider.errorMessage != null)
SliverFillRemaining(
child: Center(child: Text(provider.errorMessage!)),
)
else if (transactions.isEmpty)
const SliverFillRemaining(
child: Center(child: Text("No transactions found")),
)
else
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final month = transactions.keys.elementAt(index);
final items = transactions[month]!;
return Padding(// here why list is not comming while list data is coming
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
month,
style: const TextStyle(
fontFamily: "Poppins",
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 8),
...items.map((item) {
final type = item.atype ?? "Debit";
final title = item.narration ?? "No details";
final date = item.datetime ?? "";
final amount = type.toLowerCase() == "credit"
? "₹${item.cAmount ?? "0"}"
: "₹${item.dAmount ?? "0"}";
return _buildTransactionItem(
type: type,
title: title,
date: date,
amount: amount,
);
}),
],
),
);
},
childCount: transactions.length,
),
),
],
),
);
}
/// Summary Card Item
Widget _buildSummaryItem(String value, String label, Color color) {
return Column(
children: [
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
],
);
}
/// Transaction Item
Widget _buildTransactionItem({
required String type,
required String title,
required String date,
required String amount,
}) {
final isCredit = type.toLowerCase() == "credit";
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
),
child: Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor:
isCredit ? Colors.green.shade50 : Colors.red.shade50,
child: SvgPicture.asset(
isCredit
? "assets/svg/cross_up_arrow.svg"
: "assets/svg/cross_down_arrow.svg",
height: 18,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.normal,
color: Colors.black,
),
),
const SizedBox(height: 4),
Text(
date,
style: const TextStyle(
fontFamily: "Poppins",
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey,
),
),
],
),
),
const SizedBox(width: 12),
Text(
amount,
style: TextStyle(
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w400,
color: isCredit ? Colors.green : Colors.red,
),
),
],
),
);
}
}
\ No newline at end of file
/// base Url of api
const baseUrl = "https://erp.gengroup.in/ci/app/Service/Serv_Home/";
const baseUrl2 = "https://erp.gengroup.in/ci/app/Service/Serv_Auth/";
/// dashboard and login
const fetchMobileUrl = "${baseUrl2}fetch_mobile_number";
const fetchOtpUrl = "${baseUrl2}login";
const dashboardUrl = "${baseUrl}dashboard";
const logoutUrl = "${baseUrl2}Rental_Auth/logout";
const profileDetailsUrl = "${baseUrl2}Rental_Home/profile_details";
\ No newline at end of file
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import '../Models/AuthResponse.dart';
import '../Models/CommonResponse.dart';
import '../Models/DashboardResponse.dart';
import '../Models/TransactionListResponse.dart';
import 'api_URLs.dart';
import 'api_post_request.dart';
import 'package:http/http.dart' as http show MultipartFile;
class ApiCalling {
/// Fetch gen service contact by mobile number
static Future<FetchMobileResponse?> fetchRentalMobileApi(
String mob,
) async {
debugPrint("############################### Api calling ");
try {
Map<String, String> data = {
"mob": mob,
};
final res = await post(data, fetchMobileUrl, {});
if (res != null) {
return FetchMobileResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ API Error: $e");
return null;
}
}
static Future<FetchOTPResponse?> fetchMobileOtpApi(
String mob,
String otp,
deviceDetails
) async {
debugPrint("############################### Api fetch otpcalling ");
try {
Map<String, String> data = {
"mob": mob,
"otp": otp,
"device_details": deviceDetails.toString(),
};
print(data);
final res = await post(data, fetchOtpUrl, {});
if (res != null) {
print(res.body);
return FetchOTPResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ API Error: $e");
return null;
}
}
static Future<CommonResponse?> logoutApi(
String accId,
String sessionId,
) async {
debugPrint("############################### Api calling ");
try {
Map<String, String> data = {
"acc_id": accId,
"session_id": sessionId,
};
final res = await post(data, logoutUrl, {});
if (res != null) {
return CommonResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ Logout API Error: $e");
return null;
}
}
/// fetch Dashboard Api
static Future<DashboardResponse?> fetchDashboardApi(
String accId,
String sessionId,
) async {
debugPrint("############################### Api calling ");
try {
Map<String, String> data = {
"acc_id": accId,
"session_id": sessionId,
};
final res = await post(data, dashboardUrl, {});
if (res != null) {
return DashboardResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ Dashboard API Error: $e");
return null;
}
}
/// fetch Dashboard Api
static Future<TransactionListResponse?> fetchTransactionListApi(
String accId,
String sessionId,
) async {
debugPrint("###############################Transaction Api calling ");
try {
Map<String, String> data = {
"acc_id": accId,
"session_id": sessionId,
};
final res = await post(data, dashboardUrl, {});
debugPrint("Transaction response: ${res?.body}");
if (res != null) {
return TransactionListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ Transaction API Error: $e");
return null;
}
}
//
// /// pay_amount
// static Future<PayAmountResponse?> payAmountApi(
// String sessionId,
// String empId,
// String ammount,
// String refType,
// String refId,
// ) async {
// try {
// Map<String, String> data = {
// "session_id": sessionId,
// "acc_id": empId,
// "amount": ammount,
// "ref_type": refType,
// "ref_id": refId,
// };
//
// final res = await post(data, payAmountUrl, {});
// debugPrint("PayAmount Response ${res?.body}");
//
// if (res != null) {
// return PayAmountResponse.fromJson(jsonDecode(res.body));
// } else {
// debugPrint("Null Response");
// return null;
// }
// } catch (e) {
// debugPrint("❌ API Error (payAmountApi): $e");
// return null;
// }
// }
//
}
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
Future<http.Response?> post(
Map<String, dynamic> Body,
apiUrl,
Map<String, String> Headers,
) async {
http.Response? response;
try {
response = await http.post(Uri.parse(apiUrl), headers: Headers, body: Body);
return response;
} on Exception catch (e, s) {
print(e);
print(s);
}
return response;
}
Future<http.Response?> get(apiUrl, Map<String, String> Headers) async {
http.Response? response;
try {
response = await http.get(Uri.parse(apiUrl), headers: Headers);
return response;
} on Exception catch (e, s) {
print(e);
print(s);
}
return response;
}
Future<String?> postImage(
Map<String, String> body,
String urlLink,
Map<String, String> headers,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.add(
await http.MultipartFile.fromPath('check_in_pic', image.path),
);
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... $res");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
Future<String?> postImage2(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.add(
await http.MultipartFile.fromPath('check_out_pic', image.path),
);
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... $res");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
Future<String?> postImage3(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.add(
await http.MultipartFile.fromPath('payment_proof', image.path),
);
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... $res");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
Future<String?> postImage4(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.add(await http.MultipartFile.fromPath('fsr_file', image.path));
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... ${res.statusCode}");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
Future<String?> postImageNew(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
reqField,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers ?? {});
req.files.add(await http.MultipartFile.fromPath(reqField, image.path));
req.fields.addAll(body ?? {});
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... ${res.statusCode}");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
//travel_image
//hotel_image
//other_image
// Future<String?> PostMultipleImagesNew(
// Map<String, String> body,
// String urlLink,
// Map<String, String> headers,
// List<http.MultipartFile> newList,
// List<http.MultipartFile> newList1,
// List<http.MultipartFile> newList2,
// ) async {
// try {
// var req = http.MultipartRequest('POST', Uri.parse(urlLink));
// req.headers.addAll(headers);
// req.files.addAll(newList);
// req.files.addAll(newList1);
// req.files.addAll(newList2);
// req.fields.addAll(body);
//
// var res = await req.send();
// final resBody = await res.stream.bytesToString();
//
// if (res.statusCode >= 200 && res.statusCode < 300) {
// print("**** $resBody .... $res");
// return resBody;
// } else {
// print("error: ${res.reasonPhrase}");
// return null;
// }
// } catch (e) {
// debugPrint(e.toString());
// return null;
// }
// }
Future<String?> PostMultipleImagesNew2(
Map<String, String> body,
String urlLink,
Map<String, String> headers,
List<http.MultipartFile> newList,
List<http.MultipartFile> newList1,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.addAll(newList);
req.files.addAll(newList1);
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... $res");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
Future<String?> PostMultipleImages(
Map<String, String> body,
String urlLink,
Map<String, String> headers,
List<http.MultipartFile> newList,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.addAll(newList);
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... $res");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
Future<String?> PostMultipleImagesNew(
Map<String, String> body,
String urlLink,
Map<String, String> headers,
List<http.MultipartFile> newList) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.addAll(newList);
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... $res");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
/// Send message with multiple images
Future<String?> postMessageWithImages(
Map<String, String> body,
String urlLink,
Map<String, String> headers,
List<File> images,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
// Add all images with the key 'images[]'
for (var image in images) {
if (await image.exists()) {
req.files.add(
await http.MultipartFile.fromPath('images[]', image.path),
);
}
}
req.fields.addAll(body);
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** Message sent successfully: $resBody");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint("Error sending message with images: $e");
return null;
}
}
// utils/animated_snackbar.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AnimatedSnackBar {
static void show({
required BuildContext context,
required String message,
String? title,
IconData? icon,
Color backgroundColor = const Color(0xFF324563),
Duration duration = const Duration(seconds: 4),
SnackBarAction? action,
bool showProgressBar = false,
bool enableHaptic = true,
Curve animationCurve = Curves.elasticOut,
}) {
if (enableHaptic) {
HapticFeedback.lightImpact();
}
final overlay = Overlay.of(context);
// Create a variable to hold the overlay entry
late OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: MediaQuery.of(context).viewPadding.top + 10,
left: 16,
right: 16,
child: Material(
color: Colors.transparent,
child: _AnimatedSnackBarContent(
message: message,
title: title,
icon: icon,
backgroundColor: backgroundColor,
duration: duration,
action: action,
showProgressBar: showProgressBar,
animationCurve: animationCurve,
onClose: () {
if (overlayEntry.mounted) {
overlayEntry.remove();
}
},
),
),
),
);
overlay.insert(overlayEntry);
// Auto remove after duration
Future.delayed(duration, () {
if (overlayEntry.mounted) {
overlayEntry.remove();
}
});
}
// Quick methods for different types
static void success({
required BuildContext context,
required String message,
String title = "Success",
Color backgroundColor = const Color(0xFF059669),
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.check_circle_rounded,
backgroundColor: backgroundColor,
action: action,
enableHaptic: enableHaptic,
);
}
static void error({
required BuildContext context,
required String message,
String title = "Error",
Color backgroundColor = const Color(0xFFDC2626),
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.error_outline_rounded,
backgroundColor: backgroundColor,
action: action,
enableHaptic: enableHaptic,
);
}
static void warning({
required BuildContext context,
required String message,
String title = "Warning",
Color backgroundColor = const Color(0xFFD97706),
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.warning_amber_rounded,
backgroundColor: backgroundColor,
action: action,
enableHaptic: enableHaptic,
);
}
static void info({
required BuildContext context,
required String message,
String title = "Info",
Color backgroundColor = const Color(0xFF2563EB),
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.info_outline_rounded,
backgroundColor: backgroundColor,
action: action,
enableHaptic: enableHaptic,
);
}
}
class _AnimatedSnackBarContent extends StatefulWidget {
final String message;
final String? title;
final IconData? icon;
final Color backgroundColor;
final Duration duration;
final SnackBarAction? action;
final bool showProgressBar;
final Curve animationCurve;
final VoidCallback onClose;
const _AnimatedSnackBarContent({
required this.message,
required this.title,
required this.icon,
required this.backgroundColor,
required this.duration,
required this.action,
required this.showProgressBar,
required this.animationCurve,
required this.onClose,
});
@override
_AnimatedSnackBarContentState createState() => _AnimatedSnackBarContentState();
}
class _AnimatedSnackBarContentState extends State<_AnimatedSnackBarContent>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _slideAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, -2),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: widget.animationCurve,
));
_scaleAnimation = Tween<double>(
begin: 0.5,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: widget.animationCurve,
));
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _close() {
_controller.reverse().then((_) {
widget.onClose();
});
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: widget.backgroundColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: widget.backgroundColor.withOpacity(0.4),
blurRadius: 25,
offset: const Offset(0, 10),
spreadRadius: 2,
),
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
widget.backgroundColor,
Color.alphaBlend(Colors.white.withOpacity(0.15), widget.backgroundColor),
],
),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1,
),
),
child: Row(
children: [
// Animated Icon Container
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: widget.backgroundColor.withOpacity(0.5),
blurRadius: 8,
spreadRadius: 1,
),
],
),
child: Icon(
widget.icon ?? Icons.notifications_none_rounded,
color: Colors.white,
size: 22,
),
),
const SizedBox(width: 16),
// Content Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (widget.title != null) ...[
Text(
widget.title!,
style: const TextStyle(
fontFamily: "Poppins",
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 16,
letterSpacing: -0.3,
height: 1.2,
),
),
const SizedBox(height: 4),
],
Text(
widget.message,
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white.withOpacity(0.9),
fontSize: 14,
height: 1.4,
fontWeight: FontWeight.w400,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
// Action Button
if (widget.action != null) ...[
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.white.withOpacity(0.3),
),
),
child: TextButton(
onPressed: () {
widget.action!.onPressed();
_close();
},
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: Text(
widget.action!.label,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
],
],
),
),
const SizedBox(width: 12),
// Close Button
GestureDetector(
onTap: _close,
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.close_rounded,
color: Colors.white.withOpacity(0.7),
size: 18,
),
),
),
],
),
),
),
),
);
}
}
\ No newline at end of file
// utils/app_colors.dart
import 'dart:ui';
import 'package:flutter/cupertino.dart';
class AppColors {
// Primary colors from genrentals.in
static const Color primary = Color(0xFF008CDE);
static const Color secondary = Color(0xFFF3F4F6);
static const Color accent = Color(0xFFF59E0B);
// Text colors
static const Color headerText = Color(0xFF2D2D2D);
static const Color amountText = Color(0xFF008CDE);
static const Color normalText = Color(0xFF2D2D2D);
static const Color subtitleText = Color(0xFF777777);
static const Color warningText = Color(0xFFF00000);
static const Color nearDarkText = Color(0xFF1E1E1E);
static const Color skyBlueText = Color(0xFF48F3FF);
// Status colors
static const Color success = Color(0xFF007736);
static const Color warning = Color(0xFFF59E0B);
static const Color error = Color(0xFFEF4444);
static const Color info = Color(0xFF3B82F6);
// Status background colors
static const Color successBG = Color(0xFFDCFCE7);
static const Color greenICBg = Color(0xFF4CAF50);
static const Color warningBg = Color(0xFFFF8940);
static const Color warningBg2 = Color(0xFFFFE5E5);
static const Color errorBg = Color(0xFFDB0000);
static const Color stripSky = Color(0xFFD9F1FF);
static const Color stripGrey = Color(0xFFE0E0E0);
// Neutral colors
static const Color dark = Color(0xFF1F2937);
static const Color light = Color(0xFFF9FAFB);
static const Color gray = Color(0xFF6B7280);
// Background colors
static const Color backgroundLight = Color(0xFFFFFFFF);
static const Color backgroundDark = Color(0xFF111827);
static const Color backgroundRegular = Color(0xFFF2F2F2);
//Button
static const Color buttonColor = Color(0xFF008CDE);
/// Gradients
static const LinearGradient backgroundGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF00BBDB),
Color(0xFF4076FF),
],
);
static const LinearGradient successGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFE4F2FF),
Color(0xFFDCFCE7),
],
);
static const LinearGradient fadeGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColors.subtitleText,
Color(0xFFFFFFFF),
],
);
static const LinearGradient yellowGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFE4F2FF),
Color(0xFFFFF8D2),
],
);
static const LinearGradient yellowStripGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFFFEF96),
Color(0xFFFFFFFF),
],
);
static const LinearGradient greenStripGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFE5FFE1),
Color(0xFFFFFFFF),
],
);
static const LinearGradient greyStripGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFD7D7D7),
Color(0xFFFFFFFF),
],
);
static const LinearGradient balanceCardGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFFF8940),
Color(0xFFDB0000),
],
);
}
\ No newline at end of file
// utils/custom_snackbar.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CustomSnackBar {
static void show({
required BuildContext context,
required String message,
String? title,
IconData? icon,
Color backgroundColor = const Color(0xFF324563),
Duration duration = const Duration(seconds: 4),
SnackBarAction? action,
bool showCloseIcon = true,
bool enableHaptic = true,
BorderRadiusGeometry? borderRadius,
List<BoxShadow>? customShadow,
Gradient? gradient,
}) {
if (enableHaptic) {
HapticFeedback.lightImpact();
}
final theme = Theme.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.transparent,
elevation: 0,
duration: duration,
behavior: SnackBarBehavior.floating,
padding: EdgeInsets.zero,
content: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: borderRadius ?? BorderRadius.circular(16),
boxShadow: customShadow ?? [
BoxShadow(
color: backgroundColor.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 8),
spreadRadius: 1,
),
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
gradient: gradient ?? LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
backgroundColor,
Color.alphaBlend(Colors.white.withOpacity(0.1), backgroundColor),
],
),
border: Border.all(
color: Colors.white.withOpacity(0.1),
width: 1,
),
),
child: Row(
children: [
// Icon with glowing effect
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: backgroundColor.withOpacity(0.5),
blurRadius: 10,
spreadRadius: 2,
),
],
),
child: Icon(
icon ?? Icons.notifications_none_rounded,
color: Colors.white,
size: 22,
),
),
const SizedBox(width: 16),
// Content Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (title != null) ...[
Text(
title,
style: const TextStyle(
color: Colors.white,
fontFamily: "Poppins",
fontWeight: FontWeight.w500,
fontSize: 14,
letterSpacing: -0.2,
),
),
const SizedBox(height: 4),
],
Text(
message,
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white.withOpacity(0.9),
fontSize: 13,
height: 1.3,
fontWeight: FontWeight.w400,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
// Action Button
if (action != null) ...[
const SizedBox(height: 8),
Align(
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.white.withOpacity(0.3),
),
),
child: TextButton(
onPressed: action.onPressed,
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: Text(
action.label,
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
],
),
),
const SizedBox(width: 8),
// Close Button with better styling
if (showCloseIcon)
GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.close_rounded,
color: Colors.white.withOpacity(0.7),
size: 18,
),
),
),
],
),
),
),
);
}
// Premium Success Snackbar
static void showSuccess({
required BuildContext context,
required String message,
String title = "Success",
Color backgroundColor = const Color(0xFF059669),
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.check_circle_rounded,
backgroundColor: backgroundColor,
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF2E7D32),
Color(0xFF4CAF50),
],
),
action: action,
enableHaptic: enableHaptic,
);
}
// Premium Error Snackbar
static void showError({
required BuildContext context,
required String message,
String title = "Error",
Color backgroundColor = const Color(0xFFDC2626),
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.error_outline_rounded,
backgroundColor: backgroundColor,
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFB22222),
Color(0xFFB22222),
],
),
action: action,
enableHaptic: enableHaptic,
);
}
// Premium Warning Snackbar
static void showWarning({
required BuildContext context,
required String message,
String title = "Warning",
Color backgroundColor = const Color(0xFFFFA000),
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.warning_amber_rounded,
backgroundColor: backgroundColor,
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFF57F17),
Color(0xFFF59E0B),
],
),
action: action,
enableHaptic: enableHaptic,
);
}
// Premium Info Snackbar
static void showInfo({
required BuildContext context,
required String message,
String title = "Info",
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.info_outline_rounded,
backgroundColor: const Color(0xFF2563EB),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF2563EB),
Color(0xFF0D47A1),
],
),
action: action,
enableHaptic: enableHaptic,
);
}
// Premium Snackbar with custom color
static void showCustom({
required BuildContext context,
required String message,
required String title,
required Color backgroundColor,
required IconData icon,
SnackBarAction? action,
bool enableHaptic = true,
Gradient? gradient,
}) {
show(
context: context,
message: message,
title: title,
icon: icon,
backgroundColor: backgroundColor,
gradient: gradient,
action: action,
enableHaptic: enableHaptic,
);
}
static void showExit({
required BuildContext context,
required String message,
String title = "Exit",
SnackBarAction? action,
bool enableHaptic = true,
}) {
show(
context: context,
message: message,
title: title,
icon: Icons.exit_to_app,
backgroundColor: const Color(0xFFFF0000),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF2563EB),
Color(0xFF0D47A1),
],
),
action: action,
enableHaptic: enableHaptic,
);
}
}
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
class FullScreenImageViewer extends StatefulWidget {
final String imageUrl;
const FullScreenImageViewer({super.key, required this.imageUrl});
@override
State<FullScreenImageViewer> createState() => _FullScreenImageViewerState();
}
class _FullScreenImageViewerState extends State<FullScreenImageViewer> {
PhotoViewController controller = PhotoViewController();
PhotoViewScaleStateController scaleStateController = PhotoViewScaleStateController();
bool _showControls = true;
@override
void initState() {
super.initState();
// Auto-hide controls after 3 seconds
Future.delayed(const Duration(seconds: 3), () {
if (mounted) {
setState(() {
_showControls = false;
});
}
});
}
void _toggleControls() {
setState(() {
_showControls = !_showControls;
});
}
void _resetImage() {
controller.reset();
scaleStateController.reset();
}
void _rotateImage() {
final double currentRotation = controller.rotation;
controller.rotation = currentRotation + 90.0;
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Scaffold(
backgroundColor: Color(0xFFFFFFFF),
body: Stack(
children: [
// Main Photo View
GestureDetector(
onTap: _toggleControls,
child: PhotoView(
imageProvider: NetworkImage(widget.imageUrl),
controller: controller,
scaleStateController: scaleStateController,
backgroundDecoration: const BoxDecoration(
color: Color(0xFF444444),
),
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.covered * 4,
initialScale: PhotoViewComputedScale.contained,
basePosition: Alignment.center,
filterQuality: FilterQuality.high,
enableRotation: true,
gestureDetectorBehavior: HitTestBehavior.opaque,
loadingBuilder: (context, event) => Center(
child: Container(
width: 40,
height: 40,
child: const CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
),
),
errorBuilder: (context, error, stackTrace) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
color: Colors.white,
size: 50,
),
const SizedBox(height: 16),
Text(
'Failed to load image',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 16,
),
),
],
),
),
),
),
// Controls Overlay
if (_showControls) ...[
// Top Bar
Positioned(
top: MediaQuery.of(context).padding.top,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.7),
Colors.black.withOpacity(0.3),
Colors.transparent,
],
),
),
child: Row(
children: [
// Close Button
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
shape: BoxShape.circle,
),
child: const Icon(
Icons.close,
color: Colors.white,
size: 24,
),
),
),
const Spacer(),
// Reset Button
GestureDetector(
onTap: _resetImage,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
shape: BoxShape.circle,
),
child: const Icon(
Icons.refresh,
color: Colors.white,
size: 24,
),
),
),
const SizedBox(width: 12),
// Rotate Button
GestureDetector(
onTap: _rotateImage,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
shape: BoxShape.circle,
),
child: const Icon(
Icons.rotate_90_degrees_ccw,
color: Colors.white,
size: 24,
),
),
),
],
),
),
),
// Bottom Info Bar
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withOpacity(0.7),
Colors.black.withOpacity(0.3),
Colors.transparent,
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Zoom Instructions
Row(
children: [
_buildInstructionIcon(Icons.touch_app, "Double tap to zoom"),
const SizedBox(width: 16),
_buildInstructionIcon(Icons.pan_tool, "Pan to move"),
const SizedBox(width: 16),
_buildInstructionIcon(Icons.rotate_right, "Pinch to rotate"),
],
),
const SizedBox(height: 12),
// Image Info
Text(
"Image Preview",
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
],
// Double Tap Zoom Hint (appears briefly)
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 100,
left: 0,
right: 0,
child: AnimatedOpacity(
opacity: _showControls ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: const Center(
child: Text(
"Double tap to zoom",
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
),
),
),
],
),
// Bottom Navigation Bar with additional controls
bottomNavigationBar: _showControls
? Container(
height: 60,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Zoom In
IconButton(
onPressed: () {
final double currentScale = controller.scale ?? 1.0;
controller.scale = currentScale * 1.5;
},
icon: const Icon(Icons.zoom_in, color: Colors.white),
tooltip: "Zoom In",
),
// Zoom Out
IconButton(
onPressed: () {
final double currentScale = controller.scale ?? 1.0;
controller.scale = currentScale / 1.5;
},
icon: const Icon(Icons.zoom_out, color: Colors.white),
tooltip: "Zoom Out",
),
// Reset
IconButton(
onPressed: _resetImage,
icon: const Icon(Icons.fit_screen, color: Colors.white),
tooltip: "Reset",
),
// Rotate
IconButton(
onPressed: _rotateImage,
icon: const Icon(Icons.rotate_90_degrees_ccw, color: Colors.white),
tooltip: "Rotate 90°",
),
],
),
)
: null,
),
);
}
Widget _buildInstructionIcon(IconData icon, String text) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: Colors.white.withOpacity(0.7), size: 16),
const SizedBox(width: 4),
Text(
text,
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 12,
),
),
],
);
}
@override
void dispose() {
controller.dispose();
scaleStateController.dispose();
super.dispose();
}
}
\ No newline at end of file
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesService {
// Singleton
SharedPreferencesService._privateConstructor();
static final SharedPreferencesService instance =
SharedPreferencesService._privateConstructor();
/// Save String
Future<bool> saveString(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setString(key, value);
}
/// Save Int
Future<bool> saveInt(String key, int value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setInt(key, value);
}
/// Save Bool
Future<bool> saveBool(String key, bool value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setBool(key, value);
}
/// Save Double
Future<bool> saveDouble(String key, double value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setDouble(key, value);
}
/// Get String
Future<String?> getString(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
/// Get Int
Future<int?> getInt(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(key);
}
/// Get Bool
Future<bool?> getBool(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(key);
}
/// Get Double
Future<double?> getDouble(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getDouble(key);
}
/// Remove a key
Future<bool> remove(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.remove(key);
}
/// Clear all preferences
Future<bool> clearPreferences() async {
final prefs = await SharedPreferences.getInstance();
return prefs.clear();
}
}
// lib/Utility/connectivityService.dart
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
class MyConnectivity {
MyConnectivity._();
static final MyConnectivity _instance = MyConnectivity._();
static MyConnectivity get instance => _instance;
final Connectivity _connectivity = Connectivity();
final _controller = StreamController<bool>.broadcast();
Stream<bool> get stream => _controller.stream;
bool _lastState = true; // assume online at start
void initialise() async {
// initial check
final result = await _connectivity.checkConnectivity();
await _checkInternet(result);
// listen for changes
_connectivity.onConnectivityChanged.listen(_checkInternet);
}
Future<void> _checkInternet(List<ConnectivityResult> results) async {
final type = results.isEmpty ? ConnectivityResult.none : results.first;
if (type == ConnectivityResult.none) {
_emit(false);
return;
}
bool online = false;
final client = HttpClient()
..connectionTimeout = const Duration(seconds: 6);
try {
final request = await client.getUrl(Uri.parse('https://www.google.com'));
request.followRedirects = false;
final response = await request.close();
online = response.statusCode >= 200 && response.statusCode < 400;
} on SocketException {
online = false;
} on TimeoutException {
online = false;
} catch (_) {
online = false;
} finally {
client.close();
}
_emit(online);
}
void _emit(bool online) {
if (_lastState != online) {
_lastState = online;
_controller.sink.add(online);
}
}
void dispose() => _controller.close();
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:gen_service/Notifiers/AuthProvider.dart';
import 'package:gen_service/Notifiers/TransactionsProvider.dart';
import 'package:provider/provider.dart';
import 'Notifiers/DashboardProvider.dart';
import 'Notifiers/theme_provider.dart';
import 'Screens/AuthScreen/LoginScreen.dart';
import 'Screens/SplashScreen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()),
ChangeNotifierProvider<ThemeProvider>(create: (_) => ThemeProvider(),),
ChangeNotifierProvider(create: (_) => DashboardProvider()),
ChangeNotifierProvider(create: (_) => TransactionsProvider()),
],
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Gen Services',
theme: ThemeData(
fontFamily: 'Poppins',
),
home: SplashScreen(),
);
},
),
);
}
}
flutter/ephemeral
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "gen_service")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.gen_service")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment