improved app state management and app theme updated.
This commit is contained in:
parent
6e9ffdb9dd
commit
fe49bdf9af
@ -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);
|
||||
}
|
||||
|
||||
@ -13,4 +13,5 @@ class AppRoutePaths {
|
||||
static const brands = '/brands';
|
||||
static const products = '/products';
|
||||
static const pricing = '/pricing';
|
||||
static const myAccount = '/myAccount';
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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),
|
||||
],
|
||||
),
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
295
lib/presentation/screens/my_account/account_screen.dart
Normal file
295
lib/presentation/screens/my_account/account_screen.dart
Normal 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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user