first commit

This commit is contained in:
Anbarasu
2026-05-26 18:01:57 +05:30
commit 6d59c8daf6
297 changed files with 35238 additions and 0 deletions

View File

@@ -0,0 +1,854 @@
import 'dart:io';
import 'package:dotted_line/dotted_line.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:nearledaily/view/account/share_app.dart';
import 'package:nearledaily/view/authentication/login_view.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shimmer/shimmer.dart';
import '../../constants/color_constants.dart';
import '../../constants/font_constants.dart';
import '../../controllers/account_controller/profile.dart';
import '../../controllers/authentication/auth_controller.dart';
import '../../domain/repository/authentication/auth_repository.dart';
import '../../service/bindings.dart';
import '../../widgets/text_widget.dart';
import '../orders/orders_by_tenant.dart';
import 'edit_profile_view.dart';
import 'faq_view.dart';
import 'help/create_request.dart';
import 'notification_settings_view.dart';
class AccountPage extends StatefulWidget {
const AccountPage({super.key});
@override
State<AccountPage> createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
static const Color primaryColor = Color(0xFF662582);
final controller = Get.put(AccountController());
String Name = '';
String Profile = '';
String Number = '';
@override
void initState() {
super.initState();
_loadProfile();
}
Future<void> _loadProfile() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int? id = prefs.getInt('customerId');
if (id == null) return;
final repo = LoginRepository();
final fetchedProfile = await repo.fetchProfile(id.toString());
if (fetchedProfile != null) {
setState(() {
Name = fetchedProfile.firstname ?? '';
Profile = fetchedProfile.profileimage ?? '';
Number = fetchedProfile.contactno ?? '';
});
print(Name);
print(Profile);
print(Number);
}
}
Widget _profileShimmer() {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Shimmer.fromColors(
baseColor: Colors.grey.shade300, // shimmer base
highlightColor: Colors.grey.shade100, // shimmer highlight
child: CircleAvatar(
radius: 28,
backgroundColor: Colors.grey.shade200, // light background
child: Icon(
Icons.person,
size: 28,
color: Colors.grey.shade500, // darker icon color
),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 14,
width: 120,
decoration: BoxDecoration(
color: Colors.white, // <-- background color goes here
borderRadius: BorderRadius.circular(8), // <-- rounded corners
),
),
),
const SizedBox(height: 6),
Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 12,
width: 90,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8), // <-- add radius here too
),
),
),
],
)
],
),
);
}
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.white, // or transparent
statusBarIconBrightness: Brightness.dark, // Android
statusBarBrightness: Brightness.light, // iOS
),
);
return SafeArea(
child: Scaffold(
extendBodyBehindAppBar: false,
backgroundColor: Color(0xFFF6F6F6),
appBar: AppBar(
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
// 🔥 Prevent color overlay when scrolled
scrolledUnderElevation: 0,
animateColor: false, // ✨ prevent color change on scroll
elevation: 0,
title: ReusableTextWidget(
text: "Profile",
fontSize: 20,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.fontFamily,
color: Colors.black,
),
),
body: Obx(
() => SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const SizedBox(height: 20),
/// PROFILE CARD (EXACT LIKE IMAGE)
controller.isLoading.value
? _profileShimmer()
: GestureDetector(
onTap: () async {
final res = await Get.to(
() => EditProfilePage(),
// transition: Transition.fade, // Your desired transition
// duration: Duration(milliseconds: 400), // Duration of the transition
);
if (res != null && res['status'] == true) {
_loadProfile();
}
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 11),
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 30,
bottom: 30,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF1B1333), // Dark background (luxury dark purple/indigo)
Color(0xFF662582).withOpacity(0.9), // Primary color accent
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.black12,
width: 0.30
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 2,
offset: const Offset(0, 3),
),
],
),
child: Row(
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.grey.shade300,
backgroundImage:
Profile.isNotEmpty ? NetworkImage(Profile) : null,
child: Profile.isEmpty
? const Icon(Icons.person, size: 26)
: null,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: Name,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.fontFamily,
color: Colors.white,
),
const SizedBox(height: 4),
ReusableTextWidget(
text: Number,
fontSize: 13,
fontWeight: FontWeight.w400,
fontFamily: FontConstants.fontFamily,
color: Colors.white,
),
],
),
),
],
),
),
),
const SizedBox(height: 24),
/// ACCOUNT
controller.isLoading.value
? accountListShimmerSingleBox()
:
_section(
title: "Account",
children: [
_tile(
icon: Icons.person,
title: "Manage Profile",
onTap: () async {
final res = await Get.to(
() => EditProfilePage(),
// transition: Transition.fade, // Your desired transition
// duration: Duration(milliseconds: 400), // Duration of the transition
);
if (res != null && res['status'] == true) {
_loadProfile();
}
},
),
_divider(),
_tile(
icon: Icons.question_answer,
title: "Faq",
onTap: () => Get.to(
() => FaqView(),
// transition: Transition.fade, // or any transition you like
// duration: Duration(milliseconds: 400),
),
),
_divider(),
_tile(
icon: Icons.reorder,
title: "Your Orders",
onTap: () => Get.to(
() => const OrdersByStoreScreen(showBackArrow: true),
// transition: Transition.fade, // or any transition you prefer
// duration: Duration(milliseconds: 400),
),
),
// _divider(),
],
),
/// PREFERENCES
controller.isLoading.value
? Preferences()
:
_section(
title: "Preferences",
children: [
_tile(
icon: Icons.star_rate,
title: "Rate the app in Playstore",
onTap: controller.rateApp,
),
_divider(),
// _tile(
// icon: Icons.group_add,
// title: "Refer a Friend",
// onTap: () => Get.to(
// () => const ShowContactsScreen(),
// // transition: Transition.fade, // or any style you like
// // duration: Duration(milliseconds: 400),
// ),
// ),
],
),
/// SUPPORT
controller.isLoading.value
? Preferences()
:
_section(
title: "Support",
children: [
_tile(
icon: Icons.support_agent,
title: "Help & Support",
onTap: () => Get.to(
() => Help_Support(),
// transition: Transition.fade, // simple fade
// duration: Duration(milliseconds: 400),
),
),
_divider(),
_tile(
icon: Icons.logout,
title: "Logout",
isLogout: true,
onTap: () {
showLogoutDialog();
},
),
],
),
const SizedBox(height: 20),
/// LOGOUT
// GestureDetector(
// onTap: showLogoutDialog,
// child: Container(
// margin: const EdgeInsets.symmetric(horizontal: 16),
// padding: const EdgeInsets.symmetric(vertical: 14),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(16),
// border: Border.all(color: Colors.red, width: 0.4),
// ),
// child: Center(
// child: ReusableTextWidget(
// text: "Logout",
// color: Colors.red,
// fontSize: 16,
// fontWeight: FontWeight.w600,
// fontFamily: FontConstants.fontFamily,
// ),
// ),
// ),
// ),
const SizedBox(height: 30),
],
),
),
),
),
);
}
/// SECTION CARD
Widget _section({
required String title,
required List<Widget> children,
}) {
return Padding(
padding: const EdgeInsets.fromLTRB(11, 0, 11, 11),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.black12,
width: 0.20
),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.03),
blurRadius: 2,
offset: const Offset(0, 3),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 6),
child: ReusableTextWidget(
text: title,
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black,
fontFamily: FontConstants.fontFamily,
),
),
...children,
],
),
),
);
}
/// LIST TILE (EXACT STYLE)
Widget _tile({
required IconData icon,
required String title,
required VoidCallback onTap,
bool isLogout = false,
}) {
final Color mainColor =
isLogout ? Colors.red : Colors.black45;
return ListTile(
dense: true,
minVerticalPadding: 0,
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
visualDensity: const VisualDensity(vertical: 0.10),
leading: Icon(
icon,
size: 22,
color: mainColor,
shadows: [
Shadow(
color: Colors.grey.withOpacity(0.2),
offset: const Offset(0.30, 0.30),
blurRadius: 1,
),
],
),
title: ReusableTextWidget(
text: title,
fontSize: 13,
fontWeight: FontWeight.w700,
fontFamily: FontConstants.fontFamily,
color: isLogout
? Colors.red
: Colors.black.withOpacity(0.7),
),
// 👇 Arrow ALWAYS normal black
trailing: Icon(
Icons.arrow_forward_ios,
size: 14,
weight: 400,
color: Colors.black.withOpacity(0.9),
),
onTap: onTap,
);
}
Widget _divider() {
return Divider(
color: Color(0xFFF6F6F6),
thickness: 1.5,
height: 0.10, //
);
}
/// LOGOUT DIALOG (UNCHANGED LOGIC)
void showLogoutDialog() {
Get.dialog(
Dialog(
elevation: 0,
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
padding: const EdgeInsets.all(22),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 🔴 Icon Container
Container(
height: 64,
width: 64,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
primaryColor.withOpacity(0.9),
primaryColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Icon(
Icons.logout_rounded,
size: 30,
color: Colors.white,
),
),
const SizedBox(height: 16),
// 📝 Title
ReusableTextWidget(
text: "Logout",
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.fontFamily,
color: Colors.black,
),
const SizedBox(height: 6),
// 🧾 Subtitle
ReusableTextWidget(
text: "Are you sure you want to logout?",
fontSize: 14,
fontWeight: FontWeight.w400,
fontFamily: FontConstants.fontFamily,
color: Colors.black54,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// 🔘 Buttons
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context),
style: OutlinedButton.styleFrom(
foregroundColor: primaryColor,
side: BorderSide(color: primaryColor),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: ReusableTextWidget(
text: "Cancel",
fontFamily: FontConstants.fontFamily,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
String fcmToken = prefs.getString('fcmToken') ?? '';
String deviceId =
prefs.getString('currentDeviceId') ?? '';
await prefs.clear();
await prefs.setString('fcmToken', fcmToken);
await prefs.setString('currentDeviceId', deviceId);
Get.deleteAll();
GlobalBinding().dependencies();
Get.offAll(() => Login_view());
},
child: ReusableTextWidget(
text: "Logout",
fontFamily: FontConstants.fontFamily,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
),
),
),
);
}
Widget accountListShimmerSingleBox() {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: List.generate(4, (index) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
child: Row(
children: [
// Leading avatar
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 44,
width: 44,
decoration: const BoxDecoration(
color: Colors.white,
),
),
),
const SizedBox(width: 14),
// Title + subtitle
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Padding(
padding: const EdgeInsets.only(right: 18.0),
child: Container(
height: 14,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
),
),
),
const SizedBox(height: 8),
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 12,
width: 120,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
),
),
],
),
),
const SizedBox(width: 12),
// Trailing arrow shimmer
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 18,
width: 18,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
),
),
],
),
),
// Divider (except last)
if (index != 3)
Padding(
padding: const EdgeInsets.only(left: 74),
child: Divider(
height: 1,
thickness: 0.8,
color: Colors.grey.shade200,
),
),
],
);
}),
),
);
}
Widget Preferences() {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: List.generate(2, (index) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
child: Row(
children: [
// Leading avatar
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 44,
width: 44,
decoration: const BoxDecoration(
color: Colors.white,
),
),
),
const SizedBox(width: 14),
// Title + subtitle
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Padding(
padding: const EdgeInsets.only(right: 18.0),
child: Container(
height: 14,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
),
),
),
const SizedBox(height: 8),
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 12,
width: 120,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
),
),
],
),
),
const SizedBox(width: 12),
// Trailing arrow shimmer
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 18,
width: 18,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
),
),
],
),
),
// Divider (except last)
if (index != 1)
Padding(
padding: const EdgeInsets.only(left: 74),
child: Divider(
height: 1,
thickness: 0.8,
color: Colors.grey.shade200,
),
),
],
);
}),
),
);
}
}

View File

View File

@@ -0,0 +1,805 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Response;
import 'package:http/http.dart' as http;
import 'package:minio/io.dart';
import 'package:minio/minio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shimmer/shimmer.dart';
import 'package:image_picker/image_picker.dart';
import '../../constants/color_constants.dart';
import '../../constants/font_constants.dart';
import '../../controllers/account_controller/profile.dart';
import '../../domain/repository/authentication/auth_repository.dart';
import '../../modules/authentication/auth.dart';
import '../../modules/authentication/getbyid.dart';
import '../../widgets/text_widget.dart';
class EditProfilePage extends StatefulWidget {
const EditProfilePage({super.key});
@override
State<EditProfilePage> createState() => _EditProfilePageState();
}
class _EditProfilePageState extends State<EditProfilePage> {
CustomerFullView? profile;
bool isLoading = true;
File? pickedImage;
final AccountController accountController = Get.find<AccountController>();
String Name = '';
String Adress = '';
String Profile = '';
String Number = '';
Future<void> _pickImage() async {
final pickedFile =
await ImagePicker().pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
pickedImage = File(pickedFile.path);
});
}
}
// Controllers for editable fields
final TextEditingController _nameController = TextEditingController();
final TextEditingController _contactController = TextEditingController();
final TextEditingController _dobController = TextEditingController();
final TextEditingController _genderController = TextEditingController();
final TextEditingController _addressController = TextEditingController();
@override
void initState() {
super.initState();
_loadProfile();
}
Future<void> _loadProfile() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int? id = prefs.getInt('customerId');
if (id == null) {
Get.snackbar("Error", "Customer ID not found");
return;
}
setState(() => isLoading = true);
final repo = LoginRepository();
final fetchedProfile = await repo.fetchProfile(id.toString());
if (fetchedProfile != null) {
_nameController.text = fetchedProfile.firstname ?? '';
_contactController.text = fetchedProfile.contactno ?? '';
_dobController.text = fetchedProfile.dob != null
? fetchedProfile.dob!.toIso8601String()
: ''; _genderController.text = fetchedProfile.gender ?? '';
_addressController.text = fetchedProfile.address ?? '';
Name = fetchedProfile.firstname ?? '';
Profile = fetchedProfile.profileimage ?? '';
Number = fetchedProfile.contactno ?? '';
Adress = fetchedProfile.address ?? '';
}
setState(() {
profile = fetchedProfile;
isLoading = false;
});
}
Future<String?> uploadImageAndSave(
File selectedImage, int customerId) async {
try {
var rng = Random();
const String region = "sgp1";
const String accessKey = "DO00NQER7N2FRYZAB2HR";
const String secretKey = "nMDewX25IBEu1FM5dakK+v28/WbW3TzBAwq913+dxP0";
const String bucketName = "nearle";
const String folderName = "deals";
String fileName = 'profile-${rng.nextInt(1000)}-$customerId.jpg';
String endpointUrl =
"https://$bucketName.$region.digitaloceanspaces.com/$folderName/$fileName";
// Initialize Minio client
final minio = Minio(
endPoint: '$region.digitaloceanspaces.com',
accessKey: accessKey,
secretKey: secretKey,
region: region,
useSSL: true,
);
// Upload file
await minio.fPutObject(
bucketName,
'$folderName/$fileName',
selectedImage.path,
metadata: {
'Content-Type': 'image/jpeg',
'x-amz-acl': 'public-read', // Set ACL to public-read if needed
},
);
print("File uploaded successfully: $endpointUrl");
return endpointUrl;
} catch (e) {
Get.snackbar("Error", "Image upload failed: $e");
print("Upload error: $e");
return null;
}
}
Future<Map<String, String>> fetchAddressDetails(String address) async {
final url = Uri.parse(
'https://nominatim.openstreetmap.org/search'
'?q=${Uri.encodeComponent(address)}'
'&format=json'
'&addressdetails=1',
);
final response = await http.get(
url,
headers: {'User-Agent': 'FlutterApp'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data.isNotEmpty) {
final item = data[0];
final addr = item['address'] ?? {};
return {
"suburb": addr['suburb'] ?? addr['neighbourhood'] ?? '',
"city": addr['city'] ?? addr['town'] ?? addr['village'] ?? '',
"state": addr['state'] ?? '',
"postcode": addr['postcode'] ?? '',
"landmark": addr['road'] ?? addr['attraction'] ?? '',
"latitude": item['lat'] ?? '',
"longitude": item['lon'] ?? '',
};
}
}
/// fallback (never null)
return {
"suburb": '',
"city": '',
"state": '',
"postcode": '',
"landmark": '',
"latitude": '',
"longitude": '',
};
}
Future<void> _updateProfile() async {
if (profile == null) {
Get.snackbar("Error", "Profile data not loaded");
return;
}
SharedPreferences prefs = await SharedPreferences.getInstance();
int? customerId = prefs.getInt('customerId');
if (customerId == null) {
Get.snackbar("Error", "Customer ID not found");
return;
}
setState(() => isLoading = true);
String? uploadedFileUrl;
/// Upload image if selected
if (pickedImage != null) {
uploadedFileUrl = await uploadImageAndSave(pickedImage!, customerId);
}
/// 🌍 AUTO-FETCH ADDRESS DETAILS
final addressDetails =
await fetchAddressDetails(_addressController.text.trim());
final data = {
"customerid": customerId,
"configid": profile!.configid ?? 1,
"firstname": _nameController.text.trim(),
"applocationid": profile!.applocationid ?? 91,
"contactno": _contactController.text.trim(),
"address": _addressController.text.trim(),
"gender": _genderController.text.trim(),
"dob": _dobController.text.trim(),
"profileimage": uploadedFileUrl ?? profile!.profileimage,
// ✅ AUTO FILLED
"doorno": "",
"suburb": addressDetails['suburb'],
"city": addressDetails['city'],
"state": addressDetails['state'],
"postcode": addressDetails['postcode'],
"landmark": addressDetails['landmark'],
"latitude": addressDetails['latitude'],
"longitude": addressDetails['longitude'],
};
print("PROFILE UPDATE REQUEST => $data");
try {
final repo = LoginRepository();
final response = await repo.updateProfile(data);
setState(() => isLoading = false);
if (response != null && response['status'] == true) {
Get.snackbar("Success", response['message'] ?? "Profile updated");
Navigator.pop(context, true);
} else {
Get.snackbar(
"Error",
response?['message'] ?? "Profile update failed",
);
}
} catch (e) {
setState(() => isLoading = false);
Get.snackbar("Error", e.toString());
}
}
List<dynamic> predictions = [];
// Replace with your API key
final String googleApiKey = "AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q";
Future<void> searchPlace(String input) async {
if (input.isEmpty) {
setState(() {
predictions = [];
});
return;
}
String url =
'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input&types=geocode&components=country:in&key=$googleApiKey';
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
predictions = data['predictions'];
});
}
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
return SafeArea(
top: false,
bottom: true,
child: Scaffold(
backgroundColor: const Color(0xFFFAFAFA),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
shadowColor: Colors.black.withOpacity(0.05),
leading: IconButton(
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.arrow_back_ios_new,
color: Colors.black87, size: 18),
),
onPressed: () => Navigator.of(context).pop(),
),
title: const ReusableTextWidget(
text: "Edit Profile",
color: Colors.black87,
fontFamily: FontConstants.fontFamily,
fontSize: 20,
fontWeight: FontWeight.w700,
),
centerTitle: true,
),
body: isLoading
? _buildShimmer(screenSize)
: SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: screenSize.width * 0.05,
vertical: screenSize.height * 0.02,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile Image Section
Center(
child: Column(
children: [
Stack(
alignment: Alignment.bottomRight,
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
ColorConstants.primaryColor
.withOpacity(0.1),
ColorConstants.primaryColor
.withOpacity(0.05),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: ColorConstants.primaryColor
.withOpacity(0.15),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Container(
margin: const EdgeInsets.all(4),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: CircleAvatar(
radius: screenSize.height * 0.09,
backgroundColor: Colors.grey.shade100,
backgroundImage: pickedImage != null
? FileImage(pickedImage!)
as ImageProvider
: (profile?.profileimage != null &&
profile!.profileimage!.isNotEmpty
? NetworkImage(
profile!.profileimage!)
: null),
child: (pickedImage == null &&
(profile?.profileimage == null ||
profile!.profileimage!.isEmpty))
? Icon(
Icons.person_outline,
size: 60,
color: Colors.grey.shade400,
)
: null,
),
),
),
GestureDetector(
onTap: _pickImage,
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: ColorConstants.primaryColor,
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 3,
),
boxShadow: [
BoxShadow(
color: ColorConstants.primaryColor
.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: const Icon(
Icons.camera_alt_rounded,
color: Colors.white,
size: 20,
),
),
),
],
),
SizedBox(height: screenSize.height * 0.015),
Text(
"Tap to change profile photo",
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 13,
fontFamily: FontConstants.fontFamily,
),
),
],
),
),
SizedBox(height: screenSize.height * 0.04),
// Form Section
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel("Full Name"),
const SizedBox(height: 8),
_buildEditableField(
_nameController,
Icons.person_outline_rounded,
"Enter your name",
),
SizedBox(height: screenSize.height * 0.025),
_buildLabel("Contact Number"),
const SizedBox(height: 8),
_buildEditableField(
_contactController,
Icons.phone_outlined,
"Enter your phone number",
),
SizedBox(height: screenSize.height * 0.025),
_buildLabel("Address"),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: const Color(0xFFF5F6FA),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey.shade200,
width: 1,
),
),
child: TextFormField(
controller: _addressController,
style: const TextStyle(
fontSize: 15,
fontFamily: FontConstants.fontFamily,
),
decoration: InputDecoration(
hintText: "Search location...",
hintStyle: TextStyle(
color: Colors.grey.shade400,
fontSize: 15,
),
filled: true,
fillColor: Colors.transparent,
contentPadding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
prefixIcon: Icon(
Icons.location_on_outlined,
color: ColorConstants.primaryColor,
size: 22,
),
suffixIcon: _addressController.text.isNotEmpty
? IconButton(
icon: Icon(
Icons.close_rounded,
color: Colors.grey.shade400,
),
onPressed: () {
_addressController.clear();
setState(() {
predictions = [];
});
},
)
: null,
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: ColorConstants.primaryColor,
width: 1.5,
),
),
),
onChanged: (value) {
searchPlace(value);
setState(() {});
},
),
),
const SizedBox(height: 8),
// Display suggestions
if (predictions.isNotEmpty)
Container(
constraints: BoxConstraints(
maxHeight: screenSize.height * 0.3,
),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.grey.shade200,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ListView.separated(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: predictions.length,
separatorBuilder: (context, index) => Divider(
height: 1,
color: Colors.grey.shade100,
),
itemBuilder: (context, index) {
final prediction = predictions[index];
return ListTile(
dense: true,
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: ColorConstants.primaryColor
.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.location_on,
color: ColorConstants.primaryColor,
size: 18,
),
),
title: Text(
prediction['description'],
style: const TextStyle(
fontSize: 14,
fontFamily: FontConstants.fontFamily,
),
),
onTap: () {
_addressController.text =
prediction['description'];
setState(() {
predictions = [];
});
FocusScope.of(context).unfocus();
},
);
},
),
),
],
),
),
SizedBox(height: screenSize.height * 0.1),
],
),
),
// Bottom Button
bottomNavigationBar: Container(
padding: EdgeInsets.symmetric(
horizontal: screenSize.width * 0.05,
vertical: screenSize.height * 0.02,
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -5),
),
],
),
child: Container(
height: screenSize.height * 0.065,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
ColorConstants.primaryColor,
ColorConstants.primaryColor.withOpacity(0.8),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: ColorConstants.primaryColor.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
onPressed: _updateProfile,
child: const ReusableTextWidget(
text: "Update Profile",
color: Colors.white,
fontFamily: FontConstants.fontFamily,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
),
);
}
/// Label
Widget _buildLabel(String text) {
return ReusableTextWidget(
text: text,
color: const Color(0xFF2D3142),
fontFamily: FontConstants.fontFamily,
fontSize: 14,
fontWeight: FontWeight.w600,
);
}
/// Editable Text Field
Widget _buildEditableField(
TextEditingController controller,
IconData icon,
String hint,
) {
return Container(
decoration: BoxDecoration(
color: const Color(0xFFF5F6FA),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey.shade200,
width: 1,
),
),
child: TextFormField(
controller: controller,
style: const TextStyle(
fontSize: 15,
fontFamily: FontConstants.fontFamily,
),
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
color: Colors.grey.shade400,
fontSize: 15,
),
prefixIcon: Icon(
icon,
color: ColorConstants.primaryColor,
size: 22,
),
filled: true,
fillColor: Colors.transparent,
contentPadding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: ColorConstants.primaryColor,
width: 1.5,
),
),
),
),
);
}
/// Shimmer effect while loading
Widget _buildShimmer(Size screenSize) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: screenSize.height * 0.04),
/// Profile image shimmer
Shimmer.fromColors(
baseColor: Colors.grey.shade200,
highlightColor: Colors.grey.shade50,
child: Container(
width: screenSize.height * 0.18,
height: screenSize.height * 0.18,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade300,
),
),
),
SizedBox(height: screenSize.height * 0.01),
/// Helper text shimmer
Shimmer.fromColors(
baseColor: Colors.grey.shade200,
highlightColor: Colors.grey.shade50,
child: Container(
height: 14,
width: screenSize.width * 0.4,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(4),
),
),
),
SizedBox(height: screenSize.height * 0.04),
/// Form container shimmer
Shimmer.fromColors(
baseColor: Colors.grey.shade200,
highlightColor: Colors.grey.shade50,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: List.generate(
3,
(_) => Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 14,
width: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 8),
Container(
height: 50,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
),
],
),
),
),
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../controllers/account_controller/faq_controller.dart';
class FaqView extends GetView<FaqController> {
FaqView({super.key});
final FaqController controller = Get.put(FaqController());
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.white, // White background
statusBarIconBrightness: Brightness.dark, // Dark icons
statusBarBrightness: Brightness.light, // iOS
),
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
title: const Text(
'FAQ',
style: TextStyle(color: Colors.black),
),
iconTheme: const IconThemeData(color: Colors.black),
),
body: Obx(
() => Stack(
children: [
if (controller.webViewController != null)
WebViewWidget(controller: controller.webViewController!),
if (controller.isLoading.value)
const Center(child: CircularProgressIndicator()),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,328 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
import '../../../constants/color_constants.dart';
import '../../../constants/font_constants.dart';
import '../../../domain/provider/profile/create_request.dart';
import '../../../widgets/text_widget.dart';
import 'request_page.dart';
class Help_Support extends StatefulWidget {
const Help_Support({Key? key}) : super(key: key);
@override
State<Help_Support> createState() => _Help_SupportState();
}
class _Help_SupportState extends State<Help_Support> {
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.white,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
child: ChangeNotifierProvider(
create: (_) => CustomerRequestProvider(),
builder: (context, child) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<CustomerRequestProvider>().fetchCustomerRequests();
});
return Consumer<CustomerRequestProvider>(
builder: (context, provider, _) {
return SafeArea(
top: false,
child: PopScope(
canPop: true,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) {
Get.back();
}
},
child: Scaffold(
backgroundColor: Colors.grey[100],
/// APPBAR
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
leadingWidth: 200,
leading: Row(
children: [
IconButton(
onPressed: () =>
Navigator.of(context).pop(),
icon: const Icon(Icons.arrow_back,
color: Colors.black),
),
const Expanded(
child: ReusableTextWidget(
text: "Help & Support",
color: Colors.black,
fontFamily: FontConstants.fontFamily,
fontSize: 16,
fontWeight: FontWeight.bold,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
),
/// BODY
body: provider.isLoading
? ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 5,
itemBuilder: (_, index) => Container(
margin:
const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(14),
),
child: Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor:
Colors.grey.shade100,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
height: 18,
color: Colors.white,
),
const SizedBox(height: 10),
Container(
width: double.infinity,
height: 14,
color: Colors.white,
),
const SizedBox(height: 10),
Container(
width: 80,
height: 14,
color: Colors.white,
),
],
),
),
),
)
/// EMPTY STATE
: provider.requests.isEmpty
? Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
SizedBox(
height: 160,
child: Lottie.asset(
'assets/lotties/help.json',
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
const ReusableTextWidget(
text: "No requests found",
color: Colors.black,
fontFamily:
FontConstants.fontFamily,
fontSize: 16,
fontWeight: FontWeight.w500,
textAlign: TextAlign.center,
),
],
),
)
/// LIST
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: provider.requests.length,
itemBuilder: (context, index) {
final request =
provider.requests[index];
return Container(
margin: const EdgeInsets.only(
bottom: 12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black
.withOpacity(0.04),
blurRadius: 8,
offset:
const Offset(0, 3),
)
],
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
/// TOP ROW
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child:
ReusableTextWidget(
text:
"Subject : ${request.subject}",
color: Colors.black,
fontFamily:
FontConstants
.fontFamily,
fontSize: 14,
fontWeight:
FontWeight.bold,
overflow:
TextOverflow
.ellipsis,
maxLines: 1,
),
),
const SizedBox(width: 8),
ReusableTextWidget(
text: request.created
.split('T')
.first,
color: Colors.grey,
fontFamily:
FontConstants
.fontFamily,
fontSize: 11,
fontWeight:
FontWeight.w500,
),
],
),
const SizedBox(height: 8),
/// REMARK
ReusableTextWidget(
text:
"Remarks : ${request.remarks}",
color: Colors.black87,
fontFamily:
FontConstants.fontFamily,
fontSize: 13,
fontWeight:
FontWeight.normal,
overflow:
TextOverflow.ellipsis,
maxLines: 2,
),
const SizedBox(height: 10),
/// STATUS BADGE
Row(
children: [
const ReusableTextWidget(
text: "Status : ",
color: Colors.black,
fontFamily:
FontConstants
.fontFamily,
fontSize: 13,
),
Container(
padding:
const EdgeInsets
.symmetric(
horizontal: 10,
vertical: 4),
decoration: BoxDecoration(
color: request
.status ==
1
? Colors.green
.withOpacity(
0.1)
: Colors.red
.withOpacity(
0.1),
borderRadius:
BorderRadius
.circular(20),
),
child: ReusableTextWidget(
text: request.status ==
1
? "Completed"
: "Pending",
color: request.status ==
1
? Colors.green
: Colors.red,
fontFamily:
FontConstants
.fontFamily,
fontSize: 11,
fontWeight:
FontWeight.bold,
),
),
],
),
],
),
);
},
),
/// FAB
floatingActionButton: FloatingActionButton(
elevation: 4,
onPressed: () async {
final result = await Get.to(
() => const CustomerRequestPage(),
);
if (result == true) {
context
.read<
CustomerRequestProvider>()
.fetchCustomerRequests();
}
},
backgroundColor:
ColorConstants.primaryColor,
child: const Icon(Icons.add,
color: Colors.white),
),
),
),
);
},
);
},
),
);
}
}

View File

@@ -0,0 +1,230 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import '../../../constants/color_constants.dart';
import '../../../constants/font_constants.dart';
import '../../../domain/provider/profile/create_request.dart';
import '../../../modules/profile/customer_request.dart';
import '../../../widgets/text_widget.dart';
class CustomerRequestPage extends StatefulWidget {
const CustomerRequestPage({super.key});
@override
State<CustomerRequestPage> createState() => _CustomerRequestPageState();
}
class _CustomerRequestPageState extends State<CustomerRequestPage> {
final _formKey = GlobalKey<FormState>();
final TextEditingController subjectController = TextEditingController();
final TextEditingController remarkController = TextEditingController();
final CustomerRequestProvider provider = CustomerRequestProvider();
Future<void> _submitRequest() async {
if (_formKey.currentState!.validate()) {
final model = CustomerRequestModel(
referencedate: DateTime.now().toUtc().toIso8601String(),
referencetype: "",
customerid: 6164,
tenantid: 0,
locationid: 0,
subject: subjectController.text.trim(),
remarks: remarkController.text.trim(),
status: 0,
apptypeid: 98,
);
final success = await provider.sendRequest(
subjectController.text.trim(),
remarkController.text.trim(),
);
if (success) {
Fluttertoast.showToast(
msg: "Request submitted successfully!",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
backgroundColor: Colors.green,
textColor: Colors.white,
fontSize: 14,
);
Navigator.pop(context, true);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Failed to submit request!")),
);
}
}
}
@override
void dispose() {
subjectController.dispose();
remarkController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
elevation: 0,
leadingWidth: 200,
leading: Row(
children: [
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.arrow_back, color: Colors.black),
),
const Expanded(
child: ReusableTextWidget(
text: "Help & Support",
color: Colors.black,
fontFamily: FontConstants.fontFamily,
fontSize: 16,
fontWeight: FontWeight.bold,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
backgroundColor: Colors.white,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
spreadRadius: 2,
offset: const Offset(0, 4),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Your Requested Text Widget Usage
ReusableTextWidget(
text: "Customer Support",
color: Colors.black.withOpacity(0.7),
fontFamily: FontConstants.fontFamily,
fontSize: 10,
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
const SizedBox(height: 20),
/// SUBJECT
const ReusableTextWidget(
text: "Subject",
color: Colors.black,
fontFamily: FontConstants.fontFamily,
fontSize: 14,
fontWeight: FontWeight.w600,
),
const SizedBox(height: 8),
TextFormField(
controller: subjectController,
decoration: InputDecoration(
hintText: "Enter subject",
filled: true,
fillColor: Colors.grey[100],
contentPadding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
validator: (value) =>
value!.isEmpty ? "Please enter subject" : null,
),
const SizedBox(height: 20),
/// REMARK
const ReusableTextWidget(
text: "Remark",
color: Colors.black,
fontFamily: FontConstants.fontFamily,
fontSize: 14,
fontWeight: FontWeight.w600,
),
const SizedBox(height: 8),
TextFormField(
controller: remarkController,
maxLines: 4,
decoration: InputDecoration(
hintText: "Enter your remark",
filled: true,
fillColor: Colors.grey[100],
contentPadding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
validator: (value) =>
value!.isEmpty ? "Please enter remark" : null,
),
const SizedBox(height: 30),
/// SUBMIT BUTTON
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _submitRequest,
style: ElevatedButton.styleFrom(
elevation: 3,
backgroundColor: ColorConstants.primaryColor,
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
child: const Text(
"Submit Request",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
],
),
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,265 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../constants/font_constants.dart';
import '../../widgets/text_widget.dart';
class NotificationSettingsView extends StatefulWidget {
const NotificationSettingsView({super.key});
@override
State<NotificationSettingsView> createState() =>
_NotificationSettingsViewState();
}
class _NotificationSettingsViewState extends State<NotificationSettingsView>
with SingleTickerProviderStateMixin {
bool notificationsEnabled = true;
bool soundEnabled = true;
bool vibrationEnabled = true;
static const Color primaryColor = Color(0xFF662582);
late AnimationController _controller;
late Animation<double> _fadeAnim;
late Animation<double> _scaleAnim;
@override
void initState() {
super.initState();
_loadSettings();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_fadeAnim = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
_scaleAnim = Tween<double>(begin: 0.95, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOutBack),
);
_controller.forward();
}
Future<void> _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
notificationsEnabled = prefs.getBool('notificationsEnabled') ?? true;
soundEnabled = prefs.getBool('notificationSound') ?? true;
vibrationEnabled = prefs.getBool('notificationVibration') ?? true;
});
}
Future<void> _saveSetting(String key, bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(key, value);
}
Widget _animatedSettingCard({
required IconData icon,
required String title,
required String subtitle,
required bool value,
required ValueChanged<bool> onChanged,
}) {
return AnimatedScale(
duration: const Duration(milliseconds: 250),
scale: value ? 1 : 0.98,
child: Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 12,
offset: const Offset(0, 6),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
primaryColor,
primaryColor.withOpacity(0.7),
],
),
shape: BoxShape.circle,
),
child: Icon(icon, color: Colors.white, size: 20),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: title,
fontSize: 14,
fontWeight: FontWeight.w800,
fontFamily: FontConstants.fontFamily,
color: Colors.black.withOpacity(0.65),
),
const SizedBox(height: 4),
ReusableTextWidget(
text: subtitle,
fontSize: 12,
fontWeight: FontWeight.w700,
fontFamily: FontConstants.fontFamily,
color: Colors.grey[500],
),
],
),
),
Switch.adaptive(
value: value,
activeColor: primaryColor,
onChanged: onChanged,
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
// 🔹 Status bar like Account page
value: const SystemUiOverlayStyle(
statusBarColor: Colors.white, // white background
statusBarIconBrightness: Brightness.dark, // dark icons
statusBarBrightness: Brightness.light, // iOS
),
child: Scaffold(
backgroundColor: const Color(0xFFF6F6F6),
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
title: ReusableTextWidget(
text: "Notifications",
fontSize: 20,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.fontFamily,
color: Colors.black,
),
iconTheme: const IconThemeData(color: Colors.black),
),
body: FadeTransition(
opacity: _fadeAnim,
child: ScaleTransition(
scale: _scaleAnim,
child: ListView(
padding: const EdgeInsets.all(12),
children: [
/// 🔔 Animated Header
Container(
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.only(bottom: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
primaryColor,
primaryColor.withOpacity(0.85),
],
),
boxShadow: [
BoxShadow(
color: primaryColor.withOpacity(0.35),
blurRadius: 4,
offset: const Offset(1, 1),
),
],
),
child: Row(
children: [
const Icon(Icons.notifications_active,
color: Colors.white, size: 30),
const SizedBox(width: 16),
Expanded(
child: ReusableTextWidget(
text:
"Control alerts, audio and vibrations\nfor Nearle Daily notifications",
fontSize: 13,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.fontFamily,
color: Colors.white,
),
),
],
),
),
/// 🔕 MASTER SWITCH
_animatedSettingCard(
icon: Icons.notifications_off_outlined,
title: "Enable Notifications",
subtitle: "Turn all notifications on or off",
value: notificationsEnabled,
onChanged: (val) async {
setState(() => notificationsEnabled = val);
await _saveSetting('notificationsEnabled', val);
},
),
/// 🔊 SUB SETTINGS
IgnorePointer(
ignoring: !notificationsEnabled,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 300),
opacity: notificationsEnabled ? 1 : 0.4,
child: Column(
children: [
_animatedSettingCard(
icon: Icons.volume_up_outlined,
title: "Notification Sound",
subtitle: "Play sound for notifications",
value: soundEnabled,
onChanged: (val) async {
setState(() => soundEnabled = val);
await _saveSetting('notificationSound', val);
},
),
_animatedSettingCard(
icon: Icons.vibration,
title: "Vibration",
subtitle: "Vibrate on notification",
value: vibrationEnabled,
onChanged: (val) async {
setState(() => vibrationEnabled = val);
await _saveSetting(
'notificationVibration', val);
},
),
],
),
),
),
],
),
),
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,162 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../domain/provider/product/all_products.dart';
import '../../modules/product/product.dart';
class ProductsController extends GetxController {
final ProductsProvider provider = ProductsProvider();
var isConnected = true.obs;
var isLoading = false.obs;
var productResponse = Rxn<ProductResponse>();
var selectedIndex = 0.obs;
var searchQuery = ''.obs;
var isSearching = false.obs;
/// In-memory cache: key is "categoryId_tenantId"
final Map<String, ProductResponse> _cache = {};
@override
void onInit() {
super.onInit();
// Listen for connectivity changes
Connectivity().onConnectivityChanged.listen((status) {
isConnected.value = (status != ConnectivityResult.none);
});
}
Future<bool> hasInternet() async {
try {
final response = await http.get(Uri.parse('https://www.google.com'))
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
return true;
}
return false;
} catch (e) {
return false;
}
}
Future<void> fetchProducts(int categoryId, int tenantId, int locationId) async {
final cacheKey = '${categoryId}_${tenantId}_$locationId'; // ✅ Include locationId in cache key
// 1⃣ Use cache if available
if (_cache.containsKey(cacheKey)) {
productResponse.value = _cache[cacheKey];
return;
}
isLoading.value = true;
bool connected = await hasInternet();
if (!connected) {
isLoading.value = false;
isConnected = false.obs;
return; // Stop fetching
}
// 2⃣ Otherwise fetch from API
try {
isLoading.value = true;
final response = await provider.getProductsBySubCategory(
categoryId: categoryId,
tenantId: tenantId,
locationId: locationId, // ✅ Pass locationId to API
);
productResponse.value = response;
// 3⃣ Save in cache
_cache[cacheKey] = response!;
} finally {
isLoading.value = false;
}
}
/// Force refresh API and update cache
Future<void> refreshProducts(int categoryId, int tenantId, int locationId) async {
final cacheKey = '${categoryId}_${tenantId}_$locationId'; // ✅ Include locationId
try {
isLoading.value = true;
final response = await provider.getProductsBySubCategory(
categoryId: categoryId,
tenantId: tenantId,
locationId: locationId, // ✅ Pass locationId to API
);
productResponse.value = response;
// ✅ Update cache with new key
_cache[cacheKey] = response!;
} finally {
isLoading.value = false;
}
}
/// Returns products depending on search query and selected subcategory
List<Product> get filteredProducts {
// Check if nested data exists (main API)
final details = productResponse.value?.data?.details;
if (details != null && details.isNotEmpty) {
if (searchQuery.value.isEmpty) {
final selectedDetail = details[selectedIndex.value];
return selectedDetail.products ?? [];
}
List<Product> allProducts = [];
for (var detail in details) {
allProducts.addAll(detail.products ?? []);
}
return allProducts
.where((p) =>
(p.productname ?? '')
.toLowerCase()
.contains(searchQuery.value.toLowerCase()))
.toList();
}
// If flat details exist (variants API)
final variantDetails = productResponse.value?.details ?? [];
if (variantDetails.isNotEmpty) {
if (searchQuery.value.isEmpty) return variantDetails;
return variantDetails
.where((p) =>
(p.productname ?? '')
.toLowerCase()
.contains(searchQuery.value.toLowerCase()))
.toList();
}
return [];
}
// NEW: Dedicated method for subcategory-specific screen
List<Product> getProductsBySubcategory(String subCategoryName) {
final details = productResponse.value?.data?.details ?? [];
if (details.isEmpty) {
return [];
}
// Find matching subcategory (case-insensitive, trimmed for safety)
final matchingDetail = details.firstWhere(
(detail) =>
(detail.subcategoryname ?? '').trim().toLowerCase() ==
subCategoryName.trim().toLowerCase(),
orElse: () => Detail(), // fallback - make sure Detail() is valid in your modules
);
// Return the products of that subcategory (or empty if no match)
return matchingDetail.products ?? [];
}
}

View File

@@ -0,0 +1,379 @@
// import 'package:flutter/material.dart';
// import 'package:flutter_contacts/flutter_contacts.dart';
// import 'package:nearledaily/constants/color_constants.dart';
// import 'package:permission_handler/permission_handler.dart'
// as permission_handler;
// import 'package:url_launcher/url_launcher.dart';
// import 'package:flutter/services.dart';
//
// import '../../constants/font_constants.dart';
// import '../../widgets/text_widget.dart';
//
// class ShowContactsScreen extends StatefulWidget {
// const ShowContactsScreen({super.key});
//
// @override
// State<ShowContactsScreen> createState() => _ShowContactsScreenState();
// }
//
// class _ShowContactsScreenState extends State<ShowContactsScreen>
// with WidgetsBindingObserver {
// List<Contact> _contacts = [];
// bool _loading = false;
// bool _permissionDenied = false;
//
// /// 🔹 ADDED
// bool _showDisclaimer = true;
//
// @override
// void initState() {
// super.initState();
// WidgetsBinding.instance.addObserver(this);
// _loadContacts();
// }
//
// @override
// void dispose() {
// WidgetsBinding.instance.removeObserver(this);
// super.dispose();
// }
//
// Future<void> _loadContacts() async {
// setState(() {
// _loading = true;
// _permissionDenied = false;
// });
//
// final bool granted = await FlutterContacts.requestPermission();
//
// if (!granted) {
// setState(() {
// _loading = false;
// _permissionDenied = true;
// });
// return;
// }
//
// try {
// final List<Contact> contacts = await FlutterContacts.getContacts(
// withProperties: true,
// withPhoto: true,
// );
//
// setState(() {
// _contacts = contacts
// .where((c) => c.phones.isNotEmpty)
// .toList()
// ..sort((a, b) => a.displayName.compareTo(b.displayName));
// _loading = false;
// });
// } catch (e) {
// setState(() {
// _loading = false;
// });
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: ReusableTextWidget(
// text: "Error loading contacts: $e",
// fontSize: 14,
// fontWeight: FontWeight.w400,
// fontFamily: FontConstants.fontFamily,
// color: Colors.white,
// ),
// ),
// );
// }
// }
//
// Widget _buildAvatar(Contact contact) {
// if (contact.photo != null && contact.photo!.isNotEmpty) {
// return CircleAvatar(
// backgroundImage: MemoryImage(contact.photo!),
// );
// } else {
// String initials = "";
// final names = contact.displayName.split(" ");
// if (names.isNotEmpty) initials += names[0][0];
// if (names.length > 1) initials += names[1][0];
// return CircleAvatar(
// backgroundColor: Colors.primaries[
// contact.displayName.hashCode % Colors.primaries.length],
// child: ReusableTextWidget(
// text: initials.toUpperCase(),
// fontSize: 16,
// fontWeight: FontWeight.bold,
// fontFamily: FontConstants.fontFamily,
// color: Colors.white,
// ),
// );
// }
// }
//
// Future<void> _openWhatsApp(Contact contact) async {
// if (contact.phones.isEmpty) return;
//
// String phoneNumber =
// contact.phones.first.number.replaceAll(RegExp(r'\D'), '');
// final Uri url = Uri.parse("https://wa.me/$phoneNumber");
//
// if (await canLaunchUrl(url)) {
// await launchUrl(url, mode: LaunchMode.externalApplication);
// } else {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: ReusableTextWidget(
// text: "Could not open WhatsApp",
// fontSize: 14,
// fontWeight: FontWeight.w400,
// fontFamily: FontConstants.fontFamily,
// color: Colors.white,
// ),
// ),
// );
// }
// }
//
// Future<void> _inviteWhatsApp(Contact contact) async {
// if (contact.phones.isEmpty) return;
//
// String phoneNumber =
// contact.phones.first.number.replaceAll(RegExp(r'\D'), '');
//
// final String message = Uri.encodeComponent(
// "Hey! Join me on Nearle Daily 🚀");
//
// final Uri url = Uri.parse("https://wa.me/$phoneNumber?text=$message");
//
// if (await canLaunchUrl(url)) {
// await launchUrl(url, mode: LaunchMode.externalApplication);
// } else {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: ReusableTextWidget(
// text: "Could not open WhatsApp",
// fontSize: 14,
// fontWeight: FontWeight.w400,
// fontFamily: FontConstants.fontFamily,
// color: Colors.white,
// ),
// ),
// );
// }
// }
//
// @override
// Widget build(BuildContext context) {
// return AnnotatedRegion<SystemUiOverlayStyle>(
// value: const SystemUiOverlayStyle(
// statusBarColor: Colors.white, // White background
// statusBarIconBrightness: Brightness.dark, // Dark icons
// statusBarBrightness: Brightness.light, // iOS
// ),
// child: Scaffold(
// backgroundColor: Colors.white,
// appBar: AppBar(
// backgroundColor: Colors.white,
// surfaceTintColor: Colors.transparent,
// scrolledUnderElevation: 0,
// titleSpacing: -5,
// animateColor: false,
// elevation: 0,
// title: ReusableTextWidget(
// text: "Refer a friend",
// fontSize: 20,
// fontWeight: FontWeight.w600,
// fontFamily: FontConstants.fontFamily,
// color: Colors.black,
// ),
// iconTheme: const IconThemeData(color: Colors.black),
// ),
// body: Padding(
// padding: const EdgeInsets.only(left: 12.0, right: 12, bottom: 12),
// child: Column(
// children: [
// /// 🔹 MODIFIED DISCLAIMER ONLY
// if (_showDisclaimer)
// Stack(
// children: [
// Padding(
// padding: const EdgeInsets.only(top: 12.0),
// child: Container(
// width: double.infinity,
// padding: const EdgeInsets.all(14),
// margin: const EdgeInsets.only(bottom: 16),
// decoration: BoxDecoration(
// color: ColorConstants.primaryColor.withOpacity(0.08),
// borderRadius: BorderRadius.circular(12),
// ),
// child: const ReusableTextWidget(
// text:
// "We access contacts only to let you share\nor recommend to friends. Nothing is stored.",
// fontSize: 13,
// fontWeight: FontWeight.w500,
// fontFamily: FontConstants.fontFamily,
// color: Colors.black87,
// ),
// ),
// ),
// Positioned(
// top: 6,
// right: -3,
// child: IconButton(
// icon: const Icon(Icons.close, size: 18),
// onPressed: () {
// setState(() {
// _showDisclaimer = false;
// });
// },
// ),
// ),
// ],
// ),
//
// if (_loading)
// const Expanded(
// child: Center(child: CircularProgressIndicator()),
// ),
//
// if (_permissionDenied)
// Expanded(
// child: Center(
// child: Container(
// margin: const EdgeInsets.symmetric(horizontal: 24),
// padding: const EdgeInsets.all(24),
// decoration: BoxDecoration(
// color: Colors.red.withOpacity(0.05),
// borderRadius: BorderRadius.circular(20),
// border: Border.all(
// color: Colors.red.withOpacity(0.2),
// ),
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Container(
// padding: const EdgeInsets.all(18),
// decoration: BoxDecoration(
// color: Colors.red.withOpacity(0.12),
// shape: BoxShape.circle,
// ),
// child: const Icon(
// Icons.info_outline,
// color: Colors.red,
// size: 48,
// ),
// ),
// const SizedBox(height: 20),
// const ReusableTextWidget(
// text: "Contacts Access Needed",
// fontSize: 18,
// fontWeight: FontWeight.w600,
// fontFamily: FontConstants.fontFamily,
// color: Colors.black,
// ),
// const SizedBox(height: 8),
// const ReusableTextWidget(
// text:
// "Allow contacts permission to view\nand invite your friends easily.",
// fontSize: 14,
// fontWeight: FontWeight.w400,
// fontFamily: FontConstants.fontFamily,
// color: Colors.black54,
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 24),
// SizedBox(
// width: double.infinity,
// child: ElevatedButton(
// onPressed: permission_handler.openAppSettings,
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.red,
// elevation: 0,
// padding: const EdgeInsets.symmetric(vertical: 14),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12),
// ),
// ),
// child: const ReusableTextWidget(
// text: "Open Settings",
// fontSize: 15,
// fontWeight: FontWeight.w600,
// fontFamily: FontConstants.fontFamily,
// color: Colors.white,
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// ),
//
// if (_contacts.isNotEmpty && !_loading && !_permissionDenied)
// Expanded(
// child: RefreshIndicator(
// onRefresh: _loadContacts,
// child: ListView.builder(
// itemCount: _contacts.length,
// itemBuilder: (context, index) {
// final contact = _contacts[index];
// final phones =
// contact.phones.map((p) => p.number).toList();
// final subtitle = phones.length > 1
// ? phones.sublist(0, 2).join(", ")
// : phones.first;
//
// return ListTile(
// leading: _buildAvatar(contact),
// title: ReusableTextWidget(
// text: contact.displayName.isEmpty
// ? "No Name"
// : contact.displayName,
// fontSize: 14,
// fontWeight: FontWeight.w600,
// fontFamily: FontConstants.fontFamily,
// color: Colors.black,
// ),
// subtitle: ReusableTextWidget(
// text: subtitle,
// fontSize: 13,
// fontWeight: FontWeight.w400,
// fontFamily: FontConstants.fontFamily,
// color: Colors.grey,
// ),
// trailing: TextButton(
// onPressed: () => _inviteWhatsApp(contact),
// child: const ReusableTextWidget(
// text: "Invite",
// fontSize: 14,
// fontWeight: FontWeight.bold,
// fontFamily: FontConstants.fontFamily,
// color: Colors.green,
// ),
// ),
// onTap: () => _openWhatsApp(contact),
// );
// },
// ),
// ),
// ),
//
// if (_contacts.isEmpty && !_loading && !_permissionDenied)
// const Expanded(
// child: Center(
// child: ReusableTextWidget(
// text: "No contacts found with phone numbers",
// fontSize: 16,
// fontWeight: FontWeight.w400,
// fontFamily: FontConstants.fontFamily,
// color: Colors.grey,
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// );
// }
// }

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../controllers/account_controller/faq_controller.dart';
class test extends GetView<FaqController> {
test({super.key});
final FaqController controller = Get.put(FaqController());
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.white, // White background
statusBarIconBrightness: Brightness.dark, // Dark icons
statusBarBrightness: Brightness.light, // iOS
),
child: Scaffold(
),
);
}
}