improved app state management and app theme updated.

This commit is contained in:
bala 2025-11-30 01:34:51 +05:30
parent 6e9ffdb9dd
commit fe49bdf9af
12 changed files with 459 additions and 212 deletions

View File

@ -5,6 +5,7 @@ import 'package:autos/presentation/screens/auth/sign_up_screen.dart';
import 'package:autos/presentation/screens/brands/brands_screen.dart';
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
import 'package:autos/presentation/screens/ebay/ebay_screen.dart';
import 'package:autos/presentation/screens/my_account/account_screen.dart';
import 'package:autos/presentation/screens/pricing/pricing_screen.dart';
import 'package:autos/presentation/screens/products/products_screen.dart';
import 'package:autos/presentation/screens/store/create_location_screen.dart';
@ -69,6 +70,9 @@ class AppRouter {
case AppRoutePaths.pricing:
return slideRoute(PricingScreen());
case AppRoutePaths.myAccount:
return slideRoute(AccountScreen());
default:
return _defaultFallback(settings);
}

View File

@ -13,4 +13,5 @@ class AppRoutePaths {
static const brands = '/brands';
static const products = '/products';
static const pricing = '/pricing';
static const myAccount = '/myAccount';
}

View File

@ -1,3 +1,4 @@
import 'package:autos/core/theme/app_theme.dart';
import 'package:flutter/material.dart';
class AppTypo {
@ -50,7 +51,7 @@ class AppTypo {
fontFamily: fontFamily,
fontSize: 18,
fontWeight: FontWeight.w700,
color: Color(0xFF2272f6),
color: AppTheme.primary,
);
static const TextStyle menu = TextStyle(

View File

@ -70,7 +70,7 @@ class SideMenu extends ConsumerWidget {
// --- ACCOUNT ---
_sectionHeader("ACCOUNT"),
_menuItem(context, "👤", "My Account", AppRoutePaths.auth),
_menuItem(context, "👤", "My Account", AppRoutePaths.myAccount),
_menuItem(context, "💰", "Pricing Plan", AppRoutePaths.pricing),
],
),
@ -176,4 +176,5 @@ class SideMenu extends ConsumerWidget {
),
);
}
}

View File

@ -14,6 +14,7 @@ class UserModel extends User {
super.code,
});
/// FROM API JSON
factory UserModel.fromJson(Map<String, dynamic> json) {
final payment = json['payment'] ?? {};
return UserModel(
@ -29,6 +30,22 @@ class UserModel extends User {
);
}
/// THIS WAS MISSING VERY IMPORTANT
factory UserModel.fromEntity(User user) {
return UserModel(
id: user.id,
name: user.name,
email: user.email,
role: user.role,
plan: user.plan,
paymentStatus: user.paymentStatus,
phoneNumber: user.phoneNumber,
message: user.message,
code: user.code,
);
}
/// TO JSON FOR STORAGE
Map<String, dynamic> toJson() {
return {
'userid': id,
@ -43,10 +60,10 @@ class UserModel extends User {
};
}
/// Store user as raw JSON string
/// STORE USER AS RAW JSON STRING
String toRawJson() => jsonEncode(toJson());
/// Parse user from raw JSON string
/// RESTORE USER FROM RAW JSON STRING
factory UserModel.fromRawJson(String raw) =>
UserModel.fromJson(jsonDecode(raw));

View File

@ -50,7 +50,7 @@ class BrandsNotifier extends StateNotifier<AsyncValue<List<BrandEntity>>> {
/// SAVE SELECTED BRANDS TO SERVER (Bulk Insert)
Future<void> saveSelectedBrands(List<BrandEntity> selectedBrands) async {
try {
final userAsync = ref.read(userDetailsProvider);
final userAsync = ref.read(userProvider);
final user = userAsync.value;
if (user == null) throw Exception("User not logged in");

View File

@ -9,90 +9,41 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
/// ------------------------------------------------------------
/// API SERVICE
/// ------------------------------------------------------------
/// Provide a single ApiService instance across the app
final apiServiceProvider = Provider<ApiService>((ref) => ApiService());
/// ------------------------------------------------------------
/// USER REPOSITORY
/// ------------------------------------------------------------
/// Provide repository that depends on ApiService
final userRepositoryProvider = Provider<UserRepositoryImpl>(
(ref) => UserRepositoryImpl(ref.read(apiServiceProvider)),
);
/// ------------------------------------------------------------
/// LOGIN PROVIDER (UNCHANGED NAME)
/// ------------------------------------------------------------
final loginProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo);
});
/// ------------------------------------------------------------
/// SIGNUP PROVIDER (UNCHANGED NAME)
/// ------------------------------------------------------------
final signupProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo);
});
/// ------------------------------------------------------------
/// MAIN USER PROVIDER (UNCHANGED NAME)
/// AUTO LOADS FROM STORAGE
/// ------------------------------------------------------------
/// SINGLE GLOBAL USER PROVIDER (ONLY ONE YOU SHOULD USE)
final userProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider);
final notifier = UserNotifier(repo);
/// AUTO RESTORE SESSION ON APP START
notifier.loadUserFromStorage();
return notifier;
return UserNotifier(ref, repo);
});
/// ------------------------------------------------------------
/// USER DETAILS PROVIDER (UNCHANGED NAME)
/// ------------------------------------------------------------
final userDetailsProvider = FutureProvider<UserModel?>((ref) async {
final userAsync = ref.watch(userProvider);
if (userAsync.isLoading) return null;
final user = userAsync.value;
if (user == null) return null;
final repo = ref.read(userRepositoryProvider);
return await repo.getUserDetails(user.id);
});
/// ------------------------------------------------------------
/// AUTH STATE
/// ------------------------------------------------------------
enum AuthAction { idle, login, signup }
/// ------------------------------------------------------------
/// USER NOTIFIER
/// ------------------------------------------------------------
class UserNotifier extends StateNotifier<AsyncValue<User?>> {
final Ref ref;
final UserRepositoryImpl repository;
final _storage = const FlutterSecureStorage();
static const _userKey = 'logged_in_user';
AuthAction lastAction = AuthAction.idle;
UserNotifier(this.repository) : super(const AsyncValue.data(null));
UserNotifier(this.ref, this.repository)
: super(const AsyncValue.data(null));
/// LOAD USER FROM STORAGE (FIXED)
// AUTO LOGIN ON APP START
Future<void> loadUserFromStorage() async {
try {
final jsonString = await _storage.read(key: _userKey);
if (jsonString == null) {
debugPrint("🟡 No user found in storage");
state = const AsyncValue.data(null);
return;
}
@ -100,40 +51,32 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
final user = UserModel.fromJson(jsonData);
state = AsyncValue.data(user);
debugPrint("✅ USER RESTORED → ID: ${user.id}");
} catch (e) {
debugPrint("❌ Storage restore failed: $e");
state = const AsyncValue.data(null);
}
}
/// LOGIN
// LOGIN FLOW (FULLY SYNCHRONIZED)
Future<void> login(String email, String password) async {
lastAction = AuthAction.login;
state = const AsyncValue.loading();
try {
final user = await repository.login(email, password);
state = const AsyncValue.loading();
if (user is UserModel) {
await _storage.write(key: _userKey, value: user.toRawJson());
debugPrint("✅ USER SAVED → ID: ${user.id}");
}
// 1 Login API (partial user)
final partialUser = await repository.login(email, password);
state = AsyncValue.data(user);
// 2 Fetch FULL user details
final fullUser = await repository.getUserDetails(partialUser.id);
// 3 Store FULL user in secure storage
await _storage.write(key: _userKey, value: fullUser.toRawJson());
// 4 Update provider ONCE with FULL data
state = AsyncValue.data(fullUser);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
/// LOGOUT
Future<void> logout() async {
await _storage.delete(key: _userKey);
state = const AsyncValue.data(null);
}
/// SIGNUP
// SIGNUP
Future<void> signup(
String name,
String email,
@ -141,46 +84,44 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
String phone,
) async {
lastAction = AuthAction.signup;
state = const AsyncValue.loading();
try {
state = const AsyncValue.loading();
// 1 Signup returns USER ENTITY
final user = await repository.signup(name, email, password, phone);
// 2 Convert User UserModel for storage
final userModel = UserModel.fromEntity(user);
// 3 Store safely
await _storage.write(
key: _userKey,
value: userModel.toRawJson(),
);
// 4 Update provider state
state = AsyncValue.data(user);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
/// RESET PASSWORD
Future<void> sendPasswordResetLink(String email) async {
state = const AsyncValue.loading();
// LOGOUT
Future<void> logout() async {
await _storage.delete(key: _userKey);
state = const AsyncValue.data(null);
}
// PASSWORD RESET
Future<void> sendPasswordResetLink(String email) async {
try {
state = const AsyncValue.loading();
await repository.sendPasswordResetLink(email);
state = const AsyncValue.data(null);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
/// FETCH FULL USER DETAILS
Future<void> getUserDetails(String userId) async {
state = const AsyncValue.loading();
try {
final user = await repository.getUserDetails(userId);
await saveUserDetails(user);
state = AsyncValue.data(user);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
/// SAVE FULL DETAILS
Future<void> saveUserDetails(UserModel user) async {
await _storage.write(
key: 'logged_in_user_details',
value: user.toRawJson(),
);
}
}

View File

@ -3,7 +3,6 @@ import 'package:autos/core/theme/app_theme.dart';
import 'package:autos/core/widgets/sso_icon_button.dart';
import 'package:autos/core/widgets/wave_background.dart';
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:autos/presentation/screens/auth/forgot_password_screen.dart';
import 'package:autos/presentation/screens/auth/sign_up_screen.dart';
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
import 'package:flutter/material.dart';
@ -44,13 +43,13 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
@override
Widget build(BuildContext context) {
final userState = ref.watch(loginProvider);
final userState = ref.watch(userProvider);
final size = MediaQuery.of(context).size;
final double verticalSpace = size.height * 0.02;
final double horizontalPadding = size.width * 0.06;
ref.listen(loginProvider, (previous, next) {
ref.listen(userProvider, (previous, next) {
if (next.hasValue && next.value != null) {
_showToast("Login Successful");
Navigator.pushReplacement(
@ -160,7 +159,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
: ElevatedButton(
onPressed: () async {
await ref
.read(loginProvider.notifier)
.read(userProvider.notifier)
.login(
emailController.text.trim(),
passwordController.text.trim(),

View File

@ -1,3 +1,4 @@
import 'package:autos/core/theme/app_theme.dart';
import 'package:autos/core/widgets/wave_background.dart';
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter/material.dart';
@ -29,14 +30,15 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
@override
Widget build(BuildContext context) {
final userState = ref.watch(signupProvider);
/// FIXED: use userProvider instead of signupProvider
final userState = ref.watch(userProvider);
final size = MediaQuery.of(context).size;
final double verticalSpace = size.height * 0.02;
final double horizontalPadding =
size.width * 0.06; // dynamic horizontal padding
final double horizontalPadding = size.width * 0.06;
ref.listen(signupProvider, (previous, next) {
/// FIXED: listen to userProvider
ref.listen(userProvider, (previous, next) {
if (next.hasValue && next.value != null) {
Fluttertoast.showToast(
msg: "Sign up successful! Please log in.",
@ -73,20 +75,18 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: size.width < 500
? size.width
: 450, // better center layout on tablets
maxWidth: size.width < 500 ? size.width : 450,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.07),
/// Logo
Image.asset(
'assets/auth/autos_transp.png',
height: size.height * 0.14,
),
Row(
children: const [
Text(
@ -99,9 +99,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
Spacer(),
],
),
SizedBox(height: verticalSpace),
/// Name field
TextField(
controller: nameController,
decoration: const InputDecoration(
@ -109,9 +109,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.person_outline),
),
),
SizedBox(height: verticalSpace),
/// Email field
TextField(
controller: emailController,
decoration: const InputDecoration(
@ -119,9 +119,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.email_outlined),
),
),
SizedBox(height: verticalSpace),
/// Password field
TextField(
controller: passwordController,
obscureText: !isPasswordVisible,
@ -142,9 +142,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
),
),
),
SizedBox(height: verticalSpace),
/// Phone field
TextField(
controller: phoneController,
keyboardType: TextInputType.phone,
@ -153,24 +153,25 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.phone_outlined),
),
),
SizedBox(height: verticalSpace * 2),
/// Sign Up button
/// FIXED SIGNUP BUTTON
userState.isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () async {
await ref
.read(signupProvider.notifier)
.read(userProvider.notifier)
.signup(
nameController.text,
emailController.text,
passwordController.text,
phoneController.text,
nameController.text.trim(),
emailController.text.trim(),
passwordController.text.trim(),
phoneController.text.trim(),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B81F9),
backgroundColor: AppTheme.primary,
minimumSize: Size(
double.infinity,
size.height * 0.05,
@ -191,7 +192,6 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
SizedBox(height: verticalSpace * 2.5),
/// Already have account text
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -203,13 +203,14 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
child: const Text(
"Sign In",
style: TextStyle(
color: Color(0xFF3B81F9),
color: AppTheme.primary,
fontWeight: FontWeight.w600,
),
),
),
],
),
SizedBox(height: verticalSpace),
],
),

View File

@ -19,7 +19,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
@override
Widget build(BuildContext context) {
final user = ref.watch(userDetailsProvider);
final userAsync = ref.watch(userProvider);
final double topPadding = MediaQuery.of(context).padding.top + 16;
return Scaffold(
@ -32,7 +32,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
),
body: Stack(
children: [
// TOP BAR RESPONSIVE & CENTERED
Positioned(
top: topPadding,
left: 0,
@ -47,31 +46,20 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
],
),
),
// 👇 MAIN CONTENT SECTION
SingleChildScrollView(
padding: EdgeInsets.fromLTRB(
16,
topPadding + 70, // pushes content below title
16,
20,
),
padding: EdgeInsets.fromLTRB(16, topPadding + 70, 16, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// --- Welcome Header ---
const SizedBox(height: 8),
Row(
children: [
Text("👋", style: const TextStyle(fontSize: 32)),
const Text("Hi, 👋", style: TextStyle(fontSize: 32)),
const SizedBox(width: 8),
Text("Hi,", style: AppTypo.h3),
],
),
const SizedBox(height: 6),
Text(
"Manage your Turn14 & eBay integrations effortlessly in one platform.",
style: AppTypo.body.copyWith(
@ -79,50 +67,57 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
height: 1.4,
),
),
const SizedBox(height: 32),
// INFO CARDS RESPONSIVE
InfoCard(
// Info cards
const InfoCard(
emoji: "🎥",
heading: "How to Access Your Store",
content: "Watch the full walkthrough.",
url: "https://youtu.be/g6qV2cQ2Fhw",
),
InfoCard(
const InfoCard(
emoji: "📊",
heading: "About Data4Autos",
content:
"Data4Autos is an advanced automation platform for eCommerce sellers. Connect, manage, and automate your Turn14 and eBay stores effortlessly — all in one place.",
),
InfoCard(
const InfoCard(
emoji: "⚙️",
heading: "About Turn14 Integration",
content:
"Sync your Turn14 account to automatically import products, pricing, and stock updates. Keep your online store current without manual effort.",
),
InfoCard(
const InfoCard(
emoji: "🛒",
heading: "About eBay Integration",
content:
"Simplify eBay listing management, stock updates, and order automation directly from your dashboard.",
),
InfoCard(
const InfoCard(
emoji: "💰",
heading: "Pricing & Plans",
content:
"Explore flexible pricing options for every business size. Upgrade anytime to unlock advanced features.",
),
user.when(
loading: () => const CircularProgressIndicator(),
error: (err, _) => Text("Error: $err"),
// USER DETAILS CARD (FIXED)
userAsync.when(
loading: () => const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
),
error: (err, _) => Padding(
padding: const EdgeInsets.all(16),
child: Text("Error loading user: $err"),
),
data: (user) {
if (user == null) return SizedBox();
if (user == null) {
return const Padding(
padding: EdgeInsets.all(16),
child: Text("No user data available"),
);
}
return UserDetailsCard(
name: user.name,
@ -137,8 +132,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
],
),
),
// 🍔 Common Hamburger Button
HamburgerButton(scaffoldKey: _scaffoldKey),
],
),
@ -146,9 +139,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
}
}
// ------------------------------------------------------------
// REUSABLE INFO CARD
// ------------------------------------------------------------
// ----------------------- INFO CARD -----------------------
class InfoCard extends StatefulWidget {
final String emoji;
final String heading;
@ -202,7 +193,6 @@ class _InfoCardState extends State<InfoCard> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
Text(widget.emoji, style: const TextStyle(fontSize: 26)),
@ -224,8 +214,6 @@ class _InfoCardState extends State<InfoCard> {
style: const TextStyle(fontSize: 15, height: 1.4),
),
const SizedBox(height: 14),
// YouTube Player
if (_controller != null)
!_showPlayer
? GestureDetector(
@ -233,7 +221,6 @@ class _InfoCardState extends State<InfoCard> {
child: Stack(
alignment: Alignment.center,
children: [
// Thumbnail
Container(
height: 180,
decoration: BoxDecoration(
@ -246,7 +233,6 @@ class _InfoCardState extends State<InfoCard> {
),
),
),
// Play button overlay
Container(
decoration: const BoxDecoration(
shape: BoxShape.circle,
@ -276,6 +262,7 @@ class _InfoCardState extends State<InfoCard> {
}
}
// ----------------------- USER DETAILS CARD -----------------------
class UserDetailsCard extends StatelessWidget {
final String name;
final String email;
@ -307,13 +294,14 @@ class UserDetailsCard extends StatelessWidget {
Text("👤", style: TextStyle(fontSize: 26)),
SizedBox(width: 10),
Expanded(
child: Text("User Details", style: AppTypo.sectionHeader),
child: Text(
"User Details",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 12),
_detailRow("Name", name),
_detailRow("Email", email),
_detailRow("Phone", phone),
@ -327,34 +315,33 @@ class UserDetailsCard extends StatelessWidget {
Widget _detailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 6),
child: RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 15,
color: Colors.black87,
height: 1.4,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextSpan(
text: "$label: ",
SizedBox(
width: 60, // FIXED WIDTH FOR LEFT LABEL
child: Text(
"$label:",
style: const TextStyle(
fontSize: 15,
color: Colors.black87,
height: 1.4,
fontWeight: FontWeight.bold,
height: 1.4,
),
),
TextSpan(
text: value,
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 15,
color: Colors.black87,
height: 1.4,
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,295 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart';
import 'package:autos/core/theme/app_typography.dart';
import 'package:autos/presentation/providers/user_provider.dart';
class AccountScreen extends ConsumerStatefulWidget {
const AccountScreen({super.key});
@override
ConsumerState<AccountScreen> createState() => _AccountScreenState();
}
class _AccountScreenState extends ConsumerState<AccountScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = 'account';
@override
Widget build(BuildContext context) {
final double topPadding = MediaQuery.of(context).padding.top + 16;
final userAsync = ref.watch(userProvider); // FIXED
return Scaffold(
key: _scaffoldKey,
drawer: SideMenu(
selected: selected,
onItemSelected: (key) => setState(() => selected = key),
),
backgroundColor: const Color(0xFFF6FDFF),
body: Stack(
children: [
/// PAGE TITLE
Positioned(
top: topPadding,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"My Account",
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
),
],
),
),
/// MAIN CONTENT
SingleChildScrollView(
padding: EdgeInsets.fromLTRB(16, topPadding + 70, 16, 20),
child: Column(
children: [
_subscriptionCard(),
const SizedBox(height: 16),
_billingCard(),
const SizedBox(height: 16),
/// PROFILE CARD (FIXED)
userAsync.when(
loading: () => const Padding(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(),
),
error: (e, _) => _baseCard(
title: "Profile",
child: Text("Error loading user: $e"),
),
data: (user) {
if (user == null) {
return _baseCard(
title: "Profile",
child: const Text("No user data available."),
);
}
return _baseCard(
title: "Profile",
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_row("Full Name", user.name),
_row("Email", user.email),
_row("Phone", user.phoneNumber ?? "-"),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
child: const Text("Update Details"),
),
),
const SizedBox(width: 10),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: const Text("Change Password"),
),
),
],
),
],
),
);
},
),
const SizedBox(height: 28),
_billingHistoryCard(),
const SizedBox(height: 40),
const Divider(thickness: 0.6),
const SizedBox(height: 12),
const Text(
"© 2025. Data4Autos. All rights reserved.",
style: TextStyle(
fontSize: 13,
color: Colors.black45,
fontWeight: FontWeight.w500,
),
),
],
),
),
HamburgerButton(scaffoldKey: _scaffoldKey),
],
),
);
}
// ------------------------------------------------------------
// SUBSCRIPTION CARD
// ------------------------------------------------------------
Widget _subscriptionCard() {
return _baseCard(
title: "Subscription",
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_row("Subscription", "growth_monthly"),
_row("Period", "24 Nov 2025 - 24 Dec 2025"),
const SizedBox(height: 20),
OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
side: const BorderSide(color: Colors.red),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text("Cancel Subscription"),
),
],
),
);
}
// ------------------------------------------------------------
// BILLING CARD
// ------------------------------------------------------------
Widget _billingCard() {
return _baseCard(
title: "Billing",
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_row("Card", "•••• •••• •••• 1325 VISA"),
_row("Expires", "3 / 2028"),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
child: const Text("Update Billing"),
),
),
const SizedBox(width: 10),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: const Text("Add Coupon"),
),
),
],
),
],
),
);
}
// ------------------------------------------------------------
// BILLING HISTORY
// ------------------------------------------------------------
Widget _billingHistoryCard() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Billing History",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_tableRow(
isHeader: true,
cells: ["Invoice", "Date", "Status", "Amount", "PDF"],
),
_tableRow(
cells: ["INV-0001", "24 Nov 2025", "Paid", "\$99.00", "View"],
),
const SizedBox(height: 14),
const Text(
"Showing 1 of 1 entries",
style: TextStyle(color: Colors.black54),
),
],
),
);
}
// ------------------------------------------------------------
// HELPERS
// ------------------------------------------------------------
Widget _baseCard({required String title, required Widget child}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
child,
],
),
);
}
BoxDecoration _cardDecoration() {
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
blurRadius: 12,
color: Colors.black.withOpacity(0.05),
offset: const Offset(0, 6),
),
],
);
}
Widget _row(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(color: Colors.black54)),
Text(value, style: const TextStyle(fontWeight: FontWeight.w600)),
],
),
);
}
Widget _tableRow({bool isHeader = false, required List<String> cells}) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: isHeader ? const Color(0xFFF5F8FA) : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: cells.map((e) {
return Expanded(
child: Text(
e,
style: TextStyle(
fontWeight: isHeader ? FontWeight.bold : FontWeight.normal,
color: e == "Paid" ? Colors.green : Colors.black,
),
),
);
}).toList(),
),
);
}
}

View File

@ -24,7 +24,7 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
@override
Widget build(BuildContext context) {
final asyncUser = ref.watch(userDetailsProvider);
final asyncUser = ref.watch(userProvider);
final turn14State = ref.watch(turn14Provider);
final user = asyncUser.value;