diff --git a/analysis_options.yaml b/analysis_options.yaml
index 405c8ae..a5f616f 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -10,6 +10,7 @@
analyzer:
errors:
avoid_print: ignore
+ deprecated_member_use_from_same_package: ignore
include: package:flutter_lints/flutter.yaml
linter:
diff --git a/assets/icon/outline/home.svg b/assets/icon/outline/home.svg
new file mode 100644
index 0000000..4ce7443
--- /dev/null
+++ b/assets/icon/outline/home.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icon/outline/map.svg b/assets/icon/outline/map.svg
new file mode 100644
index 0000000..428ac11
--- /dev/null
+++ b/assets/icon/outline/map.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icon/outline/notification-bing.svg b/assets/icon/outline/notification-bing.svg
new file mode 100644
index 0000000..a13beb2
--- /dev/null
+++ b/assets/icon/outline/notification-bing.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icon/outline/search.svg b/assets/icon/outline/search.svg
new file mode 100644
index 0000000..6c41ba7
--- /dev/null
+++ b/assets/icon/outline/search.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icon/outline/setting.svg b/assets/icon/outline/setting.svg
new file mode 100644
index 0000000..180f297
--- /dev/null
+++ b/assets/icon/outline/setting.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icon/outline/user.svg b/assets/icon/outline/user.svg
new file mode 100644
index 0000000..ce6f47f
--- /dev/null
+++ b/assets/icon/outline/user.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/image/onboarding/banner-1.png b/assets/image/onboarding/banner-1.png
new file mode 100644
index 0000000..dacf803
Binary files /dev/null and b/assets/image/onboarding/banner-1.png differ
diff --git a/assets/image/onboarding/banner-2.png b/assets/image/onboarding/banner-2.png
new file mode 100644
index 0000000..b5044d2
Binary files /dev/null and b/assets/image/onboarding/banner-2.png differ
diff --git a/assets/image/onboarding/banner-3.png b/assets/image/onboarding/banner-3.png
new file mode 100644
index 0000000..db03c67
Binary files /dev/null and b/assets/image/onboarding/banner-3.png differ
diff --git a/lib/core/gen/assets.gen.dart b/lib/core/gen/assets.gen.dart
index bea6c4f..5537cbd 100644
--- a/lib/core/gen/assets.gen.dart
+++ b/lib/core/gen/assets.gen.dart
@@ -7,7 +7,24 @@
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
+import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
+import 'package:flutter_svg/flutter_svg.dart' as _svg;
+import 'package:vector_graphics/vector_graphics.dart' as _vg;
+
+class $AssetsIconGen {
+ const $AssetsIconGen();
+
+ /// Directory path: assets/icon/outline
+ $AssetsIconOutlineGen get outline => const $AssetsIconOutlineGen();
+}
+
+class $AssetsImageGen {
+ const $AssetsImageGen();
+
+ /// Directory path: assets/image/onboarding
+ $AssetsImageOnboardingGen get onboarding => const $AssetsImageOnboardingGen();
+}
class $AssetsLogoGen {
const $AssetsLogoGen();
@@ -20,9 +37,80 @@ class $AssetsLogoGen {
List get values => [appLogo];
}
+class $AssetsIconOutlineGen {
+ const $AssetsIconOutlineGen();
+
+ /// File path: assets/icon/outline/home.svg
+ SvgGenImage get home =>
+ const SvgGenImage('assets/icon/outline/home.svg', size: Size(24.0, 24.0));
+
+ /// File path: assets/icon/outline/map.svg
+ SvgGenImage get map =>
+ const SvgGenImage('assets/icon/outline/map.svg', size: Size(24.0, 24.0));
+
+ /// File path: assets/icon/outline/notification-bing.svg
+ SvgGenImage get notificationBing => const SvgGenImage(
+ 'assets/icon/outline/notification-bing.svg',
+ size: Size(24.0, 24.0),
+ );
+
+ /// File path: assets/icon/outline/search.svg
+ SvgGenImage get search => const SvgGenImage(
+ 'assets/icon/outline/search.svg',
+ size: Size(18.0, 18.0),
+ );
+
+ /// File path: assets/icon/outline/setting.svg
+ SvgGenImage get setting => const SvgGenImage(
+ 'assets/icon/outline/setting.svg',
+ size: Size(24.0, 24.0),
+ );
+
+ /// File path: assets/icon/outline/user.svg
+ SvgGenImage get user =>
+ const SvgGenImage('assets/icon/outline/user.svg', size: Size(24.0, 24.0));
+
+ /// List of all assets
+ List get values => [
+ home,
+ map,
+ notificationBing,
+ search,
+ setting,
+ user,
+ ];
+}
+
+class $AssetsImageOnboardingGen {
+ const $AssetsImageOnboardingGen();
+
+ /// File path: assets/image/onboarding/banner-1.png
+ AssetGenImage get banner1 => const AssetGenImage(
+ 'assets/image/onboarding/banner-1.png',
+ size: Size(888.0, 879.0),
+ );
+
+ /// File path: assets/image/onboarding/banner-2.png
+ AssetGenImage get banner2 => const AssetGenImage(
+ 'assets/image/onboarding/banner-2.png',
+ size: Size(888.0, 879.0),
+ );
+
+ /// File path: assets/image/onboarding/banner-3.png
+ AssetGenImage get banner3 => const AssetGenImage(
+ 'assets/image/onboarding/banner-3.png',
+ size: Size(888.0, 879.0),
+ );
+
+ /// List of all assets
+ List get values => [banner1, banner2, banner3];
+}
+
class Assets {
const Assets._();
+ static const $AssetsIconGen icon = $AssetsIconGen();
+ static const $AssetsImageGen image = $AssetsImageGen();
static const $AssetsLogoGen logo = $AssetsLogoGen();
}
@@ -95,3 +183,75 @@ class AssetGenImage {
String get keyName => _assetName;
}
+
+class SvgGenImage {
+ const SvgGenImage(this._assetName, {this.size, this.flavors = const {}})
+ : _isVecFormat = false;
+
+ const SvgGenImage.vec(this._assetName, {this.size, this.flavors = const {}})
+ : _isVecFormat = true;
+
+ final String _assetName;
+ final Size? size;
+ final Set flavors;
+ final bool _isVecFormat;
+
+ _svg.SvgPicture svg({
+ Key? key,
+ bool matchTextDirection = false,
+ AssetBundle? bundle,
+ String? package,
+ double? width,
+ double? height,
+ BoxFit fit = BoxFit.contain,
+ AlignmentGeometry alignment = Alignment.center,
+ bool allowDrawingOutsideViewBox = false,
+ WidgetBuilder? placeholderBuilder,
+ String? semanticsLabel,
+ bool excludeFromSemantics = false,
+ _svg.SvgTheme? theme,
+ ColorFilter? colorFilter,
+ Clip clipBehavior = Clip.hardEdge,
+ @deprecated Color? color,
+ @deprecated BlendMode colorBlendMode = BlendMode.srcIn,
+ @deprecated bool cacheColorFilter = false,
+ }) {
+ final _svg.BytesLoader loader;
+ if (_isVecFormat) {
+ loader = _vg.AssetBytesLoader(
+ _assetName,
+ assetBundle: bundle,
+ packageName: package,
+ );
+ } else {
+ loader = _svg.SvgAssetLoader(
+ _assetName,
+ assetBundle: bundle,
+ packageName: package,
+ theme: theme,
+ );
+ }
+ return _svg.SvgPicture(
+ loader,
+ key: key,
+ matchTextDirection: matchTextDirection,
+ width: width,
+ height: height,
+ fit: fit,
+ alignment: alignment,
+ allowDrawingOutsideViewBox: allowDrawingOutsideViewBox,
+ placeholderBuilder: placeholderBuilder,
+ semanticsLabel: semanticsLabel,
+ excludeFromSemantics: excludeFromSemantics,
+ colorFilter:
+ colorFilter ??
+ (color == null ? null : ColorFilter.mode(color, colorBlendMode)),
+ clipBehavior: clipBehavior,
+ cacheColorFilter: cacheColorFilter,
+ );
+ }
+
+ String get path => _assetName;
+
+ String get keyName => _assetName;
+}
diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart
index ec268cf..a8e84c9 100644
--- a/lib/core/routes/app_router.dart
+++ b/lib/core/routes/app_router.dart
@@ -1,35 +1,78 @@
import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:go_router/go_router.dart';
-import 'package:proxibuy/presentation/ui/screens/home/details_page.dart';
+import 'package:proxibuy/presentation/providers/cubit/user_info_cubit.dart';
+import 'package:proxibuy/presentation/ui/screens/auth/auth_page.dart';
import 'package:proxibuy/presentation/ui/screens/home/home_page.dart';
class AppRouter {
- static final home = '/';
- static final details = '/details';
+ static final initial = '/';
+ static final home = '/home';
+ static final explore = '/explore';
+ static final setting = '/settings';
+
static final GlobalKey navigatorKey =
GlobalKey();
- GoRouter createRouter() {
- return GoRouter(
- navigatorKey: navigatorKey,
- routes: [
- // Define your routes here
- GoRoute(
- path: '/',
- builder: (BuildContext context, GoRouterState state) {
- return const MyHomePage();
- },
- routes: [
- GoRoute(
- path: details,
- builder: (BuildContext context, GoRouterState state) {
- print(state.uri.queryParameters);
- return const DetailsPage();
- },
- ),
- ],
- ),
- ],
- );
- }
+ GoRouter createRouter = GoRouter(
+ navigatorKey: navigatorKey,
+ initialLocation: home,
+ routes: [
+ GoRoute(
+ path: home,
+ builder: (BuildContext context, GoRouterState state) {
+ return BlocConsumer(
+ listener: (context, state) {
+ if (state is UserInfoSuccess || state is UserInfoFail) {
+ FlutterNativeSplash.remove();
+ }
+ },
+ builder: (context, state) {
+ if (state is UserInfoSuccess) {
+ return const HomePage();
+ } else {
+ return const AuthPage();
+ }
+ },
+ );
+ },
+ routes: [],
+ ),
+ // StatefulShellRoute.indexedStack(
+ // builder: (context, state, navigationShell) =>
+ // BlocBuilder(
+ // builder: (context, state) {
+ // if (state is UserInfoSuccess) {
+ // return CustomBottomNav(child: navigationShell);
+ // } else {
+ // return AuthPage();
+ // }
+ // },
+ // ),
+ // branches: [
+ // StatefulShellBranch(routes: [
+ // GoRoute(
+ // path: initial,
+ // builder: (context, state) {
+ // return HomeScreen();
+ // },
+ // ),
+ // ]),
+ // StatefulShellBranch(routes: [
+ // GoRoute(
+ // path: explore,
+ // builder: (context, state) => ExploreScreen(),
+ // ),
+ // ]),
+ // StatefulShellBranch(routes: [
+ // GoRoute(
+ // path: setting,
+ // builder: (context, state) => SettingScreen(),
+ // ),
+ // ]),
+ // ],
+ // ),
+ ],
+ );
}
diff --git a/lib/core/services/api/api_routes.dart b/lib/core/services/api/api_routes.dart
new file mode 100644
index 0000000..c5f05b9
--- /dev/null
+++ b/lib/core/services/api/api_routes.dart
@@ -0,0 +1,3 @@
+class ApiRoutes {
+ static final userInfo = '/userinfo';
+}
diff --git a/lib/core/services/api/api_service.dart b/lib/core/services/api/api_service.dart
new file mode 100644
index 0000000..9afa80d
--- /dev/null
+++ b/lib/core/services/api/api_service.dart
@@ -0,0 +1,94 @@
+import 'package:dio/dio.dart';
+import 'package:flutter/foundation.dart';
+import 'package:pretty_dio_logger/pretty_dio_logger.dart';
+
+final ApiService apiService = ApiService();
+
+class ApiService {
+ final keycloak = 'https://keycloak.liara.run/';
+ final clientID = 'frontend';
+ final realm = 'lba';
+
+ late Dio _dio;
+
+ void setAuthToken(String token) {
+ _dio.options.headers['Authorization'] = 'Bearer $token';
+ }
+
+ ApiService() {
+ _dio = Dio(
+ BaseOptions(
+ baseUrl:
+ '$keycloak/$realm/protocol/openid-connect', // 🔹 Set your API base URL
+ connectTimeout: const Duration(seconds: 10), // Timeout settings
+ receiveTimeout: const Duration(seconds: 10),
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ ),
+ )..interceptors.add(PrettyDioLogger(
+ enabled: kDebugMode,
+ ));
+ }
+
+ /// 🔹 Handle GET requests
+ Future get(String endpoint, {Map? params}) async {
+ try {
+ Response response = await _dio.get(endpoint, queryParameters: params);
+ return response.data;
+ } catch (e) {
+ _handleError(e);
+ }
+ }
+
+ /// 🔹 Handle POST requests
+ Future post(String endpoint, {dynamic data}) async {
+ try {
+ Response response = await _dio.post(endpoint, data: data);
+ return response.data;
+ } catch (e) {
+ _handleError(e);
+ }
+ }
+
+ /// 🔹 Handle PUT requests
+ Future put(String endpoint, {dynamic data}) async {
+ try {
+ Response response = await _dio.put(endpoint, data: data);
+ return response.data;
+ } catch (e) {
+ _handleError(e);
+ }
+ }
+
+ /// 🔹 Handle DELETE requests
+ Future delete(String endpoint, {dynamic data}) async {
+ try {
+ Response response = await _dio.delete(endpoint, data: data);
+ return response.data;
+ } catch (e) {
+ _handleError(e);
+ }
+ }
+
+ /// 🔹 Error handling
+ void _handleError(dynamic error) {
+ if (error is DioException) {
+ switch (error.type) {
+ case DioExceptionType.connectionTimeout:
+ throw Exception("Connection timeout, please try again.");
+ case DioExceptionType.receiveTimeout:
+ throw Exception("Receive timeout, please try again.");
+ case DioExceptionType.badResponse:
+ throw Exception("Server error: ${error.response?.statusCode}");
+ case DioExceptionType.cancel:
+ throw Exception("Request canceled.");
+ default:
+ throw Exception("Unexpected error occurred.");
+ }
+ } else {
+ throw Exception("Unknown error: $error");
+ }
+ }
+}
diff --git a/lib/core/services/notification/notification_service_web.dart b/lib/core/services/notification/notification_service_web.dart
new file mode 100644
index 0000000..69d6d12
--- /dev/null
+++ b/lib/core/services/notification/notification_service_web.dart
@@ -0,0 +1,14 @@
+import 'package:universal_html/html.dart';
+
+class NotificationServiceWeb {
+ static void showNotification() {
+ if (Notification.supported) {
+ Notification('Handbags & Wallets | White Elm',
+ body: 'Leander, Texas (8.7 Km away)',
+ icon:
+ 'https://s3-alpha-sig.figma.com/img/652b/471b/a8059768c434c7abf4cfdc3c879e9293?Expires=1740355200&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=EVePXMyyhpSNgasV3uH1d5qMrPSkjn5KzpSxFoySpBs9uzU1aNHQoab9G9iOliFDRRSZ62RggnoomQ52og0UTPnMBUG6c-5GKT~f-C~sGnqppDoNf9UhhCBXpbE-BJYvkdXyuc7YmcdVyljgcWfChHANQL~nz5egKp-nyoL6mRfa6z1AH5IVaD-hMcA~cgcM2iRiJxddCciGyRNyr0cS8XWilO55sA1blB-q2Bp9sKZJCezw5Nhn2S8SLSVnDrNjBAJv3ph4ArsCxHH5DhCNmPiDA~AT3LOz~lQwVVJZhkIEAK6zS1TpvrsPPfNp7gAgr4MG3GM2CGl19AwrSoxDdw__');
+ } else {
+ print('Notifications are not supported on this browser.');
+ }
+ }
+}
diff --git a/lib/core/services/notification/notification_srvice_android.dart b/lib/core/services/notification/notification_srvice_android.dart
new file mode 100644
index 0000000..6fb47c7
--- /dev/null
+++ b/lib/core/services/notification/notification_srvice_android.dart
@@ -0,0 +1,26 @@
+import 'package:flutter/services.dart';
+
+class NotificationSrviceAndroid {
+ static const platform = MethodChannel('custom_notification');
+
+ NotificationSrviceAndroid() {
+ platform.setMethodCallHandler(_handleMethod);
+ }
+
+ Future _handleMethod(MethodCall call) async {
+ print(' method ${call.method}');
+ }
+
+ static Future showCustomNotification() async {
+ try {
+ await platform.invokeMethod('showCustomNotification', {
+ 'title': 'Handbags & Wallets | White Elm',
+ 'content': 'Leander, Texas (8.7 Km away)',
+ 'imageUrl':
+ 'https://s3-alpha-sig.figma.com/img/652b/471b/a8059768c434c7abf4cfdc3c879e9293?Expires=1740355200&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=EVePXMyyhpSNgasV3uH1d5qMrPSkjn5KzpSxFoySpBs9uzU1aNHQoab9G9iOliFDRRSZ62RggnoomQ52og0UTPnMBUG6c-5GKT~f-C~sGnqppDoNf9UhhCBXpbE-BJYvkdXyuc7YmcdVyljgcWfChHANQL~nz5egKp-nyoL6mRfa6z1AH5IVaD-hMcA~cgcM2iRiJxddCciGyRNyr0cS8XWilO55sA1blB-q2Bp9sKZJCezw5Nhn2S8SLSVnDrNjBAJv3ph4ArsCxHH5DhCNmPiDA~AT3LOz~lQwVVJZhkIEAK6zS1TpvrsPPfNp7gAgr4MG3GM2CGl19AwrSoxDdw__',
+ });
+ } on PlatformException catch (e) {
+ print("Failed to show notification: '${e.message}'.");
+ }
+ }
+}
diff --git a/lib/data/models/auth/user_info_model.dart b/lib/data/models/auth/user_info_model.dart
new file mode 100644
index 0000000..1559c5f
--- /dev/null
+++ b/lib/data/models/auth/user_info_model.dart
@@ -0,0 +1,40 @@
+class UserInfoModel {
+ String? sub;
+ bool? emailVerified;
+ String? name;
+ String? preferredUsername;
+ String? givenName;
+ String? familyName;
+ String? email;
+
+ UserInfoModel(
+ {this.sub,
+ this.emailVerified,
+ this.name,
+ this.preferredUsername,
+ this.givenName,
+ this.familyName,
+ this.email});
+
+ UserInfoModel.fromJson(Map json) {
+ sub = json['sub'];
+ emailVerified = json['email_verified'];
+ name = json['name'];
+ preferredUsername = json['preferred_username'];
+ givenName = json['given_name'];
+ familyName = json['family_name'];
+ email = json['email'];
+ }
+
+ Map toJson() {
+ final Map data = {};
+ data['sub'] = sub;
+ data['email_verified'] = emailVerified;
+ data['name'] = name;
+ data['preferred_username'] = preferredUsername;
+ data['given_name'] = givenName;
+ data['family_name'] = familyName;
+ data['email'] = email;
+ return data;
+ }
+}
diff --git a/lib/data/models/onboarding_banner_model.dart b/lib/data/models/onboarding_banner_model.dart
new file mode 100644
index 0000000..deebbbc
--- /dev/null
+++ b/lib/data/models/onboarding_banner_model.dart
@@ -0,0 +1,8 @@
+import 'package:proxibuy/core/gen/assets.gen.dart';
+
+class OnboardingBannerModel {
+ final String desc;
+ final AssetGenImage image;
+
+ OnboardingBannerModel({required this.desc, required this.image});
+}
diff --git a/lib/data/models/screen_model.dart b/lib/data/models/screen_model.dart
new file mode 100644
index 0000000..d7923fd
--- /dev/null
+++ b/lib/data/models/screen_model.dart
@@ -0,0 +1,8 @@
+import 'package:proxibuy/core/gen/assets.gen.dart';
+
+class ScreenModel {
+ final String title;
+ final SvgGenImage icon;
+
+ ScreenModel({required this.title, required this.icon});
+}
diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart
new file mode 100644
index 0000000..489081f
--- /dev/null
+++ b/lib/data/repositories/auth_repository.dart
@@ -0,0 +1,16 @@
+import 'package:proxibuy/core/services/api/api_routes.dart';
+import 'package:proxibuy/core/services/api/api_service.dart';
+import 'package:proxibuy/data/models/auth/user_info_model.dart';
+
+class AuthRepository {
+ static Future fetchUserInfo() async {
+ try {
+ var response = await apiService.get(ApiRoutes.userInfo);
+ print("Users: $response");
+ return UserInfoModel.fromJson(response);
+ } catch (e) {
+ print("Error: $e");
+ rethrow;
+ }
+ }
+}
diff --git a/lib/data/storage/shared_preferences_helper.dart b/lib/data/storage/shared_preferences_helper.dart
index 046698a..b094929 100644
--- a/lib/data/storage/shared_preferences_helper.dart
+++ b/lib/data/storage/shared_preferences_helper.dart
@@ -8,6 +8,7 @@ class SharedPreferencesHelper {
}
static const String themeMode = 'theme-mode';
+ static const String token = 'auth_token';
}
class ThemeModeStorage {
@@ -26,3 +27,20 @@ class ThemeModeStorage {
prefs.remove(SharedPreferencesHelper.themeMode);
}
}
+
+class AuthTokenStorage {
+ static String getToken() {
+ final prefs = SharedPreferencesHelper.preferences;
+ return prefs.getString(SharedPreferencesHelper.token) ?? "";
+ }
+
+ static void setToken(String token) {
+ final prefs = SharedPreferencesHelper.preferences;
+ prefs.setString(SharedPreferencesHelper.token, token);
+ }
+
+ static void clearToken() {
+ final prefs = SharedPreferencesHelper.preferences;
+ prefs.remove(SharedPreferencesHelper.token);
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index c869195..5040048 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -4,6 +4,7 @@ import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:proxibuy/core/routes/app_router.dart';
import 'package:proxibuy/data/storage/shared_preferences_helper.dart';
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
+import 'package:proxibuy/presentation/providers/cubit/user_info_cubit.dart';
import 'package:proxibuy/presentation/ui/theme/theme.dart';
import 'package:url_strategy/url_strategy.dart';
@@ -15,7 +16,9 @@ void main() async {
runApp(MultiBlocProvider(
providers: [
- BlocProvider(create: (context) => ThemModeCubit())
+ BlocProvider(create: (context) => ThemModeCubit()),
+ BlocProvider(
+ create: (context) => UserInfoCubit()..getUserInfo()),
],
child: const MyApp(),
));
@@ -38,13 +41,13 @@ class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return BlocBuilder(
- builder: (context, state) {
+ builder: (context, themeState) {
return MaterialApp.router(
- routerConfig: AppRouter().createRouter(),
+ routerConfig: AppRouter().createRouter,
title: 'Proxibuy',
theme: appTheme,
darkTheme: darkTheme,
- themeMode: state,
+ themeMode: themeState,
debugShowCheckedModeBanner: false,
);
},
diff --git a/lib/presentation/providers/cubit/user_info_cubit.dart b/lib/presentation/providers/cubit/user_info_cubit.dart
new file mode 100644
index 0000000..b1b584b
--- /dev/null
+++ b/lib/presentation/providers/cubit/user_info_cubit.dart
@@ -0,0 +1,27 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:proxibuy/data/models/auth/user_info_model.dart';
+
+part 'user_info_state.dart';
+
+class UserInfoCubit extends Cubit {
+ UserInfoCubit() : super(UserInfoInitial());
+
+ void getUserInfo() async {
+ emit(UserInfoLoadng());
+ try {
+ // final response = await AuthRepository.fetchUserInfo();
+ emit(UserInfoSuccess(
+ user: UserInfoModel.fromJson({
+ "sub": "f39c8216-388a-44e1-85a8-4f996e66e604",
+ "email_verified": true,
+ "name": "demo demo",
+ "preferred_username": "demo",
+ "given_name": "demo",
+ "family_name": "demo",
+ "email": "demo@gmail.com"
+ })));
+ } catch (e) {
+ emit(UserInfoFail(message: e));
+ }
+ }
+}
diff --git a/lib/presentation/providers/cubit/user_info_state.dart b/lib/presentation/providers/cubit/user_info_state.dart
new file mode 100644
index 0000000..f02db27
--- /dev/null
+++ b/lib/presentation/providers/cubit/user_info_state.dart
@@ -0,0 +1,19 @@
+part of 'user_info_cubit.dart';
+
+sealed class UserInfoState {}
+
+final class UserInfoInitial extends UserInfoState {}
+
+final class UserInfoLoadng extends UserInfoState {}
+
+final class UserInfoSuccess extends UserInfoState {
+ final UserInfoModel user;
+
+ UserInfoSuccess({required this.user});
+}
+
+final class UserInfoFail extends UserInfoState {
+ final Object? message;
+
+ UserInfoFail({required this.message});
+}
diff --git a/lib/presentation/ui/screens/auth/auth_page.dart b/lib/presentation/ui/screens/auth/auth_page.dart
new file mode 100644
index 0000000..c8ff015
--- /dev/null
+++ b/lib/presentation/ui/screens/auth/auth_page.dart
@@ -0,0 +1,226 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+import 'package:proxibuy/core/gen/assets.gen.dart';
+import 'package:proxibuy/core/routes/app_router.dart';
+import 'package:proxibuy/core/utils/empty_space.dart';
+import 'package:proxibuy/data/models/onboarding_banner_model.dart';
+import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
+import 'package:proxibuy/presentation/ui/theme/responsive.dart';
+import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart';
+import 'package:proxibuy/presentation/ui/widgets/edit_text/phone_number_input.dart';
+
+class AuthPage extends StatefulWidget {
+ const AuthPage({super.key});
+
+ @override
+ State createState() => _AuthPageState();
+}
+
+class _AuthPageState extends State {
+ bool onBoarding = true;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(
+ 'Auth',
+ style: Theme.of(context).textTheme.titleLarge,
+ ),
+ backgroundColor: Colors.transparent,
+ surfaceTintColor: Colors.transparent,
+ actions: [
+ IconButton(
+ icon: Icon(Icons.brightness_6),
+ onPressed: () {
+ context.read().changeTheme();
+ },
+ ),
+ ],
+ ),
+ body: Responsive(context).builder(
+ mobile: onBoarding
+ ? carousle(
+ withNavs: true, autoPlay: false, enableInfiniteScroll: false)
+ : auth(context),
+ tablet: Row(
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.all(50.0),
+ child: carousle(
+ withNavs: false,
+ autoPlay: true,
+ enableInfiniteScroll: true,
+ height: MediaQuery.sizeOf(context).height * 0.8),
+ ),
+ ),
+ Expanded(
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: 50),
+ child: auth(context)))
+ ],
+ ),
+ desktop: Row(
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.all(80.0),
+ child: carousle(
+ withNavs: false,
+ autoPlay: true,
+ enableInfiniteScroll: true,
+ height: MediaQuery.sizeOf(context).height * 0.8),
+ ),
+ ),
+ Expanded(
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: 160),
+ child: auth(context)))
+ ],
+ )),
+ );
+ }
+
+ Padding auth(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Center(
+ child: FlutterLogo(
+ size: 120,
+ ),
+ ),
+ Text('Proxibuy Geofencing marketing'),
+ 36.h,
+ Row(
+ children: [
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Text('Login'),
+ Text('Please enter your phone number')
+ ],
+ ),
+ ],
+ ),
+ 36.h,
+ PhoneNumberInput(),
+ 36.h,
+ SizedBox(
+ width: double.infinity,
+ height: 46,
+ child: ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(64),
+ ),
+ ),
+ onPressed: () async {
+ context.go(AppRouter.initial);
+ },
+ child: Text('Get OTP'),
+ ),
+ ),
+ 24.h,
+ Row(
+ children: [
+ Expanded(
+ child: Divider(
+ color: Theme.of(context).colorScheme.onSurface,
+ )),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Text('Or continue with'),
+ ),
+ Expanded(child: Builder(builder: (context) {
+ return Divider(
+ color: Theme.of(context).colorScheme.onSurface,
+ );
+ }))
+ ],
+ ),
+ 24.h,
+ SizedBox(
+ width: double.infinity,
+ height: 46,
+ child: OutlinedButton(
+ style: OutlinedButton.styleFrom(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(64),
+ ),
+ side: BorderSide(
+ width: 2,
+ color: Theme.of(context).colorScheme.onSurface)),
+ onPressed: () async {},
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Image.network(
+ 'https://img.icons8.com/?size=100&id=110560&format=png&color=000000',
+ width: 32, // Adjust width
+ height: 32, // Adjust height
+ ),
+ ),
+ Text(
+ 'Login with google',
+ style: Theme.of(context).textTheme.labelLarge,
+ ),
+ ],
+ )),
+ ),
+ ],
+ ),
+ );
+ }
+
+ CarouselSliderWidget carousle(
+ {required final bool withNavs,
+ required final bool autoPlay,
+ required final bool enableInfiniteScroll,
+ double? height}) {
+ return CarouselSliderWidget(
+ height: height ?? MediaQuery.sizeOf(context).height,
+ items: [
+ OnboardingBannerModel(
+ image: Assets.image.onboarding.banner1,
+ desc:
+ 'Proxibuy Geofencing marketing\n"Join the app to discover exclusive discounts and special offers in specific areas around you for a smarter shopping experience!"'),
+ OnboardingBannerModel(
+ image: Assets.image.onboarding.banner2,
+ desc:
+ 'Proxibuy Geofencing marketing\n"Join the app to discover exclusive discounts and special offers in specific areas around you for a smarter shopping experience!"'),
+ OnboardingBannerModel(
+ image: Assets.image.onboarding.banner3,
+ desc:
+ 'Proxibuy Geofencing marketing\n"Join the app to discover exclusive discounts and special offers in specific areas around you for a smarter shopping experience!"'),
+ ],
+ withNavs: withNavs,
+ autoPlay: autoPlay,
+ enableInfiniteScroll: enableInfiniteScroll,
+ onLastClick: () {
+ setState(() {
+ onBoarding = false;
+ });
+ },
+ onPageBuilder: (context, item) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Expanded(child: item.image.image()),
+ if (Responsive(context).isDesktop()) 64.h,
+ Text(item.desc)
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/lib/presentation/ui/screens/home/explore_screen.dart b/lib/presentation/ui/screens/home/explore_screen.dart
new file mode 100644
index 0000000..84ad9a2
--- /dev/null
+++ b/lib/presentation/ui/screens/home/explore_screen.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:proxibuy/core/routes/app_router.dart';
+
+class ExploreScreen extends StatelessWidget {
+ const ExploreScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: InkWell(
+ onTap: () => context.go(AppRouter.setting), child: Text('Explore')),
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/ui/screens/home/home_page.dart b/lib/presentation/ui/screens/home/home_page.dart
index 150cb15..5441acc 100644
--- a/lib/presentation/ui/screens/home/home_page.dart
+++ b/lib/presentation/ui/screens/home/home_page.dart
@@ -1,80 +1,223 @@
+import 'package:bottom_navy_bar/bottom_navy_bar.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
-import 'package:proxibuy/core/routes/app_router.dart';
-import 'package:proxibuy/core/services/permisions_services.dart';
+import 'package:proxibuy/core/gen/assets.gen.dart';
import 'package:proxibuy/core/utils/empty_space.dart';
+import 'package:proxibuy/data/models/screen_model.dart';
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
+import 'package:proxibuy/presentation/ui/screens/home/explore_screen.dart';
+import 'package:proxibuy/presentation/ui/screens/home/home_screen.dart';
+import 'package:proxibuy/presentation/ui/screens/home/setting_screen.dart';
+import 'package:proxibuy/presentation/ui/theme/responsive.dart';
+import 'package:proxibuy/presentation/ui/theme/theme.dart';
-class MyHomePage extends StatefulWidget {
- const MyHomePage({super.key});
+class HomePage extends StatefulWidget {
+ const HomePage({super.key});
@override
- State createState() => _MyHomePageState();
+ State createState() => _HomePageState();
}
-class _MyHomePageState extends State {
- static const platform = MethodChannel('custom_notification');
-
- Future showCustomNotification() async {
- try {
- final notifPer = await requestNotificationPermission();
- if (notifPer) {
- await platform.invokeMethod('showCustomNotification', {
- 'title': 'Handbags & Wallets | White Elm',
- 'content': 'Leander, Texas (8.7 Km away)',
- 'imageUrl':
- 'https://s3-alpha-sig.figma.com/img/652b/471b/a8059768c434c7abf4cfdc3c879e9293?Expires=1740355200&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=EVePXMyyhpSNgasV3uH1d5qMrPSkjn5KzpSxFoySpBs9uzU1aNHQoab9G9iOliFDRRSZ62RggnoomQ52og0UTPnMBUG6c-5GKT~f-C~sGnqppDoNf9UhhCBXpbE-BJYvkdXyuc7YmcdVyljgcWfChHANQL~nz5egKp-nyoL6mRfa6z1AH5IVaD-hMcA~cgcM2iRiJxddCciGyRNyr0cS8XWilO55sA1blB-q2Bp9sKZJCezw5Nhn2S8SLSVnDrNjBAJv3ph4ArsCxHH5DhCNmPiDA~AT3LOz~lQwVVJZhkIEAK6zS1TpvrsPPfNp7gAgr4MG3GM2CGl19AwrSoxDdw__',
- });
- }
- } on PlatformException catch (e) {
- print("Failed to show notification: '${e.message}'.");
- }
- }
-
- @override
- void initState() {
- super.initState();
- platform.setMethodCallHandler(_handleMethod);
- }
-
- Future _handleMethod(MethodCall call) async {
- print(' method ${call.method}');
- }
-
+class _HomePageState extends State {
+ int selectedIndex = 0;
+ List screens = [
+ ScreenModel(title: 'Home', icon: Assets.icon.outline.home),
+ ScreenModel(title: 'Explore', icon: Assets.icon.outline.map),
+ ScreenModel(title: 'Settings', icon: Assets.icon.outline.setting),
+ ];
@override
Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text("Proxibuy"),
- actions: [
- IconButton(
- icon: Icon(Icons.brightness_6),
- onPressed: () {
- context.read().changeTheme();
- },
- ),
- ],
- ),
- body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- ElevatedButton(
- onPressed: () async {
- await showCustomNotification();
- },
- child: Text('Show Notification')),
- 18.h,
- ElevatedButton(
- onPressed: () async {
- context.go(AppRouter.details);
- },
- child: Text('Navigate to Details')),
+ return Responsive(context).builder(
+ desktop: Scaffold(
+ body: Column(
+ children: [
+ navBar(context),
+ Expanded(child: body()),
],
),
),
+ mobile: Scaffold(
+ appBar: AppBar(
+ title: Text(
+ "Proxibuy",
+ style: Theme.of(context).textTheme.headlineSmall,
+ ),
+ actions: [
+ IconButton(
+ icon: Assets.icon.outline.notificationBing
+ .svg(color: Theme.of(context).colorScheme.onSurface),
+ onPressed: () {},
+ ),
+ 12.w,
+ IconButton(
+ icon: Icon(Icons.brightness_6),
+ onPressed: () {
+ context.read().changeTheme();
+ },
+ ),
+ ],
+ ),
+ body: body(),
+ bottomNavigationBar: bottomNavigationBar(context),
+ ),
);
}
+
+ Padding navBar(BuildContext context) {
+ final defaultBorder = OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide:
+ BorderSide(color: Theme.of(context).colorScheme.surface, width: 2));
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Flexible(
+ child: Row(
+ children: [
+ SelectableText(
+ "Proxibuy",
+ style: Theme.of(context).textTheme.displaySmall,
+ ),
+ 32.w,
+ Row(
+ children: [
+ ...List.generate(
+ screens.length,
+ (index) => Row(
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12),
+ color: selectedIndex == index
+ ? themeColor(context)
+ ?.primaryLightSurface
+ .withAlpha(90)
+ : null),
+ child: IconButton(
+ splashRadius: 12,
+ onPressed: () {
+ _onItemTapped(context, index);
+ },
+ icon: Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ screens[index].icon.svg(
+ color: selectedIndex == index
+ ? Theme.of(context).primaryColor
+ : Theme.of(context)
+ .colorScheme
+ .onSurface),
+ 12.w,
+ Text(
+ screens[index].title,
+ style: Theme.of(context)
+ .textTheme
+ .labelLarge
+ ?.copyWith(
+ color: selectedIndex == index
+ ? Theme.of(context)
+ .primaryColor
+ : Theme.of(context)
+ .colorScheme
+ .onSurface),
+ )
+ ],
+ )),
+ ),
+ 24.w
+ ],
+ ),
+ )
+ ],
+ ),
+ 8.w,
+ Flexible(
+ child: Container(
+ constraints: BoxConstraints(maxWidth: 800),
+ child: TextField(
+ decoration: InputDecoration(
+ hintText: 'what are you looking for?',
+ suffixIcon: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Assets.icon.outline.search.svg(
+ color: Theme.of(context).colorScheme.onSurface,
+ width: 16,
+ height: 16),
+ ),
+ enabledBorder: defaultBorder,
+ border: defaultBorder),
+ ),
+ )),
+ 32.w,
+ ],
+ ),
+ ),
+ Row(
+ children: [
+ IconButton(
+ icon: Assets.icon.outline.notificationBing
+ .svg(color: Theme.of(context).colorScheme.onSurface),
+ onPressed: () {},
+ ),
+ 12.w,
+ IconButton(
+ icon: Icon(Icons.brightness_6),
+ onPressed: () {
+ context.read().changeTheme();
+ },
+ ),
+ ],
+ )
+ ],
+ ),
+ );
+ }
+
+ Padding bottomNavigationBar(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: BottomNavyBar(
+ selectedIndex: selectedIndex,
+ // landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
+ // showUnselectedLabels: false,
+ onItemSelected: (index) => _onItemTapped(context, index),
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ itemCornerRadius: 12,
+ itemPadding: EdgeInsets.symmetric(horizontal: 16),
+ borderRadius: BorderRadius.circular(16),
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ showElevation: true,
+
+ items: [
+ ...List.generate(
+ screens.length,
+ (index) => BottomNavyBarItem(
+ icon: screens[index].icon.svg(
+ color: selectedIndex == index
+ ? Theme.of(context).primaryColor
+ : Theme.of(context).colorScheme.onSurface),
+ activeColor: Theme.of(context).primaryColor,
+ inactiveColor: Theme.of(context).colorScheme.onSurface,
+ title: Text(screens[index].title)),
+ )
+ ],
+ ),
+ );
+ }
+
+ IndexedStack body() {
+ return IndexedStack(
+ sizing: StackFit.expand,
+ index: selectedIndex,
+ children: [HomeScreen(), ExploreScreen(), SettingScreen()],
+ );
+ }
+
+ void _onItemTapped(BuildContext context, int index) {
+ setState(() {
+ selectedIndex = index;
+ });
+ }
}
diff --git a/lib/presentation/ui/screens/home/home_screen.dart b/lib/presentation/ui/screens/home/home_screen.dart
new file mode 100644
index 0000000..21422d1
--- /dev/null
+++ b/lib/presentation/ui/screens/home/home_screen.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+import 'package:proxibuy/core/gen/assets.gen.dart';
+import 'package:proxibuy/presentation/ui/theme/responsive.dart';
+
+class HomeScreen extends StatefulWidget {
+ const HomeScreen({super.key});
+
+ @override
+ State createState() => _HomeScreenState();
+}
+
+class _HomeScreenState extends State {
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final defaultBorder = OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide:
+ BorderSide(color: Theme.of(context).colorScheme.surface, width: 2));
+ return Responsive(context).builder(
+ mobile: Scaffold(
+ body: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ // ElevatedButton(
+ // onPressed: () async {
+ // final notifPer = await requestNotificationPermission();
+
+ // if (notifPer) {
+ // if (kIsWeb) {
+ // NotificationServiceWeb.showNotification();
+ // } else {
+ // await NotificationSrviceAndroid.showCustomNotification();
+ // }
+ // }
+ // },
+ // child: Text('Show Notification')),
+
+ Flexible(
+ child: Container(
+ constraints: BoxConstraints(maxWidth: 800),
+ child: TextField(
+ textInputAction: TextInputAction.search,
+ onSubmitted: (value) {
+ print(value);
+ },
+ decoration: InputDecoration(
+ hintText: 'what are you looking for?',
+ suffixIcon: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Assets.icon.outline.search.svg(
+ color: Theme.of(context).colorScheme.onSurface,
+ width: 16,
+ height: 16),
+ ),
+ enabledBorder: defaultBorder,
+ border: defaultBorder),
+ ),
+ )),
+ ],
+ ),
+ ),
+ ),
+ desktop: Scaffold(
+ body: SizedBox(),
+ ));
+ }
+}
diff --git a/lib/presentation/ui/screens/home/details_page.dart b/lib/presentation/ui/screens/home/setting_screen.dart
similarity index 57%
rename from lib/presentation/ui/screens/home/details_page.dart
rename to lib/presentation/ui/screens/home/setting_screen.dart
index 7cae96c..998fbaf 100644
--- a/lib/presentation/ui/screens/home/details_page.dart
+++ b/lib/presentation/ui/screens/home/setting_screen.dart
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
-class DetailsPage extends StatelessWidget {
- const DetailsPage({super.key});
+class SettingScreen extends StatelessWidget {
+ const SettingScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
- child: Text('Detail Page'),
+ child: Text('Setting'),
),
);
}
diff --git a/lib/presentation/ui/theme/custom_colors.dart b/lib/presentation/ui/theme/custom_colors.dart
new file mode 100644
index 0000000..2627a8e
--- /dev/null
+++ b/lib/presentation/ui/theme/custom_colors.dart
@@ -0,0 +1,42 @@
+import 'package:flutter/material.dart';
+
+class CustomColors extends ThemeExtension {
+ final MaterialColor primarySwatch;
+ final MaterialColor secondrySwatch;
+ final Color primaryLightSurface;
+
+ CustomColors({
+ required this.primarySwatch,
+ required this.secondrySwatch,
+ required this.primaryLightSurface,
+ });
+
+ @override
+ CustomColors copyWith({
+ MaterialColor? primarySwatch,
+ MaterialColor? secondrySwatch,
+ Color? primaryLightSurface,
+ }) {
+ return CustomColors(
+ primarySwatch: primarySwatch ?? this.primarySwatch,
+ secondrySwatch: secondrySwatch ?? this.secondrySwatch,
+ primaryLightSurface: primaryLightSurface ?? this.primaryLightSurface,
+ );
+ }
+
+ @override
+ ThemeExtension lerp(
+ ThemeExtension? other, double t) {
+ if (other is! CustomColors) return this;
+ return CustomColors(
+ primarySwatch: other.primarySwatch,
+ secondrySwatch: other.secondrySwatch,
+ primaryLightSurface:
+ Color.lerp(primaryLightSurface, other.primaryLightSurface, t)!,
+ );
+ }
+}
+
+extension AppColorsTheme on ThemeData {
+ CustomColors? get customColors => extension();
+}
diff --git a/lib/presentation/ui/theme/responsive.dart b/lib/presentation/ui/theme/responsive.dart
new file mode 100644
index 0000000..2fca6ad
--- /dev/null
+++ b/lib/presentation/ui/theme/responsive.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+
+class Responsive {
+ final BuildContext context;
+
+ Responsive(this.context);
+
+ Widget builder(
+ {required final Widget mobile,
+ required final Widget desktop,
+ final Widget? tablet,
+ final bool tabletAndMobileSame = false}) {
+ if (isMobile() || (isTablet() && tabletAndMobileSame)) {
+ return mobile;
+ } else if (isTablet() && tablet != null) {
+ return tablet;
+ } else {
+ return desktop;
+ }
+ }
+
+ Widget maxWidthInDesktop(
+ {final double maxWidth = 600,
+ required final Widget Function(
+ BuildContext contxet,
+ double mw,
+ ) child}) {
+ if (isDesktop() || isTablet()) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ ConstrainedBox(
+ constraints: BoxConstraints(maxWidth: maxWidth),
+ child: child(context, maxWidth),
+ ),
+ ],
+ );
+ } else {
+ return child(context, MediaQuery.sizeOf(context).width);
+ }
+ }
+
+ bool isMobile() => MediaQuery.sizeOf(context).width < 904;
+ bool isTablet() =>
+ MediaQuery.sizeOf(context).width < 1280 &&
+ MediaQuery.sizeOf(context).width >= 904;
+ bool isDesktop() => MediaQuery.sizeOf(context).width >= 1280;
+}
diff --git a/lib/presentation/ui/theme/theme.dart b/lib/presentation/ui/theme/theme.dart
index 11ecdde..0834cbb 100644
--- a/lib/presentation/ui/theme/theme.dart
+++ b/lib/presentation/ui/theme/theme.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:proxibuy/presentation/ui/theme/custom_colors.dart';
import 'colors.dart';
final ThemeData appTheme = ThemeData(
@@ -6,9 +7,18 @@ final ThemeData appTheme = ThemeData(
primaryColor: primarySwatch[500],
scaffoldBackgroundColor: lightBackground,
+ extensions: >[
+ CustomColors(
+ primarySwatch: primarySwatch,
+ secondrySwatch: secondarySwatch,
+ primaryLightSurface: primarySwatch[200]!,
+ ),
+ ],
+
// App Bar Theme
appBarTheme: const AppBarTheme(
- backgroundColor: primarySwatch,
+ surfaceTintColor: lightBackground,
+ backgroundColor: lightBackground,
elevation: 2,
titleTextStyle: TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
@@ -76,10 +86,18 @@ final ThemeData darkTheme = ThemeData(
primarySwatch: darkPrimarySwatch,
primaryColor: darkPrimarySwatch[500],
scaffoldBackgroundColor: darkBackground,
+ extensions: >[
+ CustomColors(
+ primarySwatch: darkPrimarySwatch,
+ secondrySwatch: darkSecondarySwatch,
+ primaryLightSurface: darkPrimarySwatch[900]!,
+ ),
+ ],
// App Bar Theme
appBarTheme: const AppBarTheme(
- backgroundColor: darkPrimarySwatch,
+ backgroundColor: darkBackground,
+ surfaceTintColor: darkBackground,
elevation: 2,
titleTextStyle: TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
@@ -142,3 +160,7 @@ final ThemeData darkTheme = ThemeData(
onSurface: fontLight,
),
);
+
+CustomColors? themeColor(BuildContext context) {
+ return Theme.of(context).extension();
+}
diff --git a/lib/presentation/ui/widgets/carousel/carousel_slider_widget.dart b/lib/presentation/ui/widgets/carousel/carousel_slider_widget.dart
new file mode 100644
index 0000000..fcca669
--- /dev/null
+++ b/lib/presentation/ui/widgets/carousel/carousel_slider_widget.dart
@@ -0,0 +1,124 @@
+import 'package:carousel_slider/carousel_slider.dart';
+import 'package:flutter/material.dart';
+import 'package:smooth_page_indicator/smooth_page_indicator.dart';
+
+class CarouselSliderWidget extends StatefulWidget {
+ final List items;
+ final Widget Function(
+ BuildContext context,
+ T item,
+ ) onPageBuilder;
+ final bool withNavs;
+ final double height;
+ final bool autoPlay;
+ final bool enableInfiniteScroll;
+ final Function()? onLastClick;
+
+ const CarouselSliderWidget({
+ super.key,
+ required this.items,
+ required this.onPageBuilder,
+ this.withNavs = false,
+ this.onLastClick,
+ this.autoPlay = true,
+ this.enableInfiniteScroll = true,
+ this.height = 200,
+ });
+
+ @override
+ State> createState() =>
+ _CarouselSliderWidgetState();
+}
+
+class _CarouselSliderWidgetState extends State> {
+ int activeIndex = 0;
+ final CarouselSliderController _controller = CarouselSliderController();
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ CarouselSlider.builder(
+ carouselController: _controller,
+ itemCount: widget.items.length,
+ itemBuilder: (context, index, realIndex) {
+ return widget.onPageBuilder(context, widget.items[index]);
+ },
+ options: CarouselOptions(
+ height: widget.height * 0.75,
+ autoPlay: widget.autoPlay,
+ enableInfiniteScroll: widget.enableInfiniteScroll,
+ enlargeCenterPage: true,
+ viewportFraction: 0.9,
+ onPageChanged: (index, reason) =>
+ setState(() => activeIndex = index),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(child: Center(child: buildIndicator())),
+ if (widget.withNavs)
+ Expanded(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Opacity(
+ opacity: activeIndex != 0 ? 1 : 0,
+ child: IconButton(
+ icon: const Icon(Icons.arrow_back_ios_new_rounded),
+ onPressed: () => _controller.previousPage(),
+ ),
+ ),
+ Container(
+ padding: EdgeInsets.all(8),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: Theme.of(context).primaryColor),
+ child: InkWell(
+ highlightColor: Theme.of(context).primaryColor,
+ onTap: () {
+ if (activeIndex == widget.items.length - 1) {
+ if (widget.onLastClick != null) {
+ widget.onLastClick!();
+ }
+ } else {
+ _controller.nextPage();
+ }
+ },
+ child: const Icon(Icons.arrow_forward_ios_rounded),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget buildImage(String urlImage, int index) => Container(
+ margin: const EdgeInsets.symmetric(horizontal: 12),
+ color: Colors.grey,
+ child: Image.network(
+ urlImage,
+ fit: BoxFit.cover,
+ ),
+ );
+
+ Widget buildIndicator() => AnimatedSmoothIndicator(
+ activeIndex: activeIndex,
+ count: widget.items.length,
+ effect: ExpandingDotsEffect(
+ dotWidth: 10,
+ dotHeight: 10,
+ activeDotColor: Theme.of(context).primaryColor,
+ dotColor: Theme.of(context).colorScheme.onSurface,
+ ),
+ );
+}
diff --git a/lib/presentation/ui/widgets/edit_text/phone_number_input.dart b/lib/presentation/ui/widgets/edit_text/phone_number_input.dart
new file mode 100644
index 0000000..6d5b2f2
--- /dev/null
+++ b/lib/presentation/ui/widgets/edit_text/phone_number_input.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/material.dart';
+import 'package:intl_phone_field/intl_phone_field.dart';
+
+class PhoneNumberInput extends StatelessWidget {
+ const PhoneNumberInput({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final border = OutlineInputBorder(
+ borderSide:
+ BorderSide(color: Theme.of(context).colorScheme.onSurface, width: 2),
+ );
+ return IntlPhoneField(
+ decoration: InputDecoration(
+ labelText: 'Phone number',
+ counter: SizedBox(),
+ border: border,
+ enabledBorder: border),
+ initialCountryCode: 'US',
+ dropdownIconPosition: IconPosition.trailing,
+ flagsButtonMargin: EdgeInsets.symmetric(horizontal: 12),
+ onChanged: (phone) {
+ print(phone.completeNumber);
+ },
+ );
+ }
+}
diff --git a/lib/presentation/ui/widgets/navigations/custom_bottom_nav.dart b/lib/presentation/ui/widgets/navigations/custom_bottom_nav.dart
new file mode 100644
index 0000000..0ac82c7
--- /dev/null
+++ b/lib/presentation/ui/widgets/navigations/custom_bottom_nav.dart
@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:proxibuy/core/routes/app_router.dart';
+
+class CustomBottomNav extends StatelessWidget {
+ final Widget child;
+
+ const CustomBottomNav({super.key, required this.child});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Stack(
+ children: [
+ child,
+ Positioned(
+ bottom: 16,
+ left: 16,
+ right: 16,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(16),
+ child: BottomNavigationBar(
+ currentIndex: _getSelectedIndex(context),
+ landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
+ onTap: (index) => _onItemTapped(context, index),
+ items: const [
+ BottomNavigationBarItem(
+ icon: Icon(Icons.home), label: "Home"),
+ BottomNavigationBarItem(
+ icon: Icon(Icons.explore), label: "Explore"),
+ BottomNavigationBarItem(
+ icon: Icon(Icons.settings), label: "Settings"),
+ ],
+ ),
+ ),
+ )
+ ],
+ ),
+ );
+ }
+
+ int _getSelectedIndex(BuildContext context) {
+ final String location = GoRouterState.of(context).uri.toString();
+ if (location.startsWith(AppRouter.explore)) return 1;
+ if (location.startsWith(AppRouter.setting)) return 2;
+ return 0;
+ }
+
+ void _onItemTapped(BuildContext context, int index) {
+ switch (index) {
+ case 0:
+ context.go(AppRouter.initial);
+ break;
+ case 1:
+ context.go(AppRouter.explore);
+ break;
+ case 2:
+ context.go(AppRouter.setting);
+ break;
+ }
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index 5aba2d8..4d84498 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -65,6 +65,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
+ bottom_navy_bar:
+ dependency: "direct main"
+ description:
+ name: bottom_navy_bar
+ sha256: ed54e0a18748106cc4d3648390bf94e88c0913db6bd3018dd30ab0810f0c26d6
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.0"
build:
dependency: transitive
description:
@@ -129,6 +137,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.9.3"
+ carousel_slider:
+ dependency: "direct main"
+ description:
+ name: carousel_slider
+ sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.0"
characters:
dependency: transitive
description:
@@ -137,6 +153,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
+ charcode:
+ dependency: transitive
+ description:
+ name: charcode
+ sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
checked_yaml:
dependency: transitive
description:
@@ -249,6 +273,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
+ dropdown_textfield:
+ dependency: "direct main"
+ description:
+ name: dropdown_textfield
+ sha256: ef8a35c52c92a563773d3efead94e5a1c162d6fe6c53974d0986aab6249928a1
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ equatable:
+ dependency: transitive
+ description:
+ name: equatable
+ sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.7"
fake_async:
dependency: transitive
description:
@@ -334,6 +374,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.4"
+ flutter_svg:
+ dependency: "direct main"
+ description:
+ name: flutter_svg
+ sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.17"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -432,6 +480,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.0"
+ intl_phone_field:
+ dependency: "direct main"
+ description:
+ name: intl_phone_field
+ sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.0"
io:
dependency: transitive
description:
@@ -672,6 +728,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.1"
+ pretty_dio_logger:
+ dependency: "direct main"
+ description:
+ name: pretty_dio_logger
+ sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
provider:
dependency: transitive
description:
@@ -773,6 +837,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ smooth_page_indicator:
+ dependency: "direct main"
+ description:
+ name: smooth_page_indicator
+ sha256: "3b28b0c545fa67ed9e5997d9f9720d486f54c0c607e056a1094544e36934dff3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0+3"
source_span:
dependency: transitive
description:
@@ -853,6 +925,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
+ universal_html:
+ dependency: "direct main"
+ description:
+ name: universal_html
+ sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.4"
universal_io:
dependency: transitive
description:
@@ -869,6 +949,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.0"
+ vector_graphics:
+ dependency: transitive
+ description:
+ name: vector_graphics
+ sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.18"
vector_graphics_codec:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 5c118ec..ad420f9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -42,6 +42,14 @@ dependencies:
url_strategy: ^0.3.0
flutter_native_splash: ^2.4.4
flutter_launcher_icons: ^0.14.3
+ carousel_slider: ^5.0.0
+ smooth_page_indicator: ^1.2.0+3
+ intl_phone_field: ^3.2.0
+ flutter_svg: ^2.0.17
+ bottom_navy_bar: ^6.1.0
+ pretty_dio_logger: ^1.4.0
+ universal_html: ^2.2.4
+ dropdown_textfield: ^1.2.0
flutter_launcher_icons:
android: true
@@ -106,6 +114,10 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/logo/
+ - assets/image/
+ - assets/icon/
+ - assets/icon/outline/
+ - assets/image/onboarding/
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
diff --git a/web/index.html b/web/index.html
index a442b53..556065d 100644
--- a/web/index.html
+++ b/web/index.html
@@ -1,4 +1,6 @@
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
+
proxibuy
-
+
-
-
-
-
-
-
-
-
-
-
-
+ margin: 0;
+ min-height: 100%;
+ background-color: #ffffff;
+ background-size: 100% 100%;
+ }
-
\ No newline at end of file
+ .center {
+ margin: 0;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ }
+
+ .contain {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ }
+
+ .stretch {
+ display: block;
+ width: 100%;
+ height: 100%;
+ }
+
+ .cover {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ .bottom {
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ -ms-transform: translate(-50%, 0);
+ transform: translate(-50%, 0);
+ }
+
+ .bottomLeft {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+
+ .bottomRight {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ body {
+ background-color: #121212;
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+