Pricing page implemented.
This commit is contained in:
parent
a978699dfc
commit
6e9ffdb9dd
@ -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/pricing/pricing_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/store.dart';
|
||||
@ -65,6 +66,9 @@ class AppRouter {
|
||||
case AppRoutePaths.products:
|
||||
return slideRoute(ProductsScreen());
|
||||
|
||||
case AppRoutePaths.pricing:
|
||||
return slideRoute(PricingScreen());
|
||||
|
||||
default:
|
||||
return _defaultFallback(settings);
|
||||
}
|
||||
|
||||
@ -12,4 +12,5 @@ class AppRoutePaths {
|
||||
static const createStoreLocation = '/createStoreLocation';
|
||||
static const brands = '/brands';
|
||||
static const products = '/products';
|
||||
static const pricing = '/pricing';
|
||||
}
|
||||
|
||||
@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTheme {
|
||||
// ✅ MAIN BRAND COLORS
|
||||
static const Color primary = Color(0xFF00BFFF); // Main Blue
|
||||
static const Color lightBlue = Color(0xFFE8F7FF); // Sidebar highlight
|
||||
static const Color background = Color(0xFFF4FBFE); // App background
|
||||
static const Color textDark = Color(0xFF1C1C1C);
|
||||
static const Color textGrey = Color(0xFF6F6F6F);
|
||||
static const Color cardBorder = Color(0xFFAEE9FF);
|
||||
|
||||
// ✅ GLOBAL APP THEME
|
||||
static ThemeData lightTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
|
||||
scaffoldBackgroundColor: background,
|
||||
|
||||
primaryColor: primary,
|
||||
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primary,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
|
||||
fontFamily: 'Poppins',
|
||||
|
||||
// ✅ TEXT STYLES
|
||||
textTheme: const TextTheme(
|
||||
headlineLarge: TextStyle(
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textDark,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textDark,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textDark,
|
||||
),
|
||||
bodyLarge: TextStyle(fontSize: 16, color: textDark),
|
||||
bodyMedium: TextStyle(fontSize: 14, color: textGrey),
|
||||
),
|
||||
|
||||
// ✅ ELEVATED BUTTON THEME (SUBSCRIBE BUTTONS)
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primary,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 6,
|
||||
shadowColor: Colors.black.withOpacity(0.2),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
||||
// ✅ OUTLINED BUTTON THEME (UNSELECTED BUTTONS)
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: primary,
|
||||
side: const BorderSide(color: primary),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
),
|
||||
|
||||
// ✅ INPUT FIELD THEME
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: cardBorder),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: cardBorder),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: primary, width: 2),
|
||||
),
|
||||
),
|
||||
|
||||
// ✅ CARD THEME (PRICING BOXES)
|
||||
cardTheme: CardThemeData(
|
||||
color: Colors.white,
|
||||
elevation: 12,
|
||||
shadowColor: Colors.black.withValues(alpha: 0.06),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(26)),
|
||||
),
|
||||
|
||||
// ✅ APP BAR THEME
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
iconTheme: IconThemeData(color: Colors.white),
|
||||
titleTextStyle: TextStyle(
|
||||
color: textDark,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
// ✅ DIVIDER THEME
|
||||
dividerTheme: const DividerThemeData(color: cardBorder, thickness: 1),
|
||||
|
||||
// ✅ ICON THEME
|
||||
iconTheme: const IconThemeData(color: primary),
|
||||
);
|
||||
}
|
||||
@ -13,15 +13,16 @@ class AppTypo {
|
||||
);
|
||||
|
||||
static const TextStyle h2 = TextStyle(
|
||||
color: Color(0xFF3B81F9),
|
||||
color: Color(0xFF00BFFF),
|
||||
fontFamily: fontFamily,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w700,
|
||||
);
|
||||
|
||||
static const TextStyle h3 = TextStyle(
|
||||
color: Colors.white,
|
||||
fontFamily: fontFamily,
|
||||
fontSize: 24,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ class HamburgerButton extends StatefulWidget {
|
||||
super.key,
|
||||
required this.scaffoldKey,
|
||||
this.backgroundColor = const Color(0xFFEAF1FF), // light background
|
||||
this.iconColor = const Color(0xFF3B81F9), // dark blue lines
|
||||
this.iconColor = const Color(0xFF00BFFF), // dark blue lines
|
||||
});
|
||||
|
||||
@override
|
||||
|
||||
@ -22,7 +22,7 @@ class SideMenu extends ConsumerWidget {
|
||||
children: [
|
||||
// Header
|
||||
DrawerHeader(
|
||||
decoration: BoxDecoration(color: Color(0xFF3B81F9)),
|
||||
decoration: BoxDecoration(color: Color(0xFF00BFFF)),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
@ -71,7 +71,7 @@ class SideMenu extends ConsumerWidget {
|
||||
// --- ACCOUNT ---
|
||||
_sectionHeader("ACCOUNT"),
|
||||
_menuItem(context, "👤", "My Account", AppRoutePaths.auth),
|
||||
_menuItem(context, "💰", "Pricing Plan", 'pricing'),
|
||||
_menuItem(context, "💰", "Pricing Plan", AppRoutePaths.pricing),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -123,7 +123,7 @@ class SideMenu extends ConsumerWidget {
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 15,
|
||||
letterSpacing: 0.5,
|
||||
color: Color(0xFF3B81F9),
|
||||
color: Color(0xFF00BFFF),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:autos/core/theme/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WaveBackground extends StatelessWidget {
|
||||
@ -19,10 +20,7 @@ class WaveBackground extends StatelessWidget {
|
||||
height: 220,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Color(0xFF3B81F9), // blue
|
||||
Color(0xFF5A96F9), // lighter blue
|
||||
],
|
||||
colors: [AppTheme.primary, AppTheme.primary],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
@ -31,31 +29,6 @@ class WaveBackground extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
|
||||
// Positioned(
|
||||
// top: 50,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// child: Row(
|
||||
// children: [
|
||||
// Image.asset(
|
||||
// 'assets/auth/ebay.png',
|
||||
// height: 40,
|
||||
// fit: BoxFit.contain,
|
||||
// ),
|
||||
// // Image.asset(
|
||||
// // 'assets/auth/data.png',
|
||||
// // height: 40,
|
||||
// // fit: BoxFit.contain,
|
||||
// // ),
|
||||
// // Image.asset(
|
||||
// // 'assets/auth/ebay.png',
|
||||
// // height: 40,
|
||||
// // fit: BoxFit.contain,
|
||||
// // ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// ✅ Optional bottom light curve (subtle, not dominant)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
@ -65,7 +38,7 @@ class WaveBackground extends StatelessWidget {
|
||||
clipper: BottomSoftWaveClipper(),
|
||||
child: Container(
|
||||
height: 80,
|
||||
color: const Color(0xFF5A96F9),
|
||||
color: AppTheme.primary,
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
import 'package:autos/domain/entities/product.dart';
|
||||
|
||||
class ProductModel extends ProductEntity {
|
||||
@ -12,10 +10,18 @@ class ProductModel extends ProductEntity {
|
||||
|
||||
factory ProductModel.fromJson(Map<String, dynamic> json) {
|
||||
return ProductModel(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
image: json['image'],
|
||||
price: (json['price'] as num).toDouble(),
|
||||
id: json['id'] ?? 0,
|
||||
|
||||
/// ✅ SAFE STRING CONVERSION
|
||||
name: json['name']?.toString() ?? '',
|
||||
|
||||
/// ✅ API sends `logo`, but entity expects `image`
|
||||
image: json['logo']?.toString() ?? '',
|
||||
|
||||
/// ✅ API DOES NOT SEND PRICE → DEFAULT TO 0
|
||||
price: json['price'] != null
|
||||
? double.tryParse(json['price'].toString()) ?? 0.0
|
||||
: 0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,8 @@ class BrandsRepositoryImpl implements BrandsRepository {
|
||||
await _storage.write(key: 'turn14_token', value: token);
|
||||
} else if (data["code"] == "TOKEN_VALID") {
|
||||
// ✅ Token still valid, read from storage
|
||||
token = data["access_token"];
|
||||
await _storage.write(key: 'turn14_token', value: token);
|
||||
token = await _storage.read(key: 'turn14_token') ?? '';
|
||||
if (token.isEmpty) {
|
||||
throw Exception("Token missing in storage");
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:autos/core/routing/app_router.dart';
|
||||
import 'package:autos/core/routing/navigation_service.dart';
|
||||
import 'package:autos/core/theme/app_theme.dart';
|
||||
import 'package:autos/presentation/providers/user_provider.dart';
|
||||
import 'package:autos/presentation/screens/auth/login_screen.dart';
|
||||
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
|
||||
@ -52,9 +53,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: "Autos",
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
),
|
||||
theme: AppTheme.lightTheme,
|
||||
|
||||
// Dynamic home based on session
|
||||
home: user != null ? const DashboardScreen() : const LoginScreen(),
|
||||
|
||||
@ -22,22 +22,30 @@ class BrandsNotifier extends StateNotifier<AsyncValue<List<BrandEntity>>> {
|
||||
BrandsNotifier(this.repository, this.ref) : super(const AsyncValue.loading());
|
||||
|
||||
/// Fetch brands every time page opens
|
||||
Future<void> fetchBrands() async {
|
||||
state = const AsyncValue.loading();
|
||||
try {
|
||||
final userAsync = ref.read(userDetailsProvider);
|
||||
final user = userAsync.value;
|
||||
Future<void> fetchBrands() async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
if (user == null) throw Exception("User not logged in");
|
||||
try {
|
||||
/// READ USER DIRECTLY FROM userProvider (NOT userDetailsProvider)
|
||||
final userAsync = ref.read(userProvider);
|
||||
|
||||
final brands = await repository.getBrands();
|
||||
final user = userAsync.value;
|
||||
|
||||
state = AsyncValue.data(brands);
|
||||
} catch (e, st) {
|
||||
state = AsyncValue.error(e, st);
|
||||
debugPrint("Brands fetch error: $e");
|
||||
if (user == null || user.id.isEmpty) {
|
||||
throw Exception("User id is empty");
|
||||
}
|
||||
|
||||
debugPrint("✅ FETCHING BRANDS FOR USER ID: ${user.id}");
|
||||
|
||||
final brands = await repository.getBrands();
|
||||
|
||||
state = AsyncValue.data(brands);
|
||||
} catch (e, st) {
|
||||
state = AsyncValue.error(e, st);
|
||||
debugPrint("❌ Brands fetch error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// SAVE SELECTED BRANDS TO SERVER (Bulk Insert)
|
||||
Future<void> saveSelectedBrands(List<BrandEntity> selectedBrands) async {
|
||||
|
||||
@ -9,95 +9,131 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/legacy.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());
|
||||
|
||||
// Provide repository that depends on ApiService
|
||||
/// ------------------------------------------------------------
|
||||
/// USER REPOSITORY
|
||||
/// ------------------------------------------------------------
|
||||
final userRepositoryProvider = Provider<UserRepositoryImpl>(
|
||||
(ref) => UserRepositoryImpl(ref.read(apiServiceProvider)),
|
||||
);
|
||||
|
||||
// Manage user state
|
||||
final loginProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((
|
||||
ref,
|
||||
) {
|
||||
/// ------------------------------------------------------------
|
||||
/// LOGIN PROVIDER ✅ (UNCHANGED NAME)
|
||||
/// ------------------------------------------------------------
|
||||
final loginProvider =
|
||||
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
|
||||
final repo = ref.read(userRepositoryProvider);
|
||||
return UserNotifier(repo);
|
||||
});
|
||||
|
||||
final signupProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((
|
||||
ref,
|
||||
) {
|
||||
/// ------------------------------------------------------------
|
||||
/// SIGNUP PROVIDER ✅ (UNCHANGED NAME)
|
||||
/// ------------------------------------------------------------
|
||||
final signupProvider =
|
||||
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
|
||||
final repo = ref.read(userRepositoryProvider);
|
||||
return UserNotifier(repo);
|
||||
});
|
||||
|
||||
final userProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((
|
||||
ref,
|
||||
) {
|
||||
/// ------------------------------------------------------------
|
||||
/// MAIN USER PROVIDER ✅ (UNCHANGED NAME)
|
||||
/// AUTO LOADS FROM STORAGE
|
||||
/// ------------------------------------------------------------
|
||||
final userProvider =
|
||||
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
|
||||
final repo = ref.read(userRepositoryProvider);
|
||||
return UserNotifier(repo);
|
||||
final notifier = UserNotifier(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);
|
||||
|
||||
// Waiting for login provider to finish
|
||||
if (userAsync.isLoading) return null;
|
||||
|
||||
final user = userAsync.value;
|
||||
if (user == null) return null;
|
||||
|
||||
// Fetch Full Details
|
||||
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 UserRepositoryImpl repository;
|
||||
final _storage = const FlutterSecureStorage();
|
||||
static const _userKey = 'logged_in_user';
|
||||
|
||||
AuthAction lastAction = AuthAction.idle;
|
||||
|
||||
UserNotifier(this.repository) : super(const AsyncValue.data(null));
|
||||
|
||||
///Load saved user from storage (auto-login)
|
||||
/// ✅ LOAD USER FROM STORAGE (FIXED)
|
||||
Future<void> loadUserFromStorage() async {
|
||||
final jsonString = await _storage.read(key: _userKey);
|
||||
if (jsonString != null) {
|
||||
debugPrint("RESULT: $jsonString");
|
||||
try {
|
||||
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);
|
||||
await Future.microtask(() {});
|
||||
|
||||
state = AsyncValue.data(user);
|
||||
|
||||
debugPrint("✅ USER RESTORED → ID: ${user.id}");
|
||||
} catch (e) {
|
||||
debugPrint("❌ Storage restore failed: $e");
|
||||
state = const AsyncValue.data(null);
|
||||
}
|
||||
}
|
||||
|
||||
///Login
|
||||
/// ✅ LOGIN
|
||||
Future<void> login(String email, String password) async {
|
||||
lastAction = AuthAction.login;
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
final user = await repository.login(email, password);
|
||||
// ✅ Save user to secure storage
|
||||
|
||||
if (user is UserModel) {
|
||||
await _storage.write(key: _userKey, value: user.toRawJson());
|
||||
debugPrint("✅ USER SAVED → ID: ${user.id}");
|
||||
}
|
||||
|
||||
state = AsyncValue.data(user);
|
||||
} catch (e, st) {
|
||||
state = AsyncValue.error(e, st);
|
||||
}
|
||||
}
|
||||
|
||||
/// Logout
|
||||
/// ✅ LOGOUT
|
||||
Future<void> logout() async {
|
||||
await _storage.delete(key: _userKey);
|
||||
state = const AsyncValue.data(null);
|
||||
}
|
||||
|
||||
///Sign up
|
||||
/// ✅ SIGNUP
|
||||
Future<void> signup(
|
||||
String name,
|
||||
String email,
|
||||
@ -106,6 +142,7 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
|
||||
) async {
|
||||
lastAction = AuthAction.signup;
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
final user = await repository.signup(name, email, password, phone);
|
||||
state = AsyncValue.data(user);
|
||||
@ -114,9 +151,10 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
|
||||
}
|
||||
}
|
||||
|
||||
///Reset password
|
||||
/// ✅ RESET PASSWORD
|
||||
Future<void> sendPasswordResetLink(String email) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
await repository.sendPasswordResetLink(email);
|
||||
state = const AsyncValue.data(null);
|
||||
@ -125,23 +163,20 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch user details from backend
|
||||
/// ✅ FETCH FULL USER DETAILS
|
||||
Future<void> getUserDetails(String userId) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
final user = await repository.getUserDetails(userId);
|
||||
|
||||
// Save full details separately
|
||||
await saveUserDetails(user);
|
||||
|
||||
state = AsyncValue.data(user);
|
||||
} catch (e, st) {
|
||||
state = AsyncValue.error(e, st);
|
||||
}
|
||||
}
|
||||
|
||||
/// Save full user details separately
|
||||
/// ✅ SAVE FULL DETAILS
|
||||
Future<void> saveUserDetails(UserModel user) async {
|
||||
await _storage.write(
|
||||
key: 'logged_in_user_details',
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:autos/core/theme/app_theme.dart';
|
||||
import 'package:autos/core/theme/app_typography.dart';
|
||||
import 'package:autos/data/models/user_model.dart';
|
||||
import 'package:autos/presentation/providers/user_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -10,7 +12,8 @@ class ForgotPasswordScreen extends ConsumerStatefulWidget {
|
||||
const ForgotPasswordScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ForgotPasswordScreen> createState() => _ForgotPasswordScreenState();
|
||||
ConsumerState<ForgotPasswordScreen> createState() =>
|
||||
_ForgotPasswordScreenState();
|
||||
}
|
||||
|
||||
class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
|
||||
@ -51,8 +54,9 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Forgot Password"),
|
||||
backgroundColor: const Color(0xFF3B81F9),
|
||||
title: Text("Forgot Password", style: AppTypo.h3),
|
||||
backgroundColor: AppTheme.primary,
|
||||
iconTheme: Theme.of(context).appBarTheme.iconTheme,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
@ -62,10 +66,7 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
|
||||
children: [
|
||||
const Text(
|
||||
"Reset your password",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
@ -104,7 +105,7 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
|
||||
Fluttertoast.showToast(
|
||||
msg: "Reset link sent to $email",
|
||||
backgroundColor: Colors.green,
|
||||
toastLength: Toast.LENGTH_LONG
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
@ -115,7 +116,7 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF3B81F9),
|
||||
backgroundColor: AppTheme.primary,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'package:autos/core/routing/route_paths.dart';
|
||||
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';
|
||||
@ -137,16 +139,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const ForgotPasswordScreen(),
|
||||
),
|
||||
AppRoutePaths.forgotPassword,
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
child: Text(
|
||||
"Forgot Password?",
|
||||
style: TextStyle(color: Color(0xFF3B81F9)),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -167,7 +167,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF3B81F9),
|
||||
backgroundColor: AppTheme.primary,
|
||||
minimumSize: Size(
|
||||
double.infinity,
|
||||
size.height * 0.05,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:autos/core/theme/app_typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:autos/core/widgets/hamburger_button.dart';
|
||||
import 'package:autos/core/widgets/side_menu.dart';
|
||||
@ -37,10 +38,7 @@ class _EbayScreenState extends State<EbayScreen> {
|
||||
child: Center(
|
||||
child: Text(
|
||||
"eBay Settings",
|
||||
style: const TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
323
lib/presentation/screens/pricing/pricing_screen.dart
Normal file
323
lib/presentation/screens/pricing/pricing_screen.dart
Normal file
@ -0,0 +1,323 @@
|
||||
import 'package:autos/core/widgets/hamburger_button.dart';
|
||||
import 'package:autos/core/widgets/side_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:autos/core/theme/app_typography.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class PricingScreen extends ConsumerStatefulWidget {
|
||||
const PricingScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<PricingScreen> createState() => _PricingScreenState();
|
||||
}
|
||||
|
||||
class _PricingScreenState extends ConsumerState<PricingScreen> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
String selected = 'pricing';
|
||||
|
||||
bool isMonthly = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double topPadding = MediaQuery.of(context).padding.top + 16;
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: SideMenu(
|
||||
selected: selected,
|
||||
onItemSelected: (key) {
|
||||
setState(() => selected = key);
|
||||
},
|
||||
),
|
||||
backgroundColor: const Color(0xFFF6FDFF),
|
||||
body: Stack(
|
||||
children: [
|
||||
// Top centered title (same style as your dashboard)
|
||||
Positioned(
|
||||
top: topPadding,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Choose Your Plan",
|
||||
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Main content
|
||||
SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(16, topPadding + 70, 16, 20),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
"🚀 Subscribe to a plan and start automating your eBay listings instantly.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 15, color: Colors.black54),
|
||||
),
|
||||
|
||||
const SizedBox(height: 22),
|
||||
|
||||
// Monthly / Yearly toggle
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
border: Border.all(color: const Color(0xFF00BFFF)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_toggleButton("Monthly", isMonthly, () {
|
||||
setState(() => isMonthly = true);
|
||||
}),
|
||||
_toggleButton("Yearly (Save 15%)", !isMonthly, () {
|
||||
setState(() => isMonthly = false);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Pricing cards
|
||||
Column(
|
||||
children: [
|
||||
_pricingCard(
|
||||
title: "Starter Sync",
|
||||
subtitle: "Upload up to 100 products per month",
|
||||
price: isMonthly ? "\$49" : "\$499",
|
||||
features: const [
|
||||
"Auto price & inventory updates",
|
||||
"Daily sync",
|
||||
"Manual sync option",
|
||||
"Basic reporting dashboard",
|
||||
],
|
||||
isPopular: false,
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
_pricingCard(
|
||||
title: "Growth Sync",
|
||||
subtitle: "Upload up to 250 products per month",
|
||||
price: isMonthly ? "\$99" : "\$999",
|
||||
features: const [
|
||||
"Everything in Starter",
|
||||
"3-hour sync interval",
|
||||
"Bulk product import",
|
||||
"Priority email support",
|
||||
],
|
||||
isPopular: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
_pricingCard(
|
||||
title: "Pro Sync",
|
||||
subtitle: "Upload up to 1000 products per month",
|
||||
price: isMonthly ? "\$249" : "\$2499",
|
||||
features: const [
|
||||
"Everything in Growth",
|
||||
"Real-time sync",
|
||||
"Advanced analytics dashboard",
|
||||
"Dedicated account manager",
|
||||
"API access",
|
||||
],
|
||||
isPopular: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
const Divider(thickness: 0.6),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const Text(
|
||||
"© 2025. Data4Autos. All rights reserved.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.black45,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Hamburger button (keeps same behavior as other screens)
|
||||
HamburgerButton(scaffoldKey: _scaffoldKey),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Toggle button
|
||||
Widget _toggleButton(String text, bool active, VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: active ? const Color(0xFF00BFFF) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: active ? Colors.white : const Color(0xFF00BFFF),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Pricing card widget
|
||||
Widget _pricingCard({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required String price,
|
||||
required List<String> features,
|
||||
required bool isPopular,
|
||||
}) {
|
||||
return Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isPopular ? const Color(0xFF00BFFF) : Colors.transparent,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 12,
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
Text(
|
||||
subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.black54),
|
||||
),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: price,
|
||||
style: const TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: " / month",
|
||||
style: TextStyle(color: Colors.black54),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Column(
|
||||
children: features
|
||||
.map(
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.check_circle,
|
||||
color: Color(0xFF00BFFF),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(e)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
|
||||
const SizedBox(height: 25),
|
||||
|
||||
// Subscribe button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isPopular
|
||||
? const Color(0xFF00BFFF)
|
||||
: Colors.white,
|
||||
foregroundColor: isPopular
|
||||
? Colors.white
|
||||
: const Color(0xFF00BFFF),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: const BorderSide(color: Color(0xFF00BFFF)),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
elevation: isPopular ? 6 : 0,
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
isMonthly ? "Subscribe Monthly" : "Subscribe Yearly",
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Most popular badge
|
||||
if (isPopular)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00BFFF),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Text(
|
||||
"MOST POPULAR",
|
||||
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,8 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
|
||||
/// Filter products based on search text and stock
|
||||
List productsFilter(List products) {
|
||||
return products.where((product) {
|
||||
final matchesSearch = _searchText.isEmpty ||
|
||||
final matchesSearch =
|
||||
_searchText.isEmpty ||
|
||||
product.name.toLowerCase().contains(_searchText.toLowerCase());
|
||||
final matchesStock = !_inStockOnly || (product.inStock ?? true);
|
||||
return matchesSearch && matchesStock;
|
||||
@ -96,94 +97,61 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
|
||||
SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20),
|
||||
child: productsState.when(
|
||||
/// ✅ LOADING
|
||||
loading: () => const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 120),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
|
||||
/// ✅ ERROR
|
||||
error: (e, _) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 120),
|
||||
child: Text(
|
||||
(() {
|
||||
final raw = e.toString();
|
||||
// Extract backend message if exists
|
||||
final match = RegExp(r'message[: ]+([^}]+)').firstMatch(raw);
|
||||
if (match != null) return match.group(1)!.trim();
|
||||
return raw.replaceFirst(RegExp(r'^Exception:\s*'), '');
|
||||
})(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// ✅ SUCCESS
|
||||
data: (products) {
|
||||
if (products.isEmpty) {
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 120),
|
||||
child: Text("No products found"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Apply search / stock filter
|
||||
final filteredProducts = productsFilter(products);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
/// ✅ SEARCH + FILTER + COUNT ROW (always visible)
|
||||
Row(
|
||||
children: [
|
||||
/// ✅ Search + Filter + Count Row
|
||||
Row(
|
||||
children: [
|
||||
// Search bar
|
||||
Expanded(
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
setState(() => _searchText = value);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: "Search products",
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Colors.grey),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
),
|
||||
// Search bar
|
||||
Expanded(
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
setState(() => _searchText = value);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: "Search products",
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 0,
|
||||
horizontal: 16,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Colors.grey),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// In Stock Only button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
setState(() => _inStockOnly = !_inStockOnly);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _inStockOnly ? Colors.green : Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"In Stock",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
// In Stock Only button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
setState(() => _inStockOnly = !_inStockOnly);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _inStockOnly
|
||||
? Colors.green
|
||||
: Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
),
|
||||
child: Text(
|
||||
"In Stock",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Count button
|
||||
ElevatedButton(
|
||||
// Count button
|
||||
productsState.when(
|
||||
data: (products) {
|
||||
final filteredProducts = productsFilter(products);
|
||||
return ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.deepPurple,
|
||||
@ -195,22 +163,72 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
|
||||
"${filteredProducts.length} products",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const SizedBox(),
|
||||
error: (_, __) => const SizedBox(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
/// ✅ Products Grid
|
||||
GridView.builder(
|
||||
/// ✅ PRODUCTS GRID / LOADING / ERROR
|
||||
productsState.when(
|
||||
/// ✅ LOADING
|
||||
loading: () => const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 120),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
|
||||
/// ✅ ERROR
|
||||
error: (e, _) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 120),
|
||||
child: Text(
|
||||
(() {
|
||||
final raw = e.toString();
|
||||
// Extract backend message if exists
|
||||
final match = RegExp(
|
||||
r'message[: ]+([^}]+)',
|
||||
).firstMatch(raw);
|
||||
if (match != null) return match.group(1)!.trim();
|
||||
return raw.replaceFirst(
|
||||
RegExp(r'^Exception:\s*'),
|
||||
'',
|
||||
);
|
||||
})(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// ✅ SUCCESS
|
||||
data: (products) {
|
||||
if (products.isEmpty) {
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 120),
|
||||
child: Text("No products found"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final filteredProducts = productsFilter(products);
|
||||
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: filteredProducts.length,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
childAspectRatio: 0.68,
|
||||
),
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
childAspectRatio: 0.68,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final product = filteredProducts[index];
|
||||
|
||||
@ -270,10 +288,10 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:autos/core/routing/route_paths.dart';
|
||||
import 'package:autos/core/theme/app_typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:autos/core/widgets/hamburger_button.dart';
|
||||
import 'package:autos/core/widgets/side_menu.dart';
|
||||
@ -34,10 +35,10 @@ class _StoreScreenState extends State<StoreScreen> {
|
||||
top: topPadding,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"eBay Locations",
|
||||
style: TextStyle(fontSize: 26, fontWeight: FontWeight.w700),
|
||||
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user