├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ │ ├── LaunchBackground.imageset │ │ │ ├── background.png │ │ │ └── Contents.json │ │ └── BrandingImage.imageset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist └── .gitignore ├── assets ├── fonts │ ├── OCRAStd.otf │ └── HadWinIcons.ttf └── images │ ├── checkmark.png │ ├── piggy-bank.png │ ├── hadwin_system │ ├── hadwin-logo.png │ ├── hadwin-name.png │ ├── hadwin-banner.png │ ├── hadwin-logo-lite.png │ ├── hadwin-adaptive-logo.png │ ├── hadwin-logo-with-name.png │ ├── hadwin-splash-screen-logo.png │ └── magicpattern-blob-1652765120695.png │ ├── notification_assets │ ├── no-wifi.png │ └── file-error.png │ ├── transparent_card_brands │ ├── jcb.png │ ├── solo.png │ ├── visa.png │ ├── rupay.png │ ├── switch.png │ ├── discover.png │ ├── maestro.png │ ├── union-pay.png │ ├── diners-club.png │ ├── mastercard.png │ └── american-express.png │ ├── card_flow_assets │ ├── visa-backside.png │ ├── default-backside.png │ ├── maestro-backside.png │ ├── visa-frontside.png │ ├── default-frontside.png │ ├── discover-backside.png │ ├── discover-frontside.png │ ├── maestro-frontside.png │ ├── mastercard-backside.png │ ├── mastercard-frontside.png │ ├── american-express-backside.png │ └── american-express-frontside.png │ └── onboarding_assets │ ├── wfh-mohamed-chahin-bg-less.png │ └── online-shopping-yuliia-osadcha-bg-less.png ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-hdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-v21 │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ └── ic_launcher.xml │ │ │ │ ├── values-v31 │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── hadwin │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── lib ├── resources │ └── api_constants.dart ├── utilities │ ├── url_external_launcher.dart │ ├── slide_right_route.dart │ ├── hadwin_icons.dart │ ├── make_api_request.dart │ ├── card_identifier.dart │ ├── display_error_alert.dart │ ├── custom_date_grouping.dart │ └── hadwin_markdown_viewer.dart ├── providers │ ├── tab_navigation_provider.dart │ ├── user_login_state_provider.dart │ └── live_transactions_provider.dart ├── components │ ├── main_app_screen │ │ ├── local_splash_screen_component.dart │ │ ├── loading_screen_component.dart │ │ └── tabbed_layout_component.dart │ ├── qr_code_scanner_screen │ │ ├── post_successful_qr_scan.dart │ │ ├── scan_error_screen.dart │ │ └── my_qr_screen.dart │ ├── wallet_screen │ │ └── available_cards_loading.dart │ ├── contacts_screen │ │ └── contacts_loading.dart │ ├── activities_screen │ │ └── activities_loading.dart │ ├── settings_screen │ │ ├── license_data.dart │ │ ├── credits_loading.dart │ │ ├── all_licenses.dart │ │ └── app_creator_info.dart │ ├── add_card_screen │ │ └── card_flipper.dart │ └── sign_up_screen │ │ ├── step_get_bank_account.dart │ │ ├── step_get_email_password.dart │ │ └── step_get_name_address.dart ├── screens │ ├── new_settings_screen.dart │ ├── sign_up_screen.dart │ ├── login_screen.dart │ ├── qr_code_scanner_screen.dart │ └── onboarding_screen.dart ├── database │ ├── user_data_storage.dart │ ├── login_info_storage.dart │ ├── hadwin_user_device_info_storage.dart │ ├── successful_transactions_storage.dart │ └── cards_storage.dart ├── hadwin_components.dart └── main.dart ├── media ├── promotional │ ├── hadwin-gif-1.gif │ ├── 3d-screenhot-tray.png │ ├── hadwin-screenshot-tray-1.png │ ├── hadwin-screenshot-with-skin-set-1.png │ ├── hadwin-screenshot-with-skin-set-2.png │ └── hadwin-devto-thumbnail-landscape-play.png ├── flowcharts │ ├── login-process-flowchart.png │ └── app-launch-process-flowchart.png └── ui_designs │ └── alert-box-to-navigate-to-sign-up-screen-design.png ├── .metadata ├── test └── widget_test.dart ├── CONTRIBUTING.md ├── analysis_options.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md └── pubspec.yaml /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /assets/fonts/OCRAStd.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/fonts/OCRAStd.otf -------------------------------------------------------------------------------- /assets/fonts/HadWinIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/fonts/HadWinIcons.ttf -------------------------------------------------------------------------------- /assets/images/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/checkmark.png -------------------------------------------------------------------------------- /assets/images/piggy-bank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/piggy-bank.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /lib/resources/api_constants.dart: -------------------------------------------------------------------------------- 1 | class ApiConstants { 2 | static const baseUrl = 'https://fruitcastle.onrender.com'; 3 | } -------------------------------------------------------------------------------- /media/promotional/hadwin-gif-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/promotional/hadwin-gif-1.gif -------------------------------------------------------------------------------- /media/promotional/3d-screenhot-tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/promotional/3d-screenhot-tray.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/hadwin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/hadwin-logo.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/hadwin-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/hadwin-name.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/hadwin-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/hadwin-banner.png -------------------------------------------------------------------------------- /assets/images/notification_assets/no-wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/notification_assets/no-wifi.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/jcb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/jcb.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/solo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/solo.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/visa.png -------------------------------------------------------------------------------- /media/flowcharts/login-process-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/flowcharts/login-process-flowchart.png -------------------------------------------------------------------------------- /media/promotional/hadwin-screenshot-tray-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/promotional/hadwin-screenshot-tray-1.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/visa-backside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/visa-backside.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/hadwin-logo-lite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/hadwin-logo-lite.png -------------------------------------------------------------------------------- /assets/images/notification_assets/file-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/notification_assets/file-error.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/rupay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/rupay.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/switch.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/default-backside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/default-backside.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/maestro-backside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/maestro-backside.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/visa-frontside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/visa-frontside.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/discover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/discover.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/maestro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/maestro.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/union-pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/union-pay.png -------------------------------------------------------------------------------- /media/flowcharts/app-launch-process-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/flowcharts/app-launch-process-flowchart.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/default-frontside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/default-frontside.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/discover-backside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/discover-backside.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/discover-frontside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/discover-frontside.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/maestro-frontside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/maestro-frontside.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/hadwin-adaptive-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/hadwin-adaptive-logo.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/hadwin-logo-with-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/hadwin-logo-with-name.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/diners-club.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/diners-club.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/mastercard.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/mastercard-backside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/mastercard-backside.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/mastercard-frontside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/mastercard-frontside.png -------------------------------------------------------------------------------- /media/promotional/hadwin-screenshot-with-skin-set-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/promotional/hadwin-screenshot-with-skin-set-1.png -------------------------------------------------------------------------------- /media/promotional/hadwin-screenshot-with-skin-set-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/promotional/hadwin-screenshot-with-skin-set-2.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/hadwin-splash-screen-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/hadwin-splash-screen-logo.png -------------------------------------------------------------------------------- /assets/images/transparent_card_brands/american-express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/transparent_card_brands/american-express.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1546A0 4 | -------------------------------------------------------------------------------- /assets/images/card_flow_assets/american-express-backside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/american-express-backside.png -------------------------------------------------------------------------------- /assets/images/card_flow_assets/american-express-frontside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/card_flow_assets/american-express-frontside.png -------------------------------------------------------------------------------- /media/promotional/hadwin-devto-thumbnail-landscape-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/promotional/hadwin-devto-thumbnail-landscape-play.png -------------------------------------------------------------------------------- /assets/images/hadwin_system/magicpattern-blob-1652765120695.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/hadwin_system/magicpattern-blob-1652765120695.png -------------------------------------------------------------------------------- /assets/images/onboarding_assets/wfh-mohamed-chahin-bg-less.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/onboarding_assets/wfh-mohamed-chahin-bg-less.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /media/ui_designs/alert-box-to-navigate-to-sign-up-screen-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/media/ui_designs/alert-box-to-navigate-to-sign-up-screen-design.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /assets/images/onboarding_assets/online-shopping-yuliia-osadcha-bg-less.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brownboycodes/HADWIN/HEAD/assets/images/onboarding_assets/online-shopping-yuliia-osadcha-bg-less.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/hadwin/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.hadwin 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 18116933e77adc82f80866c928266a5b4f1ed645 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | app/upload-keystore.jks -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/utilities/url_external_launcher.dart: -------------------------------------------------------------------------------- 1 | import 'package:url_launcher/url_launcher.dart'; 2 | 3 | Future launchExternalURL(String url) async { 4 | Uri uri =Uri.parse(url); 5 | 6 | if (await canLaunchUrl(uri)) { 7 | await launchUrl(uri,mode: LaunchMode.externalApplication); 8 | } 9 | // else { 10 | // throw 'Could not launch $url'; 11 | // } 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/BrandingImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BrandingImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "BrandingImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "BrandingImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/providers/tab_navigation_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TabNavigationProvider with ChangeNotifier { 4 | List _tabHistory = [0]; 5 | 6 | int get lastTab => _tabHistory.last; 7 | 8 | void removeLastTab() { 9 | _tabHistory.removeLast(); 10 | notifyListeners(); 11 | } 12 | 13 | void updateTabs(int tab) { 14 | if (tab == 0) { 15 | _tabHistory = [0]; 16 | } else { 17 | _tabHistory.removeWhere((element) => element == tab); 18 | _tabHistory.add(tab); 19 | } 20 | notifyListeners(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/components/main_app_screen/local_splash_screen_component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LocalSplashScreenComponent extends StatelessWidget { 4 | const LocalSplashScreenComponent({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container( 9 | color: Color(0xff2f73b9), 10 | child: Column( 11 | mainAxisAlignment: MainAxisAlignment.center, 12 | children: [ 13 | Image.asset( 14 | 'assets/images/hadwin_system/hadwin-splash-screen-logo.png', 15 | height: 128.0, 16 | ), 17 | ], 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /lib/components/main_app_screen/loading_screen_component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingScreenComponent extends StatelessWidget { 4 | const LoadingScreenComponent({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container( 9 | color: Colors.white, 10 | child: Column( 11 | mainAxisAlignment: MainAxisAlignment.center, 12 | children: [ 13 | SizedBox( 14 | height: 64, 15 | width: 64, 16 | child: CircularProgressIndicator(), 17 | ), 18 | Padding(padding: EdgeInsets.all(10), child: Text("Loading...")) 19 | ], 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // ext.kotlin_version = '1.5.10' 3 | ext.kotlin_version = '1.8.10' 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:4.2.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | } 22 | 23 | rootProject.buildDir = '../build' 24 | subprojects { 25 | project.buildDir = "${rootProject.buildDir}/${project.name}" 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/utilities/slide_right_route.dart: -------------------------------------------------------------------------------- 1 | // page slide transition effect 2 | import 'package:flutter/material.dart'; 3 | 4 | class SlideRightRoute extends PageRouteBuilder { 5 | final Widget page; 6 | SlideRightRoute({required this.page}) 7 | : super( 8 | pageBuilder: ( 9 | BuildContext context, 10 | Animation animation, 11 | Animation secondaryAnimation, 12 | ) => 13 | page, 14 | transitionsBuilder: ( 15 | BuildContext context, 16 | Animation animation, 17 | Animation secondaryAnimation, 18 | Widget child, 19 | ) => 20 | SlideTransition( 21 | textDirection: TextDirection.rtl, 22 | position: Tween( 23 | begin: const Offset(-1, 0), 24 | end: Offset.zero, 25 | ).animate(animation), 26 | child: child, 27 | ), 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /lib/screens/new_settings_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hadwin/hadwin_components.dart'; 3 | 4 | 5 | 6 | class NewSettingsScreen extends StatelessWidget { 7 | NewSettingsScreen({Key? key}) : super(key: key); 8 | 9 | final AppBar appBar = AppBar( 10 | title: Text('Settings'), 11 | centerTitle: true, 12 | backgroundColor: Colors.transparent, 13 | foregroundColor: Color(0xff243656), 14 | elevation: 0, 15 | ); 16 | 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | Column appSettings = Column( 21 | children: [ 22 | Expanded( 23 | child: Container( 24 | width: MediaQuery.of(context).size.width, 25 | height: MediaQuery.of(context).size.height - 180, 26 | child: AppSettingsComponent(), 27 | ), 28 | ) 29 | ], 30 | ); 31 | return Scaffold( 32 | backgroundColor: Colors.white, 33 | appBar: appBar, 34 | body: appSettings, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/utilities/hadwin_icons.dart: -------------------------------------------------------------------------------- 1 | /// Flutter icons HadWinIcons 2 | /// Copyright (C) 2022 by original authors @ fluttericon.com, fontello.com 3 | /// This font was generated by FlutterIcon.com, which is derived from Fontello. 4 | /// 5 | /// To use this font, place it in your fonts/ directory and include the 6 | /// following in your pubspec.yaml 7 | /// 8 | /// flutter: 9 | /// fonts: 10 | /// - family: HadWinIcons 11 | /// fonts: 12 | /// - asset: fonts/HadWinIcons.ttf 13 | /// 14 | /// 15 | /// 16 | import 'package:flutter/widgets.dart'; 17 | 18 | class HadWinIcons { 19 | HadWinIcons._(); 20 | 21 | static const _kFontFam = 'HadWinIcons'; 22 | static const String? _kFontPkg = null; 23 | 24 | static const IconData wallet_business_and_trade = IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg); 25 | static const IconData wallet_education_29 = IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg); 26 | static const IconData line_awesome_wallet_solid = IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg); 27 | } 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:hadwin/main.dart'; 11 | 12 | 13 | 14 | void main() { 15 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 16 | // Build our app and trigger a frame. 17 | await tester.pumpWidget(const MyApp()); 18 | 19 | // Verify that our counter starts at 0. 20 | expect(find.text('0'), findsOneWidget); 21 | expect(find.text('1'), findsNothing); 22 | 23 | // Tap the '+' icon and trigger a frame. 24 | await tester.tap(find.byIcon(Icons.add)); 25 | await tester.pump(); 26 | 27 | // Verify that our counter has incremented. 28 | expect(find.text('0'), findsNothing); 29 | expect(find.text('1'), findsOneWidget); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 17 | 20 | -------------------------------------------------------------------------------- /lib/components/qr_code_scanner_screen/post_successful_qr_scan.dart: -------------------------------------------------------------------------------- 1 | /* 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'package:qr_code_scanner/qr_code_scanner.dart'; 7 | 8 | class PostSuccessfulQRScanScreen extends StatelessWidget { 9 | final Barcode result; 10 | const PostSuccessfulQRScanScreen({Key? key, required this.result}) 11 | : super(key: key); 12 | 13 | 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | dynamic tests = 'untouched'; 18 | try { 19 | 20 | tests = json.decode(result.code!); 21 | } catch (e) { 22 | tests = e.toString(); 23 | } 24 | return Scaffold( 25 | appBar: AppBar( 26 | backgroundColor: Colors.white, 27 | foregroundColor: Color(0xff243656), 28 | elevation: 0, 29 | title: Padding( 30 | padding: EdgeInsets.symmetric(horizontal: 48), 31 | child: Text("QR Scan Result", 32 | style: TextStyle(color: Color(0xff243656))), 33 | ), 34 | ), 35 | body: Column( 36 | children: [ 37 | Expanded( 38 | child: Container( 39 | child: Text('Data: ${tests}'), 40 | )) 41 | ], 42 | ), 43 | ); 44 | } 45 | } 46 | */ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to HADWIN 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 🐛 6 | - Discussing the current state of the code 👩‍💻📈 7 | - Submitting a fix 🩹 8 | - Proposing new features ✨ 9 | 10 |
11 | 12 | ## Use a Consistent Coding Style 13 | 14 | - files should be named after the widget it contains, for example- if a file contains the class `UserInfo` the file name should be `user_info.dart` 15 | - the preferred file naming convention is `snake_case` 16 | - the class names should be meaningful and should be according to the purpose it serves 17 | - follow clean coding principles (check out this [cheatsheet](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29)) 18 | 19 | 20 |
21 | 22 | ## Any contributions you make will be under the GNU GENERAL PUBLIC LICENSE 3.0 23 | 24 | In short, when you submit code changes, your submissions are understood to be under the same [GNU GENERAL PUBLIC LICENSE 3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 |
27 | 28 | --- 29 | 30 | 🛑✋ 31 | 32 | 33 | guidelines for contributing to this project is being reviewed and will be updated soon... 34 |
35 | please wait till then -------------------------------------------------------------------------------- /lib/components/wallet_screen/available_cards_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:fade_shimmer/fade_shimmer.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Widget _availableCardsLoadingTile() { 5 | return Row( 6 | mainAxisAlignment: MainAxisAlignment.center, 7 | children: [ 8 | Container( 9 | child: FadeShimmer( 10 | height: 48, 11 | width: 64, 12 | fadeTheme: FadeTheme.light, 13 | ), 14 | padding: EdgeInsets.all(5), 15 | ), 16 | Column( 17 | children: [ 18 | Container( 19 | child: FadeShimmer( 20 | height: 21, 21 | width: 200, 22 | fadeTheme: FadeTheme.light, 23 | ), 24 | padding: EdgeInsets.all(5), 25 | ), 26 | Container( 27 | child: FadeShimmer( 28 | height: 18, 29 | width: 180, 30 | fadeTheme: FadeTheme.light, 31 | ), 32 | padding: EdgeInsets.all(5), 33 | ) 34 | ], 35 | ), 36 | 37 | ], 38 | ); 39 | } 40 | 41 | Widget availableCardsLoadingList(int items) { 42 | return ListView.builder( 43 | padding: EdgeInsets.all(0), 44 | itemBuilder: (_, index) => 45 | Padding(padding: EdgeInsets.all(5), child: _availableCardsLoadingTile()), 46 | itemCount: items, 47 | ) 48 | ; 49 | } 50 | -------------------------------------------------------------------------------- /lib/database/user_data_storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | class UserDataStorage { 7 | Future get _localPath async { 8 | final directory = await getApplicationDocumentsDirectory(); 9 | 10 | return directory.path; 11 | } 12 | 13 | Future get _userDataFile async { 14 | final path = await _localPath; 15 | return File('$path/hadwin_user_data.json'); 16 | } 17 | 18 | Future> getUserData() async { 19 | try { 20 | final file = await _userDataFile; 21 | 22 | final contents = await file.readAsString(); 23 | 24 | return jsonDecode(contents); 25 | } catch (e) { 26 | return {"localDBError": "unable to parse data"}; 27 | } 28 | } 29 | 30 | Future saveUserData(Map userData) async { 31 | try { 32 | final file = await _userDataFile; 33 | return file.writeAsString(jsonEncode(userData)).then((value) => true); 34 | } catch (e) { 35 | return false; 36 | } 37 | } 38 | 39 | Future deleteFile() async { 40 | try { 41 | final file = await _userDataFile; 42 | 43 | await file.delete(); 44 | //* THE USER DATA FILE HAS BEEN DELETED 45 | return true; 46 | } catch (e) { 47 | //* THE USER DATA FILE HAS NOT BEEN DELETED 48 | return false; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/components/contacts_screen/contacts_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:fade_shimmer/fade_shimmer.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Widget _contactLoadingTile() { 5 | return Row( 6 | mainAxisAlignment: MainAxisAlignment.center, 7 | children: [ 8 | Container( 9 | child: FadeShimmer.round( 10 | size: 72, 11 | fadeTheme: FadeTheme.light, 12 | ), 13 | padding: EdgeInsets.all(5), 14 | ), 15 | Column( 16 | children: [ 17 | Container( 18 | child: FadeShimmer( 19 | height: 21, 20 | width: 200, 21 | fadeTheme: FadeTheme.light, 22 | ), 23 | padding: EdgeInsets.all(5), 24 | ), 25 | Container( 26 | child: FadeShimmer( 27 | height: 18, 28 | width: 180, 29 | fadeTheme: FadeTheme.light, 30 | ), 31 | padding: EdgeInsets.all(5), 32 | ) 33 | ], 34 | ), 35 | 36 | ], 37 | ); 38 | } 39 | 40 | Widget contactsLoadingList(int items) { 41 | return Padding( 42 | padding: EdgeInsets.symmetric(horizontal: 10), 43 | child: ListView.builder( 44 | padding: EdgeInsets.all(0), 45 | itemBuilder: (_, index) => 46 | Padding(padding: EdgeInsets.all(5), child: _contactLoadingTile()), 47 | itemCount: items, 48 | ), 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /lib/hadwin_components.dart: -------------------------------------------------------------------------------- 1 | library hadwin_components; 2 | 3 | export 'resources/api_constants.dart'; 4 | 5 | //business logic 6 | export 'providers/user_login_state_provider.dart'; 7 | 8 | 9 | //components 10 | export 'components/wallet_screen/add_card_screen.dart'; 11 | export 'components/wallet_screen/available_cards_loading.dart'; 12 | export 'components/settings_screen/credits_loading.dart'; 13 | export 'components/activities_screen/activities_loading.dart'; 14 | export 'components/qr_code_scanner_screen/my_qr_screen.dart'; 15 | export 'components/contacts_screen/contacts_loading.dart'; 16 | export 'components/sign_up_screen/sign_up_steps.dart'; 17 | export 'components/settings_screen/app_settings.dart'; 18 | export 'components/login_screen/form_component.dart'; 19 | 20 | 21 | 22 | //database 23 | export 'database/cards_storage.dart'; 24 | export 'database/successful_transactions_storage.dart'; 25 | 26 | 27 | //resources 28 | export 'providers/tab_navigation_provider.dart'; 29 | 30 | 31 | //screens 32 | export 'screens/new_settings_screen.dart'; 33 | export 'screens/available_businesses_contacts_screen.dart'; 34 | export 'screens/qr_code_scanner_screen.dart'; 35 | export 'screens/fund_transfer_screen.dart'; 36 | export 'screens/sign_up_screen.dart'; 37 | 38 | //utilities 39 | export 'utilities/slide_right_route.dart'; 40 | export 'utilities/make_api_request.dart'; 41 | export 'utilities/url_external_launcher.dart'; 42 | export 'utilities/custom_date_grouping.dart'; 43 | export 'utilities/display_error_alert.dart'; 44 | export 'utilities/hadwin_markdown_viewer.dart'; 45 | -------------------------------------------------------------------------------- /lib/components/activities_screen/activities_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:fade_shimmer/fade_shimmer.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Widget _activityloadingTile() { 5 | Widget bar=Container( 6 | child: FadeShimmer( 7 | height: 18, 8 | width: 180, 9 | fadeTheme: FadeTheme.light, 10 | ), 11 | padding: EdgeInsets.all(5), 12 | ); 13 | return Row( 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | Container( 17 | child: FadeShimmer.round( 18 | size: 64, 19 | fadeTheme: FadeTheme.light, 20 | ), 21 | padding: EdgeInsets.all(5), 22 | ), 23 | Column( 24 | children: [ 25 | bar, 26 | bar 27 | ], 28 | ), 29 | Column( 30 | children: [ 31 | Container( 32 | child: FadeShimmer( 33 | height: 18, 34 | width: 30, 35 | fadeTheme: FadeTheme.light, 36 | ), 37 | padding: EdgeInsets.all(5), 38 | ), 39 | Container( 40 | height: 23, 41 | padding: EdgeInsets.all(5), 42 | ) 43 | ], 44 | ) 45 | ], 46 | ); 47 | } 48 | 49 | Widget activitiesLoadingList(int items) { 50 | return Padding( 51 | padding: EdgeInsets.symmetric(horizontal: 5), 52 | child: ListView.builder( 53 | padding: EdgeInsets.all(0), 54 | itemBuilder: (_, index) => 55 | Padding(padding: EdgeInsets.all(5), child: _activityloadingTile()), 56 | itemCount: items, 57 | ), 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /lib/utilities/make_api_request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:hadwin/hadwin_components.dart'; 4 | import 'package:http/http.dart' as http; 5 | 6 | Future> getData( 7 | {required String urlPath, String? authKey}) async { 8 | String backendServiceHost = "${ApiConstants.baseUrl}" + urlPath; 9 | var response; 10 | try { 11 | response = await http.get( 12 | Uri.parse(backendServiceHost), 13 | headers: { 14 | 'Content-Type': 'application/json', 15 | if (authKey != null) 'Authorization': authKey 16 | }, 17 | ); 18 | } on SocketException { 19 | return {'internetConnectionError': 'no internet connection'}; 20 | } 21 | return jsonDecode(response.body); 22 | } 23 | 24 | Future> sendData( 25 | {required String urlPath, 26 | required Map data, 27 | String? authKey}) async { 28 | String backendServiceHost = "${ApiConstants.baseUrl}" + urlPath; 29 | var response; 30 | try { 31 | response = await http.post( 32 | Uri.parse(backendServiceHost), 33 | headers: { 34 | 'Content-Type': 'application/json', 35 | if (authKey != null) 'Authorization': authKey 36 | }, 37 | body: jsonEncode(data), 38 | ); 39 | } on SocketException { 40 | return {'internetConnectionError': 'no internet connection'}; 41 | } 42 | return jsonDecode(response.body); 43 | } 44 | 45 | Future checkUrlValidity(String url) async { 46 | try { 47 | final response = await http.get(Uri.parse(url)); 48 | 49 | return response.statusCode; 50 | } catch (e) { 51 | return 404; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/database/login_info_storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | class LoginInfoStorage { 7 | Future get _localPath async { 8 | final directory = await getApplicationDocumentsDirectory(); 9 | return directory.path; 10 | } 11 | 12 | Future get _userLoginDataFile async { 13 | final path = await _localPath; 14 | return File('$path/hadwin_user_login_info_storage.json'); 15 | } 16 | 17 | Future setPersistentLoginData(String userId, String authToken) async { 18 | try { 19 | final file = await _userLoginDataFile; 20 | //* THE USER_ID AND AUTHENTICATION_TOKEN HAS BEEN SAVED 21 | return file 22 | .writeAsString(jsonEncode({'userId': userId, 'authToken': authToken})) 23 | .then((value) { 24 | //* THE USER_ID HAS BEEN SAVED 25 | return true; 26 | }); 27 | } catch (e) { 28 | //* THE USER_ID AND AUTHENTICATION_TOKEN COULD NOT BE SAVED 29 | return false; 30 | } 31 | } 32 | 33 | Future> get getPersistentLoginData async { 34 | try { 35 | final file = await _userLoginDataFile; 36 | final contents = await file.readAsString(); 37 | return jsonDecode(contents); 38 | } catch (e) { 39 | return {'userId': null, 'authToken': null}; 40 | } 41 | } 42 | 43 | Future deleteFile() async { 44 | try { 45 | final file = await _userLoginDataFile; 46 | 47 | await file.delete(); 48 | //* THE LOGIN DATA FILE HAS BEEN DELETED 49 | return true; 50 | } catch (e) { 51 | //* THE LOGIN DATA FILE HAS NOT BEEN DELETED 52 | return false; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/providers/user_login_state_provider.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:hadwin/database/user_data_storage.dart'; 5 | 6 | class UserLoginStateProvider with ChangeNotifier { 7 | String _userLoginAuthKey = ""; 8 | 9 | 10 | 11 | double _bankBalance = 0; 12 | 13 | String get userLoginAuthKey => _userLoginAuthKey; 14 | 15 | 16 | 17 | String get bankBalance { 18 | String stringifiedBankBalance = _bankBalance.toStringAsFixed(2); 19 | if (stringifiedBankBalance.split('.').last == '00') { 20 | return stringifiedBankBalance.split('.').first; 21 | } else { 22 | return stringifiedBankBalance; 23 | } 24 | } 25 | 26 | void setAuthKeyValue(String receivedAuthKey) { 27 | _userLoginAuthKey = receivedAuthKey; 28 | notifyListeners(); 29 | 30 | } 31 | 32 | 33 | bool initializeBankBalance(Map userData) { 34 | _bankBalance = userData['bankDetails'].fold( 35 | 0.0, (sum, account) => sum + double.parse(account['bankBalance'])); 36 | notifyListeners(); 37 | return true; 38 | } 39 | 40 | Future resetBankBalance() async{ 41 | try { 42 | Map locallySavedUserData= await UserDataStorage().getUserData(); 43 | _bankBalance = locallySavedUserData['bankDetails'].fold( 44 | 0.0, (sum, account) => sum + double.parse(account['bankBalance'])); 45 | notifyListeners(); 46 | return true; 47 | } catch (e) { 48 | return false; 49 | } 50 | 51 | } 52 | 53 | void updateBankBalance(String transactionType, String transactionAmount) { 54 | if (transactionType == 'debit') { 55 | _bankBalance -= double.parse(transactionAmount); 56 | } else { 57 | _bankBalance += double.parse(transactionAmount); 58 | } 59 | //* bank balance updated 60 | notifyListeners(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/database/hadwin_user_device_info_storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | class UserDeviceInfoStorage { 7 | String _deviceOS = Platform.operatingSystem; 8 | String _deviceOSVersion = Platform.operatingSystemVersion; 9 | 10 | Future get _localPath async { 11 | final directory = await getApplicationDocumentsDirectory(); 12 | 13 | return directory.path; 14 | } 15 | 16 | Future get _userDeviceInfoFile async { 17 | final path = await _localPath; 18 | 19 | return File('$path/hadwin_user_device_info_storage.json'); 20 | } 21 | 22 | Future initializeInstallationStatus() async { 23 | DateTime dateOfFirstUse = DateTime.now(); 24 | 25 | try { 26 | final file = await _userDeviceInfoFile; 27 | 28 | return file 29 | .writeAsString(jsonEncode({ 30 | 'deviceOS': _deviceOS, 31 | 'deviceOSVersion': _deviceOSVersion, 32 | 'dateOfFirstUse': dateOfFirstUse.toString() 33 | })) 34 | .then((value) => true); 35 | } catch (e) { 36 | return false; 37 | } 38 | } 39 | 40 | Future get wasUsedBefore async { 41 | try { 42 | final file = await _userDeviceInfoFile; 43 | final contents = await file.readAsString(); 44 | Map data = jsonDecode(contents); 45 | if (data.isNotEmpty) { 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } catch (e) { 51 | return false; 52 | } 53 | } 54 | 55 | Future deleteFile() async { 56 | try { 57 | final file = await _userDeviceInfoFile; 58 | 59 | await file.delete(); 60 | //* THE USER DEVICE INFORMATION FILE HAS BEEN DELETED 61 | return true; 62 | } catch (e) { 63 | //* THE USER DEVICE INFORMATION FILE HAS NOT BEEN DELETED 64 | return false; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/screens/sign_up_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hadwin/hadwin_components.dart'; 3 | 4 | 5 | 6 | class SignUpScreen extends StatefulWidget { 7 | const SignUpScreen({Key? key}) : super(key: key); 8 | 9 | @override 10 | _SignUpScreenState createState() => _SignUpScreenState(); 11 | } 12 | 13 | class _SignUpScreenState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return WillPopScope( 17 | child: Scaffold( 18 | body: SingleChildScrollView( 19 | child: Column( 20 | children: [ 21 | SizedBox( 22 | height: 16, 23 | ), 24 | Container( 25 | child: Image.asset('assets/images/hadwin_system/hadwin-logo-with-name.png'), 26 | height: 30, 27 | ), 28 | SizedBox( 29 | height: 30, 30 | ), 31 | 32 | SignUpSteps(), 33 | SizedBox( 34 | height: 27, 35 | ), 36 | 37 | Container( 38 | child: Center( 39 | child: InkWell( 40 | child: Text( 41 | 'Already have an account? Sign in', 42 | style: 43 | TextStyle(fontSize: 14, color: Color(0xFF929BAB)), 44 | ), 45 | onTap: () { 46 | 47 | Navigator.pop(context); 48 | }, 49 | ), 50 | ), 51 | width: double.infinity, 52 | height: 16, 53 | ), 54 | SizedBox( 55 | height: 3, 56 | ) 57 | ], 58 | ), 59 | padding: EdgeInsets.all(45), 60 | 61 | ), 62 | 63 | ), 64 | onWillPop: () => Future.value(false)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | HADWIN 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | UIStatusBarHidden 45 | 46 | io.flutter.embedded_views_preview 47 | 48 | NSCameraUsageDescription 49 | This app needs camera access to scan QR codes 50 | LSApplicationQueriesSchemes 51 | 52 | https 53 | http 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/components/settings_screen/license_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class LicenseData extends StatelessWidget { 5 | const LicenseData( 6 | {Key? key, required this.licenseName, required this.licenseData}) 7 | : super(key: key); 8 | final String licenseName; 9 | final List> licenseData; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | backgroundColor: Colors.white, 15 | appBar: AppBar( 16 | title: Text( 17 | licenseName, 18 | style: TextStyle(fontSize: 24), 19 | ), 20 | centerTitle: true, 21 | backgroundColor: Colors.transparent, 22 | foregroundColor: Color(0xff243656), 23 | elevation: 0, 24 | ), 25 | body: Column(children: [ 26 | 27 | Expanded( 28 | child: Container( 29 | width: MediaQuery.of(context).size.width - 10, 30 | height: MediaQuery.of(context).size.height - 180, 31 | padding: EdgeInsets.all(10), 32 | child: ListView.separated( 33 | itemBuilder: (context, index) { 34 | List paraBody = []; 35 | licenseData[index].forEach( 36 | (element) { 37 | paraBody.add(Container( 38 | width: MediaQuery.of(context).size.width - 10, 39 | margin: EdgeInsets.symmetric(vertical: 6.18), 40 | child: Text(element.text))); 41 | }, 42 | ); 43 | return Column( 44 | 45 | children: paraBody); 46 | }, 47 | separatorBuilder: (_, b) => Divider( 48 | height: 6, 49 | color: Colors.transparent, 50 | ), 51 | itemCount: licenseData.length, 52 | ))), 53 | ])); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/components/qr_code_scanner_screen/scan_error_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:lottie/lottie.dart'; 4 | 5 | class ScanErrorScreen extends StatefulWidget { 6 | const ScanErrorScreen({Key? key}) : super(key: key); 7 | 8 | @override 9 | State createState() => _ScanErrorScreenState(); 10 | } 11 | 12 | class _ScanErrorScreenState extends State 13 | with SingleTickerProviderStateMixin { 14 | late AnimationController scanErrorAnimationController; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | scanErrorAnimationController = AnimationController( 20 | vsync: this, 21 | ); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | scanErrorAnimationController.dispose(); 27 | 28 | super.dispose(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | backgroundColor: Color(0xffedf2f4), 35 | appBar: AppBar( 36 | title: Text("Error"), 37 | centerTitle: true, 38 | backgroundColor: Colors.transparent, 39 | elevation: 0, 40 | foregroundColor: Color(0xff243656), 41 | leading: IconButton( 42 | onPressed: () { 43 | Navigator.of(context).pop(); 44 | }, 45 | icon: Icon(Icons.arrow_back)), 46 | ), 47 | body: Container( 48 | width: double.infinity, 49 | child: Column( 50 | 51 | children: [ 52 | SizedBox( 53 | height: 100, 54 | ), 55 | Lottie.network( 56 | 'https://assets6.lottiefiles.com/packages/lf20_4fewfamh.json', 57 | width: 300, 58 | height: 300, 59 | 60 | controller: scanErrorAnimationController, 61 | onLoaded: (composition) { 62 | scanErrorAnimationController.duration = composition.duration; 63 | scanErrorAnimationController.forward(); 64 | scanErrorAnimationController.repeat(reverse: true); 65 | }), 66 | Text( 67 | "Code not recognized", 68 | textAlign: TextAlign.center, 69 | style: GoogleFonts.poppins( 70 | fontSize: 16, 71 | ), 72 | ) 73 | ], 74 | )), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/screens/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hadwin/hadwin_components.dart'; 3 | 4 | 5 | class LoginScreen extends StatefulWidget { 6 | const LoginScreen({Key? key}) : super(key: key); 7 | 8 | @override 9 | _LoginScreenState createState() => _LoginScreenState(); 10 | } 11 | 12 | class _LoginScreenState extends State { 13 | @override 14 | Widget build(BuildContext context) { 15 | Widget helpInfoContainer = Container( 16 | child: Center( 17 | child: InkWell( 18 | child: Text( 19 | 'Having trouble logging in?', 20 | style: TextStyle(fontSize: 14, color: Color(0xFF929BAB)), 21 | ), 22 | onTap: getLoginHelp, 23 | ), 24 | ), 25 | width: double.infinity, 26 | height: 36, 27 | ); 28 | 29 | Widget signUpContainer = Container( 30 | child: Center( 31 | child: InkWell( 32 | child: Text( 33 | 'Sign up', 34 | style: TextStyle(fontSize: 14, color: Color(0xFF929BAB)), 35 | ), 36 | onTap: goToSignUpScreen, 37 | ), 38 | ), 39 | width: double.infinity, 40 | height: 36, 41 | ); 42 | List loginScreenContents = [ 43 | _spacing(64), 44 | Padding( 45 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 46 | child: Image.asset('assets/images/hadwin_system/hadwin-logo-with-name.png'), 47 | ), 48 | _spacing(64), 49 | LoginFormComponent(), 50 | _spacing(30), 51 | helpInfoContainer, 52 | _spacing(10), 53 | signUpContainer 54 | ]; 55 | 56 | return Scaffold( 57 | body: SingleChildScrollView( 58 | child: Column( 59 | children: loginScreenContents, 60 | ), 61 | padding: EdgeInsets.all(45), 62 | 63 | ), 64 | 65 | 66 | ); 67 | } 68 | 69 | void getLoginHelp() { 70 | Navigator.push( 71 | context, 72 | SlideRightRoute( 73 | page: HadWinMarkdownViewer( 74 | screenName: 'Login Help', 75 | urlRequested: 76 | 'https://raw.githubusercontent.com/brownboycodes/HADWIN/master/docs/HADWIN_WIKI.md'))); 77 | } 78 | 79 | void goToSignUpScreen() { 80 | Navigator.push( 81 | context, MaterialPageRoute(builder: (context) => SignUpScreen())); 82 | } 83 | 84 | SizedBox _spacing(double height) => SizedBox( 85 | height: height, 86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /lib/utilities/card_identifier.dart: -------------------------------------------------------------------------------- 1 | String identifyCard(String cardNumber) { 2 | cardNumber = cardNumber.replaceAll('-', ''); 3 | cardNumber = cardNumber.replaceAll(' ', ''); 4 | String detectedBrand = 'default'; 5 | Map cardRegexPatterns = { 6 | 'american-express': r'^3[47][0-9]{13}$', 7 | 'bcglobal': r'^(6541|6556)[0-9]{12}$', 8 | 'carte-blanche': r'^389[0-9]{11}$', 9 | 'diners-club': r'^3(?:0[0-5]|[68][0-9])[0-9]{11}$', 10 | 'discover': 11 | r'^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})$', 12 | 'insta-payment': r'^63[7-9][0-9]{13}$', 13 | 'jcb': r'^(?:2131|1800|35\d{3})\d{11}$', 14 | 'korean-local': r'^9[0-9]{15}$', 15 | 'laser': r'^(6304|6706|6709|6771)[0-9]{12,15}$', 16 | 'maestro': r'^(5018|5020|5038|5893|6304|6759|6761|6762|6763)[0-9]{8,15}$', 17 | 'mastercard': 18 | r'^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$', 19 | 'rupay': r'^6(?!011)(?:0[0-9]{14}|52[12][0-9]{12})$', 20 | 'solo': r'^(6334|6767)[0-9]{12}|(6334|6767)[0-9]{14}|(6334|6767)[0-9]{15}$', 21 | 'switch': 22 | r'^(4903|4905|4911|4936|6333|6759)[0-9]{12}|(4903|4905|4911|4936|6333|6759)[0-9]{14}|(4903|4905|4911|4936|6333|6759)[0-9]{15}|564182[0-9]{10}|564182[0-9]{12}|564182[0-9]{13}|633110[0-9]{10}|633110[0-9]{12}|633110[0-9]{13}$', 23 | 'union-pay': r'^(62[0-9]{14,17})$', 24 | 'visa': r'^4[0-9]{12}(?:[0-9]{3})?$', 25 | }; 26 | 27 | for (MapEntry cp in cardRegexPatterns.entries) { 28 | if (RegExp(cp.value).hasMatch(cardNumber)) { 29 | detectedBrand = cp.key; 30 | break; 31 | } 32 | } 33 | 34 | return detectedBrand; 35 | } 36 | 37 | String identifyCardShorter(String cardNumber) { 38 | cardNumber = cardNumber.replaceAll('-', ''); 39 | cardNumber = cardNumber.replaceAll(' ', ''); 40 | String detectedBrand = 'default'; 41 | Map cardRegexPatterns = { 42 | 'american-express': r'^3[47][0-9]{1}', 43 | 'discover': 44 | r'^65[4-9]|64[4-9]|6011|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})$', 45 | 'maestro': r'^(5018|5020|5038|5893|6304|6759|6761|6762|6763)', 46 | 'mastercard': 47 | r'^(5[1-5][0-9]{4}|2(22[1-9][0-9]{2}|2[3-9][0-9]{3}|[3-6][0-9]{4}|7[0-1][0-9]{3}|720[0-9]{2}))', 48 | 'visa': r'^4[0-9]{3}', 49 | }; 50 | 51 | for (MapEntry cp in cardRegexPatterns.entries) { 52 | if (RegExp(cp.value).hasMatch(cardNumber)) { 53 | detectedBrand = cp.key; 54 | break; 55 | } 56 | } 57 | 58 | return detectedBrand; 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 22 | 26 | 30 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/database/successful_transactions_storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:path_provider/path_provider.dart'; 6 | 7 | class SuccessfulTransactionsStorage { 8 | Future get _localPath async { 9 | final directory = await getApplicationDocumentsDirectory(); 10 | 11 | return directory.path; 12 | } 13 | 14 | Future get _successfulTransactionsFile async { 15 | final path = await _localPath; 16 | return File('$path/hadwin_successful_transactions.json'); 17 | } 18 | 19 | Future initializeSuccessfulTransactions() async { 20 | final file = await _successfulTransactionsFile; 21 | 22 | try { 23 | final contents = await getSuccessfulTransactions(); 24 | if (contents.containsKey('transactions')) { 25 | //* pre-existing transactions loaded 26 | return true; 27 | } else { 28 | return file 29 | .writeAsString(jsonEncode({"transactions": []})) 30 | .then((value) { 31 | //* the transactions file has been initialized 32 | return true; 33 | }); 34 | 35 | } 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | Future> getSuccessfulTransactions() async { 42 | try { 43 | final file = await _successfulTransactionsFile; 44 | 45 | final contents = await file.readAsString(); 46 | var decodedFile = jsonDecode(contents); 47 | return decodedFile; 48 | } catch (e) { 49 | return {"localDBError": "unable to parse data"}; 50 | } 51 | } 52 | 53 | Future updateSuccessfulTransactions( 54 | Map transactionReceipt) async { 55 | try { 56 | final file = await _successfulTransactionsFile; 57 | 58 | final contents = await file.readAsString(); 59 | 60 | var decodedFile = jsonDecode(contents); 61 | decodedFile['transactions'].add(transactionReceipt); 62 | 63 | return file.writeAsString(jsonEncode(decodedFile)).then((value) { 64 | //* transactions updated 65 | return true; 66 | }); 67 | 68 | } catch (e) { 69 | return false; 70 | } 71 | } 72 | 73 | Future deleteFile() async { 74 | try { 75 | final file = await _successfulTransactionsFile; 76 | 77 | await file.delete(); 78 | //* THE LOCAL TRANSACTIONS FILE HAS BEEN DELETED 79 | return true; 80 | } catch (e) { 81 | //* THE LOCAL TRANSACTIONS FILE HAS NOT BEEN DELETED 82 | return false; 83 | } 84 | } 85 | 86 | Future resetLocallySavedTransactions() async { 87 | try { 88 | final file = await _successfulTransactionsFile; 89 | 90 | return file.writeAsString(jsonEncode({"transactions": []})).then((value) { 91 | //* RESET TRANSACTIONS FILE SUCCESSFUL 92 | return true; 93 | }); 94 | } catch (e) { 95 | //* //* RESET TRANSACTIONS FILE UNSUCCESSFUL 96 | return false; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/providers/live_transactions_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hadwin/database/successful_transactions_storage.dart'; 3 | import 'package:hadwin/providers/user_login_state_provider.dart'; 4 | 5 | class LiveTransactionsProvider with ChangeNotifier { 6 | late UserLoginStateProvider _userLoginStateProvider; 7 | 8 | List _successfulTransactionsInQueue = []; 9 | List _unreadTransactionsList = []; 10 | int _transactionRequests = 0; 11 | LiveTransactionsProvider(); 12 | 13 | List get successfulTransactionsInQueue => 14 | _successfulTransactionsInQueue; 15 | int get unreadTransactions => _unreadTransactionsList.length; 16 | int get transactionRequests => _transactionRequests; 17 | 18 | 19 | void update(UserLoginStateProvider userLoginStateProvider) { 20 | _userLoginStateProvider = userLoginStateProvider; 21 | } 22 | 23 | //? to keep track of successful "debit / paid" transactions 24 | Future updateSuccessfulTransactions( 25 | Map transactionReceipt) async { 26 | //? server time modified to client time 27 | transactionReceipt['transactionDate'] = "${DateTime.now()}"; 28 | 29 | //* transaction added 30 | 31 | bool isSaved = await SuccessfulTransactionsStorage() 32 | .updateSuccessfulTransactions(transactionReceipt); 33 | if (isSaved) { 34 | _userLoginStateProvider.updateBankBalance( 35 | transactionReceipt['transactionType'], 36 | transactionReceipt['transactionAmount']); 37 | notifyListeners(); 38 | } 39 | 40 | return isSaved; 41 | } 42 | 43 | //? to keep track of successful "credit / received" transactions 44 | void updateSuccessfulTransactionsInQueue( 45 | Map transactionReceipt) { 46 | _successfulTransactionsInQueue.add(transactionReceipt); 47 | notifyListeners(); 48 | } 49 | 50 | void addUnreadTransaction(String transactionId) { 51 | _unreadTransactionsList.add(transactionId); 52 | notifyListeners(); 53 | } 54 | 55 | void removeUnreadTransaction(String transactionId) { 56 | if (_unreadTransactionsList.contains(transactionId)) { 57 | _unreadTransactionsList.remove(transactionId); 58 | notifyListeners(); 59 | } 60 | } 61 | 62 | void updateTransactionRequests() async { 63 | if (_successfulTransactionsInQueue.length > 0) { 64 | Map transactionAtTop = 65 | _successfulTransactionsInQueue.first; 66 | 67 | bool isSaved = await updateSuccessfulTransactions(transactionAtTop); 68 | if (isSaved) { 69 | addUnreadTransaction(transactionAtTop['transactionID']); 70 | _successfulTransactionsInQueue.removeAt(0); 71 | _transactionRequests++; 72 | } 73 | notifyListeners(); 74 | } 75 | } 76 | 77 | Future resetTransactionsInState() async { 78 | try { 79 | _successfulTransactionsInQueue = []; 80 | _unreadTransactionsList = []; 81 | _transactionRequests = 0; 82 | return true; 83 | } catch (e) { 84 | return false; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | // flutterVersionCode = '1' 17 | throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.") 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | // flutterVersionName = '1.0' 23 | throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.") 24 | } 25 | 26 | apply plugin: 'com.android.application' 27 | apply plugin: 'kotlin-android' 28 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 29 | 30 | def keystoreProperties = new Properties() 31 | def keystorePropertiesFile = rootProject.file('key.properties') 32 | if (keystorePropertiesFile.exists()) { 33 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 34 | } 35 | 36 | android { 37 | compileSdkVersion 31 38 | 39 | compileOptions { 40 | sourceCompatibility JavaVersion.VERSION_1_8 41 | targetCompatibility JavaVersion.VERSION_1_8 42 | } 43 | 44 | kotlinOptions { 45 | jvmTarget = '1.8' 46 | } 47 | 48 | sourceSets { 49 | main.java.srcDirs += 'src/main/kotlin' 50 | } 51 | 52 | defaultConfig { 53 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 54 | applicationId "com.github.brownboycodes.hadwin" 55 | minSdkVersion 21 56 | targetSdkVersion 30 57 | versionCode flutterVersionCode.toInteger() 58 | versionName flutterVersionName 59 | multiDexEnabled true 60 | } 61 | /* 62 | buildTypes { 63 | release { 64 | // TODO: Add your own signing config for the release build. 65 | // Signing with the debug keys for now, so `flutter run --release` works. 66 | signingConfig signingConfigs.debug 67 | } 68 | } 69 | */ 70 | signingConfigs { 71 | release { 72 | keyAlias keystoreProperties['keyAlias'] 73 | keyPassword keystoreProperties['keyPassword'] 74 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 75 | storePassword keystoreProperties['storePassword'] 76 | } 77 | } 78 | buildTypes { 79 | release { 80 | signingConfig signingConfigs.release 81 | } 82 | } 83 | 84 | } 85 | 86 | flutter { 87 | source '../..' 88 | } 89 | 90 | dependencies { 91 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 92 | implementation 'com.android.support:multidex:2.0.1' 93 | } 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | .vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | # See https://www.dartlang.org/guides/libraries/private-files 49 | 50 | # Files and directories created by pub 51 | .dart_tool/ 52 | .packages 53 | build/ 54 | # If you're building an application, you may want to check-in your pubspec.lock 55 | pubspec.lock 56 | 57 | # Directory created by dartdoc 58 | # If you don't generate documentation locally you can remove this line. 59 | doc/api/ 60 | 61 | # dotenv environment variables file 62 | .env* 63 | 64 | # Avoid committing generated Javascript files: 65 | *.dart.js 66 | *.info.json # Produced by the --dump-info flag. 67 | *.js # When generated by dart2js. Don't specify *.js if your 68 | # project includes source files written in JavaScript. 69 | *.js_ 70 | *.js.deps 71 | *.js.map 72 | 73 | .flutter-plugins 74 | .flutter-plugins-dependencies 75 | 76 | 77 | # Android related 78 | **/android/**/gradle-wrapper.jar 79 | **/android/.gradle 80 | **/android/captures/ 81 | **/android/gradlew 82 | **/android/gradlew.bat 83 | **/android/local.properties 84 | **/android/**/GeneratedPluginRegistrant.java 85 | 86 | # iOS/XCode related 87 | **/ios/**/*.mode1v3 88 | **/ios/**/*.mode2v3 89 | **/ios/**/*.moved-aside 90 | **/ios/**/*.pbxuser 91 | **/ios/**/*.perspectivev3 92 | **/ios/**/*sync/ 93 | **/ios/**/.sconsign.dblite 94 | **/ios/**/.tags* 95 | **/ios/**/.vagrant/ 96 | **/ios/**/DerivedData/ 97 | **/ios/**/Icon? 98 | **/ios/**/Pods/ 99 | **/ios/**/.symlinks/ 100 | **/ios/**/profile 101 | **/ios/**/xcuserdata 102 | **/ios/.generated/ 103 | **/ios/Flutter/App.framework 104 | **/ios/Flutter/Flutter.framework 105 | **/ios/Flutter/Generated.xcconfig 106 | **/ios/Flutter/app.flx 107 | **/ios/Flutter/app.zip 108 | **/ios/Flutter/flutter_assets/ 109 | **/ios/ServiceDefinitions.json 110 | **/ios/Runner/GeneratedPluginRegistrant.* 111 | 112 | # Exceptions to above rules. 113 | !**/ios/**/default.mode1v3 114 | !**/ios/**/default.mode2v3 115 | !**/ios/**/default.pbxuser 116 | !**/ios/**/default.perspectivev3 117 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 118 | 119 | #local 120 | temp 121 | 122 | # work in progress files 123 | lib/screens/settings_screen.dart 124 | lib/screens/fund_transfer_screen_2.dart 125 | lib/screens/all_transaction_activities.dart 126 | lib/components/sign_up_screen/form_component.dart 127 | 128 | #local-test-data 129 | assets/data 130 | assets/images/local 131 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/components/settings_screen/credits_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:fade_shimmer/fade_shimmer.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Widget _creditsloadingTile(BuildContext context) { 5 | List socialLinks = List.generate( 6 | 6, 7 | (index) => Container( 8 | child: FadeShimmer( 9 | radius: 16.18, 10 | height: 48, 11 | width: 48, 12 | fadeTheme: FadeTheme.light, 13 | ), 14 | padding: EdgeInsets.all(2), 15 | ), 16 | ); 17 | return Container( 18 | width: MediaQuery.of(context).size.width - 10, 19 | 20 | padding: EdgeInsets.all(16), 21 | 22 | decoration: BoxDecoration( 23 | 24 | color: Colors.blueGrey.shade100.withOpacity(0.1618), 25 | borderRadius: BorderRadius.all(Radius.circular(16.18))), 26 | 27 | margin: EdgeInsets.symmetric(vertical: 3, horizontal: 6.18), 28 | child: Wrap( 29 | direction: Axis.vertical, 30 | children: [ 31 | Container( 32 | width: MediaQuery.of(context).size.width - 48, 33 | padding: EdgeInsets.all(6.18), 34 | child: Wrap( 35 | alignment: WrapAlignment.spaceBetween, 36 | children: [ 37 | Wrap(direction: Axis.vertical, children: [ 38 | Container( 39 | child: FadeShimmer( 40 | radius: 16.18, 41 | height: 27, 42 | width: 96, 43 | fadeTheme: FadeTheme.light, 44 | ), 45 | padding: EdgeInsets.all(2), 46 | ), 47 | Container( 48 | child: FadeShimmer( 49 | radius: 16.18, 50 | height: 20, 51 | width: 72, 52 | fadeTheme: FadeTheme.light, 53 | ), 54 | padding: EdgeInsets.all(2), 55 | ), 56 | ]), 57 | Container( 58 | child: FadeShimmer( 59 | radius: 16.18, 60 | height: 64, 61 | width: 64, 62 | fadeTheme: FadeTheme.light, 63 | ), 64 | 65 | ), 66 | ], 67 | ), 68 | ), 69 | Container( 70 | child: FadeShimmer( 71 | radius: 16.18, 72 | height: 16, 73 | width: 96, 74 | fadeTheme: FadeTheme.light, 75 | ), 76 | padding: EdgeInsets.all(2), 77 | ), 78 | Container( 79 | height: 72, 80 | width: MediaQuery.of(context).size.width - 48, 81 | 82 | padding: EdgeInsets.all(0), 83 | 84 | color: Colors.transparent, 85 | child: ListView.separated( 86 | 87 | scrollDirection: Axis.horizontal, 88 | itemBuilder: (_, index) => socialLinks[index], 89 | separatorBuilder: (_, b) => SizedBox( 90 | width: 16.18, 91 | ), 92 | itemCount: socialLinks.length), 93 | ) 94 | ], 95 | ), 96 | ); 97 | } 98 | 99 | Widget creditsLoadingList(int items, BuildContext context) { 100 | return Padding( 101 | padding: EdgeInsets.symmetric(horizontal: 5), 102 | child: ListView.builder( 103 | padding: EdgeInsets.all(0), 104 | itemBuilder: (_, index) => Padding( 105 | padding: EdgeInsets.all(5), child: _creditsloadingTile(context)), 106 | itemCount: items, 107 | ), 108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to make participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, sex characteristics, gender identity and expression, 10 | level of experience, education, socio-economic status, nationality, personal 11 | appearance, race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies within all project spaces, and it also applies when 50 | an individual is representing the project or its community in public spaces. 51 | Examples of representing a project or community include using an official 52 | project e-mail address, posting via an official social media account, or acting 53 | as an appointed representative at an online or offline event. Representation of 54 | a project may be further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | 76 | For answers to common questions about this code of conduct, see 77 | https://www.contributor-covenant.org/faq 78 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/database/cards_storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | 5 | import 'package:path_provider/path_provider.dart'; 6 | import 'package:hadwin/utilities/make_api_request.dart'; 7 | 8 | class CardsStorage { 9 | Future get _localPath async { 10 | final directory = await getApplicationDocumentsDirectory(); 11 | 12 | return directory.path; 13 | } 14 | 15 | Future get _cardsFile async { 16 | final path = await _localPath; 17 | return File('$path/hadwin_available_cards.json'); 18 | } 19 | 20 | Future> get randomCard async { 21 | var _availableCards = (await readAvailableCards())['availableCards']; 22 | 23 | return _availableCards[Random().nextInt(_availableCards.length)]; 24 | } 25 | 26 | Future initializeAvailableCards(String userAuthKey) async { 27 | final file = await _cardsFile; 28 | try { 29 | final contents = await readAvailableCards(); 30 | if (contents.containsKey('availableCards')) { 31 | //* pre-existing cards loaded 32 | return true; 33 | } else { 34 | try { 35 | Map availableCards = await getData( 36 | urlPath: "/hadwin/v1/available-cards", authKey: userAuthKey); 37 | if (availableCards.keys.join().toLowerCase().contains("error")) { 38 | return false; 39 | } else { 40 | return file.writeAsString(jsonEncode(availableCards)).then((value) { 41 | //* the cards have been saved in app memory 42 | return true; 43 | }); 44 | 45 | } 46 | } catch (er) { 47 | return false; 48 | } 49 | } 50 | } catch (e) { 51 | return false; 52 | } 53 | } 54 | 55 | Future> readAvailableCards() async { 56 | try { 57 | final file = await _cardsFile; 58 | 59 | final contents = await file.readAsString(); 60 | 61 | return jsonDecode(contents); 62 | } catch (e) { 63 | return {"localDBError": "unable to parse data"}; 64 | } 65 | } 66 | 67 | void updateAvailableCards(Map cardData) async { 68 | final file = await _cardsFile; 69 | 70 | final contents = await file.readAsString(); 71 | 72 | var decodedFile = jsonDecode(contents); 73 | decodedFile['availableCards'].add(cardData); 74 | 75 | file 76 | .writeAsString(jsonEncode(decodedFile)) 77 | .then((value) => print("new card added")); 78 | } 79 | 80 | Future deleteCard(String cardNumber) async { 81 | try { 82 | final file = await _cardsFile; 83 | 84 | final contents = await file.readAsString(); 85 | 86 | var decodedFile = jsonDecode(contents)['availableCards']; 87 | 88 | List newCardsSet = decodedFile 89 | .where((card) => 90 | card['cardNumber'].replaceAll(' ', '') != 91 | cardNumber.replaceAll(' ', '')) 92 | .toList(); 93 | return file 94 | .writeAsString(jsonEncode({'availableCards': newCardsSet})) 95 | .then((value) { 96 | //* card has been deleted 97 | return true; 98 | }); 99 | 100 | } catch (e) { 101 | return false; 102 | } 103 | } 104 | 105 | Future deleteFile() async { 106 | try { 107 | final file = await _cardsFile; 108 | 109 | await file.delete(); 110 | //* THE LOCAL CARDS FILE HAS BEEN DELETED 111 | return true; 112 | } catch (e) { 113 | //* THE LOCAL CARDS FILE HAS NOT BEEN DELETED 114 | return false; 115 | } 116 | } 117 | 118 | Future resetLocallySavedCards() async { 119 | try { 120 | final file = await _cardsFile; 121 | 122 | return file.writeAsString(jsonEncode({"availableCards": []})).then((value) { 123 | //* RESET CARDS FILE SUCCESSFUL 124 | return true; 125 | }); 126 | } catch (e) { 127 | //* RESET CARDS FILE UNSUCCESSFUL 128 | return false; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/screens/qr_code_scanner_screen.dart: -------------------------------------------------------------------------------- 1 | /* 2 | import 'dart:convert'; 3 | import 'dart:developer'; 4 | import 'dart:io'; 5 | 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'package:hadwin/components/qr_code_scanner_screen/scan_error_screen.dart'; 9 | import 'package:hadwin/screens/fund_transfer_screen.dart'; 10 | import 'package:hadwin/utilities/slide_right_route.dart'; 11 | import 'package:qr_code_scanner/qr_code_scanner.dart'; 12 | 13 | class QRCodeScannerScreen extends StatefulWidget { 14 | const QRCodeScannerScreen({Key? key}) : super(key: key); 15 | 16 | @override 17 | State createState() => _QRCodeScannerScreenState(); 18 | } 19 | 20 | class _QRCodeScannerScreenState extends State { 21 | Barcode? result; 22 | QRViewController? controller; 23 | final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); 24 | 25 | 26 | @override 27 | void reassemble() { 28 | super.reassemble(); 29 | if (Platform.isAndroid) { 30 | controller!.pauseCamera(); 31 | } 32 | controller!.resumeCamera(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Scaffold( 38 | backgroundColor: Colors.white.withOpacity(0.8), 39 | 40 | extendBodyBehindAppBar: true, 41 | appBar: AppBar( 42 | 43 | backgroundColor: Colors.transparent, 44 | foregroundColor: Color(0xff243656), 45 | elevation: 0, 46 | title: 47 | Text("Scan QR Code", style: TextStyle(color: Color(0xff243656))), 48 | centerTitle: true, 49 | ), 50 | body: Column( 51 | children: [ 52 | Expanded(flex: 4, child: _buildQrView(context)), 53 | ], 54 | ), 55 | ); 56 | } 57 | 58 | Widget _buildQrView(BuildContext context) { 59 | 60 | var scanArea = 230.0; 61 | 62 | return QRView( 63 | key: qrKey, 64 | onQRViewCreated: _onQRViewCreated, 65 | overlay: QrScannerOverlayShape( 66 | borderColor: Color(0xFF0070BA), 67 | 68 | overlayColor: Colors.white.withOpacity(0.8), 69 | 70 | borderRadius: 10, 71 | borderLength: 30, 72 | borderWidth: 15, 73 | cutOutSize: scanArea), 74 | onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p), 75 | ); 76 | } 77 | 78 | void _onQRViewCreated(QRViewController controller) { 79 | setState(() { 80 | this.controller = controller; 81 | }); 82 | controller.scannedDataStream.listen((scanData) { 83 | 84 | controller.pauseCamera(); 85 | try { 86 | final data = jsonDecode(scanData.code!); 87 | int validFields = { 88 | 'avatar', 89 | 'homepage', 90 | 'name', 91 | 'walletAddress', 92 | 'emailAddress', 93 | 'phoneNumber', 94 | 'communicationAddress' 95 | }.intersection(data.keys.toSet()).length; 96 | if (validFields >= 4) { 97 | Navigator.push( 98 | context, 99 | SlideRightRoute( 100 | page: FundTransferScreen( 101 | otherParty: data, 102 | transactionType: 'debit', 103 | ))).then((value) => controller.resumeCamera()); 104 | } else { 105 | Navigator.push(context, SlideRightRoute(page: ScanErrorScreen())) 106 | .then((value) => controller.resumeCamera()); 107 | } 108 | } catch (e) { 109 | 110 | Navigator.push(context, SlideRightRoute(page: ScanErrorScreen())) 111 | .then((value) => controller.resumeCamera()); 112 | } 113 | }); 114 | } 115 | 116 | 117 | void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) { 118 | log('${DateTime.now().toIso8601String()}_onPermissionSet $p'); 119 | if (!p) { 120 | ScaffoldMessenger.of(context).showSnackBar( 121 | const SnackBar(content: Text('no Permission')), 122 | ); 123 | } 124 | } 125 | 126 | @override 127 | void dispose() { 128 | controller?.dispose(); 129 | super.dispose(); 130 | } 131 | } 132 | */ -------------------------------------------------------------------------------- /lib/components/add_card_screen/card_flipper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class CardFlipper extends StatefulWidget { 6 | final Widget frontSide, backSide; 7 | final Duration transitionDuration; 8 | final CardFlippingController? cardFlippingController; 9 | CardFlipper( 10 | {Key? key, 11 | required this.frontSide, 12 | required this.backSide, 13 | required this.transitionDuration, 14 | this.cardFlippingController}) 15 | : super(key: key); 16 | 17 | @override 18 | _CardFlipperState createState() => _CardFlipperState(); 19 | } 20 | 21 | class _CardFlipperState extends State 22 | with SingleTickerProviderStateMixin { 23 | late AnimationController cardFlippingController; 24 | 25 | late Animation flipAnimation; 26 | Widget? displayedCard; 27 | bool isFacingUp = true; 28 | double skewFactor = 0; 29 | double defaultSkew = 0; 30 | 31 | TweenSequence leftToRight = TweenSequence(>[ 32 | TweenSequenceItem( 33 | tween: Tween(begin: 360, end: 180), weight: 3), 34 | TweenSequenceItem( 35 | tween: Tween(begin: 180, end: 190), weight: 2), 36 | TweenSequenceItem( 37 | tween: Tween(begin: 190, end: 180), weight: 1), 38 | ]); 39 | 40 | TweenSequence rightToLeft = TweenSequence(>[ 41 | TweenSequenceItem( 42 | tween: Tween(begin: 0, end: 180), weight: 3), 43 | TweenSequenceItem( 44 | tween: Tween(begin: 180, end: 170), weight: 2), 45 | TweenSequenceItem( 46 | tween: Tween(begin: 170, end: 180), weight: 1), 47 | ]); 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | 53 | 54 | cardFlippingController = 55 | AnimationController(vsync: this, duration: widget.transitionDuration); 56 | 57 | if (isFacingUp) { 58 | flipAnimation = leftToRight.animate(cardFlippingController); 59 | } else { 60 | flipAnimation = rightToLeft.animate(cardFlippingController); 61 | } 62 | 63 | cardFlippingController.addListener(() { 64 | if (cardFlippingController.isCompleted) { 65 | cardFlippingController.reset(); 66 | 67 | setState(() { 68 | isFacingUp = !isFacingUp; 69 | if (isFacingUp) { 70 | flipAnimation = leftToRight.animate(cardFlippingController); 71 | } else { 72 | flipAnimation = rightToLeft.animate(cardFlippingController); 73 | } 74 | defaultSkew = 0; 75 | skewFactor = 0; 76 | }); 77 | } else if (cardFlippingController.isAnimating) { 78 | setState(() { 79 | defaultSkew = 0.001; 80 | skewFactor = flipAnimation.value; 81 | }); 82 | setImage(); 83 | } 84 | 85 | }); 86 | widget.cardFlippingController?.cardState = this; 87 | } 88 | 89 | @override 90 | void dispose() { 91 | cardFlippingController.dispose(); 92 | super.dispose(); 93 | } 94 | 95 | void setImage() { 96 | if (isFacingUp) { 97 | if (skewFactor >= 270) { 98 | setState(() { 99 | displayedCard = widget.frontSide; 100 | 101 | }); 102 | } else { 103 | setState(() { 104 | displayedCard = Transform( 105 | transform: Matrix4.identity() 106 | ..setEntry(3, 2, 0.000) 107 | ..rotateY(180 / 180 * pi), 108 | alignment: Alignment.center, 109 | child: widget.backSide, 110 | ); 111 | 112 | }); 113 | } 114 | } else { 115 | if (skewFactor <= 90) { 116 | setState(() { 117 | displayedCard = widget.backSide; 118 | 119 | }); 120 | } else { 121 | setState(() { 122 | displayedCard = Transform( 123 | transform: Matrix4.identity() 124 | ..setEntry(3, 2, 0.000) 125 | ..rotateY(180 / 180 * pi), 126 | alignment: Alignment.center, 127 | child: widget.frontSide, 128 | ); 129 | 130 | }); 131 | } 132 | 133 | } 134 | 135 | } 136 | 137 | Future flipCard() async { 138 | 139 | return cardFlippingController.forward().then((value) => true); 140 | 141 | } 142 | 143 | @override 144 | Widget build(BuildContext context) { 145 | if (isFacingUp && skewFactor == 0) { 146 | displayedCard = widget.frontSide; 147 | } else if (!isFacingUp && skewFactor == 0) { 148 | displayedCard = widget.backSide; 149 | } 150 | 151 | 152 | return Transform( 153 | transform: Matrix4.identity() 154 | ..setEntry(3, 2, defaultSkew) 155 | ..rotateY(skewFactor / 180 * pi), 156 | alignment: Alignment.center, 157 | child: displayedCard, 158 | ); 159 | 160 | } 161 | 162 | 163 | } 164 | 165 | class CardFlippingController { 166 | _CardFlipperState? cardState; 167 | Future flipCard() async => await cardState!.flipCard(); 168 | } 169 | -------------------------------------------------------------------------------- /lib/components/sign_up_screen/step_get_bank_account.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StepGetBankAccount extends StatefulWidget { 4 | final LabeledGlobalKey bankAccountFormKey; 5 | final Function updateSignUpDetails; 6 | final Function showConfirmSignUpButton; 7 | final Function registrationDetails; 8 | final Function finalStepProccessing; 9 | const StepGetBankAccount( 10 | {Key? key, 11 | required this.updateSignUpDetails, 12 | required this.registrationDetails, 13 | required this.bankAccountFormKey, 14 | required this.showConfirmSignUpButton, 15 | required this.finalStepProccessing}) 16 | : super(key: key); 17 | 18 | @override 19 | _StepGetBankAccountState createState() => _StepGetBankAccountState(); 20 | } 21 | 22 | class _StepGetBankAccountState extends State { 23 | String bankAccount = ""; 24 | String bankAccountErrorMessage = ""; 25 | @override 26 | void initState() { 27 | super.initState(); 28 | Map signUpDetails = widget.registrationDetails(); 29 | if (mounted) { 30 | setState(() { 31 | bankAccount = signUpDetails['bankAccount']!; 32 | }); 33 | } 34 | } 35 | 36 | @override 37 | void dispose() { 38 | // widget.bankAccountFormKey.currentState?.validate(); 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Form( 45 | key: widget.bankAccountFormKey, 46 | child: Column( 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | children: [ 49 | Container( 50 | width: double.infinity, 51 | child: TextFormField( 52 | initialValue: bankAccount, 53 | onChanged: _toggleSignUpButtonVisibility, 54 | validator: _validateBankAccount, 55 | autofocus: mounted, 56 | autocorrect: false, 57 | onFieldSubmitted: (value) { 58 | if (value.isNotEmpty) { 59 | widget.finalStepProccessing(); 60 | } 61 | }, 62 | decoration: InputDecoration( 63 | fillColor: Colors.white, 64 | border: InputBorder.none, 65 | focusedBorder: InputBorder.none, 66 | enabledBorder: InputBorder.none, 67 | errorBorder: InputBorder.none, 68 | disabledBorder: InputBorder.none, 69 | contentPadding: 70 | EdgeInsets.only(left: 15, bottom: 11, top: 11, right: 15), 71 | hintText: "bank account number", 72 | hintStyle: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 73 | ), 74 | style: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 75 | ), 76 | margin: EdgeInsets.all(5), 77 | padding: EdgeInsets.all(5), 78 | decoration: BoxDecoration( 79 | color: Colors.white, 80 | border: Border.all(width: 1.0, color: Color(0xFFF5F7FA)), 81 | borderRadius: BorderRadius.circular(20), 82 | boxShadow: [ 83 | BoxShadow( 84 | blurRadius: 6.18, 85 | spreadRadius: 0.618, 86 | offset: Offset(-4, -4), 87 | color: Colors.white38), 88 | BoxShadow( 89 | blurRadius: 6.18, 90 | spreadRadius: 0.618, 91 | offset: Offset(4, 4), 92 | color: Colors.blueGrey.shade100) 93 | ]), 94 | ), 95 | if (bankAccountErrorMessage != '') 96 | Container( 97 | child: Text( 98 | "\t\t\t\t$bankAccountErrorMessage", 99 | style: TextStyle(fontSize: 10, color: Colors.red), 100 | ), 101 | margin: EdgeInsets.all(2), 102 | padding: EdgeInsets.all(2), 103 | ), 104 | ], 105 | )); 106 | } 107 | 108 | void errorMessageSetter(String message) { 109 | setState(() { 110 | bankAccountErrorMessage = message; 111 | }); 112 | } 113 | 114 | String? _validateBankAccount(String? value) { 115 | if (value == null || value.isEmpty) { 116 | errorMessageSetter('you must provide a valid Bank Account number'); 117 | } else if (value.length > 25) { 118 | errorMessageSetter( 119 | 'Bank Account number cannot contain more than 25 characters'); 120 | } else { 121 | errorMessageSetter(""); 122 | 123 | // widget.updateSignUpDetails('bankAccount', value); 124 | setState(() { 125 | bankAccount = value; 126 | }); 127 | } 128 | 129 | return null; 130 | } 131 | 132 | void _toggleSignUpButtonVisibility(String value) { 133 | widget.updateSignUpDetails('bankAccount', value); 134 | if (value.isNotEmpty) { 135 | widget.showConfirmSignUpButton(true); 136 | } else { 137 | widget.showConfirmSignUpButton(false); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hadwin 2 | description: A prototype/demo version of a fund transfer app/platform. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+2 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^1.0.2 37 | provider: ^6.0.2 38 | fluentui_system_icons: ^1.1.153 39 | http: ^0.13.4 40 | fade_shimmer: ^2.0.1 41 | # qr_code_scanner: ^0.6.1 42 | scrollable_positioned_list: ^0.2.3 43 | google_fonts: ^2.3.1 44 | lottie: ^1.2.2 45 | socket_io_client: ^1.0.2 46 | grouped_list: ^4.2.0 47 | # concentric_transition: ^1.0.1+1 48 | concentric_transition: 1.0.1 49 | dotted_line: ^3.1.0 50 | # qr_flutter: ^4.0.0 51 | url_launcher: ^6.0.20 52 | google_nav_bar: ^5.0.6 53 | path_provider: ^2.0.9 54 | markdown_widget: ^1.2.8 55 | 56 | dev_dependencies: 57 | flutter_test: 58 | sdk: flutter 59 | 60 | flutter_launcher_icons: "^0.9.2" 61 | flutter_native_splash: ^2.0.1+1 62 | 63 | flutter_native_splash: 64 | color: "#2f73b9" 65 | image: "assets/images/hadwin_system/hadwin-splash-screen-logo.png" 66 | android: true 67 | ios: true 68 | 69 | flutter_icons: 70 | android: true 71 | ios: true 72 | image_path: "assets/images/hadwin_system/hadwin-logo.png" 73 | adaptive_icon_background: "#1546A0" 74 | # foreground color is set to #1F5595 75 | adaptive_icon_foreground: "assets/images/hadwin_system/hadwin-adaptive-logo.png" 76 | remove_alpha_ios: true 77 | 78 | # The "flutter_lints" package below contains a set of recommended lints to 79 | # encourage good coding practices. The lint set provided by the package is 80 | # activated in the `analysis_options.yaml` file located at the root of your 81 | # package. See that file for information about deactivating specific lint 82 | # rules and activating additional ones. 83 | flutter_lints: ^1.0.4 84 | 85 | # For information on the generic Dart part of this file, see the 86 | # following page: https://dart.dev/tools/pub/pubspec 87 | 88 | # The following section is specific to Flutter. 89 | flutter: 90 | 91 | # The following line ensures that the Material Icons font is 92 | # included with your application, so that you can use the icons in 93 | # the material Icons class. 94 | uses-material-design: true 95 | 96 | # To add assets to your application, add an assets section, like this: 97 | assets: 98 | - assets/images/ 99 | - assets/images/hadwin_system/ 100 | - assets/images/notification_assets/ 101 | - assets/images/card_flow_assets/ 102 | - assets/images/onboarding_assets/ 103 | - assets/images/transparent_card_brands/ 104 | 105 | # - assets/data/local_test_user_data.json 106 | # - images/a_dot_burr.jpeg 107 | # - images/a_dot_ham.jpeg 108 | 109 | # An image asset can refer to one or more resolution-specific "variants", see 110 | # https://flutter.dev/assets-and-images/#resolution-aware. 111 | 112 | # For details regarding adding assets from package dependencies, see 113 | # https://flutter.dev/assets-and-images/#from-packages 114 | 115 | # To add custom fonts to your application, add a fonts section here, 116 | # in this "flutter" section. Each entry in this list should have a 117 | # "family" key with the font family name, and a "fonts" key with a 118 | # list giving the asset and other descriptors for the font. For 119 | # example: 120 | fonts: 121 | - family: OCRA 122 | fonts: 123 | - asset: assets/fonts/OCRAStd.otf 124 | 125 | - family: HadWinIcons 126 | fonts: 127 | - asset: assets/fonts/HadWinIcons.ttf 128 | 129 | # For details regarding fonts from package dependencies, 130 | # see https://flutter.dev/custom-fonts/#from-packages 131 | -------------------------------------------------------------------------------- /lib/utilities/display_error_alert.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hadwin/database/login_info_storage.dart'; 3 | import 'package:hadwin/database/user_data_storage.dart'; 4 | 5 | import 'package:hadwin/screens/login_screen.dart'; 6 | 7 | Future _deleteLoggedInUserData() async { 8 | List deletionStatus = await Future.wait( 9 | [LoginInfoStorage().deleteFile(), UserDataStorage().deleteFile()]); 10 | return deletionStatus.first && deletionStatus.last; 11 | } 12 | 13 | void showErrorAlert(BuildContext context, Map error) { 14 | Map errorTypes = { 15 | 'error': _CommonError(context, error), 16 | 'internetConnectionError': _LocalError(context, error), 17 | 'localDBError': _LocalError(context, error), 18 | 'authenticationError': _CommonError(context, error), 19 | 'apiAuthorizationError': _HazardousError(context, error), 20 | 'corruptedTokenError': _HazardousError(context, error) 21 | }; 22 | String currentError = error.keys.first; 23 | 24 | showDialog( 25 | context: context, 26 | barrierDismissible: false, 27 | builder: (context) => AlertDialog( 28 | title: Text( 29 | "Error", 30 | textAlign: TextAlign.center, 31 | ), 32 | content: Column( 33 | mainAxisSize: MainAxisSize.min, 34 | mainAxisAlignment: MainAxisAlignment.center, 35 | children: errorTypes[currentError].errorDescription, 36 | ), 37 | shape: 38 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), 39 | actionsAlignment: MainAxisAlignment.center, 40 | actions: [ 41 | Container( 42 | height: 48, 43 | decoration: BoxDecoration( 44 | boxShadow: [ 45 | BoxShadow( 46 | color: Colors.blueGrey.shade100, 47 | offset: Offset(0, 4), 48 | blurRadius: 5.0) 49 | ], 50 | 51 | gradient: RadialGradient( 52 | colors: [Color(0xff0070BA), Color(0xff1546A0)], 53 | radius: 8.4, 54 | center: Alignment(-0.24, -0.36)), 55 | borderRadius: BorderRadius.circular(10), 56 | ), 57 | child: ElevatedButton( 58 | onPressed: errorTypes[currentError].onClose, 59 | child: Text('OK'), 60 | style: ElevatedButton.styleFrom( 61 | primary: Colors.transparent, 62 | shadowColor: Colors.transparent, 63 | shape: RoundedRectangleBorder( 64 | borderRadius: BorderRadius.circular(10)), 65 | )), 66 | ) 67 | ], 68 | )); 69 | } 70 | 71 | class _CommonError { 72 | BuildContext context; 73 | Map error; 74 | _CommonError(this.context, this.error); 75 | List get errorDescription => [ 76 | Padding( 77 | padding: EdgeInsets.only(top: 24, bottom: 12), 78 | child: Text( 79 | error[error.keys.first], 80 | textAlign: TextAlign.center, 81 | ), 82 | ), 83 | ]; 84 | 85 | void onClose() { 86 | Navigator.of(context).pop(); 87 | } 88 | } 89 | 90 | class _LocalError { 91 | BuildContext context; 92 | Map error; 93 | _LocalError(this.context, this.error); 94 | 95 | List get errorDescription => [ 96 | error.keys.first == 'internetConnectionError' 97 | ? ColorFiltered( 98 | colorFilter: 99 | ColorFilter.mode(Color(0xFF0070BA), BlendMode.color), 100 | child: ColorFiltered( 101 | colorFilter: 102 | ColorFilter.mode(Colors.grey, BlendMode.saturation), 103 | child: Image.asset( 104 | 'assets/images/notification_assets/no-wifi.png', 105 | height: 48, 106 | width: 48, 107 | ), 108 | )) 109 | : Image.asset( 110 | 'assets/images/notification_assets/file-error.png', 111 | height: 48, 112 | width: 48, 113 | ), 114 | Padding( 115 | padding: EdgeInsets.only(top: 24, bottom: 12), 116 | child: Text( 117 | error[error.keys.first], 118 | textAlign: TextAlign.center, 119 | ), 120 | ), 121 | ]; 122 | 123 | void onClose() { 124 | Navigator.of(context).pop(); 125 | } 126 | } 127 | 128 | class _HazardousError { 129 | BuildContext context; 130 | Map error; 131 | _HazardousError(this.context, this.error); 132 | List get errorDescription => [ 133 | Padding( 134 | padding: EdgeInsets.only(top: 24, bottom: 12), 135 | child: Text( 136 | error[error.keys.first], 137 | textAlign: TextAlign.center, 138 | ), 139 | ), 140 | ]; 141 | 142 | void onClose() async { 143 | final logOutStatus = await _deleteLoggedInUserData(); 144 | if (logOutStatus) { 145 | Navigator.of(context).pushAndRemoveUntil( 146 | MaterialPageRoute(builder: (context) => LoginScreen()), 147 | (route) => false); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /lib/components/qr_code_scanner_screen/my_qr_screen.dart: -------------------------------------------------------------------------------- 1 | /* 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:google_fonts/google_fonts.dart'; 6 | import 'package:hadwin/database/user_data_storage.dart'; 7 | 8 | import 'package:qr_flutter/qr_flutter.dart'; 9 | 10 | class MyQRCodeScreen extends StatelessWidget { 11 | const MyQRCodeScreen({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | backgroundColor: Color(0xffedf2f4), 17 | appBar: AppBar( 18 | title: Text("My QR Code"), 19 | centerTitle: true, 20 | backgroundColor: Colors.transparent, 21 | elevation: 0, 22 | foregroundColor: Color(0xff243656), 23 | leading: IconButton( 24 | onPressed: () { 25 | Navigator.of(context).pop(); 26 | }, 27 | icon: Icon(Icons.arrow_back)), 28 | ), 29 | body: SingleChildScrollView( 30 | child: Center( 31 | child: Column( 32 | 33 | children: [ 34 | SizedBox( 35 | height: 36, 36 | ), 37 | Text( 38 | "Share this code", 39 | style: GoogleFonts.poppins(fontSize: 18), 40 | textAlign: TextAlign.center, 41 | ), 42 | Text( 43 | "( HADWIN is a prototype \nyou cannot send\nor receive real money )", 44 | style: GoogleFonts.poppins(fontSize: 15), 45 | textAlign: TextAlign.center, 46 | ), 47 | SizedBox( 48 | height: 64, 49 | ), 50 | Stack(children: [ 51 | ClipPath( 52 | clipper: QRClipper(), 53 | child: Container( 54 | width: 304, 55 | height: 304, 56 | padding: EdgeInsets.all(16), 57 | decoration: BoxDecoration( 58 | color: Colors.transparent, 59 | border: Border.all( 60 | color: Colors.red, 61 | width: 8.4, 62 | ), 63 | borderRadius: BorderRadius.all(Radius.circular(20)), 64 | ), 65 | ), 66 | ), 67 | Container( 68 | width: 304, 69 | height: 304, 70 | padding: EdgeInsets.all(24), 71 | child: FutureBuilder>( 72 | future: UserDataStorage().getUserData(), 73 | builder: (context, snapshot) { 74 | if (snapshot.hasData) { 75 | Map userData = snapshot.data!; 76 | Map qrFields = { 77 | 'avatar': userData['avatar'], 78 | 'name': userData['first_name'] + 79 | " " + 80 | userData['last_name'], 81 | 'gender': userData['gender'], 82 | 'walletAddress': userData['walletAddress'], 83 | 'emailAddress': userData['email'], 84 | 'phoneNumber': userData['phone_number'], 85 | 'communicationAddress': 86 | "${userData['address']['street_name']}, ${userData['address']['street_address']}, ${userData['address']['city']}, ${userData['address']['state']} ${userData['address']['zip_code']}, ${userData['address']['country']}" 87 | }; 88 | return QrImage( 89 | data: json.encode(qrFields), 90 | version: QrVersions.auto, 91 | 92 | size: 240, 93 | gapless: false, 94 | errorStateBuilder: (cxt, err) { 95 | return Positioned.fill( 96 | child: Align( 97 | alignment: Alignment.center, 98 | child: Text( 99 | "Uh oh! Something went wrong...", 100 | textAlign: TextAlign.center, 101 | ), 102 | ), 103 | ); 104 | }, 105 | ); 106 | } else { 107 | return Center( 108 | child: SizedBox( 109 | width: 64, 110 | height: 64, 111 | child: CircularProgressIndicator( 112 | color: Colors.cyan.shade400, 113 | strokeWidth: 6.18, 114 | ), 115 | )); 116 | } 117 | })) 118 | ]) 119 | 120 | ], 121 | ), 122 | )), 123 | ); 124 | } 125 | } 126 | 127 | class QRClipper extends CustomClipper { 128 | @override 129 | Path getClip(Size size) { 130 | Path path = Path(); 131 | path 132 | ..moveTo(size.width * .8, 0) 133 | ..lineTo(size.width, 0) 134 | ..lineTo(size.width, size.height * .2) 135 | ..moveTo(size.width, size.height * .8) 136 | ..lineTo(size.width, size.height) 137 | ..lineTo(size.width * .8, size.height) 138 | ..moveTo(size.width * .2, size.height) 139 | ..lineTo(0, size.height) 140 | ..lineTo(0, size.height * .8) 141 | ..moveTo(0, size.height * .2) 142 | ..lineTo(0, 0) 143 | ..lineTo(size.width * .2, 0) 144 | ..close(); 145 | return path; 146 | } 147 | 148 | @override 149 | bool shouldReclip(covariant CustomClipper oldClipper) { 150 | return true; 151 | 152 | } 153 | } 154 | */ -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:flutter_native_splash/flutter_native_splash.dart'; 5 | 6 | import 'package:hadwin/components/main_app_screen/local_splash_screen_component.dart'; 7 | 8 | import 'package:hadwin/components/main_app_screen/tabbed_layout_component.dart'; 9 | import 'package:hadwin/database/cards_storage.dart'; 10 | import 'package:hadwin/database/login_info_storage.dart'; 11 | import 'package:hadwin/database/hadwin_user_device_info_storage.dart'; 12 | import 'package:hadwin/database/successful_transactions_storage.dart'; 13 | import 'package:hadwin/database/user_data_storage.dart'; 14 | import 'package:hadwin/providers/live_transactions_provider.dart'; 15 | import 'package:hadwin/providers/tab_navigation_provider.dart'; 16 | 17 | import 'package:hadwin/providers/user_login_state_provider.dart'; 18 | import 'package:hadwin/screens/login_screen.dart'; 19 | import 'package:hadwin/utilities/make_api_request.dart'; 20 | import 'package:hadwin/screens/onboarding_screen.dart'; 21 | 22 | import 'package:provider/provider.dart'; 23 | 24 | void main() { 25 | WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); 26 | FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); 27 | runApp(MultiProvider( 28 | providers: [ 29 | ChangeNotifierProvider( 30 | create: (_) => UserLoginStateProvider(), 31 | ), 32 | ChangeNotifierProxyProvider( 34 | create: (BuildContext context) => LiveTransactionsProvider(), 35 | update: (context, userLoginAuthKey, liveTransactions) => 36 | liveTransactions!..update(userLoginAuthKey)), 37 | ChangeNotifierProvider(create: (_) => TabNavigationProvider()), 38 | ], 39 | child: MyApp(), 40 | )); 41 | } 42 | 43 | class MyApp extends StatefulWidget { 44 | const MyApp({Key? key}) : super(key: key); 45 | 46 | @override 47 | _MyAppState createState() => _MyAppState(); 48 | } 49 | 50 | class _MyAppState extends State { 51 | UserDeviceInfoStorage userDeviceInfoStorage = UserDeviceInfoStorage(); 52 | UserDataStorage userDataStorage = UserDataStorage(); 53 | LoginInfoStorage loginInfoStorage = LoginInfoStorage(); 54 | bool? _previousllyInstalled = null; 55 | bool? _isLoggedIn = null; 56 | Map? _loggedInUserData = null; 57 | 58 | void _checkForPreviousInstallations() async { 59 | final previousllyInstalledStatus = 60 | await userDeviceInfoStorage.wasUsedBefore; 61 | setState(() { 62 | _previousllyInstalled = previousllyInstalledStatus; 63 | }); 64 | } 65 | 66 | void _getLoggedInUserData() async { 67 | final loginData = await loginInfoStorage.getPersistentLoginData; 68 | final loggedInUserAuthKey = loginData['authToken']; 69 | final loggedInUserId = loginData['userId']; 70 | bool loginStatus; 71 | if (loggedInUserAuthKey == null || loggedInUserId == null) { 72 | loginStatus = false; 73 | } else { 74 | Provider.of(context, listen: false) 75 | .setAuthKeyValue(loggedInUserAuthKey); 76 | 77 | await CardsStorage().initializeAvailableCards(loggedInUserAuthKey); 78 | await SuccessfulTransactionsStorage().initializeSuccessfulTransactions(); 79 | 80 | final userValidity = 81 | await fetchUserId(loggedInUserAuthKey, loggedInUserId); 82 | //* user data saved 83 | 84 | loginStatus = userValidity; 85 | } 86 | setState(() { 87 | _isLoggedIn = loginStatus; 88 | }); 89 | } 90 | 91 | @override 92 | void initState() { 93 | super.initState(); 94 | SystemChrome.setPreferredOrientations([ 95 | DeviceOrientation.portraitUp, 96 | DeviceOrientation.portraitDown, 97 | ]); 98 | 99 | _checkForPreviousInstallations(); 100 | 101 | _getLoggedInUserData(); 102 | } 103 | 104 | @override 105 | void dispose() { 106 | SystemChrome.setPreferredOrientations([ 107 | DeviceOrientation.landscapeRight, 108 | DeviceOrientation.landscapeLeft, 109 | DeviceOrientation.portraitUp, 110 | DeviceOrientation.portraitDown, 111 | ]); 112 | super.dispose(); 113 | } 114 | 115 | @override 116 | Widget build(BuildContext context) { 117 | return MaterialApp( 118 | title: 'HADWIN', 119 | theme: ThemeData( 120 | primarySwatch: Colors.blue, 121 | textTheme: 122 | GoogleFonts.manropeTextTheme(Theme.of(context).textTheme)), 123 | home: Builder( 124 | builder: (context) { 125 | if (_previousllyInstalled == false) { 126 | FlutterNativeSplash.remove(); 127 | return OnboardingScreen(); 128 | } else if (_isLoggedIn == true && _loggedInUserData != null) { 129 | FlutterNativeSplash.remove(); 130 | return TabbedLayoutComponent( 131 | userData: _loggedInUserData!, 132 | ); 133 | } else if (_isLoggedIn == false) { 134 | FlutterNativeSplash.remove(); 135 | return LoginScreen(); 136 | } else { 137 | return Material( 138 | type: MaterialType.transparency, 139 | child: LocalSplashScreenComponent(), 140 | ); 141 | } 142 | }, 143 | ), 144 | debugShowCheckedModeBanner: false); 145 | } 146 | 147 | Future fetchUserId(String authKey, String userId) async { 148 | final dataReceived = 149 | await getData(urlPath: "/hadwin/v3/user/$userId", authKey: authKey); 150 | if (dataReceived.keys.join().toLowerCase().contains("error")) { 151 | return false; 152 | } else { 153 | bool userIsSaved = 154 | await UserDataStorage().saveUserData(dataReceived['user']); 155 | Provider.of(context, listen: false) 156 | .initializeBankBalance(dataReceived['user']); 157 | 158 | if (userIsSaved) { 159 | //? in case user is valid 160 | if (mounted) { 161 | setState(() { 162 | _loggedInUserData = dataReceived['user']; 163 | }); 164 | } 165 | } 166 | 167 | return userIsSaved; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /lib/utilities/custom_date_grouping.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | int calcDaysBetween(DateTime from, DateTime to) { 4 | from = DateTime(from.year, from.month, from.day); 5 | to = DateTime(to.year, to.month, to.day); 6 | return (to.difference(from).inHours ~/ 24); 7 | } 8 | 9 | int calcSecondsBetween(DateTime from, DateTime to) { 10 | from = DateTime( 11 | from.year, from.month, from.day, from.hour, from.minute, from.second); 12 | to = DateTime(to.year, to.month, to.day, to.hour, to.minute, to.second); 13 | return to.difference(from).inSeconds; 14 | } 15 | 16 | String customGroup(DateTime transactionDate) { 17 | String response = ''; 18 | DateTime latestDate = DateTime.now(); 19 | int nDaysInBetween = calcDaysBetween(transactionDate, latestDate); 20 | 21 | int years = nDaysInBetween ~/ 365; 22 | 23 | if (years == 1) { 24 | response = 'Last year'; 25 | } else if (years > 1 && years <= 5) { 26 | response = '$years years ago'; 27 | } else if (years > 5) { 28 | response = 'More than 5 years ago'; 29 | } else { 30 | nDaysInBetween -= 365 * years; 31 | 32 | int months = nDaysInBetween ~/ 30; 33 | if (months == 1) { 34 | response = 'Last month'; 35 | } else if (months > 1 && months <= 6) { 36 | response = '$months months ago'; 37 | } else if (months >= 6) { 38 | response = 'More than 6 months ago'; 39 | } else { 40 | int days = latestDate.day - transactionDate.day; 41 | 42 | if (days == 1) { 43 | response = 'Yesterday'; 44 | 45 | 46 | } else if (days > 1 && days <= 7) { 47 | response = 'This week'; 48 | } else if (days > 7 && days <= 14) { 49 | response = 'Last week'; 50 | } else if (days > 14 && days <= 30) { 51 | int weeks = days ~/ 7; 52 | response = '$weeks weeks ago'; 53 | } else { 54 | response = 'Today'; 55 | } 56 | } 57 | } 58 | return response; 59 | } 60 | 61 | int customGroupComparator(String group1, String group2) { 62 | int comparison = -1; 63 | int group1Match = 0; 64 | int group2Match = 0; 65 | List dateGroups = [ 66 | r'Today', 67 | r'Yesterday', 68 | r'This week', 69 | r'Last week', 70 | r'\d{1} weeks ago', 71 | r'Last month', 72 | r'\d{1} months ago', 73 | r'More than 6 months ago', 74 | r'Last year', 75 | r'\d{1} years ago', 76 | r'More than 5 years ago', 77 | ]; 78 | 79 | for (var i = 0; i < dateGroups.length; i++) { 80 | if (RegExp(dateGroups[i]).hasMatch(group1)) { 81 | group1Match = i; 82 | } 83 | if (RegExp(dateGroups[i]).hasMatch(group2)) { 84 | group2Match = i; 85 | } 86 | } 87 | // check 88 | if (group1Match == group2Match) { 89 | comparison = group1.compareTo(group2); 90 | } else if (group1Match < group2Match) { 91 | comparison = -1; 92 | } else { 93 | comparison = 1; 94 | } 95 | return comparison; 96 | } 97 | 98 | String dateFormatter(String dateGroup, DateTime transactionDate) { 99 | String formattedDate = ''; 100 | if (dateGroup == 'Today') { 101 | formattedDate = _formatTime1(transactionDate); 102 | } else if (dateGroup == 'Yesterday') { 103 | formattedDate = 104 | "Yesterday at ${formatTime2(transactionDate.hour, transactionDate.minute)}"; 105 | } else { 106 | formattedDate = 107 | "${transactionDate.day}-${transactionDate.month}-${transactionDate.year} at ${formatTime2(transactionDate.hour, transactionDate.minute)}"; 108 | } 109 | return formattedDate; 110 | } 111 | 112 | String formatTime2(int hrs, int mins) { 113 | int newHrs = 0; 114 | String midDayStatus = ''; 115 | String minutesAsString=mins<10?"0$mins":"$mins"; 116 | if (hrs > 12 && hrs < 24) { 117 | midDayStatus = 'PM'; 118 | newHrs = hrs - 12; 119 | } else if (hrs == 12 && mins > 0) { 120 | midDayStatus = 'PM'; 121 | newHrs = 12; 122 | } else if (hrs < 12) { 123 | midDayStatus = 'AM'; 124 | newHrs = hrs; 125 | } else if (hrs == 24) { 126 | midDayStatus = 'AM'; 127 | newHrs = 00; 128 | } 129 | 130 | return "$newHrs:$minutesAsString $midDayStatus"; 131 | } 132 | 133 | String formatTime3(String date) { 134 | DateTime someDate = DateTime.parse(date); 135 | int hrs = someDate.hour; 136 | dynamic mins = someDate.minute; 137 | int newHrs = 0; 138 | String midDayStatus = ''; 139 | 140 | if (hrs > 12 && hrs < 24) { 141 | midDayStatus = 'PM'; 142 | newHrs = hrs - 12; 143 | } else if (hrs == 12 && mins > 0) { 144 | midDayStatus = 'PM'; 145 | newHrs = 12; 146 | } else if (hrs < 12) { 147 | midDayStatus = 'AM'; 148 | newHrs = hrs; 149 | } else if (hrs == 24) { 150 | midDayStatus = 'AM'; 151 | newHrs = 00; 152 | } 153 | 154 | if (mins<=9) { 155 | mins="0$mins"; 156 | } 157 | 158 | return "$newHrs:$mins $midDayStatus"; 159 | } 160 | 161 | String _formatTime1(DateTime transactionDate) { 162 | DateTime latestDate = DateTime.now(); 163 | 164 | int seconds = calcSecondsBetween(transactionDate, latestDate); 165 | 166 | int minutes = seconds ~/ 60; 167 | int hours = minutes ~/ 60; 168 | 169 | String response = ''; 170 | if (hours == 1) { 171 | response = 'an hour ago'; 172 | } else if (hours > 1 && hours < 24) { 173 | response = '$hours hours ago'; 174 | } else { 175 | if (minutes > 30) { 176 | response = 'half an hour ago'; 177 | } else if (minutes <= 30 && minutes > 1) { 178 | response = '$minutes minutes ago'; 179 | } else if (minutes == 1) { 180 | response = 'a minute ago'; 181 | } else { 182 | if (seconds <= 0) { 183 | response = 'just now'; 184 | } else if (seconds == 1) { 185 | response = 'a second ago'; 186 | } else { 187 | response = '$seconds seconds ago'; 188 | } 189 | } 190 | } 191 | return response; 192 | } 193 | 194 | 195 | String dateToWords(DateTime transactionDate) { 196 | String day = _formatDay(transactionDate.day); 197 | String month = _findMonthInWords(transactionDate.month); 198 | return "$day $month, ${transactionDate.year}"; 199 | } 200 | 201 | //? FUNCTION FOR RETRIEVING ORDINALS 202 | String _formatDay(int day) { 203 | String suffix = ''; 204 | if (day >= 11 && day<=13) { 205 | suffix = 'th'; 206 | } else { 207 | switch (day % 10) { 208 | case 1: 209 | suffix = 'st'; 210 | break; 211 | case 2: 212 | suffix = 'nd'; 213 | break; 214 | case 3: 215 | suffix = 'rd'; 216 | break; 217 | default: 218 | suffix = 'th'; 219 | } 220 | } 221 | return "$day$suffix"; 222 | } 223 | 224 | String _findMonthInWords(int month) { 225 | List months = [ 226 | 'January', 227 | 'February', 228 | 'March', 229 | 'April', 230 | 'May', 231 | 'June', 232 | 'July', 233 | 'August', 234 | 'September', 235 | 'October', 236 | 'November', 237 | 'December' 238 | ]; 239 | return months[month - 1]; 240 | } 241 | -------------------------------------------------------------------------------- /lib/components/settings_screen/all_licenses.dart: -------------------------------------------------------------------------------- 1 | import 'package:fade_shimmer/fade_shimmer.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:hadwin/components/settings_screen/license_data.dart'; 6 | import 'package:hadwin/utilities/slide_right_route.dart'; 7 | 8 | class AllLicenses extends StatelessWidget { 9 | const AllLicenses({ 10 | Key? key, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | backgroundColor: Colors.white, 17 | appBar: AppBar( 18 | title: Text( 19 | 'Licenses', 20 | style: TextStyle(fontSize: 24), 21 | ), 22 | centerTitle: true, 23 | backgroundColor: Colors.transparent, 24 | foregroundColor: Color(0xff243656), 25 | elevation: 0, 26 | ), 27 | body: Column(children: [ 28 | Wrap( 29 | direction: Axis.vertical, 30 | crossAxisAlignment: WrapCrossAlignment.center, 31 | spacing: 6.18, 32 | children: [ 33 | 34 | Image.asset('assets/images/hadwin_system/hadwin-logo.png', 35 | height: 48, width: 48), 36 | Image.asset('assets/images/hadwin_system/hadwin-name.png', 37 | height: 48), 38 | Text('1.0.0',style: TextStyle(color: Color(0xff243656))), 39 | FlutterLogo( 40 | size: 36, 41 | ), 42 | Text('Powered by Flutter',style: TextStyle(color: Color(0xff243656)),), 43 | ], 44 | ), 45 | SizedBox( 46 | height: 16, 47 | ), 48 | Expanded( 49 | child: Container( 50 | width: double.infinity, 51 | height: MediaQuery.of(context).size.height - 180, 52 | child: FutureBuilder>( 53 | future: LicenseRegistry.licenses.toList(), 54 | 55 | builder: (BuildContext context, 56 | AsyncSnapshot> snapshot) { 57 | 58 | if (snapshot.hasData) { 59 | final allLicenceNames = snapshot.data! 60 | .map((e) => e.packages.first) 61 | .toList(); 62 | final uniqueLicences = 63 | allLicenceNames.toSet().toList(); 64 | 65 | 66 | return ListView.separated( 67 | itemBuilder: (context, index) { 68 | var licenseCount = allLicenceNames 69 | .where((element) => 70 | element == uniqueLicences[index]) 71 | .length; 72 | 73 | var licenseData = snapshot.data! 74 | .where((e) => 75 | e.packages.first == 76 | uniqueLicences[index]) 77 | .map((e) => e.paragraphs) 78 | .toList(); 79 | var suffix = 80 | licenseCount > 1 ? 'licenses' : 'license'; 81 | return ListTile( 82 | title: Text( 83 | uniqueLicences[index], 84 | style: TextStyle(fontSize: 18), 85 | ), 86 | subtitle: Text( 87 | "$licenseCount $suffix", 88 | style: TextStyle(fontSize: 15), 89 | ), 90 | onTap: () { 91 | 92 | Navigator.push( 93 | context, 94 | SlideRightRoute( 95 | page: LicenseData( 96 | licenseName: uniqueLicences[index], 97 | licenseData: licenseData, 98 | ))); 99 | }, 100 | ); 101 | }, 102 | separatorBuilder: (_, b) => Divider( 103 | height: 6, 104 | color: Colors.grey.shade300, 105 | ), 106 | itemCount: uniqueLicences.length); 107 | } else { 108 | return ListView.separated( 109 | itemBuilder: (context, index) { 110 | return Container( 111 | margin: EdgeInsets.only(left: 8.4,right: 8.4,top: 3.6,bottom: 3.6), 112 | decoration: BoxDecoration( 113 | color: Colors.blueGrey.shade100 114 | .withOpacity(0.1618), 115 | borderRadius: BorderRadius.all(Radius.circular(10)) 116 | ), 117 | child: ListTile( 118 | 119 | title: Container( 120 | child: FadeShimmer( 121 | radius: 16.18, 122 | height: 18, 123 | width: 72, 124 | fadeTheme: FadeTheme.light, 125 | ), 126 | ), 127 | 128 | subtitle: FadeShimmer( 129 | radius: 16.18, 130 | height: 18, 131 | width: 48, 132 | fadeTheme: FadeTheme.light, 133 | ), 134 | 135 | ), 136 | ); 137 | }, 138 | separatorBuilder: (_, b) => SizedBox( 139 | height: 3.6, 140 | 141 | ), 142 | itemCount: 9); 143 | } 144 | }))), 145 | ])); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /lib/screens/onboarding_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:concentric_transition/concentric_transition.dart'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:hadwin/database/hadwin_user_device_info_storage.dart'; 6 | import 'package:hadwin/screens/login_screen.dart'; 7 | import 'package:hadwin/utilities/hadwin_markdown_viewer.dart'; 8 | import 'package:hadwin/utilities/slide_right_route.dart'; 9 | 10 | class PageData { 11 | final String? title; 12 | 13 | final Image? mediaContent; 14 | final Color bgColor; 15 | final Color textColor; 16 | final Widget? optionalWidget; 17 | 18 | PageData( 19 | {this.title, 20 | this.mediaContent, 21 | this.bgColor = Colors.white, 22 | this.textColor = Colors.black, 23 | this.optionalWidget}); 24 | } 25 | 26 | class OnboardingScreen extends StatefulWidget { 27 | const OnboardingScreen({Key? key}) : super(key: key); 28 | 29 | @override 30 | State createState() => _OnboardingScreenState(); 31 | } 32 | 33 | class _OnboardingScreenState extends State { 34 | late List pages; 35 | late Widget getStarted; 36 | UserDeviceInfoStorage userDeviceInfoStorage = UserDeviceInfoStorage(); 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | 42 | getStarted = Column( 43 | children: [ 44 | Container( 45 | padding: const EdgeInsets.all(8.0), 46 | child: TextButton( 47 | onPressed: completedOrientation, 48 | child: Text( 49 | 'Get Started', 50 | style: TextStyle(fontSize: 27, color: Colors.white), 51 | ), 52 | style: TextButton.styleFrom( 53 | backgroundColor: Color(0xfffe5845), 54 | padding: EdgeInsets.all(18), 55 | shape: RoundedRectangleBorder( 56 | borderRadius: BorderRadius.circular(12.5))), 57 | ), 58 | ), 59 | InkWell( 60 | onTap: _getDocs, 61 | child: Text( 62 | "read the documentation\nif you need help", 63 | style: GoogleFonts.roboto( 64 | color: Colors.white, 65 | letterSpacing: 0.0, 66 | fontSize: 15, 67 | ), 68 | textAlign: TextAlign.center, 69 | ), 70 | ) 71 | ], 72 | ); 73 | pages = [ 74 | PageData( 75 | // textColor: Color(0xFFe8f9ec), 76 | mediaContent: null, 77 | textColor: Colors.white, 78 | bgColor: Color(0xff6baef2), 79 | title: 80 | "\nHADWIN is a prototype of a fund-transfer platform.\nHence, it cannot be used for making real payments or receiving real money as of now\nHADWIN was created from designs found on Dribbble, Behance & Pinterest\n\n\n\n\n\n\t(Swipe right or tap the circle below)"), 81 | PageData( 82 | mediaContent: Image.asset( 83 | 'assets/images/onboarding_assets/wfh-mohamed-chahin-bg-less.png'), 84 | title: 85 | "still... it's a relatively well functioning app as for something built right out of a small apartment", 86 | bgColor: Color(0xff3385c0), 87 | textColor: Colors.white, 88 | ), 89 | PageData( 90 | mediaContent: Image.asset( 91 | 'assets/images/onboarding_assets/online-shopping-yuliia-osadcha-bg-less.png'), 92 | bgColor: Color(0xff23b2f8), 93 | textColor: Colors.white, 94 | optionalWidget: getStarted), 95 | ]; 96 | } 97 | 98 | void _getDocs() { 99 | Navigator.push( 100 | context, 101 | SlideRightRoute( 102 | page: HadWinMarkdownViewer( 103 | screenName: 'Docs', 104 | urlRequested: 105 | 'https://raw.githubusercontent.com/brownboycodes/HADWIN/master/docs/HADWIN_WIKI.md'))); 106 | } 107 | 108 | void completedOrientation() async { 109 | bool isSaved = await userDeviceInfoStorage.initializeInstallationStatus(); 110 | if (isSaved) { 111 | // Navigator.push(context, MaterialPageRoute(builder: (context) { 112 | // return LoginScreen(); 113 | // })); 114 | Navigator.of(context).pushAndRemoveUntil( 115 | MaterialPageRoute(builder: (context) => LoginScreen()), 116 | (route) => false); 117 | } 118 | } 119 | 120 | List get colors => pages.map((p) => p.bgColor).toList(); 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | return MaterialApp( 125 | debugShowCheckedModeBanner: false, 126 | home: Scaffold( 127 | body: ConcentricPageView( 128 | colors: colors, 129 | radius: 30, 130 | curve: Curves.ease, 131 | duration: Duration(seconds: 2), 132 | itemCount: pages.length, 133 | itemBuilder: (index, value) { 134 | // PageData page = pages[index % pages.length]; 135 | PageData page = pages[index]; 136 | return Column(children: [ 137 | 138 | Container( 139 | child: Theme( 140 | data: ThemeData( 141 | textTheme: TextTheme( 142 | headline6: TextStyle( 143 | color: page.textColor, 144 | fontWeight: FontWeight.w600, 145 | fontFamily: 'Helvetica', 146 | letterSpacing: 0.0, 147 | fontSize: 15), 148 | subtitle2: TextStyle( 149 | color: page.textColor, 150 | fontWeight: FontWeight.w300, 151 | fontSize: 18, 152 | ), 153 | bodyText2: GoogleFonts.poppins( 154 | color: page.textColor, 155 | letterSpacing: 0.0, 156 | fontSize: 15, 157 | ), 158 | ), 159 | ), 160 | child: PageCard(page: page), 161 | ), 162 | ), 163 | ]); 164 | }, 165 | ), 166 | ), 167 | ); 168 | } 169 | } 170 | 171 | class PageCard extends StatelessWidget { 172 | final PageData page; 173 | 174 | const PageCard({ 175 | Key? key, 176 | required this.page, 177 | }) : super(key: key); 178 | 179 | @override 180 | Widget build(BuildContext context) { 181 | return Container( 182 | margin: EdgeInsets.symmetric( 183 | horizontal: 30.0, 184 | ), 185 | child: Column( 186 | children: [ 187 | _buildPicture(context), 188 | if (page.optionalWidget == null && page.mediaContent != null) 189 | SizedBox(height: 24), 190 | if (page.title != null) _buildText(context), 191 | if (page.optionalWidget != null) page.optionalWidget! 192 | ], 193 | ), 194 | ); 195 | } 196 | 197 | Widget _buildText(BuildContext context) { 198 | 199 | return Padding( 200 | padding: EdgeInsets.symmetric( 201 | horizontal: 30.0, 202 | ), 203 | child: Text( 204 | page.title!, 205 | style: Theme.of(context).textTheme.bodyText2, 206 | textAlign: TextAlign.left, 207 | ), 208 | ); 209 | } 210 | 211 | Widget _buildPicture( 212 | BuildContext context, 213 | ) { 214 | return Container( 215 | width: MediaQuery.of(context).size.width - 64, 216 | margin: EdgeInsets.only( 217 | top: 120, 218 | ), 219 | child: page.mediaContent, 220 | ); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lib/components/sign_up_screen/step_get_email_password.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StepGetEmailPassword extends StatefulWidget { 4 | final LabeledGlobalKey emailPasswordFormKey; 5 | final Function updateSignUpDetails; 6 | 7 | final Function registrationDetails; 8 | final Function proceedToNextStep; 9 | const StepGetEmailPassword( 10 | {Key? key, 11 | required this.updateSignUpDetails, 12 | required this.registrationDetails, 13 | required this.emailPasswordFormKey, 14 | required this.proceedToNextStep}) 15 | : super(key: key); 16 | 17 | @override 18 | _StepGetEmailPasswordState createState() => _StepGetEmailPasswordState(); 19 | } 20 | 21 | class _StepGetEmailPasswordState extends State { 22 | String emailId = ""; 23 | String password = ""; 24 | String emailIdErrorMessage = ""; 25 | String passwordErrorMessage = ""; 26 | RegExp validEmailFormat = RegExp( 27 | r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | Map signUpDetails = widget.registrationDetails(); 33 | if (mounted) { 34 | setState(() { 35 | emailId = signUpDetails['emailId']!; 36 | password = signUpDetails['password']!; 37 | }); 38 | } 39 | } 40 | 41 | @override 42 | void dispose() { 43 | // widget.emailPasswordFormKey.currentState?.validate(); 44 | super.dispose(); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Form( 50 | key: widget.emailPasswordFormKey, 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Container( 55 | width: double.infinity, 56 | child: TextFormField( 57 | textInputAction: TextInputAction.next, 58 | initialValue: emailId, 59 | validator: _validateEmailId, 60 | autofocus: mounted, 61 | autocorrect: false, 62 | decoration: InputDecoration( 63 | fillColor: Colors.white, 64 | border: InputBorder.none, 65 | focusedBorder: InputBorder.none, 66 | enabledBorder: InputBorder.none, 67 | errorBorder: InputBorder.none, 68 | disabledBorder: InputBorder.none, 69 | contentPadding: 70 | EdgeInsets.only(left: 15, bottom: 11, top: 11, right: 15), 71 | hintText: "email address", 72 | hintStyle: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 73 | ), 74 | style: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 75 | keyboardType: TextInputType.emailAddress, 76 | ), 77 | margin: EdgeInsets.all(5), 78 | padding: EdgeInsets.all(5), 79 | decoration: BoxDecoration( 80 | color: Colors.white, 81 | border: Border.all(width: 1.0, color: Color(0xFFF5F7FA)), 82 | borderRadius: BorderRadius.circular(20), 83 | boxShadow: [ 84 | BoxShadow( 85 | blurRadius: 6.18, 86 | spreadRadius: 0.618, 87 | offset: Offset(-4, -4), 88 | color: Colors.white38), 89 | BoxShadow( 90 | blurRadius: 6.18, 91 | spreadRadius: 0.618, 92 | offset: Offset(4, 4), 93 | color: Colors.blueGrey.shade100) 94 | ]), 95 | ), 96 | if (emailIdErrorMessage != '') 97 | Container( 98 | child: Text( 99 | "\t\t\t\t$emailIdErrorMessage", 100 | style: TextStyle(fontSize: 10, color: Colors.red), 101 | ), 102 | margin: EdgeInsets.all(2), 103 | padding: EdgeInsets.all(2), 104 | ), 105 | Container( 106 | width: double.infinity, 107 | child: TextFormField( 108 | initialValue: password, 109 | validator: _validatePassword, 110 | autofocus: mounted, 111 | autocorrect: false, 112 | obscureText: true, 113 | enableSuggestions: false, 114 | textInputAction: TextInputAction.next, 115 | onFieldSubmitted: (_) => widget.proceedToNextStep(), 116 | decoration: InputDecoration( 117 | border: InputBorder.none, 118 | focusedBorder: InputBorder.none, 119 | enabledBorder: InputBorder.none, 120 | errorBorder: InputBorder.none, 121 | disabledBorder: InputBorder.none, 122 | contentPadding: 123 | EdgeInsets.only(left: 15, bottom: 11, top: 11, right: 15), 124 | hintText: "password", 125 | hintStyle: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 126 | ), 127 | ), 128 | margin: EdgeInsets.all(5), 129 | padding: EdgeInsets.all(5), 130 | decoration: BoxDecoration( 131 | border: Border.all(width: 1.0, color: Color(0xFFF5F7FA)), 132 | borderRadius: BorderRadius.circular(20), 133 | color: Colors.white, 134 | boxShadow: [ 135 | BoxShadow( 136 | blurRadius: 6.18, 137 | spreadRadius: 0.618, 138 | offset: Offset(-4, -4), 139 | color: Colors.white38), 140 | BoxShadow( 141 | blurRadius: 6.18, 142 | spreadRadius: 0.618, 143 | offset: Offset(4, 4), 144 | color: Colors.blueGrey.shade100) 145 | ]), 146 | ), 147 | if (passwordErrorMessage != '') 148 | Container( 149 | child: Text( 150 | "\t\t\t\t$passwordErrorMessage", 151 | style: TextStyle(fontSize: 10, color: Colors.red), 152 | ), 153 | margin: EdgeInsets.all(2), 154 | padding: EdgeInsets.all(2), 155 | ), 156 | ], 157 | )); 158 | } 159 | 160 | void errorMessageSetter(String fieldName, String message) { 161 | setState(() { 162 | switch (fieldName) { 163 | case 'EMAIL-ID': 164 | emailIdErrorMessage = message; 165 | break; 166 | 167 | case 'PASSWORD1': 168 | passwordErrorMessage = message; 169 | break; 170 | } 171 | }); 172 | } 173 | 174 | String? _validateEmailId(String? value) { 175 | if (value == null || value.isEmpty) { 176 | errorMessageSetter('EMAIL-ID', 'you must provide a valid email-id'); 177 | } else if (!validEmailFormat.hasMatch(value)) { 178 | errorMessageSetter('EMAIL-ID', 'format of your email address is invalid'); 179 | } else { 180 | errorMessageSetter('EMAIL-ID', ""); 181 | widget.updateSignUpDetails('emailId', value); 182 | } 183 | 184 | return null; 185 | } 186 | 187 | String? _validatePassword(String? value) { 188 | if (value == null || value.isEmpty) { 189 | errorMessageSetter('PASSWORD1', 'password cannot be empty'); 190 | } else { 191 | errorMessageSetter('PASSWORD1', ""); 192 | 193 | widget.updateSignUpDetails('password', value); 194 | } 195 | return null; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /lib/components/main_app_screen/tabbed_layout_component.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | 4 | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:google_nav_bar/google_nav_bar.dart'; 7 | 8 | import 'package:hadwin/providers/live_transactions_provider.dart'; 9 | import 'package:hadwin/providers/tab_navigation_provider.dart'; 10 | 11 | import 'package:hadwin/providers/user_login_state_provider.dart'; 12 | 13 | import 'package:hadwin/screens/all_contacts.dart'; 14 | import 'package:hadwin/screens/all_transaction_activities_screen.dart'; 15 | 16 | import 'package:hadwin/screens/home_dashboard_screen.dart'; 17 | 18 | import 'package:hadwin/screens/wallet_screen.dart'; 19 | import 'package:hadwin/utilities/hadwin_icons.dart'; 20 | 21 | import 'package:provider/provider.dart'; 22 | 23 | class TabbedLayoutComponent extends StatefulWidget { 24 | final Map userData; 25 | const TabbedLayoutComponent({Key? key, required this.userData}) 26 | : super(key: key); 27 | @override 28 | _TabbedLayoutComponentState createState() => 29 | new _TabbedLayoutComponentState(); 30 | } 31 | 32 | class _TabbedLayoutComponentState extends State { 33 | 34 | Timer? _updateTransactionsTimer; 35 | int _currentTab = 0; 36 | int totalTransactionRequests = 0; 37 | 38 | final LabeledGlobalKey dashboardScreenKey = 39 | LabeledGlobalKey("Dashboard Screen"); 40 | final LabeledGlobalKey 41 | transactionActivitiesScreenKey = 42 | LabeledGlobalKey("Transaction Activities Screen"); 43 | 44 | @override 45 | void initState() { 46 | super.initState(); 47 | 48 | _updateTransactionsTimer = Timer.periodic( 49 | Duration(minutes: [1, 2, 3, 4][Random().nextInt(4)]), (Timer t) { 50 | Provider.of(context, listen: false) 51 | .updateTransactionRequests(); 52 | 53 | }); 54 | 55 | } 56 | 57 | @override 58 | void dispose() { 59 | _updateTransactionsTimer!.cancel(); 60 | super.dispose(); 61 | } 62 | 63 | void setTab(int index) { 64 | setState(() { 65 | _currentTab = index; 66 | }); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | String userAuthKey = 72 | Provider.of(context).userLoginAuthKey; 73 | 74 | List screens = [ 75 | HomeDashboardScreen( 76 | user: widget.userData, 77 | userAuthKey: userAuthKey, 78 | setTab: setTab, 79 | key: dashboardScreenKey, 80 | ), 81 | AllContactsScreen( 82 | userAuthKey: userAuthKey, 83 | setTab: setTab, 84 | ), 85 | AllTransactionActivities( 86 | user: widget.userData, 87 | userAuthKey: userAuthKey, 88 | setTab: setTab, 89 | key: transactionActivitiesScreenKey, 90 | ), 91 | WalletScreen( 92 | setTab: setTab, 93 | user: widget.userData, 94 | ), 95 | ]; 96 | ; 97 | return WillPopScope( 98 | onWillPop: _onBackPress, 99 | child: Scaffold( 100 | 101 | // backgroundColor: Colors.white, 102 | backgroundColor: Color(0xfffefefe), 103 | 104 | extendBodyBehindAppBar: true, 105 | 106 | bottomNavigationBar: googleNavBar(), 107 | 108 | body: screens.isEmpty ? Text("Loading...") : screens[_currentTab], 109 | ), 110 | ); 111 | } 112 | 113 | Widget googleNavBar() { 114 | int unreadTransactions = 115 | context.watch().unreadTransactions; 116 | int transactionRequests = 117 | context.watch().transactionRequests; 118 | if (transactionRequests != totalTransactionRequests) { 119 | setState(() { 120 | totalTransactionRequests = transactionRequests; 121 | }); 122 | if (_currentTab == 0) { 123 | dashboardScreenKey.currentState!.getTransactionsFromApi(); 124 | } else if (_currentTab == 2) { 125 | transactionActivitiesScreenKey.currentState!.getTransactionsFromApi(); 126 | } 127 | } 128 | 129 | return Container( 130 | 131 | child: Padding( 132 | padding: const EdgeInsets.symmetric(horizontal: 6.18, vertical: 1), 133 | child: GNav( 134 | haptic: false, 135 | gap: 6, 136 | activeColor: Color(0xFF0070BA), 137 | iconSize: 24, 138 | padding: EdgeInsets.symmetric(horizontal: 16, vertical: 11), 139 | duration: Duration(milliseconds: 300), 140 | color: Color(0xFF243656), 141 | tabs: [ 142 | GButton( 143 | icon: FluentIcons.home_32_regular, 144 | iconSize: 36, 145 | text: 'Home', 146 | ), 147 | GButton( 148 | icon: FluentIcons.people_32_regular, 149 | iconSize: 36, 150 | text: 'Contacts', 151 | ), 152 | GButton( 153 | icon: FluentIcons.alert_32_regular, 154 | iconActiveColor: Color(0xFF0070BA), 155 | text: 'Activities', 156 | leading: Stack( 157 | children: [ 158 | Icon( 159 | FluentIcons.alert_32_regular, 160 | color: _currentTab == 2 161 | ? Color(0xFF0070BA) 162 | : Color(0xFF243656), 163 | size: 36, 164 | ), 165 | if (unreadTransactions > 0) 166 | Positioned( 167 | top: 0, 168 | right: 0, 169 | child: ClipOval( 170 | child: Container( 171 | 172 | color: Color(0xffffb3c1), 173 | width: 17, 174 | height: 17, 175 | 176 | child: Center( 177 | child: Text(unreadTransactions.toString(), 178 | textAlign: TextAlign.center, 179 | style: TextStyle( 180 | fontSize: 9.6, 181 | fontWeight: FontWeight.bold, 182 | color: Color(0xffc9184a), 183 | 184 | backgroundColor: Color(0xffffb3c1))), 185 | )), 186 | ), 187 | ) 188 | ], 189 | ), 190 | ), 191 | GButton( 192 | icon: HadWinIcons.line_awesome_wallet_solid, 193 | 194 | text: 'Wallet', 195 | 196 | iconSize: 34, 197 | ), 198 | ], 199 | selectedIndex: _currentTab, 200 | onTabChange: _onTabChange, 201 | ), 202 | ), 203 | 204 | ); 205 | } 206 | 207 | void _onTabChange(index) { 208 | if (_currentTab == 1 || _currentTab == 2) { 209 | FocusManager.instance.primaryFocus?.unfocus(); 210 | } 211 | Provider.of(context, listen: false) 212 | .updateTabs(_currentTab); 213 | setState(() { 214 | _currentTab = index; 215 | }); 216 | } 217 | 218 | Future _onBackPress() { 219 | if (_currentTab == 0) { 220 | return Future.value(true); 221 | } else { 222 | int lastTab = 223 | Provider.of(context, listen: false).lastTab; 224 | Provider.of(context, listen: false) 225 | .removeLastTab(); 226 | setTab(lastTab); 227 | } 228 | return Future.value(false); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /lib/components/settings_screen/app_creator_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:fade_shimmer/fade_shimmer.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:hadwin/hadwin_components.dart'; 4 | 5 | 6 | class AppCreatorInfoScreen extends StatelessWidget { 7 | const AppCreatorInfoScreen({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | backgroundColor: Colors.white, 13 | appBar: AppBar( 14 | title: Text( 15 | 'brownboycodes', 16 | style: TextStyle(fontSize: 24), 17 | ), 18 | centerTitle: true, 19 | backgroundColor: Colors.transparent, 20 | foregroundColor: Color(0xff243656), 21 | elevation: 0, 22 | ), 23 | body: Column( 24 | children: [ 25 | Expanded( 26 | child: Container( 27 | width: MediaQuery.of(context).size.width - 10, 28 | height: MediaQuery.of(context).size.height - 180, 29 | 30 | child: FutureBuilder>( 31 | future: getData(urlPath: "/about/brownboycodes"), 32 | builder: (context, snapshot) { 33 | if (snapshot.hasData) { 34 | List socials = 35 | snapshot.data!['socialNetworkingProfiles'] 36 | .map((social) => RawMaterialButton( 37 | onPressed: () { 38 | launchExternalURL(social['url']); 39 | }, 40 | clipBehavior: Clip.antiAlias, 41 | elevation: 0, 42 | fillColor: Colors.transparent, 43 | focusColor: null, 44 | hoverColor: null, 45 | splashColor: null, 46 | highlightColor: null, 47 | focusElevation: 0, 48 | hoverElevation: 0, 49 | disabledElevation: 0, 50 | highlightElevation: 0, 51 | constraints: BoxConstraints( 52 | minWidth: 64.0, 53 | minHeight: 64.0, 54 | maxWidth: 64.0, 55 | maxHeight: 64.0), 56 | child: Image.network( 57 | '${ApiConstants.baseUrl}/dist/images/brownboycodes/social_icons/colorable/${social['avatar']}', 58 | ), 59 | 60 | )) 61 | .toList(); 62 | 63 | return Wrap( 64 | direction: Axis.vertical, 65 | runAlignment: WrapAlignment.center, 66 | spacing: 8.4, 67 | crossAxisAlignment: WrapCrossAlignment.center, 68 | children: [ 69 | CircleAvatar( 70 | radius: 64, 71 | backgroundColor: Color(0xffF5F7FA), 72 | child: ClipOval( 73 | child: AspectRatio( 74 | aspectRatio: 1.0 / 1.0, 75 | child: Image.network( 76 | '${ApiConstants.baseUrl}/dist/images/hadwin_images/attributions/${snapshot.data!['avatar']}', 77 | height: 120, 78 | width: 120, 79 | fit: BoxFit.contain, 80 | ), 81 | ), 82 | )), 83 | Text( 84 | snapshot.data!['name'], 85 | textAlign: TextAlign.center, 86 | style: TextStyle(fontSize: 28), 87 | ), 88 | SizedBox( 89 | width: MediaQuery.of(context).size.width - 96, 90 | child: Text( 91 | snapshot.data!['bio'], 92 | textAlign: TextAlign.center, 93 | style: TextStyle(fontSize: 20), 94 | ), 95 | ), 96 | Container( 97 | width: MediaQuery.of(context).size.width - 10, 98 | child: Wrap( 99 | crossAxisAlignment: WrapCrossAlignment.center, 100 | alignment: WrapAlignment.spaceEvenly, 101 | children: [ 102 | Wrap( 103 | direction: Axis.vertical, 104 | spacing: 10, 105 | children: socials, 106 | ), 107 | Text( 108 | '@brownboycodes', 109 | style: TextStyle(fontSize: 24), 110 | ) 111 | ]), 112 | ), 113 | ]); 114 | } else { 115 | return _loadingTile(context); 116 | } 117 | }), 118 | )), 119 | ], 120 | ), 121 | ); 122 | } 123 | 124 | Widget _loadingTile(BuildContext context) { 125 | List socials = List.generate( 126 | 4, 127 | (index) => Container( 128 | child: FadeShimmer( 129 | radius: 16.18, 130 | height: 64, 131 | width: 64, 132 | fadeTheme: FadeTheme.light, 133 | ), 134 | padding: EdgeInsets.all(2), 135 | )); 136 | 137 | return Wrap( 138 | direction: Axis.vertical, 139 | runAlignment: WrapAlignment.center, 140 | spacing: 10, 141 | crossAxisAlignment: WrapCrossAlignment.center, 142 | children: [ 143 | Container( 144 | child: FadeShimmer.round( 145 | size: 128, 146 | fadeTheme: FadeTheme.light, 147 | ), 148 | padding: EdgeInsets.all(5), 149 | ), 150 | Container( 151 | child: FadeShimmer( 152 | radius: 16.18, 153 | height: 28, 154 | width: 120, 155 | fadeTheme: FadeTheme.light, 156 | ), 157 | padding: EdgeInsets.all(2), 158 | ), 159 | SizedBox( 160 | width: MediaQuery.of(context).size.width - 96, 161 | child: Container( 162 | child: FadeShimmer( 163 | radius: 16.18, 164 | height: 20, 165 | width: 130, 166 | fadeTheme: FadeTheme.light, 167 | ), 168 | padding: EdgeInsets.all(2), 169 | ), 170 | ), 171 | Container( 172 | width: MediaQuery.of(context).size.width - 10, 173 | child: Wrap( 174 | crossAxisAlignment: WrapCrossAlignment.center, 175 | alignment: WrapAlignment.spaceEvenly, 176 | children: [ 177 | Wrap( 178 | direction: Axis.vertical, 179 | spacing: 10, 180 | children: socials, 181 | ), 182 | Container( 183 | child: FadeShimmer( 184 | radius: 16.18, 185 | height: 24, 186 | width: 96, 187 | fadeTheme: FadeTheme.light, 188 | ), 189 | padding: EdgeInsets.all(2), 190 | ) 191 | ]), 192 | ), 193 | ]); 194 | } 195 | 196 | 197 | 198 | } 199 | -------------------------------------------------------------------------------- /lib/components/sign_up_screen/step_get_name_address.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StepGetNameAddress extends StatefulWidget { 4 | final LabeledGlobalKey nameAddressFormKey; 5 | final Function updateSignUpDetails; 6 | 7 | final Function registrationDetails; 8 | final Function proceedToNextStep; 9 | const StepGetNameAddress( 10 | {Key? key, 11 | required this.updateSignUpDetails, 12 | required this.nameAddressFormKey, 13 | required this.registrationDetails, 14 | required this.proceedToNextStep}) 15 | : super(key: key); 16 | 17 | @override 18 | _StepGetNameAddressState createState() => _StepGetNameAddressState(); 19 | } 20 | 21 | class _StepGetNameAddressState extends State { 22 | String fullName = ""; 23 | String fullNameErrorMessage = ""; 24 | String residentialAddress = ""; 25 | String residentialAddressErrorMessage = ""; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | Map signUpDetails = widget.registrationDetails(); 31 | if (mounted) { 32 | setState(() { 33 | fullName = signUpDetails['fullname']!; 34 | residentialAddress = signUpDetails['address']!; 35 | }); 36 | } 37 | } 38 | 39 | @override 40 | void dispose() { 41 | // widget.nameAddressFormKey.currentState?.validate(); 42 | super.dispose(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Form( 48 | key: widget.nameAddressFormKey, 49 | child: Column( 50 | children: [ 51 | Container( 52 | width: double.infinity, 53 | child: TextFormField( 54 | initialValue: fullName, 55 | validator: _validateName, 56 | autofocus: mounted, 57 | autocorrect: false, 58 | decoration: InputDecoration( 59 | fillColor: Colors.white, 60 | border: InputBorder.none, 61 | focusedBorder: InputBorder.none, 62 | enabledBorder: InputBorder.none, 63 | errorBorder: InputBorder.none, 64 | disabledBorder: InputBorder.none, 65 | contentPadding: 66 | EdgeInsets.only(left: 15, bottom: 11, top: 11, right: 15), 67 | hintText: "full name", 68 | hintStyle: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 69 | ), 70 | style: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 71 | keyboardType: TextInputType.name, 72 | textInputAction: TextInputAction.next, 73 | ), 74 | margin: EdgeInsets.all(5), 75 | padding: EdgeInsets.all(5), 76 | decoration: BoxDecoration( 77 | color: Colors.white, 78 | border: Border.all(width: 1.0, color: Color(0xFFF5F7FA)), 79 | borderRadius: BorderRadius.circular(20), 80 | boxShadow: [ 81 | BoxShadow( 82 | blurRadius: 6.18, 83 | spreadRadius: 0.618, 84 | offset: Offset(-4, -4), 85 | color: Colors.white38), 86 | BoxShadow( 87 | blurRadius: 6.18, 88 | spreadRadius: 0.618, 89 | offset: Offset(4, 4), 90 | color: Colors.blueGrey.shade100) 91 | ]), 92 | ), 93 | if (fullNameErrorMessage != '') 94 | Container( 95 | child: Text( 96 | "\t\t\t\t$fullNameErrorMessage", 97 | style: TextStyle(fontSize: 10, color: Colors.red), 98 | ), 99 | margin: EdgeInsets.all(2), 100 | padding: EdgeInsets.all(2), 101 | width: double.infinity, 102 | ), 103 | 104 | Container( 105 | width: double.infinity, 106 | child: TextFormField( 107 | initialValue: residentialAddress, 108 | validator: _validateAddress, 109 | autofocus: mounted, 110 | autocorrect: false, 111 | decoration: InputDecoration( 112 | fillColor: Colors.white, 113 | border: InputBorder.none, 114 | focusedBorder: InputBorder.none, 115 | enabledBorder: InputBorder.none, 116 | errorBorder: InputBorder.none, 117 | disabledBorder: InputBorder.none, 118 | contentPadding: 119 | EdgeInsets.only(left: 15, bottom: 11, top: 11, right: 15), 120 | hintText: "residential address", 121 | hintStyle: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 122 | ), 123 | style: TextStyle(fontSize: 16, color: Color(0xFF929BAB)), 124 | keyboardType: TextInputType.name, 125 | textInputAction: TextInputAction.next, 126 | onFieldSubmitted: (_) => widget.proceedToNextStep(), 127 | ), 128 | margin: EdgeInsets.all(5), 129 | padding: EdgeInsets.all(5), 130 | decoration: BoxDecoration( 131 | color: Colors.white, 132 | border: Border.all(width: 1.0, color: Color(0xFFF5F7FA)), 133 | borderRadius: BorderRadius.circular(20), 134 | boxShadow: [ 135 | BoxShadow( 136 | blurRadius: 6.18, 137 | spreadRadius: 0.618, 138 | offset: Offset(-4, -4), 139 | color: Colors.white38), 140 | BoxShadow( 141 | blurRadius: 6.18, 142 | spreadRadius: 0.618, 143 | offset: Offset(4, 4), 144 | color: Colors.blueGrey.shade100) 145 | ]), 146 | ), 147 | if (residentialAddressErrorMessage != '') 148 | Container( 149 | child: Text( 150 | "\t\t\t\t$residentialAddressErrorMessage", 151 | style: TextStyle(fontSize: 10, color: Colors.red), 152 | ), 153 | margin: EdgeInsets.all(2), 154 | padding: EdgeInsets.all(2), 155 | width: double.infinity, 156 | ), 157 | // input field for RESIDENTIAL-ADDRESS ends here 158 | ], 159 | )); 160 | } 161 | 162 | void errorMessageSetter(String fieldName, String message) { 163 | setState(() { 164 | switch (fieldName) { 165 | case 'FULL-NAME': 166 | fullNameErrorMessage = message; 167 | break; 168 | 169 | case 'RESIDENTIAL-ADDRESS': 170 | residentialAddressErrorMessage = message; 171 | break; 172 | } 173 | }); 174 | } 175 | 176 | String? _validateName(String? value) { 177 | if (value == null || value.isEmpty) { 178 | errorMessageSetter('FULL-NAME', 'you must provide your full name'); 179 | } else if (value.length > 100) { 180 | errorMessageSetter( 181 | 'FULL-NAME', 'name cannot contain more than 100 characters'); 182 | } else { 183 | errorMessageSetter('FULL-NAME', ""); 184 | 185 | widget.updateSignUpDetails('fullname', value); 186 | } 187 | 188 | return null; 189 | } 190 | 191 | String? _validateAddress(String? value) { 192 | if (value == null || value.isEmpty) { 193 | errorMessageSetter( 194 | 'RESIDENTIAL-ADDRESS', 'you must provide your residential address'); 195 | } else if (value.length > 300) { 196 | errorMessageSetter('RESIDENTIAL-ADDRESS', 197 | 'address cannot contain more than 300 characters'); 198 | } else { 199 | errorMessageSetter('RESIDENTIAL-ADDRESS', ""); 200 | 201 | widget.updateSignUpDetails('address', value); 202 | } 203 | 204 | return null; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/utilities/hadwin_markdown_viewer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:fade_shimmer/fade_shimmer.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:google_fonts/google_fonts.dart'; 6 | 7 | 8 | import 'package:http/http.dart' as http; 9 | 10 | import 'package:markdown_widget/markdown_widget.dart'; 11 | 12 | import 'package:hadwin/utilities/display_error_alert.dart'; 13 | import 'package:hadwin/utilities/url_external_launcher.dart'; 14 | 15 | class HadWinMarkdownViewer extends StatefulWidget { 16 | final String screenName; 17 | final String urlRequested; 18 | const HadWinMarkdownViewer( 19 | {Key? key, required this.screenName, required this.urlRequested}); 20 | @override 21 | HadWinMarkdownViewerState createState() => HadWinMarkdownViewerState(); 22 | } 23 | 24 | class HadWinMarkdownViewerState extends State { 25 | @override 26 | void initState() { 27 | super.initState(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | backgroundColor: Colors.white, 34 | appBar: AppBar( 35 | title: Text( 36 | widget.screenName, 37 | style: TextStyle(fontSize: 24), 38 | ), 39 | centerTitle: true, 40 | backgroundColor: Colors.transparent, 41 | foregroundColor: Color(0xff243656), 42 | elevation: 0, 43 | ), 44 | body: Column( 45 | 46 | children: [ 47 | Expanded( 48 | child: Container( 49 | height: 100, 50 | width: MediaQuery.of(context).size.width - 20, 51 | padding: EdgeInsets.only( 52 | left: 16.18, right: 16.18, bottom: 16.18, top: 6.18), 53 | child: FutureBuilder( 54 | future: getTextData(widget.urlRequested), 55 | builder: (context, snapshot) { 56 | if (snapshot.hasData) { 57 | return MarkdownWidget( 58 | data: '''${snapshot.data}''', 59 | 60 | 61 | styleConfig: StyleConfig( 62 | blockQuoteConfig: BlockQuoteConfig( 63 | backgroundColor: Color(0xffcaf0f8), 64 | blockColor: Color(0xff0077b6), 65 | blockStyle: GoogleFonts.sora( 66 | color: Color(0xff495057)), 67 | ), 68 | tableConfig: TableConfig( 69 | headerTextConfig: 70 | TextConfig(textAlign: TextAlign.left), 71 | headChildWrapper: (columnHeader) => Padding( 72 | padding: EdgeInsets.all(7.2), 73 | child: columnHeader, 74 | ), 75 | headerStyle: GoogleFonts.ubuntu( 76 | fontWeight: FontWeight.w700), 77 | bodyChildWrapper: (cellBody) => Padding( 78 | padding: EdgeInsets.all(7.2), 79 | child: cellBody, 80 | ), 81 | bodyTextConfig: TextConfig( 82 | textAlign: TextAlign.center)), 83 | titleConfig: TitleConfig( 84 | commonStyle: GoogleFonts.ubuntu(), 85 | showDivider: false), 86 | ulConfig: UlConfig( 87 | ulWrapper: (ul) => Padding( 88 | padding: EdgeInsets.all(5), 89 | child: ul, 90 | ), 91 | textStyle: GoogleFonts.workSans(), 92 | dotWidget: (deep, index) => Text( 93 | "${index + 1}.\t", 94 | style: GoogleFonts.ubuntu(), 95 | ), 96 | ), 97 | olConfig: OlConfig( 98 | olWrapper: (ul) => Padding( 99 | padding: EdgeInsets.all(5), 100 | child: ul, 101 | ), 102 | textStyle: GoogleFonts.workSans(), 103 | indexWidget: (deep, index) => Text( 104 | "${index + 1}.\t", 105 | style: GoogleFonts.ubuntu(), 106 | ), 107 | ), 108 | pConfig: PConfig( 109 | textStyle: GoogleFonts.workSans(), 110 | onLinkTap: (url) { 111 | launchExternalURL(url!).then((value) => 112 | debugPrint( 113 | "requested to access $url")); 114 | }, 115 | emStyle: TextStyle( 116 | fontWeight: FontWeight.w600, 117 | backgroundColor: Color(0xffccff33), 118 | fontStyle: FontStyle.italic)), 119 | codeConfig: CodeConfig( 120 | 121 | // padding: EdgeInsets.all(1.618), 122 | codeStyle: GoogleFonts.spaceMono( 123 | backgroundColor: Color(0xff4a4e69), 124 | fontWeight: FontWeight.w600, 125 | color: Colors.white))), 126 | ); 127 | } 128 | return docsLoading(); 129 | }), 130 | ), 131 | ) 132 | ])); 133 | } 134 | 135 | Future getTextData(String url) async { 136 | var response; 137 | try { 138 | response = await http.get(Uri.parse(url)); 139 | } on SocketException { 140 | showErrorAlert( 141 | context, {'internetConnectionError': 'no internet connection'}); 142 | } catch (e) { 143 | showErrorAlert(context, {'error': "something went wrong"}); 144 | } 145 | return response.body; 146 | } 147 | 148 | Widget docsLoading() { 149 | return ListView.separated( 150 | itemBuilder: (context, index) { 151 | return Column( 152 | 153 | crossAxisAlignment: CrossAxisAlignment.start, 154 | children: [ 155 | Container( 156 | child: FadeShimmer( 157 | height: 27, 158 | width: 100, 159 | 160 | radius: 7.2, 161 | highlightColor: Color(0xffced4da), 162 | baseColor: Color(0xffe9ecef), 163 | ), 164 | padding: EdgeInsets.symmetric(horizontal: 5, vertical: 1.618), 165 | ), 166 | ...List.generate( 167 | 4, 168 | (i) => Container( 169 | child: FadeShimmer( 170 | height: 21, 171 | width: MediaQuery.of(context).size.width - 24, 172 | 173 | radius: 7.2, 174 | highlightColor: Color(0xffced4da), 175 | baseColor: Color(0xffe9ecef), 176 | ), 177 | padding: EdgeInsets.symmetric( 178 | horizontal: 5, vertical: 1.618), 179 | )).toList(), 180 | ], 181 | ); 182 | }, 183 | separatorBuilder: (_, b) => SizedBox( 184 | height: 10, 185 | ), 186 | itemCount: 5); 187 | } 188 | } 189 | --------------------------------------------------------------------------------