Compare commits

...

9 Commits
main ... dev

Author SHA1 Message Date
c6f2b15453 push notification implemented. 2025-12-08 00:40:20 +05:30
064e753fb9 UI changes in sidemenu. 2025-11-30 02:25:45 +05:30
fe49bdf9af improved app state management and app theme updated. 2025-11-30 01:34:51 +05:30
6e9ffdb9dd Pricing page implemented. 2025-11-29 22:28:02 +05:30
a978699dfc Products page connected to the backend. 2025-11-29 15:04:40 +05:30
595554d541 BackendCall Implemented for GET Token and Brands data. 2025-11-27 00:35:58 +05:30
41970033ad Added launcher icon and fixed navigation 2025-11-25 09:25:07 +05:30
dcc8a8ed92 Turn14 connected to backend.
eBay and Store UI implemented.
2025-11-25 00:44:07 +05:30
33c15b1a5f s 2025-11-25 00:36:35 +05:30
83 changed files with 4440 additions and 488 deletions

View File

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="autos"
android:label="Autos"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";

View File

@ -1,122 +1 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,5 +1,5 @@
class ApiEndpoints {
static const String baseUrl = 'https://ebay.backend.data4autos.com/api';
static const String baseUrl = 'https://ebay.backend.data4autos.com';
static const Duration connectTimeout = Duration(seconds: 10);
static const Duration receiveTimeout = Duration(seconds: 10);
@ -9,8 +9,21 @@ class ApiEndpoints {
};
///Authentication
static const login = '/auth/login';
static const signup = '/auth/signup';
static const forgotPassword = '/auth/forgot-password';
static const userDetails = '/auth/users/';
static const login = '/api/auth/login';
static const signup = '/api/auth/signup';
static const forgotPassword = '/api/auth/forgot-password';
static const userDetails = '/api/auth/users/';
///GET Token
static const getToken = '/api/auth/turn14/get-access-token';
///Turn14
static const turn14Save = '/api/auth/turn14/save';
static const turn14Status = '/api/auth/turn14/status';
///Brands
static const String brands = "/v1/brands";
///Products
static const product = '/api/brands/';
}

View File

@ -2,7 +2,15 @@ import 'package:autos/core/routing/route_paths.dart';
import 'package:autos/presentation/screens/auth/forgot_password_screen.dart';
import 'package:autos/presentation/screens/auth/login_screen.dart';
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/imports/imports_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';
import 'package:autos/presentation/screens/store/store.dart';
import 'package:autos/presentation/screens/turn14_screen/turn14_screen.dart';
import 'package:flutter/material.dart';
@ -45,6 +53,30 @@ class AppRouter {
case AppRoutePaths.turn14:
return slideRoute(const Turn14Screen());
case AppRoutePaths.ebay:
return slideRoute(EbayScreen());
case AppRoutePaths.store:
return slideRoute(StoreScreen());
case AppRoutePaths.createStoreLocation:
return slideRoute(CreateLocationScreen());
case AppRoutePaths.brands:
return slideRoute(BrandsScreen());
case AppRoutePaths.products:
return slideRoute(ProductsScreen());
case AppRoutePaths.pricing:
return slideRoute(PricingScreen());
case AppRoutePaths.myAccount:
return slideRoute(AccountScreen());
case AppRoutePaths.imports:
return slideRoute(ImportsScreen());
default:
return _defaultFallback(settings);
}

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
class NavigationService {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
static Future<dynamic> push(String route, {Object? arguments}) {
return navigatorKey.currentState!
.pushNamed(route, arguments: arguments);
}
static Future<dynamic> pushReplace(String route, {Object? arguments}) {
return navigatorKey.currentState!
.pushReplacementNamed(route, arguments: arguments);
}
static Future<dynamic> pushRemove(String route, {Object? arguments}) {
return navigatorKey.currentState!.pushNamedAndRemoveUntil(
route,
(route) => false,
arguments: arguments,
);
}
static void pop() {
return navigatorKey.currentState!.pop();
}
}

View File

@ -1,7 +1,22 @@
import 'package:flutter/material.dart';
ValueNotifier<String> selectedRoute = ValueNotifier<String>("/dashboard");
class AppRoutePaths {
///Auth
static const auth = '/auth';
static const signup = '/signup';
static const forgotPassword = '/forgotPassword';
///Screens
static const dashboard = '/dashboard';
static const turn14 = '/turn14';
static const ebay = '/ebay';
static const store = '/store';
static const createStoreLocation = '/createStoreLocation';
static const brands = '/brands';
static const products = '/products';
static const pricing = '/pricing';
static const myAccount = '/myAccount';
static const imports = '/imports';
}

View File

@ -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(0xFFFFFFFF); // 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),
);
}

View File

@ -1,3 +1,4 @@
import 'package:autos/core/theme/app_theme.dart';
import 'package:flutter/material.dart';
class AppTypo {
@ -13,15 +14,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,
);
@ -49,7 +51,7 @@ class AppTypo {
fontFamily: fontFamily,
fontSize: 18,
fontWeight: FontWeight.w700,
color: Color(0xFF2272f6),
color: AppTheme.primary,
);
static const TextStyle menu = TextStyle(

View File

@ -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

View File

@ -22,7 +22,7 @@ class SideMenu extends ConsumerWidget {
children: [
// Header
DrawerHeader(
decoration: BoxDecoration(color: Color(0xFF3B81F9)),
decoration: const BoxDecoration(color: Color(0xFF00BFFF)),
child: SizedBox(
width: double.infinity,
child: Row(
@ -59,19 +59,19 @@ class SideMenu extends ConsumerWidget {
// --- INTEGRATIONS ---
_sectionHeader("INTEGRATIONS"),
_menuItem(context, "", "Turn14", AppRoutePaths.turn14),
_menuItem(context, "🛍️", "eBay", 'ebay'),
_menuItem(context, "🛒", "Store", 'store'),
_menuItem(context, "🛍️", "eBay", AppRoutePaths.ebay),
_menuItem(context, "🛒", "Store", AppRoutePaths.store),
// --- MANAGE ---
_sectionHeader("MANAGE"),
_menuItem(context, "🏷️", "Brands", 'brands'),
_menuItem(context, "📦", "Products", 'products'),
_menuItem(context, "⬇️", "Imports", 'imports'),
_menuItem(context, "🏷️", "Brands", AppRoutePaths.brands),
_menuItem(context, "📦", "Products", AppRoutePaths.products),
_menuItem(context, "⬇️", "Imports", AppRoutePaths.imports),
// --- ACCOUNT ---
_sectionHeader("ACCOUNT"),
_menuItem(context, "👤", "My Account", AppRoutePaths.auth),
_menuItem(context, "💰", "Pricing Plan", 'pricing'),
_menuItem(context, "👤", "My Account", AppRoutePaths.myAccount),
_menuItem(context, "💰", "Pricing Plan", AppRoutePaths.pricing),
],
),
),
@ -112,8 +112,10 @@ class SideMenu extends ConsumerWidget {
// Section title widget
Widget _sectionHeader(String title) {
return Container(
padding: EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(color: Color.fromARGB(255, 225, 236, 255)),
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: const BoxDecoration(
color: Color.fromARGB(255, 225, 236, 255),
),
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
@ -123,45 +125,72 @@ class SideMenu extends ConsumerWidget {
fontWeight: FontWeight.w700,
fontSize: 15,
letterSpacing: 0.5,
color: Color(0xFF3B81F9),
color: Color(0xFF00BFFF),
),
),
),
);
}
// Menu item widget
// Menu item widget with persistent left indicator
Widget _menuItem(
BuildContext context,
String emoji,
String title,
String route,
) {
final bool isSelected = selected == route;
return ValueListenableBuilder(
valueListenable: selectedRoute,
builder: (_, current, __) {
final bool isSelected = current == route;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () {
onItemSelected(route);
Navigator.pushNamed(context, route);
onTap: () async {
Navigator.pop(context);
selectedRoute.value = route; // indicator ALWAYS updates
if (ModalRoute.of(context)?.settings.name != route) {
Navigator.pushReplacementNamed(context, route);
}
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? const Color(0x143B81F9) : Colors.transparent,
color: isSelected
? const Color(0x143B81F9)
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Container(
width: 4,
height: 48,
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF3B81F9)
: Colors.transparent,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
),
const SizedBox(width: 8),
Text(emoji, style: const TextStyle(fontSize: 18)),
const SizedBox(width: 10),
Text(
title,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected ? const Color(0xFF3B81F9) : Colors.black87,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.normal,
color: isSelected
? const Color(0xFF3B81F9)
: Colors.black87,
fontSize: 16,
),
),
@ -170,5 +199,7 @@ class SideMenu extends ConsumerWidget {
),
),
);
},
);
}
}

View File

@ -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(

View File

@ -0,0 +1,107 @@
import 'package:autos/domain/entities/brands.dart';
class Brand {
final String id;
final String name;
final String logo;
final bool dropship;
final List<PriceGroup> pricegroups;
Brand({
required this.id,
required this.name,
required this.logo,
required this.dropship,
required this.pricegroups,
});
factory Brand.fromJson(Map<String, dynamic> json) {
return Brand(
id: json['id'].toString(),
name: json['name'] ?? '',
logo: json['logo'] ?? '',
dropship: json['dropship'] ?? false,
pricegroups: (json['pricegroups'] as List<dynamic>?)
?.map((e) => PriceGroup.fromJson(e))
.toList() ??
[],
);
}
/// 🔥 Model Entity
BrandEntity toEntity() {
return BrandEntity(
id: id,
name: name,
logo: logo,
dropship: dropship,
pricegroups: pricegroups.map((e) => e.toEntity()).toList(),
);
}
}
class PriceGroup {
final String pricegroupId;
final String pricegroupName;
final String pricegroupPrefix;
final List<dynamic> locationRules;
final List<PurchaseRestriction> purchaseRestrictions;
PriceGroup({
required this.pricegroupId,
required this.pricegroupName,
required this.pricegroupPrefix,
required this.locationRules,
required this.purchaseRestrictions,
});
factory PriceGroup.fromJson(Map<String, dynamic> json) {
return PriceGroup(
pricegroupId: json['pricegroup_id'].toString(),
pricegroupName: json['pricegroup_name'] ?? '',
pricegroupPrefix: json['pricegroup_prefix'] ?? '',
locationRules: json['location_rules'] ?? [],
purchaseRestrictions: (json['purchase_restrictions'] as List<dynamic>?)
?.map((e) => PurchaseRestriction.fromJson(e))
.toList() ??
[],
);
}
/// 🔥 Model Entity
PriceGroupEntity toEntity() {
return PriceGroupEntity(
pricegroupId: pricegroupId,
pricegroupName: pricegroupName,
pricegroupPrefix: pricegroupPrefix,
locationRules: locationRules,
purchaseRestrictions:
purchaseRestrictions.map((e) => e.toEntity()).toList(),
);
}
}
class PurchaseRestriction {
final String program;
final String yourStatus;
PurchaseRestriction({
required this.program,
required this.yourStatus,
});
factory PurchaseRestriction.fromJson(Map<String, dynamic> json) {
return PurchaseRestriction(
program: json['program'] ?? '',
yourStatus: json['your_status'] ?? '',
);
}
/// 🔥 Model Entity
PurchaseRestrictionEntity toEntity() {
return PurchaseRestrictionEntity(
program: program,
yourStatus: yourStatus,
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:autos/domain/entities/import_product.dart';
class ImportProductModel {
final String id;
final String sku;
final String image;
final String name;
final String partNumber;
final String category;
final String subcategory;
final double price;
final int inventory;
final String offerStatus;
ImportProductModel({
required this.id,
required this.sku,
required this.image,
required this.name,
required this.partNumber,
required this.category,
required this.subcategory,
required this.price,
required this.inventory,
required this.offerStatus,
});
factory ImportProductModel.fromJson(Map<String, dynamic> json) {
return ImportProductModel(
id: json['id']?.toString() ?? '',
sku: json['sku'] ?? '',
image: json['imgSrc'] ?? '',
name: json['name'] ?? '',
partNumber: json['partNumber'] ?? '',
category: json['category'] ?? '',
subcategory: json['subcategory'] ?? '',
price: double.tryParse(json['price'].toString()) ?? 0,
inventory: json['inventory'] ?? 0,
offerStatus: json['offer_status'] ?? '',
);
}
ImportProductEntity toEntity() {
return ImportProductEntity(
id: id,
sku: sku,
image: image,
name: name,
partNumber: partNumber,
category: category,
subcategory: subcategory,
price: price,
inventory: inventory,
offerStatus: offerStatus,
);
}
}

View File

@ -0,0 +1,27 @@
import 'package:autos/domain/entities/product.dart';
class ProductModel extends ProductEntity {
ProductModel({
required super.id,
required super.name,
required super.image,
required super.price,
});
factory ProductModel.fromJson(Map<String, dynamic> json) {
return ProductModel(
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,
);
}
}

View File

@ -0,0 +1,112 @@
import 'dart:convert';
import 'package:autos/domain/entities/turn14.dart';
class Turn14Response {
final String code;
final String message;
final String userId;
final String accessToken;
const Turn14Response({
required this.code,
required this.message,
required this.userId,
required this.accessToken,
});
factory Turn14Response.fromJson(Map<String, dynamic> json) {
return Turn14Response(
code: json['code'] ?? '',
message: json['message'] ?? '',
userId: json['userid'] ?? '',
accessToken: json['access_token'] ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'code': code,
'message': message,
'userid': userId,
'access_token': accessToken,
};
}
/// Convert model raw JSON string
String toRawJson() => jsonEncode(toJson());
/// Convert raw JSON string model
factory Turn14Response.fromRawJson(String raw) =>
Turn14Response.fromJson(jsonDecode(raw));
/// Convert Response Domain Entity
Turn14Entity toEntity() {
return Turn14Entity(
code: code,
message: message,
userId: userId,
accessToken: accessToken,
);
}
@override
String toString() {
return 'Turn14Response(code: $code, message: $message, userId: $userId, accessToken: $accessToken)';
}
}
class Turn14StatusModel {
final String userId;
final bool hasCredentials;
final String? clientId;
final String? clientSecret;
final String? accessToken;
final int? expiresIn; // FIXED: should be int
final String? code; // ADDED
final String? message; // ADDED
Turn14StatusModel({
required this.userId,
required this.hasCredentials,
this.clientId,
this.clientSecret,
this.accessToken,
this.expiresIn,
this.code,
this.message,
});
/// SAFE FROM JSON
factory Turn14StatusModel.fromJson(Map<String, dynamic> json) {
final credentials = json["credentials"];
final tokenInfo = json["tokenInfo"];
return Turn14StatusModel(
userId: json["userid"]?.toString() ?? "",
hasCredentials: json["hasCredentials"] ?? false,
clientId: credentials?["turn14clientid"],
clientSecret: credentials?["turn14clientsecret"],
accessToken: tokenInfo?["access_token"],
expiresIn: tokenInfo?["expires_in"],
code: json["code"],
message: json["message"],
);
}
/// MODEL ENTITY (CLEAN ARCH)
Turn14StatusEntity toEntity() {
return Turn14StatusEntity(
userId: userId,
hasCredentials: hasCredentials,
clientId: clientId,
clientSecret: clientSecret,
accessToken: accessToken,
expiresIn: expiresIn,
);
}
}

View File

@ -1,7 +1,115 @@
import 'dart:convert';
import 'package:autos/domain/entities/user.dart';
/// STORE MODEL
class StoreModel {
final String name;
final String description;
final String url;
final String urlPath;
final String? lastOpenedTime;
final String? lastOpenedTimeRaw;
final String? logoUrl;
const StoreModel({
required this.name,
required this.description,
required this.url,
required this.urlPath,
this.lastOpenedTime,
this.lastOpenedTimeRaw,
this.logoUrl,
});
factory StoreModel.fromJson(Map<String, dynamic> json) {
return StoreModel(
name: json['name'] ?? '',
description: json['description'] ?? '',
url: json['url'] ?? '',
urlPath: json['urlPath'] ?? '',
lastOpenedTime: json['lastOpenedTime'],
lastOpenedTimeRaw: json['lastOpenedTimeRaw'],
logoUrl: json['logo']?['url'],
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'description': description,
'url': url,
'urlPath': urlPath,
'lastOpenedTime': lastOpenedTime,
'lastOpenedTimeRaw': lastOpenedTimeRaw,
'logo': {
'url': logoUrl,
},
};
}
}
/// PAYMENT MODEL
class PaymentModel {
final int id;
final String email;
final String amount;
final String plan;
final String status;
final String? stripeSessionId;
final String? stripePaymentIntentId;
final String? subscriptionId;
final String? startDate;
final String? endDate;
const PaymentModel({
required this.id,
required this.email,
required this.amount,
required this.plan,
required this.status,
this.stripeSessionId,
this.stripePaymentIntentId,
this.subscriptionId,
this.startDate,
this.endDate,
});
factory PaymentModel.fromJson(Map<String, dynamic> json) {
return PaymentModel(
id: json['id'] ?? 0,
email: json['email'] ?? '',
amount: json['amount'] ?? '',
plan: json['plan'] ?? '',
status: json['status'] ?? '',
stripeSessionId: json['stripeSessionId'],
stripePaymentIntentId: json['stripePaymentIntentId'],
subscriptionId: json['subscriptionId'],
startDate: json['startDate'],
endDate: json['endDate'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'email': email,
'amount': amount,
'plan': plan,
'status': status,
'stripeSessionId': stripeSessionId,
'stripePaymentIntentId': stripePaymentIntentId,
'subscriptionId': subscriptionId,
'startDate': startDate,
'endDate': endDate,
};
}
}
/// MAIN USER MODEL
class UserModel extends User {
final StoreModel? store;
final PaymentModel? payment;
const UserModel({
required super.id,
required super.name,
@ -12,23 +120,52 @@ class UserModel extends User {
super.phoneNumber,
super.message,
super.code,
this.store,
this.payment,
});
/// FROM API JSON
factory UserModel.fromJson(Map<String, dynamic> json) {
final payment = json['payment'] ?? {};
final paymentJson = json['payment'];
final storeJson = json['store'];
final payment =
paymentJson != null ? PaymentModel.fromJson(paymentJson) : null;
final store =
storeJson != null ? StoreModel.fromJson(storeJson) : null;
return UserModel(
id: json['userid'] ?? json['id']?.toString() ?? '',
name: json['name'] ?? '',
email: json['email'] ?? '',
role: json['role'] ?? '',
plan: payment['plan'] ?? json['plan'],
paymentStatus: payment['status'] ?? json['paymentStatus'],
plan: payment?.plan ?? json['plan'],
paymentStatus: payment?.status ?? json['paymentStatus'],
phoneNumber: json['phonenumber'] ?? '',
message: json['message'] ?? '',
code: json['code'] ?? '',
store: store,
payment: payment,
);
}
/// FROM ENTITY (LOCAL CONVERSION)
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 LOCAL STORAGE)
Map<String, dynamic> toJson() {
return {
'userid': id,
@ -40,18 +177,20 @@ class UserModel extends User {
'phonenumber': phoneNumber,
'message': message,
'code': code,
'store': store?.toJson(),
'payment': payment?.toJson(),
};
}
/// 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));
@override
String toString() {
return 'UserModel(id: $id, name: $name, email: $email, role: $role, phone: $phoneNumber, plan: $plan, status: $paymentStatus, code: $code)';
return 'UserModel(id: $id, name: $name, email: $email, role: $role, plan: $plan, status: $paymentStatus)';
}
}

View File

@ -0,0 +1,127 @@
import 'dart:convert';
import 'package:autos/core/constants/api_endpoints.dart';
import 'package:autos/data/models/brands_model.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/brands.dart';
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:autos/data/models/user_model.dart';
abstract class BrandsRepository {
Future<List<BrandEntity>> getBrands();
}
class BrandsRepositoryImpl implements BrandsRepository {
final ApiService _apiService;
final _storage = const FlutterSecureStorage();
static const _userKey = 'logged_in_user';
BrandsRepositoryImpl(this._apiService);
/// ALWAYS CALL TOKEN API (FORCE REFRESH)
Future<String> getTurn14AccessToken() async {
final jsonString = await _storage.read(key: _userKey);
if (jsonString == null) {
throw Exception("User not found. Please login again.");
}
final user = UserModel.fromJson(jsonDecode(jsonString));
final response = await _apiService.post(ApiEndpoints.getToken, {
"userid": user.id,
});
final data = response.data;
String token;
if (data["code"] == "TOKEN_UPDATED") {
// New token returned
token = data["access_token"];
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");
}
} else {
throw Exception(data["message"]);
}
debugPrint("Turn14 Token: $token");
return token;
}
/// FETCH BRANDS USING FRESH TOKEN EVERY TIME
@override
Future<List<BrandEntity>> getBrands() async {
try {
final token = await getTurn14AccessToken();
final response = await _apiService.get(
'/v1/brands',
overrideBaseUrl: 'https://turn14.data4autos.com',
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode != 200) {
throw Exception("Server error: ${response.statusCode}");
}
final data = response.data;
if (data is! Map || data["data"] == null) {
throw Exception("Invalid API response");
}
final list = data["data"] as List;
return list.map((json) => Brand.fromJson(json).toEntity()).toList();
} on DioException catch (e) {
throw Exception("Network error: ${e.response?.data ?? e.message}");
} catch (e) {
throw Exception("Unexpected error: $e");
}
}
/// BULK INSERT SELECTED BRANDS
Future<Map<String, dynamic>> saveSelectedBrands({
required String userId,
required List<BrandEntity> brands,
}) async {
try {
final payload = {
"userid": userId,
"brands": brands
.map(
(brand) => {
"id": brand.id.toString(),
"name": brand.name,
"logo": brand.logo,
"dropship": brand.dropship,
},
)
.toList(),
};
final response = await _apiService.postWithOptions(
"/api/brands/bulk-insert",
overrideBaseUrl: "https://ebay.backend.data4autos.com",
data: payload,
);
return response.data;
} on DioException catch (e) {
throw Exception("Save failed: ${e.response?.data ?? e.message}");
} catch (e) {
throw Exception("Unexpected error: $e");
}
}
}

View File

@ -0,0 +1,147 @@
import 'dart:convert';
import 'package:autos/core/constants/api_endpoints.dart';
import 'package:autos/data/models/import_product_model.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/import_product.dart';
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:autos/data/models/user_model.dart';
abstract class ImportsRepository {
Future<List<ImportProductEntity>> getUserProducts({
required int page,
required int pageSize,
String? category,
String? subcategory,
});
}
class ImportsRepositoryImpl implements ImportsRepository {
final ApiService _apiService;
final _storage = const FlutterSecureStorage();
static const _userKey = 'logged_in_user';
ImportsRepositoryImpl(this._apiService);
/// TOKEN LOGIC (UNCHANGED)
Future<String> getTurn14AccessToken() async {
final jsonString = await _storage.read(key: _userKey);
if (jsonString == null) {
throw Exception("User not found. Please login again.");
}
final user = UserModel.fromJson(jsonDecode(jsonString));
final response = await _apiService.post(
ApiEndpoints.getToken,
{"userid": user.id},
);
final data = response.data;
String token;
if (data["code"] == "TOKEN_UPDATED") {
token = data["access_token"];
await _storage.write(key: 'turn14_token', value: token);
} else if (data["code"] == "TOKEN_VALID") {
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");
}
} else {
throw Exception(data["message"]);
}
debugPrint("✅ Turn14 Token: $token");
return token;
}
/// FINAL: FETCH USER PRODUCTS WITH PAGINATION + FILTERS
@override
Future<List<ImportProductEntity>> getUserProducts({
required int page,
required int pageSize,
String? category,
String? subcategory,
}) async {
try {
final token = await getTurn14AccessToken();
final userId = await _getUserId();
/// CLEAN PARAMS (SKIPS "All")
final Map<String, dynamic> params = {
"userid": userId,
"page": page,
"pageSize": pageSize,
};
if (category != null && category.isNotEmpty && category != "All") {
params["category"] = category;
}
if (subcategory != null &&
subcategory.isNotEmpty &&
subcategory != "All") {
params["subcategory"] = subcategory;
}
debugPrint("📡 FETCH USER PRODUCTS");
debugPrint("➡️ PARAMS: $params");
final response = await _apiService.get(
"/api/user-products",
overrideBaseUrl: "https://ebay.backend.data4autos.com",
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
params: params,
);
debugPrint("✅ API STATUS: ${response.statusCode}");
debugPrint("✅ RAW RESPONSE: ${response.data}");
if (response.statusCode != 200) {
throw Exception("Server error: ${response.statusCode}");
}
final data = response.data;
/// API RETURNS `items`
if (data is! Map || data["items"] == null) {
throw Exception("Invalid API structure: 'items' key missing");
}
final List list = data["items"];
debugPrint("✅ TOTAL ITEMS LOADED: ${list.length}");
return list
.map(
(json) => ImportProductModel.fromJson(json).toEntity(),
)
.toList();
} on DioException catch (e) {
debugPrint("❌ NETWORK ERROR: ${e.response?.data ?? e.message}");
throw Exception("Network error: ${e.response?.data ?? e.message}");
} catch (e, st) {
debugPrint("❌ PARSING ERROR: $e");
debugPrintStack(stackTrace: st);
throw Exception("Unexpected error: $e");
}
}
/// GET USER ID FROM STORAGE
Future<String> _getUserId() async {
final jsonString = await _storage.read(key: _userKey);
if (jsonString == null) {
throw Exception("User not found");
}
final user = UserModel.fromJson(jsonDecode(jsonString));
return user.id;
}
}

View File

@ -0,0 +1,65 @@
import 'package:autos/core/constants/api_endpoints.dart';
import 'package:autos/data/models/product_model.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/product.dart';
import 'package:flutter/material.dart';
abstract class ProductRepository {
Future<List<ProductEntity>> getProducts(String userId);
}
class ProductRepositoryImpl implements ProductRepository {
final ApiService apiService;
ProductRepositoryImpl(this.apiService);
@override
Future<List<ProductEntity>> getProducts(String userId) async {
try {
final url = '${ApiEndpoints.product}$userId';
/// DEBUG PRINTS
debugPrint("🌍 PRODUCT API HIT");
debugPrint("➡️ URL: $url");
final response = await apiService.get(url);
debugPrint("✅ RAW PRODUCT API RESPONSE:");
debugPrint(response.data.toString());
/// SAFETY: If backend returns null return empty list
if (response.data == null) {
debugPrint("⚠️ PRODUCT API RETURNED NULL → using empty list");
return [];
}
/// SAFETY: Ensure response is really a List
if (response.data is! List) {
debugPrint("⚠️ PRODUCT API RETURNED NON-LIST DATA");
return [];
}
final List data = response.data as List;
debugPrint("✅ PARSED PRODUCT COUNT: ${data.length}");
/// EMPTY LIST IS PERFECTLY VALID
if (data.isEmpty) {
debugPrint(" NO PRODUCTS FOUND (EMPTY LIST)");
return [];
}
/// SAFE MODEL PARSING
return data
.map((e) => ProductModel.fromJson(e))
.toList();
} catch (e, st) {
debugPrint("❌ PRODUCT API REAL ERROR:");
debugPrint(e.toString());
debugPrint(st.toString());
/// ONLY THROW FOR REAL FAILURES
throw Exception("Products fetch error: $e");
}
}
}

View File

@ -0,0 +1,57 @@
import 'package:autos/core/constants/api_endpoints.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class TokenRepository {
final ApiService apiService;
final _storage = const FlutterSecureStorage();
TokenRepository(this.apiService);
static const _tokenKey = "turn14_token";
static const _tokenTimeKey = "turn14_token_time";
/// Get Valid Token (Auto Refresh If Expired)
Future<String> getValidToken(String userId) async {
final token = await _storage.read(key: _tokenKey);
final time = await _storage.read(key: _tokenTimeKey);
if (token != null && time != null) {
final savedTime = DateTime.parse(time);
final diff = DateTime.now().difference(savedTime);
/// Token Valid for 50 minutes
if (diff.inMinutes < 50) {
return token;
}
}
/// 🔄 Token Expired Refresh
return await _refreshToken(userId);
}
/// 🔄 Refresh Token API
Future<String> _refreshToken(String userId) async {
final response = await apiService.post(
ApiEndpoints.getToken,
{"userid": userId},
);
final data = response.data;
if (data["code"] != "TOKEN_UPDATED") {
throw Exception(data["message"]);
}
final token = data["access_token"];
/// Save New Token + Timestamp
await _storage.write(key: _tokenKey, value: token);
await _storage.write(
key: _tokenTimeKey,
value: DateTime.now().toIso8601String(),
);
return token;
}
}

View File

@ -0,0 +1,77 @@
import 'package:autos/core/constants/api_endpoints.dart';
import 'package:autos/data/models/turn14_model.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/turn14.dart';
import 'package:dio/dio.dart';
abstract class Turn14Repository {
/// Save Turn14 credentials
Future<Turn14Entity> save({
required String userId,
required String clientId,
required String clientSecret,
});
}
class Turn14RepositoryImpl implements Turn14Repository {
final ApiService _apiService;
Turn14RepositoryImpl(this._apiService);
@override
Future<Turn14Entity> save({
required String userId,
required String clientId,
required String clientSecret,
}) async {
try {
final response = await _apiService.post(ApiEndpoints.turn14Save, {
"userid": userId,
"turn14clientid": clientId,
"turn14clientsecret": clientSecret,
});
// Check status
if (response.statusCode == 200) {
final data = response.data;
if (data['code'] == "TURN14_SAVED") {
/// Convert Response Entity
return Turn14Response.fromJson(data).toEntity();
} else {
throw Exception(
data['message'] ?? "Failed to save Turn14 credentials",
);
}
} else {
throw Exception("Server error: ${response.statusCode}");
}
} on DioException catch (e) {
throw Exception("Network error: ${e.message}");
} catch (e) {
throw Exception("Unexpected error: $e");
}
}
Future<Turn14StatusEntity> status(String userId) async {
try {
final response = await _apiService.post(ApiEndpoints.turn14Status, {
"userid": userId,
});
if (response.statusCode == 200) {
final data = response.data;
if (data["code"] == "TURN14_STATUS") {
return Turn14StatusModel.fromJson(data).toEntity();
} else {
throw Exception(data["message"] ?? "Failed to fetch Turn14 status");
}
} else {
throw Exception("Server error: ${response.statusCode}");
}
} catch (e) {
throw Exception("Network/Unexpected error: $e");
}
}
}

View File

@ -14,7 +14,6 @@ class ApiService {
headers: ApiEndpoints.defaultHeaders,
),
) {
// Add logging interceptor in debug mode
if (kDebugMode) {
_dio.interceptors.add(
LogInterceptor(requestBody: true, responseBody: true),
@ -22,6 +21,7 @@ class ApiService {
}
}
// KEEP OLD POST (so existing files DO NOT BREAK)
Future<Response> post(
String endpoint,
Map<String, dynamic> data, {
@ -30,7 +30,39 @@ class ApiService {
return await _dio.post(endpoint, data: data, options: options);
}
Future<Response> get(String endpoint, {Map<String, dynamic>? params}) async {
return await _dio.get(endpoint, queryParameters: params);
// NEW POST (for overrideBaseUrl + headers + params)
Future<Response> postWithOptions(
String endpoint, {
dynamic data,
Map<String, dynamic>? params,
Map<String, dynamic>? headers,
String? overrideBaseUrl,
}) async {
final String requestUrl =
overrideBaseUrl != null ? overrideBaseUrl + endpoint : endpoint;
return await _dio.post(
requestUrl,
data: data,
queryParameters: params,
options: headers != null ? Options(headers: headers) : null,
);
}
// GET (unchanged)
Future<Response> get(
String endpoint, {
Map<String, dynamic>? params,
Map<String, dynamic>? headers,
String? overrideBaseUrl,
}) async {
final String requestUrl =
overrideBaseUrl != null ? overrideBaseUrl + endpoint : endpoint;
return await _dio.get(
requestUrl,
queryParameters: params,
options: headers != null ? Options(headers: headers) : null,
);
}
}

View File

@ -0,0 +1,41 @@
class BrandEntity {
final String id;
final String name;
final String logo;
final bool dropship;
final List<PriceGroupEntity> pricegroups;
BrandEntity({
required this.id,
required this.name,
required this.logo,
required this.dropship,
required this.pricegroups,
});
}
class PriceGroupEntity {
final String pricegroupId;
final String pricegroupName;
final String pricegroupPrefix;
final List<dynamic> locationRules;
final List<PurchaseRestrictionEntity> purchaseRestrictions;
PriceGroupEntity({
required this.pricegroupId,
required this.pricegroupName,
required this.pricegroupPrefix,
required this.locationRules,
required this.purchaseRestrictions,
});
}
class PurchaseRestrictionEntity {
final String program;
final String yourStatus;
PurchaseRestrictionEntity({
required this.program,
required this.yourStatus,
});
}

View File

@ -0,0 +1,25 @@
class ImportProductEntity {
final String id;
final String sku;
final String image;
final String name;
final String partNumber;
final String category;
final String subcategory;
final double price;
final int inventory;
final String offerStatus;
ImportProductEntity({
required this.id,
required this.sku,
required this.image,
required this.name,
required this.partNumber,
required this.category,
required this.subcategory,
required this.price,
required this.inventory,
required this.offerStatus,
});
}

View File

@ -0,0 +1,13 @@
class ProductEntity {
final int id;
final String name;
final String image;
final double price;
ProductEntity({
required this.id,
required this.name,
required this.image,
required this.price,
});
}

View File

@ -0,0 +1,31 @@
class Turn14Entity {
final String code;
final String message;
final String userId;
final String accessToken;
const Turn14Entity({
required this.code,
required this.message,
required this.userId,
required this.accessToken,
});
}
class Turn14StatusEntity {
final String userId;
final bool hasCredentials;
final String? clientId;
final String? clientSecret;
final String? accessToken;
final int? expiresIn;
Turn14StatusEntity({
required this.userId,
required this.hasCredentials,
this.clientId,
this.clientSecret,
this.accessToken,
this.expiresIn,
});
}

View File

@ -1,11 +1,24 @@
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';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// ADD THIS IMPORT
import 'package:onesignal_flutter/onesignal_flutter.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// ONESIGNAL SETUP (ADDED SAFELY)
OneSignal.Debug.setLogLevel(OSLogLevel.verbose);
OneSignal.initialize("271c3931-07ee-46b6-8629-7f0d63f58085");
OneSignal.Notifications.requestPermission(false);
// KEEP YOUR ORIGINAL LOGIC
runApp(const ProviderScope(child: MyApp()));
}
@ -28,16 +41,16 @@ class _MyAppState extends ConsumerState<MyApp> {
Future<void> _restoreSession() async {
final userNotifier = ref.read(userProvider.notifier);
// Load user from secure storage
// Load user from secure storage (UNCHANGED)
await userNotifier.loadUserFromStorage();
// Stop loading after user is restored
// Stop loading after user is restored (UNCHANGED)
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
// Show loading screen until user session is restored
// Loading screen (UNCHANGED)
if (_isLoading) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
@ -53,13 +66,12 @@ 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
// Dynamic home based on session (UNCHANGED)
home: user != null ? const DashboardScreen() : const LoginScreen(),
navigatorKey: NavigationService.navigatorKey,
onGenerateRoute: AppRouter.generateRoute,
);
}

View File

@ -0,0 +1,79 @@
import 'package:autos/data/repositories/brands_repository_impl.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/brands.dart';
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
/// API Service Provider
final brandsApiServiceProvider = Provider<ApiService>((ref) => ApiService());
/// Repository Provider
final brandsRepositoryProvider = Provider<BrandsRepositoryImpl>(
(ref) => BrandsRepositoryImpl(ref.read(brandsApiServiceProvider)),
);
/// Brands Notifier
class BrandsNotifier extends StateNotifier<AsyncValue<List<BrandEntity>>> {
final BrandsRepositoryImpl repository;
final Ref ref;
BrandsNotifier(this.repository, this.ref) : super(const AsyncValue.loading());
/// Fetch brands every time page opens
Future<void> fetchBrands() async {
state = const AsyncValue.loading();
try {
/// READ USER DIRECTLY FROM userProvider (NOT userDetailsProvider)
final userAsync = ref.read(userProvider);
final user = userAsync.value;
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 {
try {
final userAsync = ref.read(userProvider);
final user = userAsync.value;
if (user == null) throw Exception("User not logged in");
final response = await repository.saveSelectedBrands(
userId: user.id,
brands: selectedBrands,
);
debugPrint("✅ Save Success: ${response["message"]}");
} catch (e) {
debugPrint("❌ Save Error: $e");
rethrow;
}
}
}
/// Brands State Provider
final brandsProvider =
StateNotifierProvider<BrandsNotifier, AsyncValue<List<BrandEntity>>>(
(ref) {
final repo = ref.read(brandsRepositoryProvider);
return BrandsNotifier(repo, ref);
},
);

View File

@ -0,0 +1,84 @@
import 'package:autos/data/repositories/imports_repository_impl.dart';
import 'package:autos/domain/entities/import_product.dart';
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
/// Repository Provider
final importsRepositoryProvider = Provider<ImportsRepository>(
(ref) => ImportsRepositoryImpl(ref.read(apiServiceProvider)),
);
/// Main Imports Provider
final importsProvider = StateNotifierProvider<
ImportsNotifier, AsyncValue<List<ImportProductEntity>>>(
(ref) => ImportsNotifier(ref.read(importsRepositoryProvider)),
);
class ImportsNotifier
extends StateNotifier<AsyncValue<List<ImportProductEntity>>> {
final ImportsRepository _repo;
ImportsNotifier(this._repo) : super(const AsyncLoading());
/// STATE
int _page = 1;
final int _pageSize = 48;
String _category = "All";
String _subcategory = "All";
int get currentPage => _page;
/// LOAD PRODUCTS FROM API
Future<void> loadProducts({bool resetPage = false}) async {
try {
if (resetPage) _page = 1;
state = const AsyncLoading();
debugPrint("📡 Fetching products");
debugPrint("➡ Page: $_page");
debugPrint("➡ Category: $_category");
debugPrint("➡ SubCategory: $_subcategory");
final products = await _repo.getUserProducts(
page: _page,
pageSize: _pageSize,
category: _category == "All" ? null : _category,
subcategory: _subcategory == "All" ? null : _subcategory,
);
state = AsyncData(products);
} catch (e, st) {
state = AsyncError(e, st);
}
}
/// CATEGORY CHANGE (RESETS SUBCATEGORY AUTOMATICALLY)
void changeCategory(String value) {
_category = value;
_subcategory = "All"; // RESET HERE
loadProducts(resetPage: true);
}
/// SUBCATEGORY CHANGE
void changeSubcategory(String value) {
_subcategory = value;
loadProducts(resetPage: true);
}
/// NEXT PAGE
void nextPage() {
_page++;
loadProducts();
}
/// PREVIOUS PAGE
void prevPage() {
if (_page > 1) {
_page--;
loadProducts();
}
}
}

View File

@ -0,0 +1,55 @@
import 'package:autos/data/repositories/product_repository.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/product.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
/// API SERVICE PROVIDER
final productApiServiceProvider =
Provider<ApiService>((ref) => ApiService());
/// REPOSITORY PROVIDER
final productRepositoryProvider = Provider<ProductRepositoryImpl>((ref) {
return ProductRepositoryImpl(ref.read(productApiServiceProvider));
});
/// PRODUCT STATE PROVIDER
final productProvider = StateNotifierProvider<ProductNotifier,
AsyncValue<List<ProductEntity>>>((ref) {
final repository = ref.read(productRepositoryProvider);
return ProductNotifier(repository);
});
/// PRODUCT NOTIFIER
class ProductNotifier
extends StateNotifier<AsyncValue<List<ProductEntity>>> {
final ProductRepositoryImpl repository;
ProductNotifier(this.repository)
: super(const AsyncValue.loading());
/// FETCH PRODUCTS (EMPTY LIST IS NOT AN ERROR!)
Future<void> getProducts(String userId) async {
try {
state = const AsyncValue.loading();
debugPrint("🚀 FETCHING PRODUCTS FOR USER ID = $userId");
final products = await repository.getProducts(userId);
debugPrint("✅ PRODUCTS RECEIVED = ${products.length}");
// EVEN IF LIST IS EMPTY SUCCESS
state = AsyncValue.data(products);
} catch (e, st) {
debugPrint("❌ PRODUCT API REAL ERROR = $e");
state = AsyncValue.error(e, st);
}
}
/// CLEAR PRODUCTS SAFELY
void clear() {
state = const AsyncValue.data([]);
}
}

View File

@ -0,0 +1,7 @@
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:autos/data/repositories/token_repository.dart';
final tokenRepositoryProvider = Provider<TokenRepository>(
(ref) => TokenRepository(ref.read(apiServiceProvider)),
);

View File

@ -0,0 +1,92 @@
import 'dart:convert';
import 'package:autos/data/models/turn14_model.dart';
import 'package:autos/data/repositories/turn14_repository_impl.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/turn14.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
/// ------------------------------------------------------------
/// Service Providers
/// ------------------------------------------------------------
final turn14ApiServiceProvider =
Provider<ApiService>((ref) => ApiService());
final turn14RepositoryProvider = Provider<Turn14RepositoryImpl>((ref) {
return Turn14RepositoryImpl(ref.read(turn14ApiServiceProvider));
});
/// ------------------------------------------------------------
/// Turn14 Notifier
/// ------------------------------------------------------------
class Turn14Notifier extends StateNotifier<AsyncValue<Turn14Entity?>> {
final Turn14RepositoryImpl repository;
final _storage = const FlutterSecureStorage();
static const _turn14StorageKey = "turn14_credentials";
Turn14Notifier(this.repository) : super(const AsyncValue.data(null));
/// Save Turn14 credentials
Future<void> saveCredentials({
required String userId,
required String clientId,
required String clientSecret,
}) async {
state = const AsyncValue.loading();
try {
final response = await repository.save(
userId: userId,
clientId: clientId,
clientSecret: clientSecret,
);
// if (response is Turn14Response) {
// await _storage.write(
// key: _turn14StorageKey,
// value: response.toRawJson(),
// );
// }
state = AsyncValue.data(response);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
/// Load saved Turn14 credentials
Future<void> loadSavedCredentials() async {
final saved = await _storage.read(key: _turn14StorageKey);
if (saved == null) return;
final decoded = jsonDecode(saved);
final model = Turn14Response.fromJson(decoded);
state = AsyncValue.data(model as Turn14Entity?);
}
/// Clear saved Turn14 data
Future<void> clear() async {
await _storage.delete(key: _turn14StorageKey);
state = const AsyncValue.data(null);
}
}
/// ------------------------------------------------------------
/// Riverpod Provider
/// ------------------------------------------------------------
final turn14Provider =
StateNotifierProvider<Turn14Notifier, AsyncValue<Turn14Entity?>>((ref) {
final repository = ref.read(turn14RepositoryProvider);
return Turn14Notifier(repository);
});

View File

@ -4,100 +4,78 @@ import 'package:autos/data/models/user_model.dart';
import 'package:autos/data/repositories/user_repository_impl.dart';
import 'package:autos/data/sources/remote/api_service.dart';
import 'package:autos/domain/entities/user.dart';
import 'package:flutter/widgets.dart';
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
/// Provide a single ApiService instance across the app
final apiServiceProvider = Provider<ApiService>((ref) => ApiService());
// Provide repository that depends on ApiService
/// Provide repository that depends on ApiService
final userRepositoryProvider = Provider<UserRepositoryImpl>(
(ref) => UserRepositoryImpl(ref.read(apiServiceProvider)),
);
// Manage user state
final loginProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((
ref,
) {
/// SINGLE GLOBAL USER PROVIDER
final userProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo);
});
final signupProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((
ref,
) {
final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo);
});
final userProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((
ref,
) {
final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo);
});
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);
return UserNotifier(ref, repo);
});
enum AuthAction { idle, login, signup }
class UserNotifier extends StateNotifier<AsyncValue<User?>> {
final Ref ref;
final UserRepositoryImpl repository;
final _storage = const FlutterSecureStorage();
static const _userKey = 'logged_in_user';
AuthAction lastAction = AuthAction.idle;
UserNotifier(this.repository) : super(const AsyncValue.data(null));
UserNotifier(this.ref, this.repository)
: super(const AsyncValue.data(null));
///Load saved user from storage (auto-login)
// AUTO LOGIN ON APP START
Future<void> loadUserFromStorage() async {
final jsonString = await _storage.read(key: _userKey);
if (jsonString != null) {
debugPrint("RESULT: $jsonString");
final jsonData = jsonDecode(jsonString);
final user = UserModel.fromJson(jsonData);
await Future.microtask(() {});
state = AsyncValue.data(user);
}
if (jsonString == null) {
state = const AsyncValue.data(null);
return;
}
///Login
final jsonData = jsonDecode(jsonString);
final user = UserModel.fromJson(jsonData);
state = AsyncValue.data(user);
}
// LOGIN FLOW (FULLY SYNCHRONIZED)
Future<void> login(String email, String password) async {
lastAction = AuthAction.login;
state = const AsyncValue.loading();
try {
final user = await repository.login(email, password);
// Save user to secure storage
if (user is UserModel) {
await _storage.write(key: _userKey, value: user.toRawJson());
}
state = AsyncValue.data(user);
state = const AsyncValue.loading();
// 1 Login API (partial user)
final partialUser = await repository.login(email, password);
// 2 Fetch FULL user details
final fullUser = await repository.getUserDetails(partialUser.id);
// 3 Store FULL user in secure storage
await _storage.write(key: _userKey, value: fullUser.toRawJson());
// 4 Update provider ONCE with FULL data
state = AsyncValue.data(fullUser);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
/// Logout
Future<void> logout() async {
await _storage.delete(key: _userKey);
state = const AsyncValue.data(null);
}
///Sign up
// SIGNUP
Future<void> signup(
String name,
String email,
@ -105,47 +83,44 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
String phone,
) async {
lastAction = AuthAction.signup;
state = const AsyncValue.loading();
try {
state = const AsyncValue.loading();
// 1 Signup returns USER ENTITY
final user = await repository.signup(name, email, password, phone);
// 2 Convert User UserModel for storage
final userModel = UserModel.fromEntity(user);
// 3 Store safely
await _storage.write(
key: _userKey,
value: userModel.toRawJson(),
);
// 4 Update provider state
state = AsyncValue.data(user);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
///Reset password
// LOGOUT
Future<void> logout() async {
await _storage.delete(key: _userKey);
state = const AsyncValue.data(null);
}
// PASSWORD RESET
Future<void> 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 user details from backend
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
Future<void> saveUserDetails(UserModel user) async {
await _storage.write(
key: 'logged_in_user_details',
value: user.toRawJson(),
);
}
}

View File

@ -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),

View File

@ -1,7 +1,8 @@
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';
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';
@ -42,13 +43,13 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
@override
Widget build(BuildContext context) {
final userState = ref.watch(loginProvider);
final userState = ref.watch(userProvider);
final size = MediaQuery.of(context).size;
final double verticalSpace = size.height * 0.02;
final double horizontalPadding = size.width * 0.06;
ref.listen(loginProvider, (previous, next) {
ref.listen(userProvider, (previous, next) {
if (next.hasValue && next.value != null) {
_showToast("Login Successful");
Navigator.pushReplacement(
@ -137,16 +138,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,
),
),
],
@ -160,14 +159,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
: ElevatedButton(
onPressed: () async {
await ref
.read(loginProvider.notifier)
.read(userProvider.notifier)
.login(
emailController.text.trim(),
passwordController.text.trim(),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B81F9),
backgroundColor: AppTheme.primary,
minimumSize: Size(
double.infinity,
size.height * 0.05,

View File

@ -1,3 +1,4 @@
import 'package:autos/core/theme/app_theme.dart';
import 'package:autos/core/widgets/wave_background.dart';
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter/material.dart';
@ -29,14 +30,15 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
@override
Widget build(BuildContext context) {
final userState = ref.watch(signupProvider);
/// FIXED: use userProvider instead of signupProvider
final userState = ref.watch(userProvider);
final size = MediaQuery.of(context).size;
final double verticalSpace = size.height * 0.02;
final double horizontalPadding =
size.width * 0.06; // dynamic horizontal padding
final double horizontalPadding = size.width * 0.06;
ref.listen(signupProvider, (previous, next) {
/// FIXED: listen to userProvider
ref.listen(userProvider, (previous, next) {
if (next.hasValue && next.value != null) {
Fluttertoast.showToast(
msg: "Sign up successful! Please log in.",
@ -73,20 +75,18 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: size.width < 500
? size.width
: 450, // better center layout on tablets
maxWidth: size.width < 500 ? size.width : 450,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.07),
/// Logo
Image.asset(
'assets/auth/autos_transp.png',
height: size.height * 0.14,
),
Row(
children: const [
Text(
@ -99,9 +99,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
Spacer(),
],
),
SizedBox(height: verticalSpace),
/// Name field
TextField(
controller: nameController,
decoration: const InputDecoration(
@ -109,9 +109,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.person_outline),
),
),
SizedBox(height: verticalSpace),
/// Email field
TextField(
controller: emailController,
decoration: const InputDecoration(
@ -119,9 +119,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.email_outlined),
),
),
SizedBox(height: verticalSpace),
/// Password field
TextField(
controller: passwordController,
obscureText: !isPasswordVisible,
@ -142,9 +142,9 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
),
),
),
SizedBox(height: verticalSpace),
/// Phone field
TextField(
controller: phoneController,
keyboardType: TextInputType.phone,
@ -153,24 +153,25 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
prefixIcon: Icon(Icons.phone_outlined),
),
),
SizedBox(height: verticalSpace * 2),
/// Sign Up button
/// FIXED SIGNUP BUTTON
userState.isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () async {
await ref
.read(signupProvider.notifier)
.read(userProvider.notifier)
.signup(
nameController.text,
emailController.text,
passwordController.text,
phoneController.text,
nameController.text.trim(),
emailController.text.trim(),
passwordController.text.trim(),
phoneController.text.trim(),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B81F9),
backgroundColor: AppTheme.primary,
minimumSize: Size(
double.infinity,
size.height * 0.05,
@ -191,7 +192,6 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
SizedBox(height: verticalSpace * 2.5),
/// Already have account text
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -203,13 +203,14 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
child: const Text(
"Sign In",
style: TextStyle(
color: Color(0xFF3B81F9),
color: AppTheme.primary,
fontWeight: FontWeight.w600,
),
),
),
],
),
SizedBox(height: verticalSpace),
],
),

View File

@ -0,0 +1,367 @@
import 'package:autos/core/theme/app_typography.dart';
import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart';
import 'package:autos/presentation/providers/brand_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class BrandsScreen extends ConsumerStatefulWidget {
const BrandsScreen({super.key});
@override
ConsumerState<BrandsScreen> createState() => _BrandsScreenState();
}
class _BrandsScreenState extends ConsumerState<BrandsScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = 'brands';
String searchQuery = '';
bool dropshipOnly = false;
/// NEW: Selected Brand IDs
final Set<String> selectedBrandIds = {};
bool isSaving = false;
@override
void initState() {
super.initState();
Future.microtask(() {
ref.read(brandsProvider.notifier).fetchBrands();
});
}
@override
Widget build(BuildContext context) {
final topPadding = MediaQuery.of(context).padding.top + 16;
final brandsState = ref.watch(brandsProvider);
return Scaffold(
key: _scaffoldKey,
drawer: SideMenu(
selected: selected,
onItemSelected: (key) => setState(() => selected = key),
),
body: Stack(
children: [
/// TITLE (UNCHANGED)
Positioned(
top: topPadding,
left: 0,
right: 0,
child: Center(
child: Text(
"Turn14 Brands",
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
),
),
),
/// HAMBURGER (UNCHANGED)
HamburgerButton(scaffoldKey: _scaffoldKey),
Positioned.fill(
top: topPadding + 50,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
/// SEARCH (UNCHANGED)
TextField(
onChanged: (value) {
setState(() => searchQuery = value.toLowerCase());
},
decoration: InputDecoration(
hintText: "Search brands...",
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
const SizedBox(height: 8),
/// FILTER + SELECT ALL (UI SAME)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text("Dropship Only"),
Switch(
value: dropshipOnly,
onChanged: (val) {
setState(() => dropshipOnly = val);
},
activeColor: Colors.green,
),
],
),
/// SELECT ALL (LOGIC ONLY)
Row(
children: [
const Text("Select All"),
Checkbox(
value: brandsState.maybeWhen(
data: (brands) =>
brands.isNotEmpty &&
selectedBrandIds.length == brands.length,
orElse: () => false,
),
onChanged: (val) {
brandsState.whenData((brands) {
setState(() {
if (val == true) {
selectedBrandIds.addAll(
brands.map((e) => e.id),
);
} else {
selectedBrandIds.clear();
}
});
});
},
),
],
),
],
),
/// SAVE BUTTON (NEW FEATURE ONLY)
if (selectedBrandIds.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ElevatedButton(
onPressed: isSaving
? null
: () async {
try {
setState(() => isSaving = true);
final allBrands = brandsState.value!;
final selectedBrands = allBrands
.where(
(b) => selectedBrandIds.contains(b.id),
)
.toList();
await ref
.read(brandsProvider.notifier)
.saveSelectedBrands(selectedBrands);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"✅ Collections Saved Successfully",
),
),
);
setState(() {
selectedBrandIds.clear();
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("❌ Save Failed: $e"),
),
);
} finally {
setState(() => isSaving = false);
}
},
child: isSaving
? const CircularProgressIndicator()
: Text(
"Save Collections (${selectedBrandIds.length})",
),
),
),
/// GRID (UI UNCHANGED)
Expanded(
child: brandsState.when(
loading: () =>
const Center(child: CircularProgressIndicator()),
error: (err, _) => Center(
child: Text(
(() {
final raw = err.toString();
// Match 'message: ...' from string
final match = RegExp(
r'message[: ]+([^}]+)',
).firstMatch(raw);
if (match != null) {
return match
.group(1)!
.trim(); // just the message
}
// Fallback: remove 'Exception: ' prefix
return raw.replaceFirst(
RegExp(r'^Exception:\s*'),
'',
);
})(),
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red),
),
),
data: (brands) {
final filteredBrands = brands.where((brand) {
final nameMatch = brand.name.toLowerCase().contains(
searchQuery,
);
final idMatch = brand.id.toString().contains(
searchQuery,
);
final dropshipMatch = dropshipOnly
? brand.dropship
: true;
return (nameMatch || idMatch) && dropshipMatch;
}).toList();
return GridView.builder(
padding: const EdgeInsets.only(bottom: 20),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.95,
),
itemCount: filteredBrands.length,
itemBuilder: (context, index) {
final brand = filteredBrands[index];
final isSelected = selectedBrandIds.contains(
brand.id,
);
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 100,
width: 100,
child: ClipRRect(
borderRadius:
BorderRadius.circular(8),
child: Image.network(
brand.logo,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) =>
const Icon(Icons.image),
),
),
),
const SizedBox(height: 12),
Text("ID: ${brand.id}"),
const SizedBox(height: 4),
Flexible(
child: Text(
brand.name,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
/// DROPSHIP TAG (UNCHANGED)
if (brand.dropship)
Align(
alignment: Alignment.topLeft,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
bottomRight: Radius.circular(12),
),
),
child: const Text(
'Dropship',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
),
/// CHECKBOX ICON (UNCHANGED POSITION)
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(8),
child: GestureDetector(
onTap: () {
setState(() {
if (isSelected) {
selectedBrandIds.remove(brand.id);
} else {
selectedBrandIds.add(brand.id);
}
});
},
child: Icon(
isSelected
? Icons.check_box
: Icons.square_outlined,
color: isSelected
? Colors.green
: Colors.grey,
),
),
),
),
],
),
);
},
);
},
),
),
],
),
),
),
],
),
);
}
}

View File

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

View File

@ -0,0 +1,122 @@
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';
class EbayScreen extends StatefulWidget {
const EbayScreen({super.key});
@override
State<EbayScreen> createState() => _EbayScreenState();
}
class _EbayScreenState extends State<EbayScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = "ebay";
@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(0xFFEFFAFF),
body: Stack(
children: [
/// TITLE
Positioned(
top: topPadding,
left: 0,
right: 0,
child: Center(
child: Text(
"eBay Settings",
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
),
),
),
/// MAIN BOX UI
Center(
child: Container(
width: MediaQuery.of(context).size.width * 0.90,
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40),
margin: EdgeInsets.only(top: topPadding + 60),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 25,
offset: const Offset(0, 12),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
/// Description
Text(
"Connect your eBay store to enable product sync, inventory updates, and order flow.",
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 15,
color: Colors.black54,
height: 1.5,
),
),
const SizedBox(height: 30),
/// BUTTON
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// TODO: Add eBay authorization flow
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00CFFF),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
"Connect your eBay store",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
),
const SizedBox(height: 20),
Text(
"You'll be redirected to eBay to authorize access, then returned here.",
style: const TextStyle(fontSize: 13, color: Colors.black45),
textAlign: TextAlign.center,
),
],
),
),
),
/// HAMBURGER BUTTON
HamburgerButton(scaffoldKey: _scaffoldKey),
],
),
);
}
}

View File

@ -0,0 +1,245 @@
import 'package:autos/core/theme/app_typography.dart';
import 'package:flutter/material.dart';
class ImportsHeader extends StatefulWidget {
final int visibleCount;
final int pageSize;
final int totalCount;
final int selectedCount;
final Function(String) onSearch;
final Function(bool) onStockToggle;
final Function(String) onCategoryChange;
final Function(String) onSubcategoryChange;
final Function(String) onOfferStatusChange;
final Function(double) onPriceChange;
const ImportsHeader({
super.key,
required this.visibleCount,
required this.pageSize,
required this.totalCount,
required this.selectedCount,
required this.onSearch,
required this.onStockToggle,
required this.onCategoryChange,
required this.onSubcategoryChange,
required this.onOfferStatusChange,
required this.onPriceChange,
});
@override
State<ImportsHeader> createState() => _ImportsHeaderState();
}
class _ImportsHeaderState extends State<ImportsHeader> {
String category = "All";
String subcategory = "All";
String offerStatus = "All";
bool inStockOnly = false;
double priceFloor = 0;
@override
Widget build(BuildContext context) {
final bool isMobile = MediaQuery.of(context).size.width < 900;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// TOP SECTION (OVERFLOW SAFE)
isMobile ? _mobileTopBar() : _desktopTopBar(),
const SizedBox(height: 20),
/// FILTER ROW
Wrap(
spacing: 16,
runSpacing: 12,
children: [
/// CATEGORY (RESETS SUBCATEGORY)
_dropdown(
label: "Category",
value: category,
items: const ["All", "Floor Mats", "Wheels"],
onChanged: (v) {
final value = v ?? "All";
setState(() {
category = value;
subcategory = "All"; // RESET
});
widget.onCategoryChange(value);
widget.onSubcategoryChange("All"); // FORCE API RESET
},
),
/// SUBCATEGORY
_dropdown(
label: "Subcategory",
value: subcategory,
items: const ["All", "Wheels - Cast", "Wheels - Forged"],
onChanged: (v) {
final value = v ?? "All";
setState(() => subcategory = value);
widget.onSubcategoryChange(value);
},
),
/// OFFER STATUS
_dropdown(
label: "Offer Status",
value: offerStatus,
items: const ["All", "Published", "Draft"],
onChanged: (v) {
final value = v ?? "All";
setState(() => offerStatus = value);
widget.onOfferStatusChange(value);
},
),
/// PRICE SLIDER
SizedBox(
width: 260,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Price Floor"),
Slider(
min: 0,
max: 1000,
value: priceFloor,
onChanged: (value) {
setState(() => priceFloor = value);
widget.onPriceChange(value);
},
),
Text("Price ≥ \$${priceFloor.toInt()}"),
],
),
),
],
),
],
);
}
/// DESKTOP TOP BAR
Widget _desktopTopBar() {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: _titleSection()),
const SizedBox(width: 12),
Flexible(child: _searchBox()),
const SizedBox(width: 12),
Wrap(
spacing: 12,
crossAxisAlignment: WrapCrossAlignment.center,
children: [_stockToggle(), _actionButton()],
),
],
);
}
/// MOBILE TOP BAR
Widget _mobileTopBar() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_titleSection(),
const SizedBox(height: 12),
_searchBox(),
const SizedBox(height: 12),
Wrap(spacing: 12, children: [_stockToggle(), _actionButton()]),
],
);
}
/// TITLE
Widget _titleSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Imports", style: AppTypo.h3),
Text(
"${widget.visibleCount} of ${widget.totalCount} products",
style: AppTypo.bodySmall,
),
],
);
}
/// SEARCH BOX
Widget _searchBox() {
return SizedBox(
height: 44,
child: TextField(
onChanged: widget.onSearch,
decoration: const InputDecoration(
hintText: "Search imports...",
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
),
);
}
/// STOCK TOGGLE
Widget _stockToggle() {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Switch(
value: inStockOnly,
onChanged: (v) {
setState(() => inStockOnly = v);
widget.onStockToggle(v);
},
),
const SizedBox(width: 6),
const Text("In Stock Only"),
],
);
}
/// ACTION BUTTON
Widget _actionButton() {
return ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.0),
),
),
onPressed: () {},
child: const Text("Bulk Action"),
);
}
/// REUSABLE DROPDOWN
Widget _dropdown({
required String label,
required String value,
required List<String> items,
required Function(String?) onChanged,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
const SizedBox(height: 6),
SizedBox(
width: 180,
child: DropdownButtonFormField<String>(
value: value,
onChanged: onChanged,
items: items
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
.toList(),
decoration: const InputDecoration(border: OutlineInputBorder()),
),
),
],
);
}
}

View File

@ -0,0 +1,315 @@
import 'package:autos/core/theme/app_typography.dart';
import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart';
import 'package:autos/domain/entities/import_product.dart';
import 'package:autos/presentation/providers/imports_provider.dart';
import 'package:autos/presentation/screens/imports/imports_header.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ImportsScreen extends ConsumerStatefulWidget {
const ImportsScreen({super.key});
@override
ConsumerState<ImportsScreen> createState() => _ImportsScreenState();
}
class _ImportsScreenState extends ConsumerState<ImportsScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = 'imports';
/// Selection & Filters
Set<String> selectedProducts = {};
bool inStockOnly = false;
String searchQuery = '';
@override
void initState() {
super.initState();
/// Load API data on screen open
Future.microtask(() {
ref.read(importsProvider.notifier).loadProducts();
});
}
/// FILTER LOGIC
List<ImportProductEntity> filterProducts(List<ImportProductEntity> products) {
return products.where((product) {
final matchesSearch = product.name.toLowerCase().contains(
searchQuery.toLowerCase(),
);
final matchesStock =
!inStockOnly || product.inventory > 0; // REAL STOCK CHECK
return matchesSearch && matchesStock;
}).toList();
}
@override
Widget build(BuildContext context) {
final double topPadding = MediaQuery.of(context).padding.top + 16;
final productsAsync = ref.watch(importsProvider);
return Scaffold(
key: _scaffoldKey,
drawer: SideMenu(
selected: selected,
onItemSelected: (key) {
setState(() => selected = key);
},
),
backgroundColor: const Color(0xFFF6FDFF),
body: Stack(
children: [
/// TITLE
Positioned(
top: topPadding,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Imports",
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
),
],
),
),
/// MAIN CONTENT
SingleChildScrollView(
padding: EdgeInsets.fromLTRB(16, topPadding + 70, 16, 20),
child: Column(
children: [
ImportsHeader(
visibleCount: 47,
pageSize: 48,
totalCount: 373,
selectedCount: selectedProducts.length,
onSearch: (value) {
debugPrint("Search: $value");
},
onStockToggle: (value) {
debugPrint("Stock Only: $value");
},
onCategoryChange: (value) {
debugPrint("Category: $value");
},
onSubcategoryChange: (value) {
debugPrint("Subcategory: $value");
},
onOfferStatusChange: (value) {
debugPrint("Offer Status: $value");
},
onPriceChange: (value) {
debugPrint("Price Floor: $value");
},
),
const SizedBox(height: 10),
/// PRODUCT LIST FROM API
productsAsync.when(
loading: () => const Padding(
padding: EdgeInsets.only(top: 40),
child: CircularProgressIndicator(),
),
error: (e, _) => Padding(
padding: const EdgeInsets.all(20),
child: Text("❌ Error: $e"),
),
data: (products) {
final filtered = filterProducts(products);
return Column(
children: [
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: filtered.length,
itemBuilder: (context, index) {
final product = filtered[index];
final isSelected = selectedProducts.contains(
product.id,
);
return ImportProductCard(
product: product,
selected: isSelected,
onTap: () {
setState(() {
if (isSelected) {
selectedProducts.remove(product.id);
} else {
selectedProducts.add(product.id);
}
});
},
);
},
),
const SizedBox(height: 20),
/// SUBMIT BUTTON
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: selectedProducts.isEmpty
? null
: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Submitted ${selectedProducts.length} products',
),
),
);
},
child: Text(
'Submit (${selectedProducts.length}) selected items',
),
),
),
],
);
},
),
],
),
),
/// HAMBURGER BUTTON
HamburgerButton(scaffoldKey: _scaffoldKey),
],
),
);
}
}
class ImportProductCard extends StatelessWidget {
final ImportProductEntity product;
final bool selected;
final VoidCallback onTap;
const ImportProductCard({
super.key,
required this.product,
required this.selected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// TOP ROW
Row(
children: [
Checkbox(value: selected, onChanged: (_) => onTap()),
const Text("Select"),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(.1),
borderRadius: BorderRadius.circular(20),
),
child: Text("Will list: ${product.inventory}"),
),
],
),
/// IMAGE
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
product.image,
height: 160,
width: double.infinity,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) =>
const Icon(Icons.image_not_supported, size: 80),
),
),
const SizedBox(height: 10),
/// TITLE
Text(
product.name,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
),
const SizedBox(height: 6),
/// META INFO
Text("Part #: ${product.partNumber}"),
Text("${product.category} > ${product.subcategory}"),
const SizedBox(height: 6),
/// PRICE + INVENTORY
Text(
"Price: \$${product.price}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text("Inventory: ${product.inventory}"),
const SizedBox(height: 10),
/// DESCRIPTION PREVIEW
Text(product.name, maxLines: 2, overflow: TextOverflow.ellipsis),
const SizedBox(height: 8),
/// STATUS TAGS
Row(
children: [
_statusChip("published", product.offerStatus == "published"),
const SizedBox(width: 8),
_statusChip("Listing", true),
],
),
],
),
),
);
}
Widget _statusChip(String label, bool active) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: active ? Colors.green.withOpacity(.15) : Colors.grey[200],
borderRadius: BorderRadius.circular(20),
),
child: Text(
label,
style: TextStyle(
color: active ? Colors.green : Colors.grey[600],
fontWeight: FontWeight.w600,
),
),
);
}
}

View File

@ -0,0 +1,310 @@
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),
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(
0xFFFF4C4C,
), // override global primary color
foregroundColor: Colors.white, // text color
elevation: 6,
shadowColor: Colors.black.withValues(alpha: 0.2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 14),
textStyle: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: const Text("Cancel Subscription"),
),
),
],
),
);
}
// ------------------------------------------------------------
// BILLING CARD
// ------------------------------------------------------------
Widget _billingCard() {
return _baseCard(
title: "Billing",
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_row("Card", "•••• •••• •••• 1325 VISA"),
_row("Expires", "3 / 2028"),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
child: const Text("Update Billing"),
),
),
const SizedBox(width: 10),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: const Text("Add Coupon"),
),
),
],
),
],
),
);
}
// ------------------------------------------------------------
// BILLING HISTORY
// ------------------------------------------------------------
Widget _billingHistoryCard() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Billing History",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_tableRow(
isHeader: true,
cells: ["Invoice", "Date", "Status", "Amount", "PDF"],
),
_tableRow(
cells: ["INV-0001", "24 Nov 2025", "Paid", "\$99.00", "View"],
),
const SizedBox(height: 14),
const Text(
"Showing 1 of 1 entries",
style: TextStyle(color: Colors.black54),
),
],
),
);
}
// ------------------------------------------------------------
// HELPERS
// ------------------------------------------------------------
Widget _baseCard({required String title, required Widget child}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
child,
],
),
);
}
BoxDecoration _cardDecoration() {
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
blurRadius: 12,
color: Colors.black.withOpacity(0.05),
offset: const Offset(0, 6),
),
],
);
}
Widget _row(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(color: Colors.black54)),
Text(value, style: const TextStyle(fontWeight: FontWeight.w600)),
],
),
);
}
Widget _tableRow({bool isHeader = false, required List<String> cells}) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: isHeader ? const Color(0xFFF5F8FA) : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: cells.map((e) {
return Expanded(
child: Text(
e,
style: TextStyle(
fontWeight: isHeader ? FontWeight.bold : FontWeight.normal,
color: e == "Paid" ? Colors.green : Colors.black,
),
),
);
}).toList(),
),
);
}
}

View File

@ -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),
),
),
],
);
}
}

View File

@ -0,0 +1,304 @@
import 'package:autos/core/theme/app_typography.dart';
import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart';
import 'package:autos/presentation/providers/products_provider.dart';
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ProductsScreen extends ConsumerStatefulWidget {
const ProductsScreen({super.key});
@override
ConsumerState<ProductsScreen> createState() => _ProductsScreenState();
}
class _ProductsScreenState extends ConsumerState<ProductsScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = 'products';
bool _hasFetched = false; // prevents multiple API calls
// Filter state
String _searchText = '';
bool _inStockOnly = false;
@override
void initState() {
super.initState();
/// Wait for widget + provider to be fully ready
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadUserFromProvider();
});
}
/// LOAD USER FROM RIVERPOD (NOT SECURE STORAGE)
void _loadUserFromProvider() {
final userState = ref.read(userProvider);
final user = userState.value;
if (user == null || user.id == null || user.id!.isEmpty) {
debugPrint("❌ USER NOT FOUND IN PROVIDER");
return;
}
if (_hasFetched) return; // prevent duplicate API calls
_hasFetched = true;
debugPrint(" USER FROM PROVIDER ID: ${user.id}");
ref.read(productProvider.notifier).getProducts(user.id!);
}
/// Filter products based on search text and stock
List productsFilter(List products) {
return products.where((product) {
final matchesSearch =
_searchText.isEmpty ||
product.name.toLowerCase().contains(_searchText.toLowerCase());
final matchesStock = !_inStockOnly || (product.inStock ?? true);
return matchesSearch && matchesStock;
}).toList();
}
@override
Widget build(BuildContext context) {
final productsState = ref.watch(productProvider);
final double topPadding = MediaQuery.of(context).padding.top + 16;
return Scaffold(
key: _scaffoldKey,
drawer: SideMenu(
selected: selected,
onItemSelected: (key) {
setState(() => selected = key);
},
),
body: Stack(
children: [
/// Page Title
Positioned(
top: topPadding,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Brand Products",
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
),
],
),
),
/// Main Content
SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// SEARCH + FILTER + COUNT ROW (always visible)
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,
),
),
),
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),
),
),
const SizedBox(width: 12),
// Count button
productsState.when(
data: (products) {
final filteredProducts = productsFilter(products);
return ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"${filteredProducts.length} products",
style: const TextStyle(fontSize: 12),
),
);
},
loading: () => const SizedBox(),
error: (_, __) => const SizedBox(),
),
],
),
const SizedBox(height: 16),
/// 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,
),
itemBuilder: (context, index) {
final product = filteredProducts[index];
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
blurRadius: 10,
color: Colors.black.withOpacity(0.05),
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Product Image
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
product.image,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) =>
const Icon(Icons.image_not_supported),
),
),
),
const SizedBox(height: 10),
/// Product Name
Text(
product.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
/// Price
Text(
"${product.price}",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
);
},
);
},
),
],
),
),
/// Hamburger Button
HamburgerButton(scaffoldKey: _scaffoldKey),
],
),
);
}
}

View File

@ -0,0 +1,237 @@
import 'package:autos/core/theme/app_theme.dart';
import 'package:flutter/material.dart';
class CreateLocationScreen extends StatefulWidget {
const CreateLocationScreen({super.key});
@override
State<CreateLocationScreen> createState() => _CreateLocationScreenState();
}
class _CreateLocationScreenState extends State<CreateLocationScreen> {
// Controllers
final TextEditingController storeName = TextEditingController();
final TextEditingController phone = TextEditingController();
final TextEditingController address1 = TextEditingController();
final TextEditingController city = TextEditingController();
final TextEditingController stateCtrl = TextEditingController();
final TextEditingController postalCode = TextEditingController();
final TextEditingController country = TextEditingController();
final TextEditingController timeZone = TextEditingController(
text: "America/New_York",
);
TimeOfDay openTime = const TimeOfDay(hour: 9, minute: 0);
TimeOfDay closeTime = const TimeOfDay(hour: 18, minute: 0);
Future<void> pickTime({required bool isOpen}) async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: isOpen ? openTime : closeTime,
);
if (picked != null) {
setState(() {
if (isOpen)
openTime = picked;
else
closeTime = picked;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFEFFAFF),
appBar: AppBar(
title: const Text(
"Create New Location",
style: TextStyle(color: Colors.white),
),
backgroundColor: AppTheme.primary,
elevation: 0,
foregroundColor: Colors.black,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Container(
padding: const EdgeInsets.all(30),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_input("Store Name *", "Enter store name", storeName),
_input("Phone *", "Enter phone number", phone),
_input("Address Line 1 *", "Enter address", address1),
Row(
children: [
Expanded(child: _input("City *", "City", city)),
const SizedBox(width: 12),
Expanded(child: _input("State *", "State", stateCtrl)),
],
),
Row(
children: [
Expanded(
child: _input("Postal Code *", "Postal Code", postalCode),
),
const SizedBox(width: 12),
Expanded(
child: _input(
"Country *",
"Country Code (e.g. US)",
country,
),
),
],
),
_input("Time Zone *", "America/New_York", timeZone),
const SizedBox(height: 15),
Row(
children: [
Expanded(
child: _timePicker(
label: "Open Time *",
time: openTime,
onTap: () => pickTime(isOpen: true),
),
),
const SizedBox(width: 12),
Expanded(
child: _timePicker(
label: "Close Time *",
time: closeTime,
onTap: () => pickTime(isOpen: false),
),
),
],
),
const SizedBox(height: 25),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
final json = {
"store_name": storeName.text,
"phone": phone.text,
"address1": address1.text,
"city": city.text,
"state": stateCtrl.text,
"postal_code": postalCode.text,
"country": country.text,
"timezone": timeZone.text,
"open_time": openTime.format(context),
"close_time": closeTime.format(context),
};
print("📦 LOCATION JSON → $json");
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00CFFF),
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
"Save & Console JSON",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
),
],
),
),
),
);
}
// Reusable TextField
Widget _input(String label, String hint, TextEditingController controller) {
return Padding(
padding: const EdgeInsets.only(bottom: 18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14),
),
const SizedBox(height: 8),
TextField(
controller: controller,
decoration: InputDecoration(
hintText: hint,
filled: true,
fillColor: const Color(0xFFF0F6FF),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
),
],
),
);
}
// Time Picker Widget
Widget _timePicker({
required String label,
required TimeOfDay time,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 18),
decoration: BoxDecoration(
color: const Color(0xFFF0F6FF),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Text(
time.format(context),
style: const TextStyle(fontSize: 16),
),
const Spacer(),
const Icon(Icons.access_time, size: 20),
],
),
),
],
),
);
}
}

View File

@ -0,0 +1,132 @@
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';
class StoreScreen extends StatefulWidget {
const StoreScreen({super.key});
@override
State<StoreScreen> createState() => _StoreScreenState();
}
class _StoreScreenState extends State<StoreScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = "ebay";
@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);
},
),
body: Stack(
children: [
/// TITLE
Positioned(
top: topPadding,
left: 0,
right: 0,
child: Center(
child: Text(
"eBay Locations",
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
),
),
),
/// MAIN WITH BUTTONS
Center(
child: Container(
width: MediaQuery.of(context).size.width * 0.90,
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40),
margin: EdgeInsets.only(top: topPadding + 60),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 25,
offset: const Offset(0, 12),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
/// SAVE SELECTED BUTTON
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// TODO: Save Selected Flow
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00CFFF),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
"Save Selected",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
),
const SizedBox(height: 20),
/// NEW LOCATION BUTTON
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
AppRoutePaths.createStoreLocation,
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00CFFF),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
"Create New Location",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
),
],
),
),
),
/// HAMBURGER BUTTON
HamburgerButton(scaffoldKey: _scaffoldKey),
],
),
);
}
}

View File

@ -2,8 +2,10 @@ import 'package:autos/core/theme/app_typography.dart';
import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart';
import 'package:autos/presentation/providers/user_provider.dart';
import 'package:autos/presentation/providers/turn14_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
class Turn14Screen extends ConsumerStatefulWidget {
const Turn14Screen({super.key});
@ -16,9 +18,16 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = 'turn14';
// controllers
final TextEditingController clientIdController = TextEditingController();
final TextEditingController clientSecretController = TextEditingController();
@override
Widget build(BuildContext context) {
final user = ref.watch(userDetailsProvider);
final asyncUser = ref.watch(userProvider);
final turn14State = ref.watch(turn14Provider);
final user = asyncUser.value;
final double topPadding = MediaQuery.of(context).padding.top + 16;
return Scaffold(
@ -46,13 +55,13 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
),
),
/// Main Scrollable UI
SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Description Row
Row(
children: [
Text("", style: const TextStyle(fontSize: 28)),
@ -70,21 +79,16 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
),
const SizedBox(height: 20),
// Client ID Input
_inputField(
label: "Client ID",
controller: TextEditingController(),
),
_inputField(label: "Client ID", controller: clientIdController),
const SizedBox(height: 20),
// Secret Key Input With Eye Icon
_passwordField(
label: "Secret Key",
controller: TextEditingController(),
controller: clientSecretController,
),
const SizedBox(height: 25),
// Save Button
/// SAVE BUTTON
SizedBox(
width: double.infinity,
child: ElevatedButton(
@ -95,8 +99,55 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
),
backgroundColor: const Color(0xFF00C9FF),
),
onPressed: () {},
child: const Text(
onPressed: turn14State.isLoading
? null
: () async {
if (user == null) {
Fluttertoast.showToast(
msg: "⚠️ User not found",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0,
);
return;
}
if (clientIdController.text.trim().isEmpty ||
clientSecretController.text.trim().isEmpty) {
Fluttertoast.showToast(
msg: "⚠️ Please fill all fields",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.orange,
textColor: Colors.white,
fontSize: 16.0,
);
return;
}
await ref
.read(turn14Provider.notifier)
.saveCredentials(
userId: user.id,
clientId: clientIdController.text.trim(),
clientSecret:
clientSecretController.text.trim(),
);
Fluttertoast.showToast(
msg: "✅ Turn14 Credentials Saved Successfully",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.green,
textColor: Colors.white,
fontSize: 16.0,
);
},
child: turn14State.isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"Save Credentials",
style: TextStyle(
color: Colors.white,
@ -106,54 +157,12 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
),
),
),
const SizedBox(height: 20),
// Info Box
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFE8F1FF),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: const [
Icon(Icons.info, color: Colors.blue),
SizedBox(width: 10),
Text(
"No credentials saved yet.",
style: TextStyle(color: Colors.black87),
),
],
),
),
_infoBox(),
const SizedBox(height: 20),
// Tips Box
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFE8FCFF),
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
"💡 Connection Tips",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
SizedBox(height: 10),
Text("• Ensure your credentials are valid and active."),
Text("• Credentials are encrypted before saving."),
Text("• Contact Turn14 support for API setup help."),
],
),
),
_tipsBox(),
],
),
),
@ -164,6 +173,7 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
);
}
/// UI COMPONENTS
Widget _inputField({
required String label,
required TextEditingController controller,
@ -224,4 +234,49 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
},
);
}
Widget _infoBox() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFE8F1FF),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: const [
Icon(Icons.info, color: Colors.blue),
SizedBox(width: 10),
Text(
"No credentials saved yet.",
style: TextStyle(color: Colors.black87),
),
],
),
);
}
Widget _tipsBox() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFE8FCFF),
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
"💡 Connection Tips",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
SizedBox(height: 10),
Text("• Ensure your credentials are valid and active."),
Text("• Credentials are encrypted before saving."),
Text("• Contact Turn14 support for API setup help."),
],
),
);
}
}

View File

@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.7.1"
archive:
dependency: transitive
description:
name: archive
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
url: "https://pub.dev"
source: hosted
version: "4.0.7"
args:
dependency: transitive
description:
@ -49,6 +57,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
cli_config:
dependency: transitive
description:
@ -57,6 +73,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.0"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
@ -214,6 +238,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
flutter_launcher_icons:
dependency: "direct main"
description:
name: flutter_launcher_icons
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
url: "https://pub.dev"
source: hosted
version: "0.14.4"
flutter_lints:
dependency: "direct dev"
description:
@ -328,6 +360,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image:
dependency: transitive
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev"
source: hosted
version: "4.5.4"
io:
dependency: transitive
description:
@ -344,6 +384,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
@ -424,6 +472,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
onesignal_flutter:
dependency: "direct main"
description:
name: onesignal_flutter
sha256: b5bb43bf496ddb5e3975ba54c6477cc2d1fcd18fb3698f195d2e0bfd376ddafd
url: "https://pub.dev"
source: hosted
version: "5.3.4"
package_config:
dependency: transitive
description:
@ -488,6 +544,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
platform:
dependency: transitive
description:
@ -512,6 +576,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.2"
posix:
dependency: transitive
description:
name: posix
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
pub_semver:
dependency: transitive
description:
@ -733,6 +805,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yaml:
dependency: transitive
description:

View File

@ -20,6 +20,8 @@ dependencies:
fluttertoast: ^9.0.0
flutter_secure_storage: ^9.2.4
youtube_player_flutter: ^9.1.3
flutter_launcher_icons: ^0.14.4
onesignal_flutter: ^5.3.4
dev_dependencies:
flutter_test:
@ -49,3 +51,10 @@ flutter:
weight: 600
- asset: assets/fonts/Nunito-Bold.ttf
weight: 700
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/auth/autos_transp.png"
adaptive_icon_background: "#FFFFFF"
adaptive_icon_foreground: "assets/auth/autos_transp.png"