first commit
This commit is contained in:
854
lib/view/account/account_view.dart
Normal file
854
lib/view/account/account_view.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
0
lib/view/account/demo.dart
Normal file
0
lib/view/account/demo.dart
Normal file
805
lib/view/account/edit_profile_view.dart
Normal file
805
lib/view/account/edit_profile_view.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/view/account/faq_view.dart
Normal file
43
lib/view/account/faq_view.dart
Normal 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()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
328
lib/view/account/help/create_request.dart
Normal file
328
lib/view/account/help/create_request.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
230
lib/view/account/help/request_page.dart
Normal file
230
lib/view/account/help/request_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
265
lib/view/account/notification_settings_view.dart
Normal file
265
lib/view/account/notification_settings_view.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
162
lib/view/account/product.dart
Normal file
162
lib/view/account/product.dart
Normal 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 ?? [];
|
||||
}
|
||||
}
|
||||
379
lib/view/account/share_app.dart
Normal file
379
lib/view/account/share_app.dart
Normal 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,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
25
lib/view/account/test.dart
Normal file
25
lib/view/account/test.dart
Normal 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(
|
||||
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user