diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index a4ee8f0..741431d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,5 +1,8 @@ plugins { id("com.android.application") + // START: FlutterFire Configuration + id("com.google.gms.google-services") + // END: FlutterFire Configuration id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") @@ -24,7 +27,8 @@ android { applicationId = "zw.co.qantra.qpay" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + // minSdk = flutter.minSdkVersion + minSdk = 23 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName @@ -45,4 +49,4 @@ dependencies { flutter { source = "../.." -} +} \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..978b7b8 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "77433712483", + "project_id": "peak-c376b", + "storage_bucket": "peak-c376b.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:77433712483:android:c6508ec8c6a23889ab6d56", + "android_client_info": { + "package_name": "zw.co.qantra.qpay" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyARyIWVumzj4UWNwplKZRxxVYmw_H6hBKI" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index d06ffdf..b7e87e7 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 48ea1fe..09c4b19 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 98660b0..65d2b43 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 5509de8..842c79c 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index e605276..97dc50b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index a439442..9e2d35c 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,6 +19,9 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false + // START: FlutterFire Configuration + id("com.google.gms.google-services") version("4.3.15") apply false + // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "1.8.22" apply false } diff --git a/assets/.env b/assets/.env index f248619..38a31d4 100644 --- a/assets/.env +++ b/assets/.env @@ -1,7 +1,8 @@ ; BASE_URL=https://peakapi.qantra.co.zw/api - BASE_URL=http://192.168.100.8:6950/api + BASE_URL=http://192.168.100.26:6950/api ; BASE_URL=http://192.168.1.164:6950/api ; BASE_URL=http://192.168.120.160:6950/api ; BASE_URL=http://10.10.2.92:6950/api ; BASE_URL=http://172.20.5.105:6950/api -; BASE_URL=http://10.10.3.92:6950/api \ No newline at end of file +; BASE_URL=http://10.10.3.92:6950/api +CLIENTID=77433712483-ng7pntvcpf6tnjccriuqm8dbna8vvp3b.apps.googleusercontent.com \ No newline at end of file diff --git a/assets/login.png b/assets/login.png new file mode 100644 index 0000000..5ef9b88 Binary files /dev/null and b/assets/login.png differ diff --git a/assets/phone.jpg b/assets/phone.jpg new file mode 100644 index 0000000..a594766 Binary files /dev/null and b/assets/phone.jpg differ diff --git a/assets/phone.png b/assets/phone.png new file mode 100644 index 0000000..edda097 Binary files /dev/null and b/assets/phone.png differ diff --git a/assets/social.jpg b/assets/social.jpg new file mode 100644 index 0000000..0790c60 Binary files /dev/null and b/assets/social.jpg differ diff --git a/assets/social.png b/assets/social.png new file mode 100644 index 0000000..e5f56f7 Binary files /dev/null and b/assets/social.png differ diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..4a42b88 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"peak-c376b","configurations":{"android":"1:77433712483:android:c6508ec8c6a23889ab6d56","ios":"1:77433712483:ios:f21e81701d8a2740ab6d56","macos":"1:77433712483:ios:f21e81701d8a2740ab6d56","web":"1:77433712483:web:89d62f69ed8ffb09ab6d56","windows":"1:77433712483:web:03b1df2872cd9723ab6d56"}}},"android":{"default":{"projectId":"peak-c376b","appId":"1:77433712483:android:c6508ec8c6a23889ab6d56","fileOutput":"android/app/google-services.json"}}}}} \ No newline at end of file diff --git a/firepit-log.txt b/firepit-log.txt new file mode 100644 index 0000000..3b1f233 --- /dev/null +++ b/firepit-log.txt @@ -0,0 +1,13 @@ +Welcome to firepit v1.1.0! +Doing JSON parses for version checks at /snapshot/firepit/vendor/node_modules/firebase-tools/package.json +is-ci,mime,rc,yaml,abbrev,abort-controller,accepts,agent-base,ajv,ajv-formats,ansi-align,ansi-escapes,ansi-regex,ansi-styles,any-promise,anymatch,archiver,archiver-utils,argparse,array-flatten,arrify,as-array,ast-types,async,async-lock,asynckit,b4a,balanced-match,bare-events,base64-js,basic-auth,basic-auth-connect,basic-ftp,bignumber.js,binary-extensions,bl,body-parser,boxen,brace-expansion,braces,buffer,buffer-crc32,buffer-equal-constant-time,bytes,cacache,call-bind-apply-helpers,call-bound,call-me-maybe,camelcase,chalk,char-regex,chardet,chokidar,chownr,ci-info,cjson,cli-boxes,cli-cursor,cli-highlight,cli-spinners,cli-table3,cli-width,cliui,clone,color,color-convert,color-name,color-string,colorette,colorspace,combined-stream,commander,compress-commons,compressible,compression,concat-map,config-chain,configstore,connect,content-disposition,content-type,cookie,cookie-signature,core-util-is,cors,crc-32,crc32-stream,cross-env,cross-spawn,crypto-random-string,csv-parse,data-uri-to-buffer,debug,deep-equal-in-any-order,deep-extend,deep-freeze,deep-is,defaults,degenerator,delayed-stream,depd,destroy,discontinuous-range,dot-prop,dunder-proto,duplexify,eastasianwidth,ecdsa-sig-formatter,ee-first,emoji-regex,emojilib,enabled,encodeurl,encoding,end-of-stream,env-paths,environment,err-code,es-define-property,es-errors,es-object-atoms,es-set-tostringtag,escalade,escape-goat,escape-html,escodegen,esprima,estraverse,esutils,etag,event-target-shim,events,events-listener,eventsource,eventsource-parser,exegesis,exegesis-express,exponential-backoff,express,express-rate-limit,extend,fast-deep-equal,fast-fifo,fast-json-stable-stringify,fast-uri,fecha,fetch-blob,filesize,fill-range,finalhandler,firebase-tools,fn.name,foreground-child,form-data,formdata-polyfill,forwarded,fresh,fs-extra,fs-minipass,function-bind,fuzzy,gaxios,gcp-metadata,get-caller-file,get-intrinsic,get-proto,get-uri,glob,glob-parent,glob-slash,glob-slasher,global-dirs,google-auth-library,google-gax,google-logging-utils,googleapis-common,gopd,graceful-fs,gtoken,has-flag,has-symbols,has-tostringtag,has-yarn,hasown,heap-js,highlight.js,http-cache-semantics,http-errors,http-proxy-agent,https-proxy-agent,iconv-lite,ieee754,ignore,import-lazy,imurmurhash,inherits,ini,install-artifact-from-github,ip-address,ip-regex,ipaddr.js,is-arrayish,is-binary-path,is-buffer,is-extglob,is-fullwidth-code-point,is-glob,is-installed-globally,is-interactive,is-npm,is-number,is-obj,is-path-inside,is-promise,is-stream,is-stream-ended,is-typedarray,is-unicode-supported,is-url,is-wsl,is-yarn-global,is2,isarray,isexe,isomorphic-fetch,jackspeak,jju,join-path,js-yaml,json-bigint,json-parse-helpfulerror,json-ptr,json-schema-traverse,jsonfile,jsonwebtoken,jwa,jws,kind-of,kuler,lazystream,leven,libsodium,libsodium-wrappers,lodash,lodash._objecttypes,lodash.camelcase,lodash.includes,lodash.isboolean,lodash.isinteger,lodash.isnumber,lodash.isobject,lodash.isplainobject,lodash.isstring,lodash.mapvalues,lodash.once,lodash.snakecase,log-symbols,logform,long,lru-cache,lsofi,make-dir,make-fetch-happen,marked,marked-terminal,math-intrinsics,media-typer,merge-descriptors,methods,mime-db,mime-types,mimic-fn,minimatch,minimist,minipass,minipass-collect,minipass-fetch,minipass-flush,minipass-pipeline,minipass-sized,minizlib,mkdirp,moo,morgan,ms,mute-stream,mz,nan,nearley,negotiator,netmask,node-domexception,node-emoji,node-fetch,node-gyp,nopt,normalize-path,object-assign,object-hash,object-inspect,on-finished,on-headers,once,one-time,onetime,open,openapi3-ts,ora,p-defer,p-limit,p-map,p-throttle,pac-proxy-agent,pac-resolver,package-json-from-dist,parse5,parse5-htmlparser2-tree-adapter,parseurl,path-key,path-scurry,path-to-regexp,pg,pg-cloudflare,pg-connection-string,pg-gateway,pg-int8,pg-pool,pg-protocol,pg-types,pglite-2,pgpass,picomatch,pkce-challenge,portfinder,postgres-array,postgres-bytea,postgres-date,postgres-interval,proc-log,process,process-nextick-args,progress,promise-breaker,promise-retry,proto-list,proto3-json-serializer,protobufjs,proxy-addr,proxy-agent,proxy-from-env,punycode,pupa,qs,railroad-diagrams,randexp,range-parser,raw-body,re2,readable-stream,readdir-glob,readdirp,registry-auth-token,registry-url,require-directory,require-from-string,restore-cursor,ret,retry,retry-request,router,safe-buffer,safe-stable-stringify,safer-buffer,semver,semver-diff,send,serve-static,setprototypeof,shebang-command,shebang-regex,side-channel,side-channel-list,side-channel-map,side-channel-weakmap,signal-exit,simple-swizzle,skin-tone,smart-buffer,socks,socks-proxy-agent,sort-any,source-map,split2,sprintf-js,sql-formatter,ssri,stack-trace,statuses,stream-chain,stream-events,stream-json,stream-shift,streamx,string-width,string-width-cjs,string_decoder,strip-ansi,strip-ansi-cjs,strip-json-comments,stubs,superstatic,supports-color,supports-hyperlinks,tar,tar-stream,tcp-port-used,teeny-request,text-decoder,text-hex,thenify,thenify-all,through2,tinyglobby,tmp,to-regex-range,toidentifier,toxic,tr46,triple-beam,tslib,tsscmp,type-fest,type-is,typedarray-to-buffer,undici-types,unicode-emoji-modifier-base,unique-filename,unique-slug,unique-string,universal-analytics,universalify,unpipe,update-notifier-cjs,uri-js,url-join,url-template,util-deprecate,utils-merge,uuid,valid-url,vary,wcwidth,web-streams-polyfill,webidl-conversions,whatwg-fetch,whatwg-url,which,widest-line,winston,winston-transport,wrap-ansi,wrap-ansi-cjs,wrappy,write-file-atomic,ws,xdg-basedir,xtend,y18n,yallist,yargs,yargs-parser,yocto-queue,yoctocolors-cjs,zip-stream,zod,zod-to-json-schema,@apidevtools,@colors,@dabh,@electric-sql,@google-cloud,@googleapis,@grpc,@inquirer,@isaacs,@js-sdsl,@jsdevtools,@modelcontextprotocol,@npmcli,@opentelemetry,@pkgjs,@pnpm,@protobufjs,@sindresorhus,@tootallnate,@types +Installed ft@14.16.0 and packaged ft@14.16.0 +Checking for npm/bin/npm-cli install at /home/vkhoza/.cache/firebase/tools/lib/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /home/vkhoza/.cache/firebase/tools/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /snapshot/firepit/node_modules/npm/bin/npm-cli +Found npm/bin/npm-cli install. +Checking for npm/bin/npm-cli install at /home/vkhoza/.cache/firebase/tools/lib/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /home/vkhoza/.cache/firebase/tools/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /snapshot/firepit/node_modules/npm/bin/npm-cli +Found npm/bin/npm-cli install. +ShellJSInternalError: ENOENT: no such file or directory, chmod '/home/vkhoza/.cache/firebase/runtime/shell' \ No newline at end of file diff --git a/lib/abstracts/AbstractController.dart b/lib/abstracts/AbstractController.dart new file mode 100644 index 0000000..904f299 --- /dev/null +++ b/lib/abstracts/AbstractController.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class AbstractController extends ChangeNotifier { + late BuildContext context; + void showErrorSnackBar(String? message) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message.toString()), + behavior: SnackBarBehavior.floating, + backgroundColor: Colors.deepOrange, + ), + ); + }); + } + +} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..1ee0f8f --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,89 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + return macos; + case TargetPlatform.windows: + return windows; + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyDqcGjTQTrZjhFoBvbhwsOuDaxvA4p8Ch4', + appId: '1:77433712483:web:89d62f69ed8ffb09ab6d56', + messagingSenderId: '77433712483', + projectId: 'peak-c376b', + authDomain: 'peak-c376b.firebaseapp.com', + storageBucket: 'peak-c376b.firebasestorage.app', + measurementId: 'G-B94Y2DVT96', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyD3XsySzfF9Tp84jbAc_T0AwI-RSDRawmI', + appId: '1:77433712483:ios:f21e81701d8a2740ab6d56', + messagingSenderId: '77433712483', + projectId: 'peak-c376b', + storageBucket: 'peak-c376b.firebasestorage.app', + iosBundleId: 'zw.co.qantra.qpay', + ); + + static const FirebaseOptions windows = FirebaseOptions( + apiKey: 'AIzaSyDqcGjTQTrZjhFoBvbhwsOuDaxvA4p8Ch4', + appId: '1:77433712483:web:03b1df2872cd9723ab6d56', + messagingSenderId: '77433712483', + projectId: 'peak-c376b', + authDomain: 'peak-c376b.firebaseapp.com', + storageBucket: 'peak-c376b.firebasestorage.app', + measurementId: 'G-DMD0HQ4W18', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyD3XsySzfF9Tp84jbAc_T0AwI-RSDRawmI', + appId: '1:77433712483:ios:f21e81701d8a2740ab6d56', + messagingSenderId: '77433712483', + projectId: 'peak-c376b', + storageBucket: 'peak-c376b.firebasestorage.app', + iosBundleId: 'zw.co.qantra.qpay', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyARyIWVumzj4UWNwplKZRxxVYmw_H6hBKI', + appId: '1:77433712483:android:c6508ec8c6a23889ab6d56', + messagingSenderId: '77433712483', + projectId: 'peak-c376b', + storageBucket: 'peak-c376b.firebasestorage.app', + ); + +} \ No newline at end of file diff --git a/lib/http/http.dart b/lib/http/http.dart index 6cd0388..ec8094d 100644 --- a/lib/http/http.dart +++ b/lib/http/http.dart @@ -17,7 +17,7 @@ class Http { LogInterceptor( request: true, responseBody: false, - requestBody: false, + requestBody: true, error: true, ), ); diff --git a/lib/main.dart b/lib/main.dart index 481914a..d4c92be 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,9 +3,12 @@ import 'package:provider/provider.dart'; import 'package:go_router/go_router.dart'; import 'package:qpay/screens/confirm/confirm_screen.dart'; import 'package:qpay/screens/gateway/gateway_screen.dart'; +import 'package:qpay/screens/onboarding/complete_screen.dart'; import 'package:qpay/screens/onboarding/landing/landing_screen.dart'; import 'package:qpay/screens/onboarding/login/login_screen.dart'; +import 'package:qpay/screens/onboarding/onboarding_controller.dart'; import 'package:qpay/screens/onboarding/password/password_screen.dart'; +import 'package:qpay/screens/onboarding/phone/phone_screen.dart'; import 'package:qpay/screens/onboarding/register/register_screen.dart'; import 'package:qpay/screens/onboarding/verify/verify_screen.dart'; import 'package:qpay/screens/pay/pay_screen.dart'; @@ -18,15 +21,25 @@ import 'screens/profile_screen.dart'; import 'screens/splash_screen.dart'; import 'theme/app_theme.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'firebase_options.dart'; const String testEnv = "assets/.env"; const String liveEnv = "assets/.env"; void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + await dotenv.load(fileName: testEnv); runApp( - ChangeNotifierProvider( - create: (context) => TransactionController(), + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => TransactionController()), + ChangeNotifierProvider(create: (_) => OnboardingController()), + ], child: const MyApp(), ), ); @@ -124,6 +137,10 @@ class _MyAppState extends State { path: '/onboarding/register', builder: (context, state) => const RegisterScreen(), ), + GoRoute( + path: '/onboarding/phone', + builder: (context, state) => const PhoneScreen(), + ), GoRoute( path: '/onboarding/verify', builder: (context, state) => const VerifyScreen(), @@ -136,6 +153,10 @@ class _MyAppState extends State { path: '/onboarding/login', builder: (context, state) => const LoginScreen(), ), + GoRoute( + path: '/onboarding/complete', + builder: (context, state) => const CompleteScreen(), + ), ], ), ], diff --git a/lib/screens/home/home_controller.dart b/lib/screens/home/home_controller.dart index 6c22de4..4e92413 100644 --- a/lib/screens/home/home_controller.dart +++ b/lib/screens/home/home_controller.dart @@ -280,7 +280,7 @@ class HomeController extends ChangeNotifier { description: 'USD Wallet', image: 'united-states.png', currency: 'USD', - balance: '4.02', + balance: '47655.02', ), Account( name: 'Vusumuzi Khoza', diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index ad52d6f..710d850 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -21,13 +21,12 @@ class _HomeScreenState extends State late AnimationController _controller; late Animation _alignAnimation; late TransactionController transactionController; - late bool _loading = true; late SharedPreferences prefs; late HomeController homeController; late final router = GoRouter.of(context); + late String initials; - Timer? _loadingTimer; - bool get _isLoggedIn => false; + bool _isLoggedIn = false; String _selectedFilter = 'All'; final List> _animations = []; @@ -36,15 +35,6 @@ class _HomeScreenState extends State @override void initState() { super.initState(); - router.routerDelegate.addListener(_onRouteChanged); - - transactionController = Provider.of( - context, - listen: false, - ); - - // Reset the transaction controller model - transactionController.model = TransactionModel(); _controller = AnimationController( duration: const Duration(milliseconds: 600), @@ -69,18 +59,19 @@ class _HomeScreenState extends State ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); setupDataAndTransitions(); - - // Simulate a delay to mimic loading state - _loadingTimer = Timer(const Duration(seconds: 1), () { - if (mounted) { - setState(() { - _loading = false; - }); - } - }); } Future setupDataAndTransitions() async { + router.routerDelegate.addListener(_onRouteChanged); + + transactionController = Provider.of( + context, + listen: false, + ); + + // Reset the transaction controller model + transactionController.model = TransactionModel(); + homeController = HomeController(context); await homeController.getAccounts(); await homeController.getCategories(); @@ -103,8 +94,11 @@ class _HomeScreenState extends State prefs = await SharedPreferences.getInstance(); - if (prefs.getString("userId") == null) { - prefs.setString("userId", Uuid().v4()); + if (prefs.getString("token") != null) { + setState(() { + _isLoggedIn = true; + initials = prefs.getString("initials")!; + }); } await homeController.getTransactions(prefs.getString("userId")!); @@ -126,16 +120,21 @@ class _HomeScreenState extends State ); } - void _onRouteChanged() { + void _onRouteChanged() async { final location = router.routerDelegate.currentConfiguration.uri.toString(); if (location.startsWith('/home')) { - setupDataAndTransitions(); + prefs = await SharedPreferences.getInstance(); + + if (prefs.getString("userId") == null) { + prefs.setString("userId", Uuid().v4()); + } + + await homeController.getTransactions(prefs.getString("userId")!); } } @override void dispose() { - _loadingTimer?.cancel(); _controller.dispose(); homeController.dispose(); router.routerDelegate.removeListener(_onRouteChanged); @@ -159,45 +158,83 @@ class _HomeScreenState extends State surfaceTintColor: Colors.transparent, floating: true, title: Image(image: AssetImage('assets/peak.png'), width: 85), - leading: IconButton( - icon: Container( - width: 32, - height: 32, - decoration: BoxDecoration( - border: Border.all(color: Colors.black87.withAlpha(20)), - shape: BoxShape.circle, - gradient: LinearGradient( - colors: [ - Theme.of( - context, - ).colorScheme.primary.withValues(alpha: 0.1), - Theme.of( - context, - ).colorScheme.tertiary.withValues(alpha: 0.05), - ], - stops: const [0.3, 0.7], - begin: Alignment.topRight, - end: Alignment.bottomLeft, + leading: _isLoggedIn ? Container( + padding: const EdgeInsets.only(left: 10.0), + child: IconButton( + icon: Container( + width: 52, + height: 52, + decoration: BoxDecoration( + border: Border.all(color: Colors.black87.withAlpha(20)), + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [ + Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.1), + Theme.of( + context, + ).colorScheme.tertiary.withValues(alpha: 0.05), + ], + stops: const [0.3, 0.7], + begin: Alignment.topRight, + end: Alignment.bottomLeft, + ), ), - ), - child: Center( - child: Text( - 'VK', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, + child: Center( + child: Text( + initials, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), ), ), + onPressed: () {}, ), - onPressed: () {}, - ), - // actions: [ - // IconButton( - // icon: const Icon(Icons.notifications_outlined), - // onPressed: () {}, - // ), - // ], + ) : SizedBox(), + actions: [ + if(_isLoggedIn) + IconButton( + icon: const Icon(Icons.exit_to_app_sharp), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: const Text('Confirm Logout'), + content: const Text('Are you sure you want to log out?'), + actions: [ + TextButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(dialogContext).pop(); // Dismiss the dialog + }, + ), + TextButton( + child: const Text('Logout'), + onPressed: () { + Navigator.of(dialogContext).pop(); // Dismiss the dialog + prefs.remove("token"); + setState(() { + _isLoggedIn = false; + }); + // It's generally not recommended to call initState directly. + // Consider moving the logic from initState that needs to be rerun + // into a separate method and call that method here. + // For example, if you have a method like _reloadData(), call it here. + // _reloadData(); + setupDataAndTransitions(); // Or specific parts of it if not all is needed + }, + ), + ], + ); + }, + ); + }, + ), + ], ), SliverToBoxAdapter( child: Padding( @@ -209,7 +246,7 @@ class _HomeScreenState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!_isLoggedIn) _buildLogin(homeController), - if (_isLoggedIn) _buildAccounts(homeController), + // if (_isLoggedIn) _buildAccounts(homeController), _buildFilterChips(homeController), SizedBox(height: 10), Column( @@ -361,7 +398,7 @@ class _HomeScreenState extends State // context.push("/wallet"); }, child: Container( - width: 150, + width: 170, padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, @@ -388,11 +425,11 @@ class _HomeScreenState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset('assets/${e.image}', width: 20), - SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ + Image.asset('assets/${e.image}', width: 20), + SizedBox(width: 10), Text( e.currency, style: TextStyle( @@ -402,12 +439,15 @@ class _HomeScreenState extends State ), ), SizedBox(width: 5), - Text( - e.balance, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 22, - color: Colors.black, + Expanded( + child: Text( + e.balance, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + color: Colors.black, + overflow: TextOverflow.ellipsis, + ), ), ), ], diff --git a/lib/screens/onboarding/complete_screen.dart b/lib/screens/onboarding/complete_screen.dart new file mode 100644 index 0000000..2fe67c4 --- /dev/null +++ b/lib/screens/onboarding/complete_screen.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:qpay/screens/onboarding/login/login_controller.dart'; + +class CompleteScreen extends StatefulWidget { + const CompleteScreen({super.key}); + + @override + State createState() => _CompleteScreenState(); +} + +class _CompleteScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(25), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 75), + Text( + 'Registration Complete', + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ), + SizedBox(height: 5), + Text( + 'Thank you for registering with Peak payments', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + Image.asset('assets/landing.png', width: 300), + Text( + 'Tap Login to get started', + style: TextStyle(fontSize: 18), + ), + SizedBox(height: 10), + ], + ), + ), + ), + bottomNavigationBar: SizedBox( + height: 130, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + context.go('/onboarding/login'); + }, + child: Text('Go to Login', style: TextStyle(fontSize: 16)), + ), + ), + ], + ), + ), + SizedBox(height: 5), + ], + ), + ), + ); + } +} diff --git a/lib/screens/onboarding/login/login_controller.dart b/lib/screens/onboarding/login/login_controller.dart index 823fbaf..1b649fa 100644 --- a/lib/screens/onboarding/login/login_controller.dart +++ b/lib/screens/onboarding/login/login_controller.dart @@ -54,6 +54,8 @@ class LoginController extends ChangeNotifier { prefs.setString('firstName', response['firstName']); prefs.setString('lastName', response['lastName']); prefs.setString('phone', response['phone']); + prefs.setString('userId', response['uuid']); + prefs.setString('initials', response['firstName'].substring(0, 1) + response['lastName'].substring(0, 1)); return model; } else { @@ -64,7 +66,7 @@ class LoginController extends ChangeNotifier { } } on DioException catch (e, s) { logger.e(s); - _showErrorSnackBar("Network error. Please try again or contact support"); + _showErrorSnackBar(e.response?.data['errors'][0] ?? "Unknown error"); } catch (e, s) { logger.e(s); _showErrorSnackBar( diff --git a/lib/screens/onboarding/login/login_screen.dart b/lib/screens/onboarding/login/login_screen.dart index 97c4a41..ff87c5f 100644 --- a/lib/screens/onboarding/login/login_screen.dart +++ b/lib/screens/onboarding/login/login_screen.dart @@ -1,6 +1,12 @@ +import 'dart:async'; +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:go_router/go_router.dart'; +import 'package:google_sign_in/google_sign_in.dart'; import 'package:qpay/screens/onboarding/login/login_controller.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -10,9 +16,13 @@ class LoginScreen extends StatefulWidget { } class _LoginScreenState extends State { + late SharedPreferences prefs; final _formKey = GlobalKey(); - final _usernameController = TextEditingController(); - final _passwordController = TextEditingController(); + bool _loading = false; + + List scopes = [ + 'https://www.googleapis.com/auth/contacts.readonly', + ]; late LoginController _loginController; @@ -22,6 +32,65 @@ class _LoginScreenState extends State { _loginController = LoginController(context); } + Future signInWithGoogle() async { + final GoogleSignIn googleSignIn = GoogleSignIn.instance; + + unawaited(googleSignIn.initialize(serverClientId: dotenv.env['CLIENTID'])); + + if (GoogleSignIn.instance.supportsAuthenticate()){ + + unawaited(GoogleSignIn.instance.authenticate().then((value) async { + print(value); + + saveUser(value); + await _loginController.login(value.email, value.id); + context.go('/'); + + googleSignIn.authenticationEvents + .listen(_handleAuthenticationEvent) + .onError(_handleAuthenticationError); + + })); + } + } + + void saveUser(GoogleSignInAccount user) async { + final Map data = { + 'displayName': user.displayName, + 'email': user.email, + 'id': user.id, + 'photoUrl': user.photoUrl, + }; + + prefs = await SharedPreferences.getInstance(); + if (prefs.getString("googleUser") == null) { + prefs.setString("googleUser", jsonEncode(data)); + } + } + + Future _handleAuthenticationEvent( + GoogleSignInAuthenticationEvent event, + ) async { + // #docregion CheckAuthorization + final GoogleSignInAccount? user = // ... + // #enddocregion CheckAuthorization + switch (event) { + GoogleSignInAuthenticationEventSignIn() => event.user, + GoogleSignInAuthenticationEventSignOut() => null, + }; + + // Check for existing authorization. + // #docregion CheckAuthorization + final GoogleSignInClientAuthorization? authorization = await user + ?.authorizationClient + .authorizationForScopes(scopes); + // #enddocregion CheckAuthorization + } + + Future _handleAuthenticationError(Object e) async { + print(e.toString()); + } + @override void dispose() { _loginController.dispose(); @@ -56,55 +125,15 @@ class _LoginScreenState extends State { textAlign: TextAlign.center, ), SizedBox(height: 40), + Image.asset('assets/password.png', width: 300), + Text( + 'Tap on your preferred social media platform to get started', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), // Username Field - TextFormField( - controller: _usernameController, - decoration: InputDecoration( - labelText: 'Phone Number', - prefixIcon: Icon(Icons.person_outline), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a phone number'; - } - return null; - }, - ), - SizedBox(height: 20), - // Password Field - TextFormField( - controller: _passwordController, - decoration: InputDecoration( - labelText: 'Password', - prefixIcon: Icon(Icons.lock_outline), - suffixIcon: Icon(Icons.visibility_outlined), - ), - obscureText: true, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a password'; - } - return null; - }, - ), SizedBox(height: 20), // Forgot Password Link - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: () { - // TODO: Add forgot password logic - }, - child: Text( - 'Forgot Password?', - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontSize: 14, - ), - ), - ), - ), - SizedBox(height: 40), // Divider with "or" text Row( children: [ @@ -112,11 +141,12 @@ class _LoginScreenState extends State { Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text( - 'or continue with', + 'choose from the options below to continue', style: TextStyle( color: Colors.grey.shade600, fontSize: 14, ), + textAlign: TextAlign.center, ), ), Expanded(child: Divider(color: Colors.grey.shade300)), @@ -128,8 +158,9 @@ class _LoginScreenState extends State { children: [ Expanded( child: OutlinedButton.icon( - onPressed: () { - // TODO: Add Google login logic + onPressed: () async { + await signInWithGoogle(); + }, icon: Image.asset( 'assets/google.png', @@ -146,27 +177,6 @@ class _LoginScreenState extends State { ), ), ), - SizedBox(width: 16), - Expanded( - child: OutlinedButton.icon( - onPressed: () { - // TODO: Add Apple login logic - }, - icon: Image.asset( - 'assets/apple.png', - width: 20, - height: 20, - ), - label: Text('Apple', style: TextStyle(fontSize: 16)), - style: OutlinedButton.styleFrom( - padding: EdgeInsets.symmetric(vertical: 16), - side: BorderSide(color: Colors.grey.shade300), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - ), ], ), SizedBox(height: 40), @@ -179,43 +189,6 @@ class _LoginScreenState extends State { height: 130, child: Column( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 25), - child: Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () async { - // TODO: Add login logic - if (_formKey.currentState!.validate()) { - LoginScreenModel model = await _loginController.login( - _usernameController.text, - _passwordController.text, - ); - - if (model.status == 'SUCCESS') { - context.go('/'); - } - } - }, - child: Text('Login', style: TextStyle(fontSize: 16)), - ), - ), - SizedBox(width: 16), - Expanded( - child: TextButton( - onPressed: () { - context.go('/onboarding/register'); - }, - child: Text( - 'Create Account', - style: TextStyle(fontSize: 16), - ), - ), - ), - ], - ), - ), SizedBox(height: 5), TextButton( onPressed: () { diff --git a/lib/screens/onboarding/onboarding_controller.dart b/lib/screens/onboarding/onboarding_controller.dart new file mode 100644 index 0000000..e3864f9 --- /dev/null +++ b/lib/screens/onboarding/onboarding_controller.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:qpay/http/http.dart'; + +class OnboardingModel { + String? phone; + String? workflowId; + String? username; + GoogleSignInAccount? googleUser; + bool isLoading = false; +} + +class OnboardingController extends ChangeNotifier { + OnboardingModel model = OnboardingModel(); + + void resetState() { + model = OnboardingModel(); + } + + void updateModel(Map mapModel) { + model.workflowId = mapModel['workflowId']; + model.phone = mapModel['phone']; + model.username = mapModel['username']; + notifyListeners(); + } + + updatePhone(String phone) { + model.phone = phone; + notifyListeners(); + } + + updateGoogleUser(GoogleSignInAccount googleUser) { + model.googleUser = googleUser; + notifyListeners(); + } + +} \ No newline at end of file diff --git a/lib/screens/onboarding/phone/phone_controller.dart b/lib/screens/onboarding/phone/phone_controller.dart new file mode 100644 index 0000000..3efb701 --- /dev/null +++ b/lib/screens/onboarding/phone/phone_controller.dart @@ -0,0 +1,73 @@ + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:qpay/abstracts/AbstractController.dart'; +import 'package:qpay/http/http.dart'; + +import '../onboarding_controller.dart'; + +class PhoneModel { + bool isLoading = false; + String? errorMessage; + String? status = 'failed'; +} + +class PhoneController extends AbstractController { + PhoneModel model = PhoneModel(); + @override + BuildContext context; + + late OnboardingController onboardingController; + final Http http = Http(); + + PhoneController(this.context) { + onboardingController = Provider.of( + context, + listen: false, + ); + } + + void updatePhone(String phone) { + onboardingController.updatePhone(phone); + notifyListeners(); + } + + Future register() async { + Map user = { + "username": onboardingController.model.googleUser?.email, + "email": onboardingController.model.googleUser?.email, + "firstName": onboardingController.model.googleUser?.displayName?.split(" ").first, + "lastName": onboardingController.model.googleUser?.displayName?.split(" ").last, + "password": onboardingController.model.googleUser?.id, + "photoUrl": onboardingController.model.googleUser?.photoUrl, + "phone": onboardingController.model.phone + }; + + try { + model.isLoading = true; + + Map response = await http.post( + '/auth/register', + user + ); + model.status = response['state']; + if(response['state'] == 'failed') { + model.errorMessage = response['message']; + showErrorSnackBar(model.errorMessage); + } + Map body = response['body']; + body['workflowId'] = response['workflowId']; + onboardingController.updateModel(body); + } catch (e) { + logger.e(e); + showErrorSnackBar( + "Problem during registration, please try again or contact support?", + ); + } + + model.isLoading = false; + notifyListeners(); + } + +} \ No newline at end of file diff --git a/lib/screens/onboarding/phone/phone_screen.dart b/lib/screens/onboarding/phone/phone_screen.dart new file mode 100644 index 0000000..1835815 --- /dev/null +++ b/lib/screens/onboarding/phone/phone_screen.dart @@ -0,0 +1,270 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_contacts/flutter_contacts.dart'; +import 'package:go_router/go_router.dart'; +import 'package:qpay/screens/onboarding/phone/phone_controller.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class PhoneScreen extends StatefulWidget { + const PhoneScreen({super.key}); + + @override + State createState() => _PhoneScreenState(); +} + +class _PhoneScreenState extends State { + late PhoneController phoneController; + final _formKey = GlobalKey(); + final TextEditingController _phoneController = TextEditingController(); + + String selectedCountryCode = '+263'; // Default to ZW + + // Common country codes with flags and names + final List> countryCodes = [ + {'code': '+263', 'name': 'ZW', 'flag': '🇿🇼'}, + {'code': '+27', 'name': 'ZA', 'flag': '🇿🇦'}, + {'code': '+1', 'name': 'US', 'flag': '🇺🇸'}, + {'code': '+44', 'name': 'UK', 'flag': '🇬🇧'}, + {'code': '+61', 'name': 'AU', 'flag': '🇦🇺'}, + {'code': '+91', 'name': 'IN', 'flag': '🇮🇳'}, + {'code': '+86', 'name': 'CN', 'flag': '🇨🇳'}, + {'code': '+81', 'name': 'JP', 'flag': '🇯🇵'}, + {'code': '+49', 'name': 'DE', 'flag': '🇩🇪'}, + {'code': '+33', 'name': 'FR', 'flag': '🇫🇷'}, + {'code': '+39', 'name': 'IT', 'flag': '🇮🇹'}, + {'code': '+34', 'name': 'ES', 'flag': '🇪🇸'}, + {'code': '+7', 'name': 'RU', 'flag': '🇷🇺'}, + {'code': '+55', 'name': 'BR', 'flag': '🇧🇷'}, + {'code': '+52', 'name': 'MX', 'flag': '🇲🇽'}, + {'code': '+971', 'name': 'AE', 'flag': '🇦🇪'}, + {'code': '+966', 'name': 'SA', 'flag': '🇸🇦'}, + {'code': '+234', 'name': 'NG', 'flag': '🇳🇬'}, + {'code': '+254', 'name': 'KE', 'flag': '🇰🇪'}, + {'code': '+233', 'name': 'GH', 'flag': '🇬🇭'}, + {'code': '+256', 'name': 'UG', 'flag': '🇺🇬'}, + ]; + + @override + void initState() { + super.initState(); + phoneController = PhoneController(context); + } + + Future updatePhoneNumber(String phoneNumber) async { + String existingPhone = phoneNumber; + if (existingPhone.isNotEmpty) { + // Try to extract country code from existing phone number + for (var country in countryCodes) { + if (existingPhone.startsWith(country['code']!)) { + setState(() { + selectedCountryCode = country['code']!; + }); + _phoneController.text = existingPhone.substring( + country['code']!.length, + ); + break; + } + } + // If no country code found, use default and set the full number + if (_phoneController.text.isEmpty) { + _phoneController.text = existingPhone; + } + } + } + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: phoneController, + builder: (context, child) { + return Scaffold( + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(25.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 75), + Text( + 'Verify Your Device', + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + color: Theme + .of(context) + .colorScheme + .primary, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + Text( + 'We will send you a verification code to this number', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + Image.asset('assets/phone.png', width: 300), + SizedBox(height: 20), + _buildPhoneField(), + SizedBox(height: 30), + ], + ), + ), + ), + ), + bottomNavigationBar: SizedBox( + height: 130, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: Skeletonizer( + enabled: phoneController.model.isLoading, + child: Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + String fullPhoneNumber = + selectedCountryCode + _phoneController.text; + + phoneController.updatePhone(fullPhoneNumber); + await phoneController.register(); + if(phoneController.model.status != 'failed') { + context.go('/onboarding/verify'); + } + } + }, + child: Text('Send Code', style: TextStyle(fontSize: 16)), + ), + ), + SizedBox(width: 16), + Expanded( + child: TextButton( + onPressed: () { + context.go('/onboarding/login'); + }, + child: Text( + 'Go to Login', + style: TextStyle(fontSize: 16), + ), + ), + ), + ], + ), + ), + ), + SizedBox(height: 5), + TextButton( + onPressed: () { + context.go('/'); + }, + child: Text('Continue as guest?', style: TextStyle(fontSize: 14)), + ), + ], + ), + ), + ); + } + ); + } + + Widget _buildPhoneField() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + // Country code dropdown + Container( + width: 100, + height: 55, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.primary.withOpacity(0.2), + ), + borderRadius: BorderRadius.circular(12), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: selectedCountryCode, + isExpanded: true, + icon: const Icon(Icons.arrow_drop_down), + padding: const EdgeInsets.symmetric(horizontal: 8), + items: + countryCodes.map((country) { + return DropdownMenuItem( + value: country['code'], + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(country['flag']!), + const SizedBox(width: 4), + Text( + country['code']!, + style: const TextStyle(fontSize: 16), + ), + ], + ), + ); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + setState(() { + selectedCountryCode = newValue; + }); + } + }, + ), + ), + ), + const SizedBox(width: 8), + // Phone number field + Expanded( + child: TextFormField( + controller: _phoneController, + keyboardType: TextInputType.numberWithOptions(decimal: true), + decoration: InputDecoration( + hintText: 'Enter phone number', + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.secondary, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Theme.of( + context, + ).colorScheme.primary.withOpacity(0.2), + ), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a phone number'; + } + // Remove any non-digit characters for validation + String digitsOnly = value.replaceAll(RegExp(r'[^\d]'), ''); + if (digitsOnly.length < 7) { + return 'Phone number must be at least 7 digits'; + } + if (digitsOnly.length > 15) { + return 'Phone number is too long'; + } + return null; + }, + ), + ), + ], + ), + ], + ); + } + +} diff --git a/lib/screens/onboarding/register/register_screen.dart b/lib/screens/onboarding/register/register_screen.dart index f85bd67..b9e5b2b 100644 --- a/lib/screens/onboarding/register/register_screen.dart +++ b/lib/screens/onboarding/register/register_screen.dart @@ -1,5 +1,12 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:go_router/go_router.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:provider/provider.dart'; +import 'package:qpay/screens/onboarding/onboarding_controller.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); @@ -9,186 +16,237 @@ class RegisterScreen extends StatefulWidget { } class _RegisterScreenState extends State { + late OnboardingController onboardingController; + + bool _loading = false; + + List scopes = [ + 'https://www.googleapis.com/auth/contacts.readonly', + ]; + + @override + void initState() { + super.initState(); + onboardingController = Provider.of( + context, + listen: false, + ); + // onboardingController.resetState(); + } + + void signInWithGoogle() { + final GoogleSignIn googleSignIn = GoogleSignIn.instance; + + unawaited(googleSignIn.initialize(serverClientId: dotenv.env['CLIENTID'])); + + if (GoogleSignIn.instance.supportsAuthenticate()){ + unawaited(GoogleSignIn.instance.authenticate().then((value) { + print(value); + onboardingController.updateGoogleUser(value); + googleSignIn.authenticationEvents + .listen(_handleAuthenticationEvent) + .onError(_handleAuthenticationError); + + })); + } + } + + Future _handleAuthenticationEvent( + GoogleSignInAuthenticationEvent event, + ) async { + // #docregion CheckAuthorization + final GoogleSignInAccount? user = // ... + // #enddocregion CheckAuthorization + switch (event) { + GoogleSignInAuthenticationEventSignIn() => event.user, + GoogleSignInAuthenticationEventSignOut() => null, + }; + + // Check for existing authorization. + // #docregion CheckAuthorization + final GoogleSignInClientAuthorization? authorization = await user + ?.authorizationClient + .authorizationForScopes(scopes); + // #enddocregion CheckAuthorization + } + + Future _handleAuthenticationError(Object e) async { + print(e.toString()); + } + @override Widget build(BuildContext context) { - return Scaffold( - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(25), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: 75), - Text( - 'Registration', - style: TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ), - SizedBox(height: 5), - Text( - 'Join Peak and start making fast payments', - style: TextStyle(fontSize: 18), - textAlign: TextAlign.center, - ), - SizedBox(height: 40), - // First Name Field - TextField( - decoration: InputDecoration( - labelText: 'First Name', - prefixIcon: Icon(Icons.person_outline), - ), - ), - SizedBox(height: 20), - // Last Name Field - TextField( - decoration: InputDecoration( - labelText: 'Last Name', - prefixIcon: Icon(Icons.person_outline), - ), - ), - SizedBox(height: 20), - // Phone Field - TextField( - decoration: InputDecoration( - labelText: 'Phone Number', - prefixIcon: Icon(Icons.phone_outlined), - ), - keyboardType: TextInputType.phone, - ), - SizedBox(height: 20), - // Email Field - TextField( - decoration: InputDecoration( - labelText: 'Email Address', - prefixIcon: Icon(Icons.email_outlined), - ), - keyboardType: TextInputType.emailAddress, - ), - SizedBox(height: 20), - // Password Field - TextField( - decoration: InputDecoration( - labelText: 'Password', - prefixIcon: Icon(Icons.lock_outline), - suffixIcon: Icon(Icons.visibility_outlined), - ), - obscureText: true, - ), - SizedBox(height: 40), - // Divider with "or" text - Row( + return ListenableBuilder( + listenable: onboardingController, + builder: (context, child) { + return Scaffold( + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(25), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded(child: Divider(color: Colors.grey.shade300)), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Text( - 'or continue with', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 14, - ), + SizedBox(height: 75), + Text( + 'Registration', + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, ), ), - Expanded(child: Divider(color: Colors.grey.shade300)), - ], - ), - SizedBox(height: 30), - // Social Login Buttons - Row( - children: [ - Expanded( - child: OutlinedButton.icon( - onPressed: () { - // TODO: Add Google login logic - }, - icon: Image.asset( - 'assets/google.png', - width: 20, - height: 20, + SizedBox(height: 5), + Text( + "Let's start by getting some of your details online to get you started", + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + Image.asset('assets/social.png', width: 300), + SizedBox(height: 20), + + // Social Login Buttons + if(onboardingController.model.googleUser == null) + Column( + children: [ + Row( + children: [ + Expanded(child: Divider(color: Colors.grey.shade300)), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text( + 'Choose from the options below to continue', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ), + Expanded(child: Divider(color: Colors.grey.shade300)), + ], ), - label: Text('Google', style: TextStyle(fontSize: 16)), - style: OutlinedButton.styleFrom( - padding: EdgeInsets.symmetric(vertical: 16), - side: BorderSide(color: Colors.grey.shade300), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + SizedBox(height: 20), + Skeletonizer( + enabled: _loading, + child: Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () { + signInWithGoogle(); + }, + icon: Image.asset( + 'assets/google.png', + width: 20, + height: 20, + ), + label: Text('Google', style: TextStyle(fontSize: 16)), + style: OutlinedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 16), + side: BorderSide(color: Colors.grey.shade300), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + SizedBox(width: 16), + ], ), ), - ), + ], ), - SizedBox(width: 16), - Expanded( - child: OutlinedButton.icon( - onPressed: () { - // TODO: Add Apple login logic - }, - icon: Image.asset( - 'assets/apple.png', - width: 20, - height: 20, - ), - label: Text('Apple', style: TextStyle(fontSize: 16)), - style: OutlinedButton.styleFrom( - padding: EdgeInsets.symmetric(vertical: 16), - side: BorderSide(color: Colors.grey.shade300), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + if(onboardingController.model.googleUser != null) + Column( + children: [ + SizedBox( + width: 100, + child: Divider( + color: Colors.grey.shade300, + thickness: 1, + ), ), - ), + SizedBox(height: 20,), + Text( + "Welcome, ${onboardingController.model.googleUser?.displayName}", + style: TextStyle(fontSize: 25), + textAlign: TextAlign.center, + ), + SizedBox(height: 10,), + Text("Tap Proceed to continue", style: TextStyle(fontSize: 18),) + ], ), - ), + SizedBox(height: 40), + ], ), - SizedBox(height: 40), - ], + ), ), - ), - ), - bottomNavigationBar: SizedBox( - height: 130, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 25), - child: Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () { - context.go('/onboarding/verify'); - }, - child: Text('Submit', style: TextStyle(fontSize: 16)), - ), - ), - SizedBox(width: 16), - Expanded( - child: TextButton( - onPressed: () { - context.go('/onboarding/login'); - }, - child: Text( - 'Go to Login', - style: TextStyle(fontSize: 16), + bottomNavigationBar: SizedBox( + height: 130, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + if (onboardingController.model.googleUser == null) { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: const Text('Sign-In Required'), + content: const Text( + "Please sign in with one of the social media platforms to proceed."), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(dialogContext).pop(); // Close the dialog + }, + ), + ], + ); + }, + ); + } else { + context.go('/onboarding/phone'); + } + }, + child: Text('Proceed', style: TextStyle(fontSize: 16)), + ), ), - ), + SizedBox(width: 16), + Expanded( + child: TextButton( + onPressed: () { + context.go('/onboarding/login'); + }, + child: Text( + 'Go to Login', + style: TextStyle(fontSize: 16), + ), + ), + ), + ], ), - ], - ), + ), + SizedBox(height: 5), + TextButton( + onPressed: () { + context.go('/'); + }, + child: Text('Continue as guest?', style: TextStyle(fontSize: 14)), + ), + ], ), - SizedBox(height: 5), - TextButton( - onPressed: () { - context.go('/'); - }, - child: Text('Continue as guest?', style: TextStyle(fontSize: 14)), - ), - ], - ), - ), + ), + ); + } ); } } diff --git a/lib/screens/onboarding/verify/verify_controller.dart b/lib/screens/onboarding/verify/verify_controller.dart new file mode 100644 index 0000000..0d5e141 --- /dev/null +++ b/lib/screens/onboarding/verify/verify_controller.dart @@ -0,0 +1,95 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:qpay/abstracts/AbstractController.dart'; +import 'package:qpay/http/http.dart'; +import 'package:qpay/screens/onboarding/onboarding_controller.dart'; + +class VerifyModel { + String? code; + bool isLoading = false; + String? errorMessage; + String? status = 'failed'; +} + +class VerifyController extends AbstractController { + late OnboardingController onboardingController; + final Http http = Http(); + BuildContext context; + + VerifyModel model = VerifyModel(); + + VerifyController(this.context){ + onboardingController = Provider.of( + context, + listen: false, + ); + } + + void updateCode(String code) { + model.code = code; + notifyListeners(); + } + + Future verifyOtp () async { + Map user = { + "username": onboardingController.model.phone, + "otpCode": model.code, + "otpType": "REGISTRATION", + "workflowId": onboardingController.model.workflowId, + }; + + try { + model.isLoading = true; + notifyListeners(); + + Map response = await http.post( + '/auth/verify', + user + ); + model.status = response['state']; + if(response['state'] == 'failed') { + model.errorMessage = response['message']; + showErrorSnackBar(model.errorMessage); + } + } catch (e) { + logger.e(e); + showErrorSnackBar( + "Problem during registration, please try again or contact support?", + ); + } + + model.isLoading = false; + notifyListeners(); + } + + Future resendOtp () async { + Map user = { + "username": onboardingController.model.phone, + "phone": onboardingController.model.phone, + "workflowId": onboardingController.model.workflowId, + }; + + try { + model.isLoading = true; + notifyListeners(); + + Map response = await http.post( + '/auth/resend', + user + ); + model.status = response['state']; + if(response['state'] == 'failed') { + model.errorMessage = response['message']; + showErrorSnackBar(model.errorMessage); + } + } catch (e) { + logger.e(e); + showErrorSnackBar( + "Problem during registration, please try again or contact support?", + ); + } + + model.isLoading = false; + notifyListeners(); + } +} \ No newline at end of file diff --git a/lib/screens/onboarding/verify/verify_screen.dart b/lib/screens/onboarding/verify/verify_screen.dart index 98a73e2..a86e61d 100644 --- a/lib/screens/onboarding/verify/verify_screen.dart +++ b/lib/screens/onboarding/verify/verify_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:qpay/screens/onboarding/verify/verify_controller.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class VerifyScreen extends StatefulWidget { const VerifyScreen({super.key}); @@ -9,6 +11,16 @@ class VerifyScreen extends StatefulWidget { } class _VerifyScreenState extends State { + late VerifyController verifyController; + String? errorMessage; + + @override + void initState() { + super.initState(); + + verifyController = VerifyController(context); + } + final List _codeControllers = List.generate( 6, (index) => TextEditingController(), @@ -26,6 +38,12 @@ class _VerifyScreenState extends State { super.dispose(); } + void showErrorMessage(String? message) { + setState(() { + errorMessage = message; + }); + } + void _onCodeChanged(String value, int index) { if (value.length == 1 && index < 5) { _focusNodes[index + 1].requestFocus(); @@ -34,155 +52,180 @@ class _VerifyScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(25), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: 75), - Text( - 'Verify Your Account', - style: TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ), - SizedBox(height: 5), - Text( - 'Enter the 6-digit code sent to your phone/email', - style: TextStyle(fontSize: 18), - textAlign: TextAlign.center, - ), - Image.asset('assets/verify.png', width: 300), - // Verification Code Input - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate( - 6, - (index) => SizedBox( - width: 50, - child: TextField( - controller: _codeControllers[index], - focusNode: _focusNodes[index], - textAlign: TextAlign.center, - keyboardType: TextInputType.number, - maxLength: 1, - decoration: InputDecoration( - counterText: '', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey.shade300), + return ListenableBuilder( + listenable: verifyController, + builder: (context, child) { + return Scaffold( + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(25), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 75), + Text( + 'Verify Your Account', + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ), + SizedBox(height: 5), + Text( + 'Enter the 6-digit code sent to your phone/email', + style: TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + Image.asset('assets/verify.png', width: 300), + // Verification Code Input + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate( + 6, + (index) => SizedBox( + width: 50, + child: TextFormField( + controller: _codeControllers[index], + focusNode: _focusNodes[index], + textAlign: TextAlign.center, + keyboardType: TextInputType.number, + maxLength: 1, + decoration: InputDecoration( + counterText: '', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), + ), + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + onChanged: (value) => _onCodeChanged(value, index), ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( + ), + ), + ), + SizedBox(height: 5), + if(errorMessage != null) + Text(errorMessage ?? '', style: TextStyle(color: Colors.red)), + SizedBox(height: 30), + // Resend Code Section + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Didn't receive the code? ", + style: TextStyle(color: Colors.grey.shade600, fontSize: 16), + ), + TextButton( + onPressed: () { + verifyController.resendOtp(); + }, + child: Text( + 'Resend Code', + style: TextStyle( color: Theme.of(context).colorScheme.primary, - width: 2, + fontSize: 16, + fontWeight: FontWeight.w600, ), ), - contentPadding: EdgeInsets.symmetric(vertical: 16), ), - onChanged: (value) => _onCodeChanged(value, index), + ], + ), + // SizedBox(height: 20), + // // Timer for resend (optional) + // Text( + // 'Resend available in 2:30', + // style: TextStyle(color: Colors.grey.shade500, fontSize: 14), + // ), + SizedBox(height: 40), + ], + ), + ), + ), + bottomNavigationBar: SizedBox( + height: 130, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: Skeletonizer( + enabled: verifyController.model.isLoading, + child: Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () async { + showErrorMessage(null); + String code = ''; + for (var controller in _codeControllers) { + code += controller.text; + } + + if(code.length != 6) { + showErrorMessage('Invalid code'); + return; + } + verifyController.updateCode(code); + await verifyController.verifyOtp(); + if(verifyController.model.status == 'done') { + context.go('/onboarding/complete'); + } + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Verify Code', + style: TextStyle(fontSize: 16), + ), + ), + ), + SizedBox(width: 16), + Expanded( + child: OutlinedButton( + onPressed: () { + context.go('/onboarding/login'); + }, + style: OutlinedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 16), + side: BorderSide(color: Colors.grey.shade300), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Back to Login', + style: TextStyle(fontSize: 16), + ), + ), + ), + ], ), ), ), - ), - SizedBox(height: 30), - // Resend Code Section - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Didn't receive the code? ", - style: TextStyle(color: Colors.grey.shade600, fontSize: 16), - ), - TextButton( - onPressed: () { - // TODO: Add resend code logic - }, - child: Text( - 'Resend Code', - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - SizedBox(height: 20), - // Timer for resend (optional) - Text( - 'Resend available in 2:30', - style: TextStyle(color: Colors.grey.shade500, fontSize: 14), - ), - SizedBox(height: 40), - ], + SizedBox(height: 20), + TextButton( + onPressed: () { + context.go('/'); + }, + child: Text('Continue as guest?', style: TextStyle(fontSize: 14)), + ), + ], + ), ), - ), - ), - bottomNavigationBar: SizedBox( - height: 130, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 25), - child: Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () { - context.go('/onboarding/password'); - }, - style: ElevatedButton.styleFrom( - padding: EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text( - 'Verify Code', - style: TextStyle(fontSize: 16), - ), - ), - ), - SizedBox(width: 16), - Expanded( - child: OutlinedButton( - onPressed: () { - context.go('/onboarding/login'); - }, - style: OutlinedButton.styleFrom( - padding: EdgeInsets.symmetric(vertical: 16), - side: BorderSide(color: Colors.grey.shade300), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text( - 'Back to Login', - style: TextStyle(fontSize: 16), - ), - ), - ), - ], - ), - ), - SizedBox(height: 20), - TextButton( - onPressed: () { - context.go('/'); - }, - child: Text('Continue as guest?', style: TextStyle(fontSize: 14)), - ), - ], - ), - ), + ); + } ); } } diff --git a/lib/screens/receipt/receipt_controller.dart b/lib/screens/receipt/receipt_controller.dart index 573b4a0..9a46a1e 100644 --- a/lib/screens/receipt/receipt_controller.dart +++ b/lib/screens/receipt/receipt_controller.dart @@ -87,6 +87,7 @@ class ReceiptController extends ChangeNotifier { creditPhone: model.receiptData?['creditPhone'] ?? '', paymentProcessorLabel: model.receiptData?['paymentProcessorLabel'] ?? '', paymentProcessorName: model.receiptData?['paymentProcessorName'] ?? '', + id: null ); setLoading(false); diff --git a/lib/screens/transaction_controller.dart b/lib/screens/transaction_controller.dart index 6848b3c..6f8b950 100644 --- a/lib/screens/transaction_controller.dart +++ b/lib/screens/transaction_controller.dart @@ -15,7 +15,6 @@ class TransactionModel { BillProvider? selectedProvider; Map? receiptData; FormData formData = FormData( - id: '', type: '', billClientId: '', debitRef: '', @@ -48,9 +47,9 @@ class TransactionModel { @freezed abstract class FormData with _$FormData { const factory FormData({ + String? id, required String type, // CONFIRM, REQUEST, REVERSE required String billClientId, - required String id, required String debitRef, required String debitCurrency, required String amount, @@ -117,7 +116,7 @@ class TransactionController extends ChangeNotifier { clearFormData() { model.formData = FormData( - id: '', + id: null, type: '', billClientId: '', debitRef: '', diff --git a/lib/screens/transaction_controller.freezed.dart b/lib/screens/transaction_controller.freezed.dart index c204d16..8b49421 100644 --- a/lib/screens/transaction_controller.freezed.dart +++ b/lib/screens/transaction_controller.freezed.dart @@ -16,8 +16,8 @@ T _$identity(T value) => value; /// @nodoc mixin _$FormData { - String get type;// CONFIRM, REQUEST, REVERSE - String get billClientId; String get id; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount; + String? get id; String get type;// CONFIRM, REQUEST, REVERSE + String get billClientId; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount; /// Create a copy of FormData /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -30,16 +30,16 @@ $FormDataCopyWith get copyWith => _$FormDataCopyWithImpl(thi @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.id, id) || other.id == id)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,type,billClientId,id,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]); +int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]); @override String toString() { - return 'FormData(type: $type, billClientId: $billClientId, id: $id, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)'; + return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)'; } @@ -50,7 +50,7 @@ abstract mixin class $FormDataCopyWith<$Res> { factory $FormDataCopyWith(FormData value, $Res Function(FormData) _then) = _$FormDataCopyWithImpl; @useResult $Res call({ - String type, String billClientId, String id, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount + String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount }); @@ -67,11 +67,11 @@ class _$FormDataCopyWithImpl<$Res> /// Create a copy of FormData /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? billClientId = null,Object? id = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) { return _then(_self.copyWith( -type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as String,billClientId: null == billClientId ? _self.billClientId : billClientId // ignore: cast_nullable_to_non_nullable -as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,debitRef: null == debitRef ? _self.debitRef : debitRef // ignore: cast_nullable_to_non_nullable as String,debitCurrency: null == debitCurrency ? _self.debitCurrency : debitCurrency // ignore: cast_nullable_to_non_nullable as String,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable @@ -108,13 +108,13 @@ as String?, @JsonSerializable() class _FormData implements FormData { - const _FormData({required this.type, required this.billClientId, required this.id, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, this.debitPhone, this.debitAccount, this.productUid, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount}); + const _FormData({this.id, required this.type, required this.billClientId, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, this.debitPhone, this.debitAccount, this.productUid, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount}); factory _FormData.fromJson(Map json) => _$FormDataFromJson(json); +@override final String? id; @override final String type; // CONFIRM, REQUEST, REVERSE @override final String billClientId; -@override final String id; @override final String debitRef; @override final String debitCurrency; @override final String amount; @@ -154,16 +154,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.id, id) || other.id == id)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,type,billClientId,id,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]); +int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,trace,authType,charge,gatewayCharge,tax,totalAmount]); @override String toString() { - return 'FormData(type: $type, billClientId: $billClientId, id: $id, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)'; + return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount)'; } @@ -174,7 +174,7 @@ abstract mixin class _$FormDataCopyWith<$Res> implements $FormDataCopyWith<$Res> factory _$FormDataCopyWith(_FormData value, $Res Function(_FormData) _then) = __$FormDataCopyWithImpl; @override @useResult $Res call({ - String type, String billClientId, String id, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount + String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount }); @@ -191,11 +191,11 @@ class __$FormDataCopyWithImpl<$Res> /// Create a copy of FormData /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? billClientId = null,Object? id = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,}) { return _then(_FormData( -type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as String,billClientId: null == billClientId ? _self.billClientId : billClientId // ignore: cast_nullable_to_non_nullable -as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,debitRef: null == debitRef ? _self.debitRef : debitRef // ignore: cast_nullable_to_non_nullable as String,debitCurrency: null == debitCurrency ? _self.debitCurrency : debitCurrency // ignore: cast_nullable_to_non_nullable as String,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable diff --git a/lib/screens/transaction_controller.g.dart b/lib/screens/transaction_controller.g.dart index 19be559..9b326ff 100644 --- a/lib/screens/transaction_controller.g.dart +++ b/lib/screens/transaction_controller.g.dart @@ -7,9 +7,9 @@ part of 'transaction_controller.dart'; // ************************************************************************** _FormData _$FormDataFromJson(Map json) => _FormData( + id: json['id'] as String?, type: json['type'] as String, billClientId: json['billClientId'] as String, - id: json['id'] as String, debitRef: json['debitRef'] as String, debitCurrency: json['debitCurrency'] as String, amount: json['amount'] as String, @@ -38,9 +38,9 @@ _FormData _$FormDataFromJson(Map json) => _FormData( ); Map _$FormDataToJson(_FormData instance) => { + 'id': instance.id, 'type': instance.type, 'billClientId': instance.billClientId, - 'id': instance.id, 'debitRef': instance.debitRef, 'debitCurrency': instance.debitCurrency, 'amount': instance.amount, diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 9581ce5..eec2b5b 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -38,7 +38,7 @@ class AppTheme { const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), ), ), - cardTheme: CardTheme( + cardTheme: CardThemeData( elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 313ef1e..9005b53 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,16 @@ import FlutterMacOS import Foundation +import firebase_core +import google_sign_in_ios import path_provider_foundation import share_plus import shared_preferences_foundation import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 73125d5..f639c1f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,6 +225,30 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "967dae9a65f69377beb9f4ab292ea63ce5befa1ce24682cab1b69ca4b7a46927" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: f7ee08febc1c4451588ce58ffcf28edaee857e9a196fee88b85deb889990094a + url: "https://pub.dev" + source: hosted + version: "3.1.0" fixnum: dependency: transitive description: @@ -336,6 +360,54 @@ packages: url: "https://pub.dev" source: hosted version: "6.2.1" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: "939a8b58f84c4053811b8c1bc9adbcb59449a15b37958264bbf60020698cca0e" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: d2c80c17ede83c2f3348847520bf78bc9ada3ff4f94361b0d7e93a3b9d7815a0 + url: "https://pub.dev" + source: hosted + version: "7.1.0" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "10830fd37e6b4402b5c71373d3d8687e95ce7e9504738605237f23a76456c973" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "7f59208c42b415a3cca203571128d6f84f885fead2d5b53eb65a9e27f2965bb5" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "2fc1f941e6443b2d6984f4056a727a3eaeab15d8ee99ba7125d79029be75a1da" + url: "https://pub.dev" + source: hosted + version: "1.1.0" graphs: dependency: transitive description: @@ -1015,4 +1087,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7228adb..59602a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,8 @@ dependencies: share_plus: ^11.0.0 flutter_contacts: ^1.1.9+2 gif_view: ^0.4.0 + google_sign_in: ^7.1.1 + firebase_core: ^4.1.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c3384ec..c3d3b6a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 01d3836..c04ddae 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_core share_plus url_launcher_windows )