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/brands/brands_screen.dart';
import 'package:autos/presentation/screens/dashboard/dashboard_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/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/pricing/pricing_screen.dart';
import 'package:autos/presentation/screens/products/products_screen.dart'; import 'package:autos/presentation/screens/products/products_screen.dart';
import 'package:autos/presentation/screens/store/create_location_screen.dart'; import 'package:autos/presentation/screens/store/create_location_screen.dart';
@ -69,6 +70,9 @@ class AppRouter {
case AppRoutePaths.pricing: case AppRoutePaths.pricing:
return slideRoute(PricingScreen()); return slideRoute(PricingScreen());
case AppRoutePaths.myAccount:
return slideRoute(AccountScreen());
default: default:
return _defaultFallback(settings); return _defaultFallback(settings);
} }

View File

@ -13,4 +13,5 @@ class AppRoutePaths {
static const brands = '/brands'; static const brands = '/brands';
static const products = '/products'; static const products = '/products';
static const pricing = '/pricing'; 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'; import 'package:flutter/material.dart';
class AppTypo { class AppTypo {
@ -50,7 +51,7 @@ class AppTypo {
fontFamily: fontFamily, fontFamily: fontFamily,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Color(0xFF2272f6), color: AppTheme.primary,
); );
static const TextStyle menu = TextStyle( static const TextStyle menu = TextStyle(

View File

@ -70,7 +70,7 @@ class SideMenu extends ConsumerWidget {
// --- ACCOUNT --- // --- ACCOUNT ---
_sectionHeader("ACCOUNT"), _sectionHeader("ACCOUNT"),
_menuItem(context, "👤", "My Account", AppRoutePaths.auth), _menuItem(context, "👤", "My Account", AppRoutePaths.myAccount),
_menuItem(context, "💰", "Pricing Plan", AppRoutePaths.pricing), _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, super.code,
}); });
/// FROM API JSON
factory UserModel.fromJson(Map<String, dynamic> json) { factory UserModel.fromJson(Map<String, dynamic> json) {
final payment = json['payment'] ?? {}; final payment = json['payment'] ?? {};
return UserModel( 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() { Map<String, dynamic> toJson() {
return { return {
'userid': id, '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()); String toRawJson() => jsonEncode(toJson());
/// Parse user from raw JSON string /// RESTORE USER FROM RAW JSON STRING
factory UserModel.fromRawJson(String raw) => factory UserModel.fromRawJson(String raw) =>
UserModel.fromJson(jsonDecode(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) /// SAVE SELECTED BRANDS TO SERVER (Bulk Insert)
Future<void> saveSelectedBrands(List<BrandEntity> selectedBrands) async { Future<void> saveSelectedBrands(List<BrandEntity> selectedBrands) async {
try { try {
final userAsync = ref.read(userDetailsProvider); final userAsync = ref.read(userProvider);
final user = userAsync.value; final user = userAsync.value;
if (user == null) throw Exception("User not logged in"); if (user == null) throw Exception("User not logged in");

View File

@ -9,178 +9,119 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart'; import 'package:flutter_riverpod/legacy.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
/// ------------------------------------------------------------ /// Provide a single ApiService instance across the app
/// API SERVICE
/// ------------------------------------------------------------
final apiServiceProvider = Provider<ApiService>((ref) => ApiService()); final apiServiceProvider = Provider<ApiService>((ref) => ApiService());
/// ------------------------------------------------------------ /// Provide repository that depends on ApiService
/// USER REPOSITORY
/// ------------------------------------------------------------
final userRepositoryProvider = Provider<UserRepositoryImpl>( final userRepositoryProvider = Provider<UserRepositoryImpl>(
(ref) => UserRepositoryImpl(ref.read(apiServiceProvider)), (ref) => UserRepositoryImpl(ref.read(apiServiceProvider)),
); );
/// ------------------------------------------------------------ /// SINGLE GLOBAL USER PROVIDER (ONLY ONE YOU SHOULD USE)
/// 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
/// ------------------------------------------------------------
final userProvider = final userProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) { StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider); final repo = ref.read(userRepositoryProvider);
final notifier = UserNotifier(repo); return UserNotifier(ref, repo);
/// AUTO RESTORE SESSION ON APP START
notifier.loadUserFromStorage();
return notifier;
}); });
/// ------------------------------------------------------------
/// 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 } enum AuthAction { idle, login, signup }
/// ------------------------------------------------------------
/// USER NOTIFIER
/// ------------------------------------------------------------
class UserNotifier extends StateNotifier<AsyncValue<User?>> { class UserNotifier extends StateNotifier<AsyncValue<User?>> {
final Ref ref;
final UserRepositoryImpl repository; final UserRepositoryImpl repository;
final _storage = const FlutterSecureStorage(); final _storage = const FlutterSecureStorage();
static const _userKey = 'logged_in_user'; static const _userKey = 'logged_in_user';
AuthAction lastAction = AuthAction.idle; 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 { Future<void> loadUserFromStorage() async {
try { final jsonString = await _storage.read(key: _userKey);
final jsonString = await _storage.read(key: _userKey);
if (jsonString == null) { if (jsonString == null) {
debugPrint("🟡 No user found in storage");
return;
}
final jsonData = jsonDecode(jsonString);
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); state = const AsyncValue.data(null);
return;
} }
final jsonData = jsonDecode(jsonString);
final user = UserModel.fromJson(jsonData);
state = AsyncValue.data(user);
} }
/// LOGIN // LOGIN FLOW (FULLY SYNCHRONIZED)
Future<void> login(String email, String password) async { Future<void> login(String email, String password) async {
lastAction = AuthAction.login; lastAction = AuthAction.login;
state = const AsyncValue.loading();
try { try {
final user = await repository.login(email, password); state = const AsyncValue.loading();
if (user is UserModel) { // 1 Login API (partial user)
await _storage.write(key: _userKey, value: user.toRawJson()); final partialUser = await repository.login(email, password);
debugPrint("✅ USER SAVED → ID: ${user.id}");
}
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) { } catch (e, st) {
state = AsyncValue.error(e, st); state = AsyncValue.error(e, st);
} }
} }
/// LOGOUT // SIGNUP
Future<void> signup(
String name,
String email,
String password,
String phone,
) async {
lastAction = AuthAction.signup;
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);
}
}
// LOGOUT
Future<void> logout() async { Future<void> logout() async {
await _storage.delete(key: _userKey); await _storage.delete(key: _userKey);
state = const AsyncValue.data(null); state = const AsyncValue.data(null);
} }
/// SIGNUP // PASSWORD RESET
Future<void> signup(
String name,
String email,
String password,
String phone,
) async {
lastAction = AuthAction.signup;
state = const AsyncValue.loading();
try {
final user = await repository.signup(name, email, password, phone);
state = AsyncValue.data(user);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
/// RESET PASSWORD
Future<void> sendPasswordResetLink(String email) async { Future<void> sendPasswordResetLink(String email) async {
state = const AsyncValue.loading();
try { try {
state = const AsyncValue.loading();
await repository.sendPasswordResetLink(email); await repository.sendPasswordResetLink(email);
state = const AsyncValue.data(null); state = const AsyncValue.data(null);
} catch (e, st) { } catch (e, st) {
state = AsyncValue.error(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/sso_icon_button.dart';
import 'package:autos/core/widgets/wave_background.dart'; import 'package:autos/core/widgets/wave_background.dart';
import 'package:autos/presentation/providers/user_provider.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/auth/sign_up_screen.dart';
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart'; import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -44,13 +43,13 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final userState = ref.watch(loginProvider); final userState = ref.watch(userProvider);
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
final double verticalSpace = size.height * 0.02; final double verticalSpace = size.height * 0.02;
final double horizontalPadding = size.width * 0.06; final double horizontalPadding = size.width * 0.06;
ref.listen(loginProvider, (previous, next) { ref.listen(userProvider, (previous, next) {
if (next.hasValue && next.value != null) { if (next.hasValue && next.value != null) {
_showToast("Login Successful"); _showToast("Login Successful");
Navigator.pushReplacement( Navigator.pushReplacement(
@ -160,7 +159,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
: ElevatedButton( : ElevatedButton(
onPressed: () async { onPressed: () async {
await ref await ref
.read(loginProvider.notifier) .read(userProvider.notifier)
.login( .login(
emailController.text.trim(), emailController.text.trim(),
passwordController.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/core/widgets/wave_background.dart';
import 'package:autos/presentation/providers/user_provider.dart'; import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -29,14 +30,15 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
@override @override
Widget build(BuildContext context) { 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 size = MediaQuery.of(context).size;
final double verticalSpace = size.height * 0.02; final double verticalSpace = size.height * 0.02;
final double horizontalPadding = final double horizontalPadding = size.width * 0.06;
size.width * 0.06; // dynamic horizontal padding
ref.listen(signupProvider, (previous, next) { /// FIXED: listen to userProvider
ref.listen(userProvider, (previous, next) {
if (next.hasValue && next.value != null) { if (next.hasValue && next.value != null) {
Fluttertoast.showToast( Fluttertoast.showToast(
msg: "Sign up successful! Please log in.", msg: "Sign up successful! Please log in.",
@ -73,20 +75,18 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
child: SingleChildScrollView( child: SingleChildScrollView(
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: size.width < 500 maxWidth: size.width < 500 ? size.width : 450,
? size.width
: 450, // better center layout on tablets
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
SizedBox(height: size.height * 0.07), SizedBox(height: size.height * 0.07),
/// Logo
Image.asset( Image.asset(
'assets/auth/autos_transp.png', 'assets/auth/autos_transp.png',
height: size.height * 0.14, height: size.height * 0.14,
), ),
Row( Row(
children: const [ children: const [
Text( Text(
@ -99,9 +99,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
Spacer(), Spacer(),
], ],
), ),
SizedBox(height: verticalSpace), SizedBox(height: verticalSpace),
/// Name field
TextField( TextField(
controller: nameController, controller: nameController,
decoration: const InputDecoration( decoration: const InputDecoration(
@ -109,9 +109,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.person_outline), prefixIcon: Icon(Icons.person_outline),
), ),
), ),
SizedBox(height: verticalSpace), SizedBox(height: verticalSpace),
/// Email field
TextField( TextField(
controller: emailController, controller: emailController,
decoration: const InputDecoration( decoration: const InputDecoration(
@ -119,9 +119,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.email_outlined), prefixIcon: Icon(Icons.email_outlined),
), ),
), ),
SizedBox(height: verticalSpace), SizedBox(height: verticalSpace),
/// Password field
TextField( TextField(
controller: passwordController, controller: passwordController,
obscureText: !isPasswordVisible, obscureText: !isPasswordVisible,
@ -142,9 +142,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
), ),
), ),
), ),
SizedBox(height: verticalSpace), SizedBox(height: verticalSpace),
/// Phone field
TextField( TextField(
controller: phoneController, controller: phoneController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
@ -153,24 +153,25 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.phone_outlined), prefixIcon: Icon(Icons.phone_outlined),
), ),
), ),
SizedBox(height: verticalSpace * 2), SizedBox(height: verticalSpace * 2),
/// Sign Up button /// FIXED SIGNUP BUTTON
userState.isLoading userState.isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: ElevatedButton( : ElevatedButton(
onPressed: () async { onPressed: () async {
await ref await ref
.read(signupProvider.notifier) .read(userProvider.notifier)
.signup( .signup(
nameController.text, nameController.text.trim(),
emailController.text, emailController.text.trim(),
passwordController.text, passwordController.text.trim(),
phoneController.text, phoneController.text.trim(),
); );
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B81F9), backgroundColor: AppTheme.primary,
minimumSize: Size( minimumSize: Size(
double.infinity, double.infinity,
size.height * 0.05, size.height * 0.05,
@ -191,7 +192,6 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
SizedBox(height: verticalSpace * 2.5), SizedBox(height: verticalSpace * 2.5),
/// Already have account text
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -203,13 +203,14 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
child: const Text( child: const Text(
"Sign In", "Sign In",
style: TextStyle( style: TextStyle(
color: Color(0xFF3B81F9), color: AppTheme.primary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
), ),
], ],
), ),
SizedBox(height: verticalSpace), SizedBox(height: verticalSpace),
], ],
), ),

View File

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