From 5dd5bd742f6119907b97c4ef1d7d444921a20e70 Mon Sep 17 00:00:00 2001 From: tall3at <91608104+tall3at@users.noreply.github.com> Date: Tue, 7 Dec 2021 20:43:12 +0500 Subject: [PATCH] First Commit --- assets/icons/ic_face_id.png | Bin 0 -> 6282 bytes assets/icons/ic_fingerprint.png | Bin 0 -> 12319 bytes assets/icons/ic_sms.png | Bin 0 -> 6433 bytes assets/icons/ic_whatsapp.png | Bin 0 -> 7235 bytes ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Runner.xcodeproj/project.pbxproj | 68 ++++ .../contents.xcworkspacedata | 3 + lib/config/app_provider.dart | 19 ++ lib/config/background_loader.dart | 34 ++ lib/config/constants.dart | 8 + lib/config/dependencies.dart | 38 +++ lib/config/localization.dart | 18 + lib/config/routes.dart | 37 +++ lib/generated/codegen_loader.g.dart | 94 ++++++ lib/main.dart | 139 +++----- lib/models/account.dart | 43 +++ lib/models/config_model.dart | 12 + lib/models/parent_list.dart | 26 ++ lib/models/response_models.dart | 34 ++ lib/models/user.dart | 9 + lib/pages/a.dart | 0 lib/pages/dashboard/dashboard_page.dart | 95 ++++++ lib/pages/user/complete_profile_page.dart | 67 ++++ lib/pages/user/forget_password_page.dart | 50 +++ lib/pages/user/login_verification_page.dart | 91 +++++ lib/pages/user/login_verify_account_page.dart | 93 ++++++ lib/pages/user/register_page.dart | 50 +++ lib/pages/user/register_selection_page.dart | 54 +++ lib/pages/user/splash_page.dart | 48 +++ lib/provider/counter.dart | 20 ++ lib/repo/account_repository.dart | 49 +++ lib/services/backend_service.dart | 127 +++++++ lib/services/firebase_service.dart | 180 ++++++++++ lib/services/http_service.dart | 36 ++ lib/services/media_service.dart | 26 ++ lib/services/network_service.dart | 25 ++ lib/services/secure_storage.dart | 33 ++ lib/services/shared_preferences.dart | 119 +++++++ lib/theme/app_theme.dart | 29 ++ lib/theme/colors.dart | 13 + lib/utils/AppPermissionHandler.dart | 36 ++ lib/utils/dialogs.dart | 35 ++ lib/utils/navigator.dart | 9 + lib/utils/utils.dart | 311 ++++++++++++++++++ lib/widgets/app_bar.dart | 28 ++ lib/widgets/blurry_container.dart | 62 ++++ lib/widgets/button/show_circular_button.dart | 23 ++ lib/widgets/button/show_image_button.dart | 43 +++ lib/widgets/dialog/dialogs.dart | 16 + lib/widgets/dialog/message_dialog.dart | 38 +++ lib/widgets/dialog/otp_dialog.dart | 74 +++++ lib/widgets/dragable_sheet.dart | 26 ++ lib/widgets/extensions/extensions_widget.dart | 235 +++++++++++++ lib/widgets/gradient_app_bar.dart | 63 ++++ lib/widgets/images/circular_image.dart | 30 ++ lib/widgets/show_card_buttton.dart | 32 ++ lib/widgets/show_fill_button.dart | 41 +++ lib/widgets/txt.dart | 170 ++++++++++ lib/widgets/txt_field.dart | 152 +++++++++ lib/widgets/user_image.dart | 26 ++ pubspec.lock | 307 ++++++++++++++++- pubspec.yaml | 18 +- resources/langs/en-US.json | 38 +++ resources/langs/en.json | 38 +++ resources/s.dart | 0 test/widget_test.dart | 2 +- 67 files changed, 3534 insertions(+), 108 deletions(-) create mode 100644 assets/icons/ic_face_id.png create mode 100644 assets/icons/ic_fingerprint.png create mode 100644 assets/icons/ic_sms.png create mode 100644 assets/icons/ic_whatsapp.png create mode 100644 lib/config/app_provider.dart create mode 100644 lib/config/background_loader.dart create mode 100644 lib/config/constants.dart create mode 100644 lib/config/dependencies.dart create mode 100644 lib/config/localization.dart create mode 100644 lib/config/routes.dart create mode 100644 lib/generated/codegen_loader.g.dart create mode 100644 lib/models/account.dart create mode 100644 lib/models/config_model.dart create mode 100644 lib/models/parent_list.dart create mode 100644 lib/models/response_models.dart create mode 100644 lib/models/user.dart create mode 100644 lib/pages/a.dart create mode 100644 lib/pages/dashboard/dashboard_page.dart create mode 100644 lib/pages/user/complete_profile_page.dart create mode 100644 lib/pages/user/forget_password_page.dart create mode 100644 lib/pages/user/login_verification_page.dart create mode 100644 lib/pages/user/login_verify_account_page.dart create mode 100644 lib/pages/user/register_page.dart create mode 100644 lib/pages/user/register_selection_page.dart create mode 100644 lib/pages/user/splash_page.dart create mode 100644 lib/provider/counter.dart create mode 100644 lib/repo/account_repository.dart create mode 100644 lib/services/backend_service.dart create mode 100644 lib/services/firebase_service.dart create mode 100644 lib/services/http_service.dart create mode 100644 lib/services/media_service.dart create mode 100644 lib/services/network_service.dart create mode 100644 lib/services/secure_storage.dart create mode 100644 lib/services/shared_preferences.dart create mode 100644 lib/theme/app_theme.dart create mode 100644 lib/theme/colors.dart create mode 100644 lib/utils/AppPermissionHandler.dart create mode 100644 lib/utils/dialogs.dart create mode 100644 lib/utils/navigator.dart create mode 100644 lib/utils/utils.dart create mode 100644 lib/widgets/app_bar.dart create mode 100644 lib/widgets/blurry_container.dart create mode 100644 lib/widgets/button/show_circular_button.dart create mode 100644 lib/widgets/button/show_image_button.dart create mode 100644 lib/widgets/dialog/dialogs.dart create mode 100644 lib/widgets/dialog/message_dialog.dart create mode 100644 lib/widgets/dialog/otp_dialog.dart create mode 100644 lib/widgets/dragable_sheet.dart create mode 100644 lib/widgets/extensions/extensions_widget.dart create mode 100644 lib/widgets/gradient_app_bar.dart create mode 100644 lib/widgets/images/circular_image.dart create mode 100644 lib/widgets/show_card_buttton.dart create mode 100644 lib/widgets/show_fill_button.dart create mode 100644 lib/widgets/txt.dart create mode 100644 lib/widgets/txt_field.dart create mode 100644 lib/widgets/user_image.dart create mode 100644 resources/langs/en-US.json create mode 100644 resources/langs/en.json create mode 100644 resources/s.dart diff --git a/assets/icons/ic_face_id.png b/assets/icons/ic_face_id.png new file mode 100644 index 0000000000000000000000000000000000000000..913e850d43c2df6b5a2109c2898167ebcffc2855 GIT binary patch literal 6282 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 W2txEMD5U;301S0ab(*vhkN*c^tNOD5 literal 0 HcmV?d00001 diff --git a/assets/icons/ic_fingerprint.png b/assets/icons/ic_fingerprint.png new file mode 100644 index 0000000000000000000000000000000000000000..bf731975d839e1023097654b284f9f991b42f844 GIT binary patch literal 12319 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 literal 0 HcmV?d00001 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4d90e43..fb75e06 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5E9559759AEA0E089FDD9FD7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFCFDCCDB03A7D75BCB22117 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -32,6 +33,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 589B0447F002C308698802BD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -42,6 +44,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B5D2F47A7F9A7D8C8678860A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + BFCFDCCDB03A7D75BCB22117 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F095357B123772C701252686 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,6 +54,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5E9559759AEA0E089FDD9FD7 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -72,6 +78,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + CF641C50961DBDC07AA7AD64 /* Pods */, + C0F0C01C1B0541A9B1F8E6D5 /* Frameworks */, ); sourceTree = ""; }; @@ -98,6 +106,25 @@ path = Runner; sourceTree = ""; }; + C0F0C01C1B0541A9B1F8E6D5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + BFCFDCCDB03A7D75BCB22117 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CF641C50961DBDC07AA7AD64 /* Pods */ = { + isa = PBXGroup; + children = ( + 589B0447F002C308698802BD /* Pods-Runner.debug.xcconfig */, + F095357B123772C701252686 /* Pods-Runner.release.xcconfig */, + B5D2F47A7F9A7D8C8678860A /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -105,12 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 719FDEEFEE98A49E1AC9E990 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + C9CF36692C973DF3840AB8D9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -183,6 +212,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 719FDEEFEE98A49E1AC9E990 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -197,6 +248,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + C9CF36692C973DF3840AB8D9 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/config/app_provider.dart b/lib/config/app_provider.dart new file mode 100644 index 0000000..75a898c --- /dev/null +++ b/lib/config/app_provider.dart @@ -0,0 +1,19 @@ +import 'package:car_provider_app/provider/counter.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class AppProvider extends StatelessWidget { + final Widget child; + + AppProvider({required this.child}); + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => Counter()), + ], + child: child, + ); + } +} diff --git a/lib/config/background_loader.dart b/lib/config/background_loader.dart new file mode 100644 index 0000000..5d18ec0 --- /dev/null +++ b/lib/config/background_loader.dart @@ -0,0 +1,34 @@ +//class which loads components "in the background", i.e. ui does not depend on it + +import 'package:car_provider_app/services/shared_preferences.dart'; +import 'package:injector/injector.dart'; +//import 'package:revocheckapp/services/firebase_service.dart'; + + +class BackgroundLoader { + Future loadBackgroundData() async { + //init notification setting + try { + /* + final isPromotionNotificationEnabled = await Injector.appInstance + .getDependency() + .promotionNotificationsEnabled; + if (isPromotionNotificationEnabled == null) { + await Injector.appInstance + .getDependency() + .setPromotionNotificationEnabled(true); + Injector.appInstance + .getDependency() + .subscribeForPromotions(); + } */ + } catch (_) { + //something wend wrong, set it to true + await Injector.appInstance + .getDependency() + .setPromotionNotificationEnabled(true); + /*Injector.appInstance + .getDependency() + .subscribeForPromotions();*/ + } + } +} diff --git a/lib/config/constants.dart b/lib/config/constants.dart new file mode 100644 index 0000000..87c2c46 --- /dev/null +++ b/lib/config/constants.dart @@ -0,0 +1,8 @@ +enum YesOrNo { + no, + yes, +} + +const String icons = "assets/icons/"; +const String categorySvgIcons = "assets/category/svg/"; +const String svgIcons = "assets/svg/"; diff --git a/lib/config/dependencies.dart b/lib/config/dependencies.dart new file mode 100644 index 0000000..2780417 --- /dev/null +++ b/lib/config/dependencies.dart @@ -0,0 +1,38 @@ +// import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +// import 'package:flutter/material.dart'; + +import 'package:car_provider_app/repo/account_repository.dart'; +import 'package:injector/injector.dart'; + +import 'background_loader.dart'; + + +class AppDependencies { + static void addDependencies() { + Injector injector = Injector.appInstance; + + //add dependencies as needed + injector.registerSingleton(() => AcRepository()); + + // injector.registerSingleton((injector) => AcRepository()); + + _addCrashlytics(); + _loadBackgroundTasksNonBlocking(); + } + + static void _addCrashlytics() { + // Set `enableInDevMode` to true to see reports while in debug mode + // This is only to be used for confirming that reports are being + // submitted as expected. It is not intended to be used for everyday + // development. + //Crashlytics.instance.enableInDevMode = true; + + // Pass all uncaught errors from the framework to Crashlytics. + // FlutterError.onError = Crashlytics.instance.recordFlutterError; + } + + static void _loadBackgroundTasksNonBlocking() { + final backgroundLoader = BackgroundLoader(); + backgroundLoader.loadBackgroundData(); + } +} diff --git a/lib/config/localization.dart b/lib/config/localization.dart new file mode 100644 index 0000000..5d7acdf --- /dev/null +++ b/lib/config/localization.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; + +class AppLocalizations { + static const Iterable> localizationsDelegates = + [ + // ... app-specific localization delegate[s] here + // S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate + ]; + + static const List supportedLocales = [ + const Locale("en", "US") + ]; +} diff --git a/lib/config/routes.dart b/lib/config/routes.dart new file mode 100644 index 0000000..b0dd2bc --- /dev/null +++ b/lib/config/routes.dart @@ -0,0 +1,37 @@ +import 'package:car_provider_app/pages/dashboard/dashboard_page.dart'; +import 'package:car_provider_app/pages/user/complete_profile_page.dart'; +import 'package:car_provider_app/pages/user/forget_password_page.dart'; +import 'package:car_provider_app/pages/user/login_verification_page.dart'; +import 'package:car_provider_app/pages/user/login_verify_account_page.dart'; +import 'package:car_provider_app/pages/user/register_page.dart'; +import 'package:car_provider_app/pages/user/register_selection_page.dart'; +import 'package:car_provider_app/pages/user/splash_page.dart'; +import 'package:flutter/material.dart'; + +class AppRoutes { + //User + static final String splash = "/splash"; + static final String registerSelection = "/registerSelection"; + static final String loginVerifyAccount = "/loginVerifyAccount"; + static final String register = "/register"; + static final String forgetPassword = "/forgetPassword"; + static final String loginVerification = "/loginVerification"; + static final String dashboard = "/dashboard"; + static final String completeProfile = "/completeProfile"; + + + + static final String initialRoute = splash; + + static final Map routes = { + //User + splash: (context) => SplashPage(), + registerSelection: (context) => RegisterSelectionPage(), + loginVerifyAccount: (context) => LoginVerifyAccountPage(), + register: (context) => RegisterPage(), + forgetPassword: (context) => ForgetPasswordPage(), + loginVerification: (context) => LoginVerificationPage(), + dashboard: (context) => DashboardPage(), + completeProfile: (context) => CompleteProfilePage(), + }; +} diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart new file mode 100644 index 0000000..1157a89 --- /dev/null +++ b/lib/generated/codegen_loader.g.dart @@ -0,0 +1,94 @@ +// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart + +// ignore_for_file: prefer_single_quotes + +import 'dart:ui'; + +import 'package:easy_localization/easy_localization.dart' show AssetLoader; + +class CodegenLoader extends AssetLoader{ + const CodegenLoader(); + + @override + Future> load(String fullPath, Locale locale ) { + return Future.value(mapLocales[locale.toString()]); + } + + static const Map en = { + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +}; +static const Map en_US = { + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +}; +static const Map> mapLocales = {"en": en, "en_US": en_US}; +} diff --git a/lib/main.dart b/lib/main.dart index 202509b..f712e24 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,115 +1,50 @@ +import 'package:car_provider_app/config/app_provider.dart'; +import 'package:car_provider_app/config/dependencies.dart'; +import 'package:car_provider_app/theme/app_theme.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); +import 'package:sizer/sizer.dart'; + +import 'config/localization.dart'; +import 'config/routes.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await EasyLocalization.ensureInitialized(); + runApp( + EasyLocalization( + supportedLocales: [ + Locale('en', 'US'), + ], + path: 'resources/langs', + child: MyApp(), + ), + ); } class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); + MyApp() { + AppDependencies.addDependencies(); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), + return AppProvider( + child: Sizer( + builder: (context, orientation, deviceType) { + return MaterialApp( + theme: AppTheme.getTheme(), + debugShowCheckedModeBanner: false, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + initialRoute: AppRoutes.initialRoute, + routes: AppRoutes.routes, + ); + }, ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } diff --git a/lib/models/account.dart b/lib/models/account.dart new file mode 100644 index 0000000..5e51f95 --- /dev/null +++ b/lib/models/account.dart @@ -0,0 +1,43 @@ +// To parse this JSON data, do +// +// final account = accountFromJson(jsonString); + +import 'dart:convert'; + +import 'package:car_provider_app/models/parent_list.dart'; + + + +Account accountFromJson(String str) => Account.fromJson(json.decode(str)); + +String accountToJson(Account data) => json.encode(data.toJson()); + +class Account { + Account({ + required this.parentList, + required this.selectedItem, + }); + + List? parentList; + int selectedItem; + + factory Account.fromJson(Map json) => Account( + parentList: json["parentList"] == null + ? null + : List.from( + json["parentList"].map((x) => ParentList.fromJson(x))), + selectedItem: + json["selectedItem"] == null ? null : json["selectedItem"], + ); + + Map toJson() => { + "parentList": parentList == null + ? null + : List.from(parentList!.map((x) => x.toJson())), + "selectedItem": selectedItem == null ? null : selectedItem, + }; + + Map toJsonData() => { + "selectedItem": selectedItem == null ? null : selectedItem, + }; +} diff --git a/lib/models/config_model.dart b/lib/models/config_model.dart new file mode 100644 index 0000000..0245ede --- /dev/null +++ b/lib/models/config_model.dart @@ -0,0 +1,12 @@ +class ConfigModel { + ConfigModel(this.endpoint, this.organizationName); + + String endpoint; + + String organizationName; + + factory ConfigModel.fromJson(Map json) => + ConfigModel("", ""); + +// Map toJson() => _$ConfigModelToJson(this); +} diff --git a/lib/models/parent_list.dart b/lib/models/parent_list.dart new file mode 100644 index 0000000..3f2286f --- /dev/null +++ b/lib/models/parent_list.dart @@ -0,0 +1,26 @@ +class ParentList { + ParentList({ + required this.dbId, + required this.text, + required this.path, + required this.isSelected, + }); + + int dbId; + String text; + String path; + bool isSelected; + + factory ParentList.fromJson(Map json) => ParentList( + dbId: json["dbId"] == null ? null : json["dbId"], + text: json["text"] == null ? null : json["text"], + path: json["path"] == null ? null : json["path"], + isSelected: false, + ); + + Map toJson() => { + "dbId": dbId == null ? null : dbId, + "text": text == null ? null : text, + "path": path == null ? null : path, + }; +} diff --git a/lib/models/response_models.dart b/lib/models/response_models.dart new file mode 100644 index 0000000..872893b --- /dev/null +++ b/lib/models/response_models.dart @@ -0,0 +1,34 @@ +/// +/// This example was taken from +/// https://flutter.dev/docs/development/data-and-backend/json +/// + +/// This allows the `User` class to access private members in +/// the generated file. The value for this is *.g.dart, where +/// the star denotes the source file name. + +/// An annotation for the code generator to know that this class needs the +/// JSON serialization logic to be generated. + +class BackendResponse { + BackendResponse({required this.id, required this.isOk, required this.result}); + + int id; + bool isOk; + dynamic result; + + /// A necessary factory constructor for creating a new User instance + /// from a map. Pass the map to the generated `_$UserFromJson()` constructor. + /// The constructor is named after the source class, in this case, User. + factory BackendResponse.fromJson(Map json) => + BackendResponse( + id: 1, + isOk: false, + result: null, + ); +// +// /// `toJson` is the convention for a class to declare support for serialization +// /// to JSON. The implementation simply calls the private, generated +// /// helper method `_$UserToJson`. +// Map toJson() => _$BackendResponseToJson(this); +} diff --git a/lib/models/user.dart b/lib/models/user.dart new file mode 100644 index 0000000..e09c282 --- /dev/null +++ b/lib/models/user.dart @@ -0,0 +1,9 @@ +class User { + int id; + + User(this.id, this.userName, this.userImage, this.createdDate); + + String userName; + String userImage; + String createdDate; +} diff --git a/lib/pages/a.dart b/lib/pages/a.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/pages/dashboard/dashboard_page.dart b/lib/pages/dashboard/dashboard_page.dart new file mode 100644 index 0000000..3691c3e --- /dev/null +++ b/lib/pages/dashboard/dashboard_page.dart @@ -0,0 +1,95 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class DashboardPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar( + title: "Logo/Brand", + ), + drawer: showDrawer(context), + body: Container( + child: Center( + child: Txt( + "Dashboard/Main Page", + txtType: TxtType.heading3, + ), + ), + ), + ); + } + + Widget showDrawer(BuildContext context) { + return Drawer( + child: Container( + child: Column( + children: [ + Container( + width: double.infinity, + height: 200, + color: accentColor.withOpacity(0.3), + child: Icon( + Icons.person, + size: 80, + color: accentColor.withOpacity(0.3), + ), + ), + Container( + width: double.infinity, + color: accentColor.withOpacity(0.1), + padding: EdgeInsets.all(20), + child: Row( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Txt( + "User Name", + txtType: TxtType.heading3, + ), + Txt( + "User role or title", + ), + ], + ), + ), + ShowFillButton( + title: "EDIT", + onPressed: () {}, + ), + ], + ), + ), + ListTile( + leading: Icon(Icons.notifications), + title: Txt("Notifications"), + ), + ListTile( + leading: Icon(Icons.settings), + title: Txt("General"), + ), + ListTile( + leading: Icon(Icons.person), + title: Txt("Account"), + ), + ListTile( + leading: Icon(Icons.logout), + title: Txt("Sign Out"), + onTap: () { + pop(context); + pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/user/complete_profile_page.dart b/lib/pages/user/complete_profile_page.dart new file mode 100644 index 0000000..e3e6d5c --- /dev/null +++ b/lib/pages/user/complete_profile_page.dart @@ -0,0 +1,67 @@ +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; +import 'package:flutter/material.dart'; + +class CompleteProfilePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Sign Up"), + body: Container( + width: double.infinity, + height: double.infinity, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(40), + child: Column( + children: [ + Txt( + "Complete Profile", + txtType: TxtType.heading3, + ), + mHeight(12), + TxtField( + hint: "First Name", + ), + mHeight(12), + TxtField( + hint: "Surname", + ), + mHeight(12), + TxtField( + hint: "Email", + ), + mHeight(12), + TxtField( + hint: "Create Password", + ), + mHeight(12), + TxtField( + hint: "Confirm Password", + ), + mHeight(12), + TxtField( + hint: "Phone Number", + ), + mHeight(50), + Txt( + "By creating an account you agree to our Terms of Service and Privacy Policy", + textAlign: TextAlign.center, + ), + mHeight(16), + ShowFillButton( + title: "Continue", + width: double.infinity, + onPressed: () {}, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/user/forget_password_page.dart b/lib/pages/user/forget_password_page.dart new file mode 100644 index 0000000..1313322 --- /dev/null +++ b/lib/pages/user/forget_password_page.dart @@ -0,0 +1,50 @@ +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/dialog/dialogs.dart'; +import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; +import 'package:flutter/material.dart'; + +class ForgetPasswordPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Forget Password"), + body: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(40), + child: Column( + children: [ + Txt( + "Retrieve Password", + txtType: TxtType.heading3, + ), + mHeight(12), + TxtField( + hint: "Phone Number", + ), + TxtField( + hint: "Email", + ), + mHeight(40), + ShowFillButton( + title: "Continue", + width: double.infinity, + onPressed: () { + showMDialog( + context, + child: MessageDialog( + title: "New Password sent used", + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/user/login_verification_page.dart b/lib/pages/user/login_verification_page.dart new file mode 100644 index 0000000..0414f82 --- /dev/null +++ b/lib/pages/user/login_verification_page.dart @@ -0,0 +1,91 @@ +import 'package:car_provider_app/config/constants.dart'; +import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/button/show_image_button.dart'; +import 'package:car_provider_app/widgets/dialog/dialogs.dart'; +import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; +import 'package:flutter/material.dart'; + +class LoginVerificationPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Log In"), + body: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(40), + child: Column( + children: [ + Txt( + "Verify Account", + txtType: TxtType.heading3, + ), + mFlex(2), + Row( + children: [ + Expanded( + child: ShowImageButton( + onClick: () { + showMDialog( + context, + child: MessageDialog( + title: "Account Verified", + onClick: () { + pop(context); + navigateWithName(context, AppRoutes.dashboard); + }, + ), + ); + }, + title: 'Finger Print', + icon: icons + "ic_fingerprint.png", + ), + ), + mWidth(20), + Expanded( + child: ShowImageButton( + onClick: () { + navigateWithName(context, AppRoutes.dashboard); + }, + title: 'Face Recognition', + icon: icons + "ic_face_id.png", + ), + ), + ], + ), + mHeight(40), + Row( + children: [ + Expanded( + child: ShowImageButton( + onClick: () { + navigateWithName(context, AppRoutes.dashboard); + }, + title: 'With SMS', + icon: icons + "ic_sms.png", + ), + ), + mWidth(20), + Expanded( + child: ShowImageButton( + onClick: () { + navigateWithName(context, AppRoutes.dashboard); + }, + title: 'With Whatsapp', + icon: icons + "ic_whatsapp.png", + ), + ), + ], + ), + mFlex(10), + ], + ), + ), + ); + } +} diff --git a/lib/pages/user/login_verify_account_page.dart b/lib/pages/user/login_verify_account_page.dart new file mode 100644 index 0000000..fce3a7b --- /dev/null +++ b/lib/pages/user/login_verify_account_page.dart @@ -0,0 +1,93 @@ +import 'package:car_provider_app/config/constants.dart'; +import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/button/show_image_button.dart'; +import 'package:car_provider_app/widgets/dialog/dialogs.dart'; +import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; +import 'package:car_provider_app/widgets/dialog/otp_dialog.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; +import 'package:flutter/material.dart'; + +class LoginVerifyAccountPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Log In"), + body: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(40), + child: Column( + children: [ + Txt( + "Verify Account", + txtType: TxtType.heading3, + ), + mFlex(1), + TxtField( + hint: "Enter Phone number to verify", + ), + mFlex(2), + Row( + children: [ + Expanded( + child: ShowImageButton( + onClick: () { + showMDialog(context, child: OtpDialog( + onClick: () { + pop(context); + delay(300).then( + (value) => showMDialog( + context, + child: MessageDialog( + title: "Phone Number Verified", + onClick: () { + //navigateWithName(context, AppRoutes.completeProfile); + }, + ), + ), + ); + }, + )); + }, + title: 'With SMS', + icon: icons + "ic_sms.png", + ), + ), + mWidth(20), + Expanded( + child: ShowImageButton( + onClick: () { + showMDialog(context, child: OtpDialog( + onClick: () { + pop(context); + delay(300).then( + (value) => showMDialog( + context, + child: MessageDialog( + title: "Phone Number Verified", + onClick: () { + // navigateWithName(context, AppRoutes.completeProfile); + }, + ), + ), + ); + }, + )); + }, + title: 'With Whatsapp', + icon: icons + "ic_whatsapp.png", + ), + ), + ], + ), + mFlex(10), + ], + ), + ), + ); + } +} diff --git a/lib/pages/user/register_page.dart b/lib/pages/user/register_page.dart new file mode 100644 index 0000000..6fd8b36 --- /dev/null +++ b/lib/pages/user/register_page.dart @@ -0,0 +1,50 @@ +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; +import 'package:flutter/material.dart'; + +class RegisterPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Sign Up"), + body: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(40), + child: Column( + children: [ + Txt( + "Enter Phone Number", + txtType: TxtType.heading3, + ), + mHeight(12), + TxtField( + hint: "Enter Phone number to Register", + ), + TxtField( + hint: "Enter Phone number to Register", + ), + mHeight(40), + Txt( + "Or Enter Email", + txtType: TxtType.heading3, + ), + mHeight(12), + TxtField( + hint: "Enter Email to Register", + ), + mHeight(50), + ShowFillButton( + title: "Continue", + width: double.infinity, + onPressed: () {}, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/user/register_selection_page.dart b/lib/pages/user/register_selection_page.dart new file mode 100644 index 0000000..1717baf --- /dev/null +++ b/lib/pages/user/register_selection_page.dart @@ -0,0 +1,54 @@ +import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class RegisterSelectionPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Log In"), + body: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(40), + child: Column( + children: [ + Txt( + "Welcome Message", + txtType: TxtType.heading3, + ), + mFlex(1), + ShowFillButton( + title: "Log In", + width: double.infinity, + onPressed: () { + navigateWithName(context, AppRoutes.loginVerifyAccount); + }, + ), + mHeight(20), + ShowFillButton( + title: "Sign Up", + width: double.infinity, + onPressed: () { + navigateWithName(context, AppRoutes.register); + }, + ), + mHeight(20), + ShowFillButton( + title: "Forget Password", + width: double.infinity, + onPressed: () { + navigateWithName(context, AppRoutes.forgetPassword); + }, + ), + mFlex(2), + ], + ), + ), + ); + } +} diff --git a/lib/pages/user/splash_page.dart b/lib/pages/user/splash_page.dart new file mode 100644 index 0000000..145c30b --- /dev/null +++ b/lib/pages/user/splash_page.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class SplashPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + mFlex(5), + Txt( + "Logo", + fontSize: 45, + bold: true, + ), + mFlex(3), + Txt( + "First Time Log In", + txtType: TxtType.heading1, + isFlatButton: true, + onTap: () { + navigateWithName(context, AppRoutes.registerSelection); + }, + ), + mFlex(1), + Txt( + "Already Signed Up and Logged In", + txtType: TxtType.heading1, + isFlatButton: true, + onTap: () { + navigateWithName(context, AppRoutes.loginVerification); + }, + ), + mFlex(5), + ], + ), + ), + ); + } +} diff --git a/lib/provider/counter.dart b/lib/provider/counter.dart new file mode 100644 index 0000000..0272327 --- /dev/null +++ b/lib/provider/counter.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class Counter with ChangeNotifier, DiagnosticableTreeMixin { + int _count = 0; + + int get count => _count; + + void increment() { + _count++; + notifyListeners(); + } + + /// Makes `Counter` readable inside the devtools by listing all of its properties + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IntProperty('count', count)); + } +} \ No newline at end of file diff --git a/lib/repo/account_repository.dart b/lib/repo/account_repository.dart new file mode 100644 index 0000000..e645063 --- /dev/null +++ b/lib/repo/account_repository.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:car_provider_app/models/account.dart'; +import 'package:car_provider_app/models/response_models.dart'; +import 'package:car_provider_app/services/backend_service.dart'; +import 'package:injector/injector.dart'; + +abstract class IAcRepository { + Future getAccountList(); + + Future updateAccount(String dataAsJson); +} + +class AcRepository implements IAcRepository { + static const String ACCOUNT_API_CONTROLLER_MOBILE = + "AccountApiControllerMobile/"; + + static const String ACCOUNT_LIST = ACCOUNT_API_CONTROLLER_MOBILE + "list"; + static const String UPDATE_LIST = + ACCOUNT_API_CONTROLLER_MOBILE + "saveaccountselected"; + + @override + Future getAccountList() async { + BackendResponse response = await Injector.appInstance + .getDependency() + .getAuthenticatedAPI(ACCOUNT_LIST); + + if (response != null && response.isOk) { + return Account.fromJson(response.result); + } else { + throw Exception(); + } + } + + @override + Future updateAccount(String dataAsJson) async { + BackendResponse response = await Injector.appInstance + .getDependency() + .postAuthenticatedAPI(UPDATE_LIST, dataAsJson); + + if (response != null && response.isOk) { + //if parsing failed, throw exception + return response; + } else { + throw Exception(); + } + } +} diff --git a/lib/services/backend_service.dart b/lib/services/backend_service.dart new file mode 100644 index 0000000..311b03e --- /dev/null +++ b/lib/services/backend_service.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:car_provider_app/models/response_models.dart'; +import 'package:car_provider_app/services/secure_storage.dart'; +import 'package:http/http.dart'; +import 'package:injector/injector.dart'; + +import 'http_service.dart'; +import 'network_service.dart'; + +abstract class IBackendApiService { + Future getUnauthenticatedAPI(String route); + + Future getAuthenticatedAPI(String route); + + Future postUnauthenticatedAPI( + String route, String dataAsJson); + + Future postAuthenticatedAPI(String route, String dataAsJson); + + Future deleteAuthenticatedAPI(String route, String id); +} + +class BackendApiService implements IBackendApiService { + static String _homeUrl = "https://check.revotec.eu/check2/"; + static String _serverApiBaseUrl = _homeUrl + "mapi/v1/"; + + static String get homeUrl => _homeUrl; + + final ISecureStorage _secureStorage = + Injector.appInstance.getDependency(); + final IHttpService _httpService = + Injector.appInstance.getDependency(); + + ///internal helper functions which executes the given api call + ///and wraps the response + Future _callApi(Future callback) async { + Response response; + try { + //execute future + response = await callback; + //check response code, and if not ok return isOk = false + //200 for Get + //201 for Post + + // print("res121: " + + // response.statusCode.toString() + + // " Body:" + + // response.body.toString()); + //if delete request sent so server is returning 204 in case of success. + if (response.statusCode == 204) + return BackendResponse(id: 1, isOk: true, result: null); + + if (response.statusCode != 200 && response.statusCode != 201) + return BackendResponse(id: -1, isOk: false, result: null); + //if response code is good then parse message and return parsed response + return BackendResponse.fromJson(json.decode(response.body)); + //return BackendResponse.fromJson(dioResponse.body); + } catch (e) { + return BackendResponse(id: -1, isOk: false, result: null); + // try { + // return BackendResponse.fromJson(json.decode(response.body)); + // } catch (e) { + // return BackendResponse(id:-1, isOk:false,result: null); + // } + } + } + + @override + Future getAuthenticatedAPI(String route) async { + await checkConnection(); + final token = await _secureStorage.readBearerToken(); + return _callApi(_httpService.get(_serverApiBaseUrl + route, headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*', + HttpHeaders.authorizationHeader: "Bearer $token" + })); + } + + @override + Future getUnauthenticatedAPI(String route) async { + await checkConnection(); + return _callApi(_httpService.get(_serverApiBaseUrl + route, + headers: {'Content-Type': 'application/json', 'Accept': '*/*'})); + } + + @override + Future postAuthenticatedAPI( + String route, String dataAsJson) async { + await checkConnection(); + final token = await _secureStorage.readBearerToken(); + // print("res121: " + _serverApiBaseUrl + route); + return _callApi(_httpService + .post(_serverApiBaseUrl + route, body: dataAsJson, headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*', + HttpHeaders.authorizationHeader: "Bearer $token" + })); + } + + @override + Future postUnauthenticatedAPI( + String route, String dataAsJson) async { + await checkConnection(); + return _callApi(_httpService.post(_serverApiBaseUrl + route, + body: dataAsJson, headers: {'Content-Type': 'application/json'})); + } + + Future checkConnection() async { + if (!(await Injector.appInstance + .getDependency() + .isHostAvailable(_homeUrl))) throw NetworkException(); + } + + @override + Future deleteAuthenticatedAPI( + String route, String id) async { + await checkConnection(); + final token = await _secureStorage.readBearerToken(); + return _callApi( + _httpService.delete(_serverApiBaseUrl + route + "/" + id, headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*', + HttpHeaders.authorizationHeader: "Bearer $token" + })); + } +} diff --git a/lib/services/firebase_service.dart b/lib/services/firebase_service.dart new file mode 100644 index 0000000..ad76959 --- /dev/null +++ b/lib/services/firebase_service.dart @@ -0,0 +1,180 @@ +/* +import 'dart:io' show Platform; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +abstract class IFirebaseService { + Future get token; + + Future backgroundMessageHandler(Map message); + + Future messageHandler(Map message); + + Future onLaunch(Map message); + + Future onResume(Map message); + + void subscribeForPromotions(); + + void unsubscribeFromPromotions(); +} + +//https://medium.com/@SebastianEngel/easy-push-notifications-with-flutter-and-firebase-cloud-messaging-d96084f5954f +class FirebaseService implements IFirebaseService { + FirebaseMessaging _firebaseMessaging; + + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; + + FirebaseService() {. + _firebaseMessaging = FirebaseMessaging(); + + //https://github.com/FirebaseExtended/flutterfire/issues/1695 + _firebaseMessaging.configure( + onMessage: messageHandler, + onBackgroundMessage: + Platform.isAndroid ? myBackgroundMessageHandler : null, + onLaunch: onLaunch, + onResume: onResume, + ); + + //monitor firebase token changes + //https://firebase.google.com/docs/cloud-messaging/android/client#sample-register + ///The registration token may change when: + // + //The app deletes Instance ID + //The app is restored on a new device + //The user uninstalls/reinstall the app + //The user clears app data. + /// + + //for the first release we don't care about token refreshes + /*Stream fcmStream = _firebaseMessaging.onTokenRefresh; + fcmStream.listen((token) { + + });*/ + + //ios specific settings + //taken from https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_messaging/example/lib/main.dart + _firebaseMessaging.requestNotificationPermissions( + const IosNotificationSettings( + sound: true, badge: true, alert: true, provisional: true)); + _firebaseMessaging.onIosSettingsRegistered + .listen((IosNotificationSettings settings) { + print("Settings registered: $settings"); + }); + + var initializationSettingsAndroid = + AndroidInitializationSettings('app_icon'); + var initializationSettingsIOS = + IOSInitializationSettings(onDidReceiveLocalNotification: onDidReceiveLocalNotification); + var initializationSettings = InitializationSettings( + initializationSettingsAndroid, initializationSettingsIOS); + flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + flutterLocalNotificationsPlugin.initialize(initializationSettings, + onSelectNotification: selectNotification); + } + + Future onDidReceiveLocalNotification(int id, String title, String body, String payload) async{ + var androidPlatformChannelSpecifics = AndroidNotificationDetails( + 'new_message_channel_id', + 'Neue Nachricht', + 'Channel für neue Nachrichten', + importance: Importance.Max, + priority: Priority.High, + ticker: 'ticker'); + var iOSPlatformChannelSpecifics = IOSNotificationDetails(); + var platformChannelSpecifics = NotificationDetails( + androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show( + 0, + title, + body, + platformChannelSpecifics); + } + + @override + Future backgroundMessageHandler(Map message) async { + await myBackgroundMessageHandler(message); + } + + @override + Future messageHandler(Map message) async { + print("onMessage: $message"); + +// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + + var androidPlatformChannelSpecifics = AndroidNotificationDetails( + 'new_message_channel_id', + 'Neue Nachricht', + 'Channel für neue Nachrichten', + importance: Importance.Max, + priority: Priority.High, + ticker: 'ticker'); + var iOSPlatformChannelSpecifics = IOSNotificationDetails(); + var platformChannelSpecifics = NotificationDetails( + androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); + + if(Platform.isAndroid) { + await flutterLocalNotificationsPlugin.show( + 0, + message["notification"]["title"], + message["notification"]["body"], + platformChannelSpecifics); + }else if(Platform.isIOS){ + await flutterLocalNotificationsPlugin.show( + 0, + message["aps"]["alert"]["title"], + message["aps"]["alert"]["body"], + platformChannelSpecifics); + } + } + + Future selectNotification(String payload) async { + if (payload != null) { + debugPrint('notification payload: ' + payload); + } + } + + @override + Future onLaunch(Map message) async { + print("onLaunch: $message"); + } + + @override + Future onResume(Map message) async { + print("onResume: $message"); + } + + @override + Future get token => _firebaseMessaging.getToken(); + + @override + void subscribeForPromotions() { + _firebaseMessaging.subscribeToTopic("promotions"); + } + + @override + void unsubscribeFromPromotions() { + _firebaseMessaging.unsubscribeFromTopic("promotions"); + } +} + +Future myBackgroundMessageHandler(Map message) { + debugPrint("BACKGROUND MESSAGE RECEIVED"); + print("BACKGROUND MESSAGE RECEIVED"); + return Future.value(() => true); + + /*if (message.containsKey('data')) { + // Handle data message + final dynamic data = message['data']; + } + + if (message.containsKey('notification')) { + // Handle notification message + final dynamic notification = message['notification']; + }*/ + + // Or do other work. +} +*/ \ No newline at end of file diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart new file mode 100644 index 0000000..ff99ed0 --- /dev/null +++ b/lib/services/http_service.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:async'; + + +import 'package:http/http.dart' as http; +import 'package:http/http.dart'; +import 'package:path/path.dart'; +import 'package:injector/injector.dart'; + +abstract class IHttpService { + Future post(url, + {Map headers, body, Encoding encoding}); + + Future get(url, {Map headers}); + + Future delete(url, {Map headers}); +} + +class HttpService implements IHttpService { + @override + Future delete(url, {Map? headers}) { + return http.delete(url, headers: headers); + } + + @override + Future get(url, {Map? headers}) { + return http.get(url, headers: headers); + } + + @override + Future post(url, + {Map? headers, body, Encoding? encoding}) { + return http.post(url, headers: headers, body: body, encoding: encoding); + } +} diff --git a/lib/services/media_service.dart b/lib/services/media_service.dart new file mode 100644 index 0000000..0762d7f --- /dev/null +++ b/lib/services/media_service.dart @@ -0,0 +1,26 @@ +// import 'dart:io'; +// +// import 'package:image_picker/image_picker.dart'; +// +// abstract class IMediaService { +// Future takePicture(); +// +// Future openImageFromGallery(); +// } +// +// class MediaService implements IMediaService { +// @override +// Future openImageFromGallery() async { +// final pickedFile = +// await ImagePicker().getImage(source: ImageSource.gallery); +// if (pickedFile == null) return null; +// return File(pickedFile.path); +// } +// +// @override +// Future takePicture() async { +// final pickedFile = await ImagePicker().getImage(source: ImageSource.camera); +// if (pickedFile == null) return null; +// return File(pickedFile.path); +// } +// } diff --git a/lib/services/network_service.dart b/lib/services/network_service.dart new file mode 100644 index 0000000..50a861d --- /dev/null +++ b/lib/services/network_service.dart @@ -0,0 +1,25 @@ + +import 'dart:io'; + +abstract class INetworkService { + Future isHostAvailable(String endpoint); +} + +class NetworkService implements INetworkService{ + @override + Future isHostAvailable(String endpoint) async { + try { + final result = await InternetAddress.lookup(endpoint.substring(endpoint.indexOf('//')+2).substring(0,endpoint.substring(endpoint.indexOf('//')+2).indexOf('/'))); + if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { + return true; + } else{ + return false; + } + } on SocketException catch (_) { + return false; + } + } +} + +class NetworkException implements Exception { +} diff --git a/lib/services/secure_storage.dart b/lib/services/secure_storage.dart new file mode 100644 index 0000000..7622f7f --- /dev/null +++ b/lib/services/secure_storage.dart @@ -0,0 +1,33 @@ +abstract class ISecureStorage { + Future readBearerToken(); + + Future clearUserCredentials(); +} + +class SecureStorage implements ISecureStorage { + ///return bearer token if present, or null if not + @override + Future readBearerToken() async { + try { + return ""; + } catch (_) { + //an error occured returning null + return ""; + } + } + + ///returns true if write was successful, false otherwise + @override + Future writeBearerToken(String token) async { + try { + await ""; + return true; + } catch (_) { + //an error occured returning false + return false; + } + } + + @override + Future clearUserCredentials() async {} +} diff --git a/lib/services/shared_preferences.dart b/lib/services/shared_preferences.dart new file mode 100644 index 0000000..dd02cd1 --- /dev/null +++ b/lib/services/shared_preferences.dart @@ -0,0 +1,119 @@ +import 'dart:convert'; + +import 'package:car_provider_app/models/config_model.dart'; + +import 'package:shared_preferences/shared_preferences.dart' +as SharedPrefsPlugin; + +/// +/// Taken from AlarmGuide Project +/// + +abstract class ISharedPreferences { + Future get authState; + + Future setAuthState(int authState); + + Future get configState; + + Future setConfigState(int confState); + + Future get config; + + Future setConfig(ConfigModel config); + + Future get promotionNotificationsEnabled; + + Future setPromotionNotificationEnabled(bool newSetting); + + Future get helpAlreadyShown; + + Future setHelpAlreadyShown(); + + Future get useS3; + + Future setUseS3(int value); +} + +class SharedPreferences implements ISharedPreferences { + static const String _AUTH_STATE_KEY = "auth_key"; + static const String _CONFIG_KEY = "config"; + static const String _CONFIG_STATE_KEY = "config_key"; + static const String _PROMOTION_NOTIFICATION_KEY = "promotion"; + static const String _HELP_ALREADY_SHOWN = "help_shown"; + static const String _USE_S3 = "s3"; + + @override + Future get authState async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getInt(_AUTH_STATE_KEY); + } + + @override + Future setAuthState(int authState) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setInt(_AUTH_STATE_KEY, authState); + } + + @override + Future get config async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + final configAsJson = sharedPrefs.getString(_CONFIG_KEY); + return ConfigModel.fromJson(jsonDecode(configAsJson!)); + } + + @override + Future setConfig(ConfigModel config) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setString(_CONFIG_KEY, jsonEncode(config)); + setConfigState(1); + } + + @override + Future get promotionNotificationsEnabled async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getBool(_PROMOTION_NOTIFICATION_KEY); + } + + @override + Future setPromotionNotificationEnabled(bool newSetting) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setBool(_PROMOTION_NOTIFICATION_KEY, newSetting); + } + + @override + Future get helpAlreadyShown async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getBool(_HELP_ALREADY_SHOWN); + } + + @override + Future setHelpAlreadyShown() async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setBool(_HELP_ALREADY_SHOWN, true); + } + + @override + Future get configState async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getInt(_CONFIG_STATE_KEY); + } + + @override + Future setConfigState(int confState) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setInt(_CONFIG_STATE_KEY, confState); + } + + @override + Future setUseS3(int value) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setInt(_USE_S3, value); + } + + @override + Future get useS3 async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getInt(_USE_S3); + } +} diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart new file mode 100644 index 0000000..5202825 --- /dev/null +++ b/lib/theme/app_theme.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 'colors.dart'; + +class AppTheme { + static getTheme() => ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primaryColor: primaryColor, + primarySwatch: Colors.blue, + backgroundColor: Colors.white, + primaryTextTheme: TextTheme( + headline6: TextStyle(color: Colors.white), + ), + ); +} + +extension ExtendedRevoCheckTheme on TextTheme { + //add custom styles and colors here + //taken from https://medium.com/@crizantlai/flutter-how-to-extend-themedata-b5b987a95bb5 + TextStyle get price => const TextStyle(color: Colors.redAccent); +} diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart new file mode 100644 index 0000000..8510500 --- /dev/null +++ b/lib/theme/colors.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +const Color primaryColor = Colors.white; +const Color accentColor = Colors.blue; +const Color appBackgroundColor = Colors.white; +Color? accentColorDark = Colors.green[800]; +const Color borderColor = Colors.blueGrey; +Color? borderLightColor = Colors.blueGrey[50]; +Color backgroudColor = + Colors.blueGrey[50]!.withOpacity(0.5) ?? Colors.transparent; +const Color iconColor = Colors.blueGrey; +Color? headingColor = Colors.blueGrey[800]; +Color? txtColor = Colors.blueGrey[500]; diff --git a/lib/utils/AppPermissionHandler.dart b/lib/utils/AppPermissionHandler.dart new file mode 100644 index 0000000..3e3877e --- /dev/null +++ b/lib/utils/AppPermissionHandler.dart @@ -0,0 +1,36 @@ +import 'package:permission_handler/permission_handler.dart'; + +import 'dialogs.dart'; + +enum ConfirmAction { CANCEL, ACCEPT } + +Future requestPermissionGranted( + context, Permission requestPermissions) async { + var result = await requestPermissions.request(); + + switch (result) { + case PermissionStatus.granted: + // Application has been given permission to use the feature. + return true; + case PermissionStatus.denied: + // Application has been denied permission to use the feature. + return false; + case PermissionStatus.permanentlyDenied: + ConfirmAction? res = await showConfirmDialogs( + context, + 'You was denied Permission. You have give manual permission from app setting. ', + 'Open App Setting', + 'Cancel'); + if (res == ConfirmAction.ACCEPT) { + return false; + } else if (res == ConfirmAction.CANCEL) { + return false; + } + return false; + case PermissionStatus.restricted: + // iOS has restricted access to a specific feature. + return false; + default: + return false; + } +} diff --git a/lib/utils/dialogs.dart b/lib/utils/dialogs.dart new file mode 100644 index 0000000..a6bff7e --- /dev/null +++ b/lib/utils/dialogs.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import 'AppPermissionHandler.dart'; + +Future showConfirmDialogs( + context, msg, positiveText, negativeText) async { + return showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + backgroundColor: Colors.white, + title: Text(msg, style: TextStyle(fontSize: 16)), + actions: [ + TextButton( + child: Text( + negativeText, + ), + onPressed: () { + Navigator.of(context).pop(ConfirmAction.CANCEL); + }, + ), + TextButton( + child: Text( + positiveText, + ), + onPressed: () { + Navigator.of(context).pop(ConfirmAction.ACCEPT); + }, + ) + ], + ); + }, + ); +} diff --git a/lib/utils/navigator.dart b/lib/utils/navigator.dart new file mode 100644 index 0000000..f9855a9 --- /dev/null +++ b/lib/utils/navigator.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +navigateWithName(BuildContext context, String routeName, {Object? arguments}) { + Navigator.pushNamed(context, routeName, arguments: arguments); +} + +pop(BuildContext context) { + Navigator.of(context).pop(); +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart new file mode 100644 index 0000000..a00ee95 --- /dev/null +++ b/lib/utils/utils.dart @@ -0,0 +1,311 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:car_provider_app/config/constants.dart'; +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import 'package:sizer/sizer.dart'; + +Color getColorFromHex(String hexColor) { + hexColor = hexColor.toUpperCase().replaceAll('#', ''); + + if (hexColor.length == 6) { + hexColor = 'FF' + hexColor; + } + + return Color(int.parse(hexColor, radix: 16)); +} + +Widget spacerVertical(double v) { + return Container( + height: v, + width: double.infinity, + ); +} +Future delay(int millis) async { + return await Future.delayed(Duration(milliseconds: millis)); +} + +inkWellCorner({double? r}) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular(r ?? 4), + ); +} + +Widget spacerHorizontal(double v) { + return Container( + height: v, + width: v, + ); +} + +Widget mHeight(double f) { + return Container( + width: f, + height: f, + ); +} + +Widget mDivider(Color color, {double? h}) { + return Container( + width: double.infinity, + height: h ?? 1, + color: color, + ); +} + +Widget mDivider3({double? h}) { + return Container( + width: double.infinity, + height: h ?? 1, + color: borderLightColor!.withOpacity(0.7) ?? Colors.transparent, + ); +} + +Widget mDivider2(Color color, double w) { + return Container( + width: w, + height: 1, + color: color, + ); +} + +InputDecoration txtField(String label) { + return new InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + hintText: label, + hintStyle: TextStyle(color: Colors.grey), + disabledBorder: InputBorder.none, + isDense: false, + contentPadding: EdgeInsets.only(left: 15, right: 15), + ); +} + +Widget mWidth(double f) { + return Container( + width: f, + height: f, + ); +} + +Widget mFlex(int f) { + return Flexible( + flex: f, + child: Container( + width: double.infinity, + height: double.infinity, + ), + ); +} +Widget mExp(int f) { + return Expanded( + flex: f, + child: Container( + width: double.infinity, + ), + ); +} + +spacer() { + return SizedBox( + height: 8, + ); +} + +Widget floatButton(String icon, {Color? color, required Function onClick, String? title}) { + return Padding( + padding: const EdgeInsets.only(top: 12, bottom: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FloatingActionButton( + onPressed: () { + onClick(); + }, + heroTag: icon, + backgroundColor: accentColor, + elevation: 4, + child: Container( + child: SvgPicture.asset( + categorySvgIcons + icon, + color: color, + ), + width: double.infinity, + height: double.infinity, + decoration: containerRadius(Colors.white, 200), + clipBehavior: Clip.antiAlias, + padding: EdgeInsets.all(15), + margin: EdgeInsets.all(1), + ), + ), + if (title != null) mHeight(2.w), + if (title != null) + Txt( + title, + fontSize: 12.sp, + bold: true, + color: headingColor, + ) + ], + ), + ); +} + +navigateTo(context, page) { + Navigator.push(context, MaterialPageRoute(builder: (context) => page)); +} + +circularImage(String im, double width, double height) { + return new Container(width: 190.0, height: 190.0, decoration: new BoxDecoration(shape: BoxShape.circle, image: new DecorationImage(fit: BoxFit.fill, image: new AssetImage(im)))); +} + +circularImage2(String im, double width, double height) { + return new Container(width: width, height: height, decoration: new BoxDecoration(shape: BoxShape.circle, image: new DecorationImage(fit: BoxFit.fill, image: new AssetImage(im)))); +} + +cardRadius(double radius) { + return RoundedRectangleBorder( + side: BorderSide(color: Colors.transparent, width: 1), + borderRadius: BorderRadius.circular(radius), + ); +} + +cardRadiusWithoutBorder(double radius) { + return RoundedRectangleBorder( + side: BorderSide(color: Colors.transparent, width: 1), + borderRadius: BorderRadius.circular(radius), + ); +} + +Image imageFromBase64String(String base64String) { + return Image.memory(base64Decode(base64String)); +} + +Uint8List dataFromBase64String(String base64String) { + return base64Decode(base64String); +} + +String base64String(Uint8List data) { + return base64Encode(data); +} + +Widget overLayWidget({double? width, double? height,List? color}) { + return Container( + width: width ?? double.infinity, + height: height ?? 60, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: color!=null?color:[ + Colors.black.withOpacity(0.2), + Colors.black.withOpacity(0.1), + Colors.black.withOpacity(0.004), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + tileMode: TileMode.clamp, + ), + ), + ); +} + +Decoration containerRadius(Color color, double r) { + return BoxDecoration( + color: color, + borderRadius: BorderRadius.all(Radius.circular(r)), + ); +} + +Decoration containerRadiusTop({Color? color, double? r}) { + return BoxDecoration( + color: color ?? Colors.white, + borderRadius: BorderRadius.only(topRight: Radius.circular(r ?? 12), topLeft: Radius.circular(r ?? 12)), + ); +} + +Decoration containerRadiusBorder(Color color, double r) { + return BoxDecoration( + color: Colors.transparent, + border: Border.all(color: color, width: 1), + borderRadius: BorderRadius.all(Radius.circular(r)), + ); +} + +Decoration containerRadiusBottom(Color color, double r) { + return BoxDecoration( + color: color, + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(r), bottomRight: Radius.circular(r)), + ); +} + +ShapeBorder cardRadiusTop(double radius) { + return RoundedRectangleBorder( + side: BorderSide(color: Colors.transparent, width: 0), + borderRadius: BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)), + ); +} + +Decoration containerColorRadiusBorderWidth(Color background, double radius, Color color, double w) { + return BoxDecoration( + color: background, + border: Border.all( + width: w, // + color: color // <--- border width here + ), + borderRadius: BorderRadius.circular(radius), + ); +} + +ShapeBorder cardRadiusTop2(double radius) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)), + ); +} + +ShapeBorder cardRadiusBottom(double radius) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(radius), bottomRight: Radius.circular(radius)), + ); +} + +Decoration containerColorRadiusBorder(Color background, double radius, Color color) { + return BoxDecoration( + color: background, + border: Border.all( + width: 1, // + color: color // <--- border width here + ), + borderRadius: BorderRadius.circular(radius), + ); +} + + + + + + +//Decoration appGradient = BoxDecoration( +// gradient: LinearGradient( +// colors: [ +// Colors.green[200], +// Colors.green, +// ], +// begin: Alignment.topCenter, +// end: Alignment.bottomCenter, +// ), +//); +// launchURL(String url) async { +// if (await canLaunch(url)) { +// await launch(url); +// } else { +// throw 'Could not launch $url'; +// } +// } diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart new file mode 100644 index 0000000..b16637f --- /dev/null +++ b/lib/widgets/app_bar.dart @@ -0,0 +1,28 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; + +AppBar appBar({ + Color? backgroundColor, + double? elevation, + String? title, + Color? titleColor, + bool? isTitleCenter, + Color? backIconColor, + List? actions, +}) { + return AppBar( + backgroundColor: backgroundColor ?? appBackgroundColor, + elevation: elevation ?? 0, + centerTitle: isTitleCenter ?? true, + iconTheme: IconThemeData( + color: backIconColor ?? Colors.black, //change your color here + ), + actions: actions, + title: Txt( + title ?? "", + txtType: TxtType.appBar, + ), + ); +} diff --git a/lib/widgets/blurry_container.dart b/lib/widgets/blurry_container.dart new file mode 100644 index 0000000..8bebc4a --- /dev/null +++ b/lib/widgets/blurry_container.dart @@ -0,0 +1,62 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +const kDemoText = Center( + child: Text( + '', + style: TextStyle( + fontSize: 25, + color: Colors.white, + letterSpacing: 2, + ), + textAlign: TextAlign.center, + ), +); +const double kBlur = 1.0; +const EdgeInsetsGeometry kDefaultPadding = EdgeInsets.all(16); +const Color kDefaultColor = Colors.transparent; +const BorderRadius kBorderRadius = BorderRadius.all(Radius.circular(20)); +const double kColorOpacity = 0.0; + +class BlurryContainer extends StatelessWidget { + final Widget child; + final double blur; + final double? height, width; + final EdgeInsetsGeometry padding; + final Color bgColor; + + final BorderRadius borderRadius; + + //final double colorOpacity; + + BlurryContainer({ + this.child = kDemoText, + this.blur = 5, + required this.height, + required this.width, + this.padding = kDefaultPadding, + this.bgColor = kDefaultColor, + this.borderRadius = kBorderRadius, + //this.colorOpacity = kColorOpacity, + }); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: borderRadius, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), + child: Container( + height: height!, + width: width!, + padding: padding, + color: bgColor == Colors.transparent + ? bgColor + : bgColor.withOpacity(0.5), + child: child, + ), + ), + ); + } +} diff --git a/lib/widgets/button/show_circular_button.dart b/lib/widgets/button/show_circular_button.dart new file mode 100644 index 0000000..fd4e781 --- /dev/null +++ b/lib/widgets/button/show_circular_button.dart @@ -0,0 +1,23 @@ +import 'package:car_provider_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class ShowCircularButton extends StatelessWidget { + VoidCallback onPressed; + IconData? iconData; + ShowCircularButton({this.iconData,required this.onPressed}); + @override + Widget build(BuildContext context) { + return Card( + shape: cardRadius(1000), + color: Colors.black.withOpacity(0.2), + margin: EdgeInsets.all(12), + child: IconButton( + onPressed: onPressed, + icon: Icon( + iconData?? Icons.amp_stories_outlined, + color: Colors.white, + ), + ), + ); + } +} diff --git a/lib/widgets/button/show_image_button.dart b/lib/widgets/button/show_image_button.dart new file mode 100644 index 0000000..cac9a55 --- /dev/null +++ b/lib/widgets/button/show_image_button.dart @@ -0,0 +1,43 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +import '../txt.dart'; + +class ShowImageButton extends StatelessWidget { + String icon, title; + VoidCallback onClick; + + ShowImageButton( + {required this.icon, required this.title, required this.onClick}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onClick, + child: Container( + width: double.infinity, + child: Column( + children: [ + Container( + width: double.infinity, + height: 120, + color: accentColor, + padding: EdgeInsets.all(30), + child: Image.asset( + icon, + color: Colors.white, + ), + ), + mHeight(12), + Txt( + title, + txtType: TxtType.heading2, + color: Colors.blue, + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/dialog/dialogs.dart b/lib/widgets/dialog/dialogs.dart new file mode 100644 index 0000000..9a5a822 --- /dev/null +++ b/lib/widgets/dialog/dialogs.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +void showMDialog( + context, { + Widget? child, +}) async { + return showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + return Dialog( + child: child, + ); + }, + ); +} diff --git a/lib/widgets/dialog/message_dialog.dart b/lib/widgets/dialog/message_dialog.dart new file mode 100644 index 0000000..871b2e9 --- /dev/null +++ b/lib/widgets/dialog/message_dialog.dart @@ -0,0 +1,38 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class MessageDialog extends StatelessWidget { + String? title, buttonTitle; + VoidCallback? onClick; + MessageDialog({this.title, this.buttonTitle,this.onClick}); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + padding: EdgeInsets.all(30), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Txt( + title ?? "message", + txtType: TxtType.heading3, + ), + mHeight(40), + ShowFillButton( + title: buttonTitle ?? "Continue", + width: double.infinity, + onPressed: () { + onClick!(); + }, + ) + ], + ), + ); + } +} diff --git a/lib/widgets/dialog/otp_dialog.dart b/lib/widgets/dialog/otp_dialog.dart new file mode 100644 index 0000000..eae0664 --- /dev/null +++ b/lib/widgets/dialog/otp_dialog.dart @@ -0,0 +1,74 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class OtpDialog extends StatelessWidget { + VoidCallback onClick; + + OtpDialog({required this.onClick}); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + padding: EdgeInsets.all(30), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Txt( + "Please insert OTP Code", + txtType: TxtType.heading3, + ), + mHeight(20), + Row( + children: [ + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + mWidth(12), + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + mWidth(12), + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + mWidth(12), + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + ], + ), + mHeight(40), + ShowFillButton( + title: "Check Code", + width: double.infinity, + onPressed: () { + onClick(); + }, + ) + ], + ), + ); + } +} diff --git a/lib/widgets/dragable_sheet.dart b/lib/widgets/dragable_sheet.dart new file mode 100644 index 0000000..4d91118 --- /dev/null +++ b/lib/widgets/dragable_sheet.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +showDraggableDialog(BuildContext context, Widget child) { + showGeneralDialog( + barrierLabel: "Label", + barrierDismissible: false, + barrierColor: Colors.black.withOpacity(0.2), + transitionDuration: Duration(milliseconds: 200), + context: context, + pageBuilder: (context, anim1, anim2) { + return Dismissible( + direction: DismissDirection.vertical, + key: const Key('key'), + onDismissed: (_) => Navigator.of(context).pop(), + child: child, + ); + }, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1), + child: child, + ); + }, + ); +} diff --git a/lib/widgets/extensions/extensions_widget.dart b/lib/widgets/extensions/extensions_widget.dart new file mode 100644 index 0000000..caaaba9 --- /dev/null +++ b/lib/widgets/extensions/extensions_widget.dart @@ -0,0 +1,235 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +extension ExtendedText on Widget { + showOverlay({double? width, double? height}){ + return Container( + width: width ?? double.infinity, + height: height ?? 60, + decoration: BoxDecoration( + gradient: LinearGradient(colors: [ + Colors.black.withOpacity(0.2), + Colors.black.withOpacity(0.1), + Colors.black.withOpacity(0.004), + ], begin: Alignment.topCenter, end: Alignment.bottomCenter, tileMode: TileMode.clamp), + ), + child: this, + ); + } +} + + + + + +extension ImageExt on Image { + Widget circular() { + return ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(100), + child: this, + ); + } +} + +extension WidgetExt on Widget { + Widget toWidget() { + return this as Widget; + } + + Widget sized({double? width, double? height}) { + return SizedBox( + width: width, + height: height, + child: this, + ); + } + Widget inkWell({required VoidCallback onTap,double radius=0}){ + return InkWell( + child: Material(child: this,color: Colors.transparent,), + onTap: onTap, + ); + } + Widget sizeSq(double size) { + return SizedBox( + width: size, + height: size, + child: this, + ); + } + + Widget fillParent({double hFactor = 1, double? vFactor}) { + return FractionallySizedBox( + heightFactor: null, + widthFactor: hFactor, + child: this, + ); + } + + Widget margin( + {double top = 0, double bottom = 0, double left = 0, double right = 0}) { + var pad = + EdgeInsets.only(top: top, left: left, bottom: bottom, right: right); + try { + (this as dynamic).margin = pad; + } catch (err) { + return Padding( + padding: pad, + child: this, + ); + } + return this; + } + + Widget toClip({double r = 40}) { + return ClipRRect( + borderRadius: BorderRadius.all( + Radius.circular(r), + ), + clipBehavior: Clip.antiAlias, + child: this, + ); + } + + // Widget scrollable({Axis? direction, bool fadingEdge = true}) { + // var scrollview = SingleChildScrollView( + // child: this, + // controller: ScrollController(), + // scrollDirection: direction ?? Axis.vertical, + // ); + // return fadingEdge ? scrollview.fadingEdge() : scrollview; + // } + + Widget expand() { + return Expanded( + child: this, + ); + } + + Widget align(Alignment alignment) { + return Align( + alignment: alignment, + child: this, + ); + } + + Widget center() { + return Center( + child: this, + ); + } + + Widget padding(EdgeInsets padding) { + return Padding( + padding: padding, + child: this, + ); + } + + Widget paddingAll(double padding) { + return Padding( + padding: EdgeInsets.all(padding), + child: this, + ); + } + + // Widget onTap(VoidCallback onTap, {double corners = 0}) { + // return Clickable.widget(child: this, corners: corners, onTap: onTap); + // } + + Widget ignoreInteraction() { + return IgnorePointer( + child: this, + ); + } +} + +// extension xScrollView on ScrollView { +// Widget fadingEdge() => FadingEdgeScrollView.fromScrollView( +// child: this, +// gradientFractionOnEnd: 0.08, +// gradientFractionOnStart: 0.08, +// shouldDisposeScrollController: true, +// ); +// } +// +// extension x2ScrollView on SingleChildScrollView { +// Widget fadingEdge() => FadingEdgeScrollView.fromSingleChildScrollView( +// child: this, +// gradientFractionOnEnd: 0.08, +// gradientFractionOnStart: 0.08, +// shouldDisposeScrollController: true, +// ); +// } + +class Position { + final double x, y, w, h; + + Position(this.x, this.y, this.w, this.h); +} + +extension KeyExt on GlobalKey { + Position globalPosition() { + RenderBox box = currentContext!.findRenderObject() as RenderBox; + Offset xy = box.localToGlobal(Offset.zero); + Size wh = box.size; + return Position(xy.dx, xy.dy, wh.width, wh.height); + } +} + +extension StateExt on State { + reload({VoidCallback? b}) { + setState(b ?? () {}); + } +} + +// extension LocatiionExt on Location { +// LatLng toLatLng() { +// return LatLng(lat!, lng!); +// } +// } + +// extension xList on List { +// T randomItem() { +// final random = new Random(); +// var i = random.nextInt(this.length); +// return this[i]; +// } +// +// List append(List items) { +// this.addAll(items); +// return this; +// } +// +// bool isFirst(T item) => first == item; +// +// bool isLast(T item) => last == item; +// +// getOneByOne( +// {required int delayMillis, +// required Function(T) callback, VoidCallback? complete}) async { +// for (var i in this) { +// await delay(delayMillis); +// callback(i); +// } +// complete!(); +// } +// } + +extension xFuture on Future { + thenWithDelay(int millis, Function(T) thenBlock) { + then((value) { + Future.delayed(Duration(milliseconds: millis)) + .then((value) => thenBlock(value)); + }); + } +} + +extension xDouble on int { + Duration durationMillis() => Duration(milliseconds: this); + + Duration durationSec() => Duration(seconds: this); + + Duration durationHour() => Duration(hours: this); +} diff --git a/lib/widgets/gradient_app_bar.dart b/lib/widgets/gradient_app_bar.dart new file mode 100644 index 0000000..c3ded30 --- /dev/null +++ b/lib/widgets/gradient_app_bar.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +class GradientAppBar extends StatelessWidget { + final String title; + final double barHeight = 50.0; + IconData? iconData; + + GradientAppBar(this.title, {this.iconData}); + + @override + Widget build(BuildContext context) { + final double statusbarHeight = MediaQuery.of(context).padding.top; + + return new Container( + padding: EdgeInsets.only(top: statusbarHeight), + height: statusbarHeight + barHeight, + child: Row( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), + ), + Expanded( + child: Center( + child: Text( + title, + style: TextStyle( + fontSize: 20.0, + color: Colors.white, + fontWeight: FontWeight.bold), + ), + ), + ), + if (iconData != null) + IconButton( + onPressed: () {}, + icon: Icon( + iconData, + color: Colors.white, + ), + ), + ], + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withOpacity(0.4), + Colors.black.withOpacity(0.2), + Colors.black.withOpacity(0.0001), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + tileMode: TileMode.clamp, + ), + ), + ); + } +} diff --git a/lib/widgets/images/circular_image.dart b/lib/widgets/images/circular_image.dart new file mode 100644 index 0000000..89fb361 --- /dev/null +++ b/lib/widgets/images/circular_image.dart @@ -0,0 +1,30 @@ +import 'package:car_provider_app/config/constants.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class CircularImage extends StatelessWidget { + double? w, h, padding; + String? image; + + CircularImage({this.w, this.h, this.image, this.padding}); + + @override + Widget build(BuildContext context) { + return Container( + width: w ?? 120, + height: h ?? 120, + child: Card( + shape: cardRadius(2000), + clipBehavior: Clip.antiAlias, + elevation: 4, + child: Card( + shape: cardRadius(2000), + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.all(padding ?? 0), + child: Image.asset(image ?? icons + "green.jpg"), + ), + ), + ); + } +} diff --git a/lib/widgets/show_card_buttton.dart b/lib/widgets/show_card_buttton.dart new file mode 100644 index 0000000..6fd2255 --- /dev/null +++ b/lib/widgets/show_card_buttton.dart @@ -0,0 +1,32 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:flutter/material.dart'; + +class ShowCardButton extends StatelessWidget { + String title; + VoidCallback onPressed; + Color txtColor; + + ShowCardButton({ + required this.title, + required this.onPressed, + this.txtColor = Colors.white, + }); + + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.zero, + elevation: 20, + child: Container( + width: double.infinity, + padding: EdgeInsets.all(12), + child: ShowFillButton( + title: title, + onPressed: onPressed, + txtColor: txtColor, + ), + ), + ); + } +} diff --git a/lib/widgets/show_fill_button.dart b/lib/widgets/show_fill_button.dart new file mode 100644 index 0000000..b98eec7 --- /dev/null +++ b/lib/widgets/show_fill_button.dart @@ -0,0 +1,41 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class ShowFillButton extends StatelessWidget { + String title; + VoidCallback onPressed; + Color txtColor; + double elevation, radius,width; + + ShowFillButton({ + required this.title, + required this.onPressed, + this.txtColor = Colors.white, + this.elevation = 4, + this.radius = 6, + this.width=88, + }); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + onPrimary: Colors.black87, + primary: accentColor, + minimumSize: Size(width, 45), + padding: EdgeInsets.symmetric(horizontal: 16), + elevation: elevation, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(radius)), + ), + ), + onPressed: onPressed, + child: Txt( + title.toUpperCase(), + color: txtColor, + txtType: TxtType.heading1, + ), + ); + } +} diff --git a/lib/widgets/txt.dart b/lib/widgets/txt.dart new file mode 100644 index 0000000..bb4b816 --- /dev/null +++ b/lib/widgets/txt.dart @@ -0,0 +1,170 @@ +// import 'package:auto_size_text/auto_size_text.dart'; +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; + +enum TxtType { + small, + normal, + heading1, + heading2, + heading3, + appBar, +} + +class Txt extends StatelessWidget { + String text; + int? maxLines; + double? fontSize; + Color? color; + bool? bold; + bool? isUnderline; + bool? isFlatButton; + double? pedding; + TextAlign? textAlign; + FontWeight? fontWeight; + Function? onTap; + TxtType txtType; + + Txt(this.text, {this.maxLines, this.color, this.bold, this.fontSize, this.isUnderline, this.isFlatButton, this.pedding, this.textAlign, this.fontWeight, this.onTap, this.txtType = TxtType.normal}); + + @override + Widget build(BuildContext context) { + if (isFlatButton != null) + return Padding( + padding: EdgeInsets.only(right: pedding ?? 0, left: pedding ?? 0), + child: InkWell( + onTap: () { + onTap!(); + }, + customBorder: inkWellCorner(r: 4), + child: Padding( + padding: const EdgeInsets.only( + left: 14, + right: 14, + top: 6, + bottom: 6, + ), + child: getText(), + ), + ), + ); + else + return getText(); + } + + Widget getText() { + return Material( + type: MaterialType.transparency, + child: Text( + text, + maxLines: maxLines, + textAlign: textAlign, + overflow: maxLines != null ? TextOverflow.ellipsis : null, + style: TextStyle( + fontSize: fontSize ?? + (txtType == TxtType.small + ? 8.sp + : txtType == TxtType.normal + ? 10.sp + : txtType == TxtType.heading1 + ? 11.sp + : txtType == TxtType.heading2 + ? 12.sp + : txtType == TxtType.heading3 + ? 13.sp + : txtType == TxtType.appBar + ? 14.sp + : 8.sp), + color: color ?? + (txtType == TxtType.appBar + ? Colors.black + : txtType == TxtType.heading1 + ? headingColor + : txtType == TxtType.heading2 + ? headingColor + : txtType == TxtType.heading3 + ? headingColor + : null), + fontWeight: (fontWeight != null) + ? fontWeight + : ((bold != null) + ? FontWeight.bold + : (txtType == TxtType.appBar + ? FontWeight.bold + : txtType == TxtType.heading1 + ? FontWeight.bold + : txtType == TxtType.heading2 + ? FontWeight.bold + : txtType == TxtType.heading3 + ? FontWeight.bold + : null)), + decoration: (isUnderline != null) ? TextDecoration.underline : null, + ), + ), + ); + } +} + +// class TxtAuto extends StatelessWidget { +// String text; +// int? maxLines; +// double? fontSize; +// Color? color; +// bool? bold; +// bool? isUnderline; +// bool? isFlatButton; +// double? pedding; +// TextAlign? textAlign; +// +// TxtAuto( +// this.text, { +// this.maxLines, +// this.color, +// this.bold, +// this.fontSize, +// this.isUnderline, +// this.isFlatButton, +// this.pedding, +// this.textAlign, +// }); +// +// @override +// Widget build(BuildContext context) { +// if (isFlatButton != null) +// return Padding( +// padding: EdgeInsets.only(right: pedding ?? 0, left: pedding ?? 0), +// child: InkWell( +// onTap: () {}, +// customBorder: inkWellCorner(r: 4), +// child: Padding( +// padding: const EdgeInsets.only( +// left: 14, +// right: 14, +// top: 6, +// bottom: 6, +// ), +// child: getText(), +// ), +// ), +// ); +// else +// return getText(); +// } +// +// Widget getText() { +// return AutoSizeText( +// text, +// maxLines: maxLines, +// textAlign: textAlign, +// overflow: maxLines != null ? TextOverflow.ellipsis : null, +// style: TextStyle( +// fontSize: fontSize, +// color: color, +// fontWeight: (bold != null) ? FontWeight.bold : null, +// decoration: (isUnderline != null) ? TextDecoration.underline : null, +// ), +// ); +// } +// } diff --git a/lib/widgets/txt_field.dart b/lib/widgets/txt_field.dart new file mode 100644 index 0000000..f572d83 --- /dev/null +++ b/lib/widgets/txt_field.dart @@ -0,0 +1,152 @@ +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; + +class TxtField extends StatelessWidget { + TextEditingController controller = new TextEditingController(); + String? title; + String? hint; + String? lable; + IconData? prefixData; + IconData? postfixData; + bool isNeedFilterButton; + bool isNeedClickAll; + bool isButtonEnable; + double? elevation; + Function? onTap; + String? buttonTitle; + int? maxLines; + bool isSidePaddingZero; + bool isNeedBorder; + + TxtField({ + this.title, + this.lable, + this.hint, + this.prefixData, + this.postfixData, + this.isNeedClickAll = false, + this.isNeedFilterButton = false, + this.elevation, + this.onTap, + this.isButtonEnable = false, + this.buttonTitle, + this.maxLines, + this.isSidePaddingZero=false, + this.isNeedBorder=true, + }); + + @override + Widget build(BuildContext context) { + controller.text = title ?? ""; + return InkWell( + onTap: isNeedClickAll == false + ? null + : () { + onTap!(); + }, + customBorder: inkWellCorner(), + child: Row( + children: [ + Expanded( + child: Card( + elevation: elevation, + margin: isSidePaddingZero?EdgeInsets.zero:null, + child: TextField( + autofocus: false, + controller: controller, + enabled: isNeedClickAll == true ? false : true, + maxLines: maxLines, + onTap: () {}, + decoration: InputDecoration( + labelText: lable, + alignLabelWithHint: true, + fillColor: Colors.white, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: accentColor, width:isNeedBorder? 1.0:0), + borderRadius: BorderRadius.circular(4.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: borderColor, width:isNeedBorder? 1.0:0), + borderRadius: BorderRadius.circular(4.0), + ), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: borderColor, width:isNeedBorder? 1.0:0), + borderRadius: BorderRadius.circular(4.0), + ), + prefixIcon: prefixData != null + ? Icon( + Icons.search, + color: borderColor, + ) + : null, + labelStyle: TextStyle(color: borderColor, fontSize: 13.sp), + hintStyle: TextStyle(color: borderColor, fontSize: 9.sp), + hintText: hint ?? "", + contentPadding: prefixData == null + ? EdgeInsets.only( + left: 12, + right: 12, + top: maxLines != null ? 12 : 0, + bottom: maxLines != null ? 12 : 0, + ) + : EdgeInsets.zero, + ), + ), + ), + ), + if (isNeedFilterButton) mWidth(8), + if (isNeedFilterButton) + InkWell( + onTap: isNeedClickAll + ? null + : () { + controller.clear(); + }, + child: Container( + width: 55, + height: 55, + child: Card( + color: accentColor, + // margin: EdgeInsets.all(4), + // shape: cardRadius(0), + child: Icon( + postfixData ?? Icons.filter_alt, + color: Colors.white, + ), + ), + ), + ), + if (isButtonEnable) + Material( + child: InkWell( + onTap: () {}, + customBorder: inkWellCorner(), + child: Container( + height: 55, + child: Card( + color: accentColor, + // margin: EdgeInsets.all(4), + // shape: cardRadius(0), + child: Center( + child: Padding( + padding: const EdgeInsets.only(left: 12, right: 12), + child: Txt( + buttonTitle ?? "Search", + color: Colors.white, + fontSize: 18, + bold: true, + ), + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/user_image.dart b/lib/widgets/user_image.dart new file mode 100644 index 0000000..78d2236 --- /dev/null +++ b/lib/widgets/user_image.dart @@ -0,0 +1,26 @@ +import 'package:car_provider_app/config/constants.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class UserImage extends StatelessWidget { + double? size; + String? url; + + UserImage({this.size, this.url}); + + @override + Widget build(BuildContext context) { + return Container( + height: size ?? 60, + width: size ?? 60, + decoration: containerRadius(Colors.transparent, 1000), + clipBehavior: Clip.antiAlias, + child: Image.asset( + url ?? icons + "Blue Masked.jpg", + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 18fcc0f..7579b66 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" async: dependency: transitive description: @@ -43,6 +50,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" cupertino_icons: dependency: "direct main" description: @@ -50,6 +64,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + easy_logger: + dependency: transitive + description: + name: easy_logger + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2" fake_async: dependency: transitive description: @@ -57,6 +85,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" flutter: dependency: "direct main" description: flutter @@ -69,11 +111,63 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "0.22.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + injector: + dependency: "direct main" + description: + name: injector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" lints: dependency: transitive description: @@ -95,6 +189,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -102,6 +203,181 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1+1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.0" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.1" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + sizer: + dependency: "direct main" + description: + name: sizer + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" sky_engine: dependency: transitive description: flutter @@ -156,6 +432,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" vector_math: dependency: transitive description: @@ -163,5 +446,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.14.0 <3.0.0" + flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index 482a2d4..fc602a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,15 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + path_provider: ^2.0.4 + injector: ^2.0.0 + provider: ^6.0.0 + easy_localization: ^3.0.0 + http: ^0.13.3 + permission_handler: 7.1.0 + flutter_svg: ^0.22.0 + sizer: ^2.0.15 + dev_dependencies: flutter_test: @@ -58,10 +67,11 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - + assets: + - resources/langs/ + - assets/ + - assets/icons/ + - assets/images/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/resources/langs/en-US.json b/resources/langs/en-US.json new file mode 100644 index 0000000..c9f87fc --- /dev/null +++ b/resources/langs/en-US.json @@ -0,0 +1,38 @@ +{ + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +} \ No newline at end of file diff --git a/resources/langs/en.json b/resources/langs/en.json new file mode 100644 index 0000000..c9f87fc --- /dev/null +++ b/resources/langs/en.json @@ -0,0 +1,38 @@ +{ + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +} \ No newline at end of file diff --git a/resources/s.dart b/resources/s.dart new file mode 100644 index 0000000..e69de29 diff --git a/test/widget_test.dart b/test/widget_test.dart index 1231718..320226f 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:car_provider_app/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget( MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);