From fe49bdf9afb341fafb1379d0e6cd5f794ecad4c5 Mon Sep 17 00:00:00 2001 From: bala Date: Sun, 30 Nov 2025 01:34:51 +0530 Subject: [PATCH] improved app state management and app theme updated. --- lib/core/routing/app_router.dart | 4 + lib/core/routing/route_paths.dart | 1 + lib/core/theme/app_typography.dart | 3 +- lib/core/widgets/side_menu.dart | 3 +- lib/data/models/user_model.dart | 21 +- .../providers/brand_provider.dart | 2 +- lib/presentation/providers/user_provider.dart | 187 ++++------- .../screens/auth/login_screen.dart | 7 +- .../screens/auth/sign_up_screen.dart | 43 +-- .../screens/dashboard/dashboard_screen.dart | 103 +++--- .../screens/my_account/account_screen.dart | 295 ++++++++++++++++++ .../screens/turn14_screen/turn14_screen.dart | 2 +- 12 files changed, 459 insertions(+), 212 deletions(-) create mode 100644 lib/presentation/screens/my_account/account_screen.dart diff --git a/lib/core/routing/app_router.dart b/lib/core/routing/app_router.dart index 132973a..e95c739 100644 --- a/lib/core/routing/app_router.dart +++ b/lib/core/routing/app_router.dart @@ -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); } diff --git a/lib/core/routing/route_paths.dart b/lib/core/routing/route_paths.dart index 71b4ee5..7bf25ec 100644 --- a/lib/core/routing/route_paths.dart +++ b/lib/core/routing/route_paths.dart @@ -13,4 +13,5 @@ class AppRoutePaths { static const brands = '/brands'; static const products = '/products'; static const pricing = '/pricing'; + static const myAccount = '/myAccount'; } diff --git a/lib/core/theme/app_typography.dart b/lib/core/theme/app_typography.dart index 27c4a99..b0a7c92 100644 --- a/lib/core/theme/app_typography.dart +++ b/lib/core/theme/app_typography.dart @@ -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( diff --git a/lib/core/widgets/side_menu.dart b/lib/core/widgets/side_menu.dart index 0d61205..24b89d5 100644 --- a/lib/core/widgets/side_menu.dart +++ b/lib/core/widgets/side_menu.dart @@ -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 { ), ); } + } diff --git a/lib/data/models/user_model.dart b/lib/data/models/user_model.dart index 8cba21c..575e7fc 100644 --- a/lib/data/models/user_model.dart +++ b/lib/data/models/user_model.dart @@ -14,6 +14,7 @@ class UserModel extends User { super.code, }); + /// ✅ FROM API JSON factory UserModel.fromJson(Map 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 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)); diff --git a/lib/presentation/providers/brand_provider.dart b/lib/presentation/providers/brand_provider.dart index 610d683..868f1f4 100644 --- a/lib/presentation/providers/brand_provider.dart +++ b/lib/presentation/providers/brand_provider.dart @@ -50,7 +50,7 @@ class BrandsNotifier extends StateNotifier>> { /// SAVE SELECTED BRANDS TO SERVER (Bulk Insert) Future saveSelectedBrands(List 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"); diff --git a/lib/presentation/providers/user_provider.dart b/lib/presentation/providers/user_provider.dart index 1946f26..50dcce1 100644 --- a/lib/presentation/providers/user_provider.dart +++ b/lib/presentation/providers/user_provider.dart @@ -9,178 +9,119 @@ 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((ref) => ApiService()); -/// ------------------------------------------------------------ -/// USER REPOSITORY -/// ------------------------------------------------------------ +/// ✅ Provide repository that depends on ApiService final userRepositoryProvider = Provider( (ref) => UserRepositoryImpl(ref.read(apiServiceProvider)), ); -/// ------------------------------------------------------------ -/// LOGIN PROVIDER ✅ (UNCHANGED NAME) -/// ------------------------------------------------------------ -final loginProvider = - StateNotifierProvider>((ref) { - final repo = ref.read(userRepositoryProvider); - return UserNotifier(repo); -}); - -/// ------------------------------------------------------------ -/// SIGNUP PROVIDER ✅ (UNCHANGED NAME) -/// ------------------------------------------------------------ -final signupProvider = - StateNotifierProvider>((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>((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((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> { + 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 loadUserFromStorage() async { - try { - final jsonString = await _storage.read(key: _userKey); + final jsonString = await _storage.read(key: _userKey); - 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"); + if (jsonString == 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 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 + // ✅ ✅ SIGNUP + Future 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 logout() async { await _storage.delete(key: _userKey); state = const AsyncValue.data(null); } - /// ✅ SIGNUP - Future 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 + // ✅ ✅ PASSWORD RESET Future sendPasswordResetLink(String email) async { - state = const AsyncValue.loading(); - 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 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 saveUserDetails(UserModel user) async { - await _storage.write( - key: 'logged_in_user_details', - value: user.toRawJson(), - ); - } } diff --git a/lib/presentation/screens/auth/login_screen.dart b/lib/presentation/screens/auth/login_screen.dart index 2de3ba4..905c453 100644 --- a/lib/presentation/screens/auth/login_screen.dart +++ b/lib/presentation/screens/auth/login_screen.dart @@ -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 { @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 { : ElevatedButton( onPressed: () async { await ref - .read(loginProvider.notifier) + .read(userProvider.notifier) .login( emailController.text.trim(), passwordController.text.trim(), diff --git a/lib/presentation/screens/auth/sign_up_screen.dart b/lib/presentation/screens/auth/sign_up_screen.dart index e7d2bb6..259850e 100644 --- a/lib/presentation/screens/auth/sign_up_screen.dart +++ b/lib/presentation/screens/auth/sign_up_screen.dart @@ -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 { @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 { 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 { Spacer(), ], ), + SizedBox(height: verticalSpace), - /// Name field TextField( controller: nameController, decoration: const InputDecoration( @@ -109,9 +109,9 @@ class _SignUpScreenState extends ConsumerState { prefixIcon: Icon(Icons.person_outline), ), ), + SizedBox(height: verticalSpace), - /// Email field TextField( controller: emailController, decoration: const InputDecoration( @@ -119,9 +119,9 @@ class _SignUpScreenState extends ConsumerState { prefixIcon: Icon(Icons.email_outlined), ), ), + SizedBox(height: verticalSpace), - /// Password field TextField( controller: passwordController, obscureText: !isPasswordVisible, @@ -142,9 +142,9 @@ class _SignUpScreenState extends ConsumerState { ), ), ), + SizedBox(height: verticalSpace), - /// Phone field TextField( controller: phoneController, keyboardType: TextInputType.phone, @@ -153,24 +153,25 @@ class _SignUpScreenState extends ConsumerState { 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 { SizedBox(height: verticalSpace * 2.5), - /// Already have account text Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -203,13 +203,14 @@ class _SignUpScreenState extends ConsumerState { child: const Text( "Sign In", style: TextStyle( - color: Color(0xFF3B81F9), + color: AppTheme.primary, fontWeight: FontWeight.w600, ), ), ), ], ), + SizedBox(height: verticalSpace), ], ), diff --git a/lib/presentation/screens/dashboard/dashboard_screen.dart b/lib/presentation/screens/dashboard/dashboard_screen.dart index d8434b7..c56d021 100644 --- a/lib/presentation/screens/dashboard/dashboard_screen.dart +++ b/lib/presentation/screens/dashboard/dashboard_screen.dart @@ -19,7 +19,7 @@ class _DashboardScreenState extends ConsumerState { @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 { ), body: Stack( children: [ - // TOP BAR — RESPONSIVE & CENTERED Positioned( top: topPadding, left: 0, @@ -47,31 +46,20 @@ class _DashboardScreenState extends ConsumerState { ], ), ), - - // 👇 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 { 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 { ], ), ), - - // 🍔 Common Hamburger Button HamburgerButton(scaffoldKey: _scaffoldKey), ], ), @@ -146,9 +139,7 @@ class _DashboardScreenState extends ConsumerState { } } -// ------------------------------------------------------------ -// ⭐ REUSABLE INFO CARD -// ------------------------------------------------------------ +// ----------------------- INFO CARD ----------------------- class InfoCard extends StatefulWidget { final String emoji; final String heading; @@ -202,7 +193,6 @@ class _InfoCardState extends State { 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 { 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 { child: Stack( alignment: Alignment.center, children: [ - // Thumbnail Container( height: 180, decoration: BoxDecoration( @@ -246,7 +233,6 @@ class _InfoCardState extends State { ), ), ), - // Play button overlay Container( decoration: const BoxDecoration( shape: BoxShape.circle, @@ -276,6 +262,7 @@ class _InfoCardState extends State { } } +// ----------------------- 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,33 +315,32 @@ 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, - ), - children: [ - TextSpan( - text: "$label: ", + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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, ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/presentation/screens/my_account/account_screen.dart b/lib/presentation/screens/my_account/account_screen.dart new file mode 100644 index 0000000..725dee8 --- /dev/null +++ b/lib/presentation/screens/my_account/account_screen.dart @@ -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 createState() => _AccountScreenState(); +} + +class _AccountScreenState extends ConsumerState { + final GlobalKey _scaffoldKey = GlobalKey(); + 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 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(), + ), + ); + } +} diff --git a/lib/presentation/screens/turn14_screen/turn14_screen.dart b/lib/presentation/screens/turn14_screen/turn14_screen.dart index a013373..5a6a7d5 100644 --- a/lib/presentation/screens/turn14_screen/turn14_screen.dart +++ b/lib/presentation/screens/turn14_screen/turn14_screen.dart @@ -24,7 +24,7 @@ class _Turn14ScreenState extends ConsumerState { @override Widget build(BuildContext context) { - final asyncUser = ref.watch(userDetailsProvider); + final asyncUser = ref.watch(userProvider); final turn14State = ref.watch(turn14Provider); final user = asyncUser.value;