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/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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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 {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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));
|
||||||
|
|
||||||
|
|||||||
@ -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");
|
||||||
|
|||||||
@ -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(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
@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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user