From 866bf1fc6fc8214fe1d3b35d49c501e3a816705d Mon Sep 17 00:00:00 2001 From: Michael Suleiman Date: Wed, 7 Oct 2020 19:31:51 +0400 Subject: [PATCH] Added Login and Sign-up Screens and functionality - The validation for inputs was pretty basic - Used mock register and login apis from https://reqres.in/ * File structure needs to be fixed though --- .packages | 123 +++- android/gradle.properties | 1 + lib/injection.config.dart | 45 ++ lib/injection.dart | 12 + lib/main.dart | 119 +--- lib/models/auth_enums.dart | 1 + lib/models/auth_model.dart | 40 ++ lib/pages/auth/bloc/auth_bloc.dart | 46 ++ lib/pages/auth/bloc/auth_event.dart | 12 + lib/pages/auth/bloc/auth_state.dart | 17 + lib/pages/auth/login/bloc/login_bloc.dart | 81 +++ lib/pages/auth/login/bloc/login_event.dart | 30 + lib/pages/auth/login/bloc/login_state.dart | 28 + lib/pages/auth/login/login_form.dart | 167 ++++++ lib/pages/auth/login/login_page.dart | 18 + lib/pages/auth/signup/bloc/signup_bloc.dart | 127 +++++ lib/pages/auth/signup/bloc/signup_event.dart | 48 ++ lib/pages/auth/signup/bloc/signup_state.dart | 36 ++ lib/pages/auth/signup/signup_form.dart | 173 ++++++ lib/pages/auth/signup/signup_page.dart | 39 ++ .../auth/widgets/already_have_an_account.dart | 36 ++ lib/pages/auth/widgets/auth_input.dart | 139 +++++ lib/pages/core/app.dart | 29 + lib/pages/core/splash_page.dart | 26 + lib/pages/home/home_page.dart | 11 + lib/pages/routes/router.dart | 16 + lib/pages/routes/router.gr.dart | 81 +++ lib/repository/auth_repository.dart | 54 ++ lib/utils/apis.dart | 3 +- lib/utils/injectable_modules.dart | 16 + pubspec.lock | 538 ++++++++++++++++++ pubspec.yaml | 65 +-- test/widget_test.dart | 5 +- 33 files changed, 1989 insertions(+), 193 deletions(-) create mode 100644 lib/injection.config.dart create mode 100644 lib/injection.dart create mode 100644 lib/models/auth_enums.dart create mode 100644 lib/models/auth_model.dart create mode 100644 lib/pages/auth/bloc/auth_bloc.dart create mode 100644 lib/pages/auth/bloc/auth_event.dart create mode 100644 lib/pages/auth/bloc/auth_state.dart create mode 100644 lib/pages/auth/login/bloc/login_bloc.dart create mode 100644 lib/pages/auth/login/bloc/login_event.dart create mode 100644 lib/pages/auth/login/bloc/login_state.dart create mode 100644 lib/pages/auth/login/login_form.dart create mode 100644 lib/pages/auth/login/login_page.dart create mode 100644 lib/pages/auth/signup/bloc/signup_bloc.dart create mode 100644 lib/pages/auth/signup/bloc/signup_event.dart create mode 100644 lib/pages/auth/signup/bloc/signup_state.dart create mode 100644 lib/pages/auth/signup/signup_form.dart create mode 100644 lib/pages/auth/signup/signup_page.dart create mode 100644 lib/pages/auth/widgets/already_have_an_account.dart create mode 100644 lib/pages/auth/widgets/auth_input.dart create mode 100644 lib/pages/core/app.dart create mode 100644 lib/pages/core/splash_page.dart create mode 100644 lib/pages/routes/router.dart create mode 100644 lib/pages/routes/router.gr.dart create mode 100644 lib/repository/auth_repository.dart create mode 100644 lib/utils/injectable_modules.dart diff --git a/.packages b/.packages index 0bffa68..c0aa0b6 100644 --- a/.packages +++ b/.packages @@ -1,24 +1,101 @@ -# Generated by pub on 2020-10-02 20:49:43.904476. -async:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/async-2.5.0-nullsafety.1/lib/ -boolean_selector:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0-nullsafety.1/lib/ -characters:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0-nullsafety.3/lib/ -charcode:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/charcode-1.2.0-nullsafety.1/lib/ -clock:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0-nullsafety.1/lib/ -collection:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0-nullsafety.3/lib/ -cupertino_icons:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/cupertino_icons-1.0.0/lib/ -fake_async:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0-nullsafety.1/lib/ -flutter:file:///Users/abhishek/development/flutter/packages/flutter/lib/ -flutter_test:file:///Users/abhishek/development/flutter/packages/flutter_test/lib/ -matcher:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10-nullsafety.1/lib/ -meta:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/meta-1.3.0-nullsafety.3/lib/ -path:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/path-1.8.0-nullsafety.1/lib/ -sky_engine:file:///Users/abhishek/development/flutter/bin/cache/pkg/sky_engine/lib/ -source_span:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.2/lib/ -stack_trace:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0-nullsafety.1/lib/ -stream_channel:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0-nullsafety.1/lib/ -string_scanner:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0-nullsafety.1/lib/ -term_glyph:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0-nullsafety.1/lib/ -test_api:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.19-nullsafety.2/lib/ -typed_data:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0-nullsafety.3/lib/ -vector_math:file:///Users/abhishek/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0-nullsafety.3/lib/ +# Generated by pub on 2020-10-07 17:48:51.924540. +_fe_analyzer_shared:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-7.0.0/lib/ +analyzer:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/analyzer-0.39.17/lib/ +args:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/args-1.6.0/lib/ +async:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/async-2.5.0-nullsafety.1/lib/ +auto_route:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/auto_route-0.6.7/lib/ +auto_route_generator:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/auto_route_generator-0.6.8/lib/ +bloc:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/bloc-6.0.3/lib/ +boolean_selector:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0-nullsafety.1/lib/ +build:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/build-1.3.0/lib/ +build_config:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/build_config-0.4.2/lib/ +build_daemon:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/build_daemon-2.1.4/lib/ +build_resolvers:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/build_resolvers-1.3.11/lib/ +build_runner:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/build_runner-1.10.2/lib/ +build_runner_core:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/build_runner_core-6.0.1/lib/ +built_collection:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/built_collection-4.3.2/lib/ +built_value:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/built_value-7.1.0/lib/ +characters:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0-nullsafety.3/lib/ +charcode:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/charcode-1.2.0-nullsafety.1/lib/ +checked_yaml:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/checked_yaml-1.0.2/lib/ +cli_util:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/cli_util-0.2.0/lib/ +clock:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0-nullsafety.1/lib/ +code_builder:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/code_builder-3.4.1/lib/ +collection:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0-nullsafety.3/lib/ +convert:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/convert-2.1.1/lib/ +crypto:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.5/lib/ +csslib:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/csslib-0.16.2/lib/ +cupertino_icons:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/cupertino_icons-1.0.0/lib/ +dart_style:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/dart_style-1.3.6/lib/ +equatable:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/equatable-1.2.5/lib/ +fake_async:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0-nullsafety.1/lib/ +ffi:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/ffi-0.1.3/lib/ +file:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/file-5.2.1/lib/ +fixnum:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/fixnum-0.10.11/lib/ +flutter:file:///home/codingstoic/development/flutter/packages/flutter/lib/ +flutter_bloc:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/flutter_bloc-6.0.6/lib/ +flutter_test:file:///home/codingstoic/development/flutter/packages/flutter_test/lib/ +flutter_web_plugins:file:///home/codingstoic/development/flutter/packages/flutter_web_plugins/lib/ +formz:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/formz-0.3.1/lib/ +get_it:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/get_it-4.0.4/lib/ +glob:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/glob-1.2.0/lib/ +graphs:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/graphs-0.2.0/lib/ +html:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/html-0.14.0+4/lib/ +http:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/http-0.12.2/lib/ +http_multi_server:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.2.0/lib/ +http_parser:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/http_parser-3.1.4/lib/ +injectable:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/injectable-1.0.4/lib/ +injectable_generator:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/injectable_generator-1.0.4/lib/ +intl:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/intl-0.16.1/lib/ +io:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/io-0.3.4/lib/ +js:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/js-0.6.2/lib/ +json_annotation:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/json_annotation-3.1.0/lib/ +logging:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/logging-0.11.4/lib/ +matcher:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10-nullsafety.1/lib/ +meta:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/meta-1.3.0-nullsafety.3/lib/ +mime:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/mime-0.9.7/lib/ +nested:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/nested-0.0.4/lib/ +node_interop:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/node_interop-1.1.1/lib/ +node_io:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/node_io-1.1.1/lib/ +package_config:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/package_config-1.9.3/lib/ +path:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/path-1.8.0-nullsafety.1/lib/ +path_provider_linux:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/lib/ +path_provider_platform_interface:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/path_provider_platform_interface-1.0.3/lib/ +path_provider_windows:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+1/lib/ +pedantic:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/pedantic-1.9.2/lib/ +platform:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/platform-2.2.1/lib/ +plugin_platform_interface:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-1.0.3/lib/ +pool:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/pool-1.4.0/lib/ +process:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/process-3.0.13/lib/ +provider:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/provider-4.3.2+2/lib/ +pub_semver:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.4/lib/ +pubspec_parse:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/pubspec_parse-0.1.5/lib/ +quiver:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/quiver-2.1.3/lib/ +rx_shared_preferences:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/rx_shared_preferences-1.3.1/lib/ +rxdart:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/rxdart-0.24.1/lib/ +shared_preferences:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.12/lib/ +shared_preferences_linux:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shared_preferences_linux-0.0.2+2/lib/ +shared_preferences_macos:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shared_preferences_macos-0.0.1+10/lib/ +shared_preferences_platform_interface:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shared_preferences_platform_interface-1.0.4/lib/ +shared_preferences_web:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shared_preferences_web-0.1.2+7/lib/ +shared_preferences_windows:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shared_preferences_windows-0.0.1+1/lib/ +shelf:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.9/lib/ +shelf_web_socket:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.3/lib/ +sky_engine:file:///home/codingstoic/development/flutter/bin/cache/pkg/sky_engine/lib/ +source_gen:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/source_gen-0.9.7+1/lib/ +source_span:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.2/lib/ +stack_trace:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0-nullsafety.1/lib/ +stream_channel:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0-nullsafety.1/lib/ +stream_transform:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/stream_transform-1.2.0/lib/ +string_scanner:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0-nullsafety.1/lib/ +term_glyph:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0-nullsafety.1/lib/ +test_api:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.19-nullsafety.2/lib/ +timing:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/timing-0.1.1+2/lib/ +typed_data:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0-nullsafety.3/lib/ +vector_math:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0-nullsafety.3/lib/ +watcher:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+15/lib/ +web_socket_channel:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.1.0/lib/ +win32:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/win32-1.7.3/lib/ +xdg_directories:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/xdg_directories-0.1.2/lib/ +yaml:file:///home/codingstoic/.pub-cache/hosted/pub.dartlang.org/yaml-2.2.1/lib/ ecommerce_flutter:lib/ diff --git a/android/gradle.properties b/android/gradle.properties index 94adc3a..a673820 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +android.enableR8=true diff --git a/lib/injection.config.dart b/lib/injection.config.dart new file mode 100644 index 0000000..3c434bb --- /dev/null +++ b/lib/injection.config.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// InjectableConfigGenerator +// ************************************************************************** + +import 'package:http/http.dart'; +import 'package:get_it/get_it.dart'; +import 'package:injectable/injectable.dart'; +import 'package:rx_shared_preferences/rx_shared_preferences.dart'; + +import 'pages/auth/bloc/auth_bloc.dart'; +import 'repository/auth_repository.dart'; +import 'utils/injectable_modules.dart'; +import 'pages/auth/login/bloc/login_bloc.dart'; +import 'pages/auth/signup/bloc/signup_bloc.dart'; + +/// adds generated dependencies +/// to the provided [GetIt] instance + +GetIt $initGetIt( + GetIt get, { + String environment, + EnvironmentFilter environmentFilter, +}) { + final gh = GetItHelper(get, environment, environmentFilter); + final injectableModules = _$InjectableModules(); + gh.lazySingleton(() => injectableModules.httpClient); + gh.lazySingleton( + () => injectableModules.rxSharedPreference); + gh.lazySingleton( + () => AuthRepository(httpClient: get())); + gh.factory(() => LoginBloc( + authRepository: get(), + rxSharedPreferences: get())); + gh.factory(() => SignupBloc( + rxSharedPreferences: get(), + authRepository: get())); + gh.factory(() => AuthBloc( + rxSharedPreferences: get(), + authRepository: get())); + return get; +} + +class _$InjectableModules extends InjectableModules {} diff --git a/lib/injection.dart b/lib/injection.dart new file mode 100644 index 0000000..0bdb192 --- /dev/null +++ b/lib/injection.dart @@ -0,0 +1,12 @@ +import 'package:ecommerce_flutter/injection.config.dart'; +import 'package:get_it/get_it.dart'; +import 'package:injectable/injectable.dart'; + +/// +final GetIt getIt = GetIt.instance; + +/// +@injectableInit +void configureInjection(String env) { + $initGetIt(getIt, environment: env); +} diff --git a/lib/main.dart b/lib/main.dart index 11655b6..8fb43e0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,117 +1,10 @@ +import 'package:ecommerce_flutter/injection.dart'; +import 'package:ecommerce_flutter/pages/core/app.dart'; import 'package:flutter/material.dart'; +import 'package:injectable/injectable.dart'; void main() { - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - // This makes the visual density adapt to the platform that you run - // the app on. For desktop platforms, the controls will be smaller and - // closer together (more dense) than on mobile platforms. - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } + WidgetsFlutterBinding.ensureInitialized(); + configureInjection(Environment.prod); + runApp(App()); } diff --git a/lib/models/auth_enums.dart b/lib/models/auth_enums.dart new file mode 100644 index 0000000..22b580b --- /dev/null +++ b/lib/models/auth_enums.dart @@ -0,0 +1 @@ +enum AuthStatus { unknown, authenticated, unauthenticated } diff --git a/lib/models/auth_model.dart b/lib/models/auth_model.dart new file mode 100644 index 0000000..95e2319 --- /dev/null +++ b/lib/models/auth_model.dart @@ -0,0 +1,40 @@ +import 'package:formz/formz.dart'; + +enum NameValidationError { empty } + +class Name extends FormzInput { + const Name.pure() : super.pure(''); + + const Name.dirty([String value = '']) : super.dirty(value); + + @override + NameValidationError validator(String value) { + return value?.isNotEmpty == true ? null : NameValidationError.empty; + } +} + +enum EmailValidationError { empty } + +class Email extends FormzInput { + const Email.pure() : super.pure(''); + + const Email.dirty([String value = '']) : super.dirty(value); + + @override + EmailValidationError validator(String value) { + return value?.isNotEmpty == true ? null : EmailValidationError.empty; + } +} + +enum PasswordValidationError { empty } + +class Password extends FormzInput { + const Password.pure() : super.pure(''); + + const Password.dirty([String value = '']) : super.dirty(value); + + @override + PasswordValidationError validator(String value) { + return value?.isNotEmpty == true ? null : PasswordValidationError.empty; + } +} diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart new file mode 100644 index 0000000..3d865d3 --- /dev/null +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:ecommerce_flutter/models/auth_enums.dart'; +import 'package:ecommerce_flutter/repository/auth_repository.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:injectable/injectable.dart'; +import 'package:rx_shared_preferences/rx_shared_preferences.dart'; + +part 'auth_event.dart'; + +part 'auth_state.dart'; + +@injectable +class AuthBloc extends Bloc { + final AuthRepository _authRepository; + final RxSharedPreferences _rxSharedPreferences; + + AuthBloc({ + @required RxSharedPreferences rxSharedPreferences, + @required AuthRepository authRepository, + }) : assert(authRepository != null), + _authRepository = authRepository, + _rxSharedPreferences = rxSharedPreferences, + super(AuthState.unknown()); + + @override + Stream mapEventToState( + AuthEvent event, + ) async* { + if (event is AuthStatusRequested) { + yield await _mapAuthStatusChangedToState(event); + } else if (event is AuthLogoutRequested) { + _authRepository.logout(); + } + } + + Future _mapAuthStatusChangedToState( + AuthStatusRequested event) async { + String value = await _rxSharedPreferences.getString('TOKEN'); + return value != null + ? const AuthState.authenticated() + : const AuthState.unauthenticated(); + } +} diff --git a/lib/pages/auth/bloc/auth_event.dart b/lib/pages/auth/bloc/auth_event.dart new file mode 100644 index 0000000..5d865b1 --- /dev/null +++ b/lib/pages/auth/bloc/auth_event.dart @@ -0,0 +1,12 @@ +part of 'auth_bloc.dart'; + +abstract class AuthEvent extends Equatable { + const AuthEvent(); + + @override + List get props => []; +} + +class AuthStatusRequested extends AuthEvent {} + +class AuthLogoutRequested extends AuthEvent {} diff --git a/lib/pages/auth/bloc/auth_state.dart b/lib/pages/auth/bloc/auth_state.dart new file mode 100644 index 0000000..45b8e49 --- /dev/null +++ b/lib/pages/auth/bloc/auth_state.dart @@ -0,0 +1,17 @@ +part of 'auth_bloc.dart'; + +class AuthState extends Equatable { + final AuthStatus status; + + const AuthState._({this.status}); + + const AuthState.unknown() : this._(); + + const AuthState.authenticated() : this._(status: AuthStatus.authenticated); + + const AuthState.unauthenticated() + : this._(status: AuthStatus.unauthenticated); + + @override + List get props => [status]; +} diff --git a/lib/pages/auth/login/bloc/login_bloc.dart b/lib/pages/auth/login/bloc/login_bloc.dart new file mode 100644 index 0000000..c02f3ec --- /dev/null +++ b/lib/pages/auth/login/bloc/login_bloc.dart @@ -0,0 +1,81 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:ecommerce_flutter/models/auth_model.dart'; +import 'package:ecommerce_flutter/repository/auth_repository.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:formz/formz.dart'; +import 'package:injectable/injectable.dart'; +import 'package:rx_shared_preferences/rx_shared_preferences.dart'; + +part 'login_event.dart'; +part 'login_state.dart'; + +@injectable +class LoginBloc extends Bloc { + final AuthRepository _authRepository; + final RxSharedPreferences _rxSharedPreferences; + + LoginBloc({ + @required AuthRepository authRepository, + @required RxSharedPreferences rxSharedPreferences, + }) : assert(authRepository != null), + _authRepository = authRepository, + _rxSharedPreferences = rxSharedPreferences, + super(const LoginState()); + + @override + Stream mapEventToState( + LoginEvent event, + ) async* { + if (event is LoginEmailChanged) { + yield _mapEmailChangedToState(event, state); + } else if (event is LoginPasswordChanged) { + yield _mapPasswordChangedToState(event, state); + } else if (event is LoginSubmitted) { + yield* _mapLoginSubmittedToState(event, state); + } + } + + LoginState _mapEmailChangedToState( + LoginEmailChanged event, + LoginState state, + ) { + final email = Email.dirty(event.email); + return state.copyWith( + email: email, + status: Formz.validate([state.password, email]), + ); + } + + LoginState _mapPasswordChangedToState( + LoginPasswordChanged event, + LoginState state, + ) { + final password = Password.dirty(event.password); + return state.copyWith( + password: password, + status: Formz.validate([password, state.email]), + ); + } + + Stream _mapLoginSubmittedToState( + LoginSubmitted event, + LoginState state, + ) async* { + if (state.status.isValidated) { + yield state.copyWith(status: FormzStatus.submissionInProgress); + try { + String token = await _authRepository.login( + email: state.email.value, + password: state.password.value, + ); + _rxSharedPreferences.setString('TOKEN', token); + yield state.copyWith(status: FormzStatus.submissionSuccess); + } on Exception catch (_) { + yield state.copyWith(status: FormzStatus.submissionFailure); + } + } + } +} diff --git a/lib/pages/auth/login/bloc/login_event.dart b/lib/pages/auth/login/bloc/login_event.dart new file mode 100644 index 0000000..b8b1e29 --- /dev/null +++ b/lib/pages/auth/login/bloc/login_event.dart @@ -0,0 +1,30 @@ +part of 'login_bloc.dart'; + +abstract class LoginEvent extends Equatable { + const LoginEvent(); + + @override + List get props => []; +} + +class LoginEmailChanged extends LoginEvent { + final String email; + + const LoginEmailChanged({this.email}); + + @override + List get props => [this.email]; +} + +class LoginPasswordChanged extends LoginEvent { + final String password; + + const LoginPasswordChanged({this.password}); + + @override + List get props => [this.password]; +} + +class LoginSubmitted extends LoginEvent { + const LoginSubmitted(); +} diff --git a/lib/pages/auth/login/bloc/login_state.dart b/lib/pages/auth/login/bloc/login_state.dart new file mode 100644 index 0000000..51237a0 --- /dev/null +++ b/lib/pages/auth/login/bloc/login_state.dart @@ -0,0 +1,28 @@ +part of 'login_bloc.dart'; + +class LoginState extends Equatable { + const LoginState({ + this.status = FormzStatus.pure, + this.email = const Email.pure(), + this.password = const Password.pure(), + }); + + final FormzStatus status; + final Email email; + final Password password; + + LoginState copyWith({ + FormzStatus status, + Email email, + Password password, + }) { + return LoginState( + status: status ?? this.status, + email: email ?? this.email, + password: password ?? this.password, + ); + } + + @override + List get props => [status, email, password]; +} diff --git a/lib/pages/auth/login/login_form.dart b/lib/pages/auth/login/login_form.dart new file mode 100644 index 0000000..0ef9df1 --- /dev/null +++ b/lib/pages/auth/login/login_form.dart @@ -0,0 +1,167 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:ecommerce_flutter/pages/auth/login/bloc/login_bloc.dart'; +import 'package:ecommerce_flutter/pages/auth/signup/signup_page.dart'; +import 'package:ecommerce_flutter/pages/auth/widgets/already_have_an_account.dart'; +import 'package:ecommerce_flutter/pages/auth/widgets/auth_input.dart'; +import 'package:ecommerce_flutter/pages/routes/router.gr.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:formz/formz.dart'; + +class LoginForm extends StatelessWidget { + const LoginForm({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (BuildContext context, LoginState state) { + if (state.status.isSubmissionFailure) { + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + const SnackBar( + backgroundColor: Colors.red, + content: Text( + 'Login Failure', + style: TextStyle(color: Colors.white), + ), + ), + ); + } else if (state.status.isSubmissionSuccess) { + return ExtendedNavigator.of(context).push(Routes.homePage); + } + }, + child: ListView( + children: [ + Container( + height: MediaQuery.of(context).size.height * .3, + ), + Container( + height: MediaQuery.of(context).size.height * .7, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(88.0), + ), + ), + child: LayoutBuilder( + builder: + (BuildContext context, BoxConstraints viewportConstraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: Text( + 'Login', + style: Theme.of(context) + .textTheme + .headline3 + .copyWith(color: Colors.black), + ), + ), + BlocBuilder( + buildWhen: (previous, current) => + previous.email != current.email, + builder: (BuildContext context, state) { + return EmailInput( + validator: (value) { + return ''; + }, + labelText: 'Email', + onChanged: (email) => context + .bloc() + .add(LoginEmailChanged(email: email)), + hintText: 'test@gmail.com', + errorText: + state.email.invalid ? 'invalid email' : null, + ); + }, + ), + BlocBuilder( + buildWhen: (previous, current) => + previous.password != current.password, + builder: (BuildContext context, state) { + return PasswordInput( + onChanged: (password) => context + .bloc() + .add( + LoginPasswordChanged(password: password)), + hintText: '************', + labelText: 'Password', + validator: (value) { + return ''; + }, + errorText: state.password.invalid + ? 'invalid password' + : null, + ); + }, + ), + Padding( + padding: const EdgeInsets.only(top: 48.0), + child: BlocBuilder( + buildWhen: (previous, current) => + previous.status != current.status, + builder: (BuildContext context, state) { + return state.status.isSubmissionInProgress + ? CircularProgressIndicator() + : ButtonTheme( + minWidth: + MediaQuery.of(context).size.width * + .3, + height: 42, + child: RaisedButton( + key: const Key( + 'loginForm_continue_raisedButton'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0), + bottomRight: Radius.circular(10.0), + ), + ), + color: Colors.black87, + textColor: Colors.white, + child: Text('Login'), + onPressed: state.status.isValidated + ? () => context + .bloc() + .add(LoginSubmitted()) + : null, + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 40.0), + child: AlreadyHaveAnAccountCheck( + login: true, + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SignUpPage())), + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/auth/login/login_page.dart b/lib/pages/auth/login/login_page.dart new file mode 100644 index 0000000..d0eec42 --- /dev/null +++ b/lib/pages/auth/login/login_page.dart @@ -0,0 +1,18 @@ +import 'package:ecommerce_flutter/injection.dart'; +import 'package:ecommerce_flutter/pages/auth/login/bloc/login_bloc.dart'; +import 'package:ecommerce_flutter/pages/auth/login/login_form.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class LoginPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: BlocProvider( + create: (BuildContext context) => getIt(), + child: LoginForm()), + ); + } +} diff --git a/lib/pages/auth/signup/bloc/signup_bloc.dart b/lib/pages/auth/signup/bloc/signup_bloc.dart new file mode 100644 index 0000000..49bbac2 --- /dev/null +++ b/lib/pages/auth/signup/bloc/signup_bloc.dart @@ -0,0 +1,127 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:ecommerce_flutter/models/auth_model.dart'; +import 'package:ecommerce_flutter/repository/auth_repository.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:formz/formz.dart'; +import 'package:injectable/injectable.dart'; +import 'package:rx_shared_preferences/rx_shared_preferences.dart'; + +part 'signup_event.dart'; +part 'signup_state.dart'; + +@injectable +class SignupBloc extends Bloc { + final AuthRepository _authRepository; + final RxSharedPreferences _rxSharedPreferences; + + SignupBloc({ + @required RxSharedPreferences rxSharedPreferences, + @required AuthRepository authRepository, + }) : assert(authRepository != null), + _authRepository = authRepository, + _rxSharedPreferences = rxSharedPreferences, + super(const SignupState()); + + @override + Stream mapEventToState( + SignupEvent event, + ) async* { + if (event is SignupNameChanged) { + yield _mapNameChangedToState(event, state); + } else if (event is SignupEmailChanged) { + yield _mapEmailChangedToState(event, state); + } else if (event is SignupPasswordChanged) { + yield _mapPasswordChangedToState(event, state); + } else if (event is SignupConfirmPasswordChanged) { + yield _mapConfirmPasswordChangedToState(event, state); + } else if (event is SignupSubmitted) { + yield* _mapSignupSubmittedToState(event, state); + } + } + + SignupState _mapNameChangedToState( + SignupNameChanged event, + SignupState state, + ) { + final name = Name.dirty(event.name); + return state.copyWith( + name: name, + status: Formz.validate([ + state.password, + state.email, + state.confirmPassword, + name, + ]), + ); + } + + SignupState _mapEmailChangedToState( + SignupEmailChanged event, + SignupState state, + ) { + final email = Email.dirty(event.email); + return state.copyWith( + email: email, + status: Formz.validate([ + state.name, + state.password, + state.confirmPassword, + email, + ]), + ); + } + + SignupState _mapPasswordChangedToState( + SignupPasswordChanged event, + SignupState state, + ) { + final password = Password.dirty(event.password); + return state.copyWith( + password: password, + status: Formz.validate([ + password, + state.email, + state.confirmPassword, + state.name, + ]), + ); + } + + SignupState _mapConfirmPasswordChangedToState( + SignupConfirmPasswordChanged event, + SignupState state, + ) { + final confirmPassword = Password.dirty(event.confirmPassword); + return state.copyWith( + confirmPassword: confirmPassword, + status: Formz.validate([ + confirmPassword, + state.email, + state.password, + state.name, + ]), + ); + } + + Stream _mapSignupSubmittedToState( + SignupSubmitted event, + SignupState state, + ) async* { + if (state.status.isValidated) { + yield state.copyWith(status: FormzStatus.submissionInProgress); + try { + String token = await _authRepository.signup( + email: state.email.value, + password: state.password.value, + ); + _rxSharedPreferences.setString('TOKEN', token); + yield state.copyWith(status: FormzStatus.submissionSuccess); + } on Exception catch (_) { + yield state.copyWith(status: FormzStatus.submissionFailure); + } + } + } +} diff --git a/lib/pages/auth/signup/bloc/signup_event.dart b/lib/pages/auth/signup/bloc/signup_event.dart new file mode 100644 index 0000000..986ede1 --- /dev/null +++ b/lib/pages/auth/signup/bloc/signup_event.dart @@ -0,0 +1,48 @@ +part of 'signup_bloc.dart'; + +abstract class SignupEvent extends Equatable { + const SignupEvent(); + + @override + List get props => []; +} + +class SignupEmailChanged extends SignupEvent { + final String email; + + const SignupEmailChanged({this.email}); + + @override + List get props => [this.email]; +} + +class SignupPasswordChanged extends SignupEvent { + final String password; + + const SignupPasswordChanged({this.password}); + + @override + List get props => [this.password]; +} + +class SignupConfirmPasswordChanged extends SignupEvent { + final String confirmPassword; + + const SignupConfirmPasswordChanged({this.confirmPassword}); + + @override + List get props => [this.confirmPassword]; +} + +class SignupNameChanged extends SignupEvent { + final String name; + + const SignupNameChanged({this.name}); + + @override + List get props => [this.name]; +} + +class SignupSubmitted extends SignupEvent { + const SignupSubmitted(); +} diff --git a/lib/pages/auth/signup/bloc/signup_state.dart b/lib/pages/auth/signup/bloc/signup_state.dart new file mode 100644 index 0000000..cd0e689 --- /dev/null +++ b/lib/pages/auth/signup/bloc/signup_state.dart @@ -0,0 +1,36 @@ +part of 'signup_bloc.dart'; + +class SignupState extends Equatable { + const SignupState({ + this.status = FormzStatus.pure, + this.name = const Name.pure(), + this.email = const Email.pure(), + this.password = const Password.pure(), + this.confirmPassword = const Password.pure(), + }); + + final FormzStatus status; + final Name name; + final Email email; + final Password password; + final Password confirmPassword; + + SignupState copyWith({ + FormzStatus status, + Name name, + Email email, + Password password, + Password confirmPassword, + }) { + return SignupState( + status: status ?? this.status, + name: name ?? this.name, + email: email ?? this.email, + password: password ?? this.password, + confirmPassword: confirmPassword ?? this.confirmPassword, + ); + } + + @override + List get props => [status, name, email, password, confirmPassword]; +} diff --git a/lib/pages/auth/signup/signup_form.dart b/lib/pages/auth/signup/signup_form.dart new file mode 100644 index 0000000..c487dfe --- /dev/null +++ b/lib/pages/auth/signup/signup_form.dart @@ -0,0 +1,173 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:ecommerce_flutter/pages/auth/signup/bloc/signup_bloc.dart'; +import 'package:ecommerce_flutter/pages/auth/widgets/already_have_an_account.dart'; +import 'package:ecommerce_flutter/pages/auth/widgets/auth_input.dart'; +import 'package:ecommerce_flutter/pages/routes/router.gr.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:formz/formz.dart'; + +class SignupForm extends StatelessWidget { + const SignupForm({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final Size size = MediaQuery.of(context).size; + return BlocListener( + listener: (BuildContext context, SignupState state) { + if (state.status.isSubmissionFailure) { + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + const SnackBar( + backgroundColor: Colors.red, + content: Text( + 'Login Failure', + style: TextStyle(color: Colors.white), + ), + ), + ); + } else if (state.status.isSubmissionSuccess) { + return ExtendedNavigator.of(context).push(Routes.homePage); + } + }, + child: Container( + height: size.height * .875, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(88.0), + ), + ), + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints viewportConstraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: viewportConstraints.maxHeight), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BlocBuilder( + buildWhen: (previous, current) => + previous.name != current.name, + builder: (BuildContext context, state) { + return AuthInput( + hintText: 'John Doe', + labelText: 'Full name', + onChanged: (name) => context + .bloc() + .add(SignupNameChanged(name: name)), + validator: (value) { + return ''; + }, + errorText: state.name.invalid ? 'Invalid name' : null, + ); + }, + ), + BlocBuilder( + builder: (BuildContext context, state) { + return EmailInput( + validator: (value) { + return ''; + }, + labelText: 'Email', + onChanged: (email) => context + .bloc() + .add(SignupEmailChanged(email: email)), + hintText: 'test@gmail.com', + errorText: + state.email.invalid ? 'Invalid email' : null, + ); + }, + ), + BlocBuilder( + builder: (BuildContext context, state) { + return PasswordInput( + onChanged: (password) => context + .bloc() + .add(SignupPasswordChanged(password: password)), + hintText: '************', + labelText: 'Password', + validator: (value) { + return ''; + }, + errorText: state.password.invalid + ? 'Invalid Password' + : null, + ); + }, + ), + BlocBuilder( + builder: (BuildContext context, state) { + return PasswordInput( + onChanged: (confirmPassword) => context + .bloc() + .add(SignupConfirmPasswordChanged( + confirmPassword: confirmPassword)), + hintText: '************', + labelText: 'Confirm Password', + validator: (confirmPassword) { + return ''; + }, + errorText: state.confirmPassword.invalid + ? 'Password Mismatch' + : null, + ); + }, + ), + Padding( + padding: const EdgeInsets.only(top: 48.0), + child: BlocBuilder( + buildWhen: (previous, current) => + previous.status != current.status, + builder: (BuildContext context, state) { + return state.status.isSubmissionInProgress + ? CircularProgressIndicator() + : ButtonTheme( + minWidth: + MediaQuery.of(context).size.width * .3, + height: 42, + child: RaisedButton( + key: const Key( + 'signupForm_continue_raisedButton'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0), + bottomRight: Radius.circular(10.0), + ), + ), + color: Colors.black87, + textColor: Colors.white, + child: Text('Login'), + onPressed: state.status.isValidated + ? () => context + .bloc() + .add(SignupSubmitted()) + : null, + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 40.0), + child: AlreadyHaveAnAccountCheck( + login: false, + onPressed: () => Navigator.pop(context), + ), + ) + ], + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pages/auth/signup/signup_page.dart b/lib/pages/auth/signup/signup_page.dart new file mode 100644 index 0000000..a99cfdc --- /dev/null +++ b/lib/pages/auth/signup/signup_page.dart @@ -0,0 +1,39 @@ +import 'package:ecommerce_flutter/injection.dart'; +import 'package:ecommerce_flutter/pages/auth/signup/bloc/signup_bloc.dart'; +import 'package:ecommerce_flutter/pages/auth/signup/signup_form.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SignUpPage extends StatelessWidget { + const SignUpPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final Size size = MediaQuery.of(context).size; + return Scaffold( + backgroundColor: Colors.black, + appBar: PreferredSize( + preferredSize: Size.fromHeight(size.height * .125), + child: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + title: Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + 'Sign Up', + style: Theme.of(context) + .textTheme + .headline3 + .copyWith(color: Colors.white), + ), + ), + centerTitle: true, + ), + ), + body: BlocProvider( + create: (BuildContext context) => getIt(), + child: SignupForm()), + ); + } +} diff --git a/lib/pages/auth/widgets/already_have_an_account.dart b/lib/pages/auth/widgets/already_have_an_account.dart new file mode 100644 index 0000000..b833b37 --- /dev/null +++ b/lib/pages/auth/widgets/already_have_an_account.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class AlreadyHaveAnAccountCheck extends StatelessWidget { + final bool login; + + final VoidCallback onPressed; + + const AlreadyHaveAnAccountCheck({ + Key key, + @required this.login, + @required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + login ? "Don't have an Account? " : 'Already have an Account? ', + style: TextStyle(color: Colors.black87), + ), + GestureDetector( + onTap: onPressed, + child: Text( + login ? 'Sign Up' : 'Login', + style: TextStyle( + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/auth/widgets/auth_input.dart b/lib/pages/auth/widgets/auth_input.dart new file mode 100644 index 0000000..bf3d06d --- /dev/null +++ b/lib/pages/auth/widgets/auth_input.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; + +class AuthInput extends StatelessWidget { + final String hintText, labelText, errorText; + final ValueChanged onChanged; + final FormFieldValidator validator; + + const AuthInput({ + Key key, + @required this.hintText, + @required this.labelText, + @required this.onChanged, + @required this.validator, + @required this.errorText, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + left: 48.0, + right: 48.0, + top: 32.0, + ), + child: TextFormField( + decoration: InputDecoration( + hintText: hintText, + labelText: labelText, + errorText: errorText, + labelStyle: TextStyle(color: Colors.black54), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black54, width: .5), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black54, width: .5), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + onChanged: onChanged, + validator: validator, + ), + ); + } +} + +class PasswordInput extends StatelessWidget { + final String hintText, labelText, errorText; + final ValueChanged onChanged; + final FormFieldValidator validator; + + const PasswordInput({ + Key key, + @required this.hintText, + @required this.labelText, + @required this.onChanged, + @required this.validator, + @required this.errorText, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + left: 48.0, + right: 48.0, + top: 32.0, + ), + child: TextFormField( + key: const Key('loginForm_passwordInput_textField'), + decoration: InputDecoration( + hintText: hintText, + errorText: errorText, + labelText: labelText, + labelStyle: TextStyle(color: Colors.black54), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black54, width: .5), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black54, width: .5), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + obscureText: true, + onChanged: onChanged, + validator: validator, + ), + ); + } +} + +class EmailInput extends StatelessWidget { + final String hintText, labelText, errorText; + final ValueChanged onChanged; + final FormFieldValidator validator; + + const EmailInput({ + Key key, + @required this.hintText, + @required this.labelText, + @required this.onChanged, + @required this.validator, + @required this.errorText, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + left: 48.0, + right: 48.0, + top: 32.0, + ), + child: TextFormField( + key: const Key('loginForm_usernameInput_textField'), + decoration: InputDecoration( + hintText: hintText, + labelText: labelText, + errorText: errorText, + labelStyle: TextStyle(color: Colors.black54), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black54, width: .5), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black54, width: .5), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + onChanged: onChanged, + validator: validator, + ), + ); + } +} diff --git a/lib/pages/core/app.dart b/lib/pages/core/app.dart new file mode 100644 index 0000000..32e957f --- /dev/null +++ b/lib/pages/core/app.dart @@ -0,0 +1,29 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:ecommerce_flutter/injection.dart'; +import 'package:ecommerce_flutter/pages/auth/bloc/auth_bloc.dart'; +import 'package:ecommerce_flutter/pages/routes/router.gr.dart' as router; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class App extends StatelessWidget { + const App({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => + getIt()..add(AuthStatusRequested()), + child: MaterialApp( + title: 'Flutter Demo', + debugShowCheckedModeBanner: false, + theme: ThemeData( + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + builder: ExtendedNavigator.builder( + router: router.Router(), + ), + ), + ); + } +} diff --git a/lib/pages/core/splash_page.dart b/lib/pages/core/splash_page.dart new file mode 100644 index 0000000..8f8cd87 --- /dev/null +++ b/lib/pages/core/splash_page.dart @@ -0,0 +1,26 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:ecommerce_flutter/models/auth_enums.dart'; +import 'package:ecommerce_flutter/pages/auth/bloc/auth_bloc.dart'; +import 'package:ecommerce_flutter/pages/routes/router.gr.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SplashPage extends StatelessWidget { + const SplashPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (BuildContext context, state) { + if (state.status == AuthStatus.authenticated) { + return ExtendedNavigator.of(context).push(Routes.homePage); + } else { + return ExtendedNavigator.of(context).push(Routes.loginPage); + } + }, + child: Scaffold( + body: CircularProgressIndicator(), + ), + ); + } +} diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 534379f..87a11ed 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -7,3 +7,14 @@ Add bottom navigation bar with four tabs. And link with respective page. Add routes in main.dart */ + +import 'package:flutter/material.dart'; + +class HomePage extends StatelessWidget { + const HomePage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold(); + } +} diff --git a/lib/pages/routes/router.dart b/lib/pages/routes/router.dart new file mode 100644 index 0000000..960af95 --- /dev/null +++ b/lib/pages/routes/router.dart @@ -0,0 +1,16 @@ +import 'package:auto_route/auto_route_annotations.dart'; +import 'package:ecommerce_flutter/pages/auth/login/login_page.dart'; +import 'package:ecommerce_flutter/pages/auth/signup/signup_page.dart'; +import 'package:ecommerce_flutter/pages/core/splash_page.dart'; +import 'package:ecommerce_flutter/pages/home/home_page.dart'; + +@MaterialAutoRouter( + generateNavigationHelperExtension: true, + routes: [ + MaterialRoute(page: LoginPage), + MaterialRoute(page: SignUpPage), + MaterialRoute(page: HomePage), + MaterialRoute(page: SplashPage, initial: true), + ], +) +class $Router {} diff --git a/lib/pages/routes/router.gr.dart b/lib/pages/routes/router.gr.dart new file mode 100644 index 0000000..dfb5963 --- /dev/null +++ b/lib/pages/routes/router.gr.dart @@ -0,0 +1,81 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouteGenerator +// ************************************************************************** + +// ignore_for_file: public_member_api_docs + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../auth/login/login_page.dart'; +import '../auth/signup/signup_page.dart'; +import '../core/splash_page.dart'; +import '../home/home_page.dart'; + +class Routes { + static const String loginPage = '/login-page'; + static const String signUpPage = '/sign-up-page'; + static const String homePage = '/home-page'; + static const String splashPage = '/'; + static const all = { + loginPage, + signUpPage, + homePage, + splashPage, + }; +} + +class Router extends RouterBase { + @override + List get routes => _routes; + final _routes = [ + RouteDef(Routes.loginPage, page: LoginPage), + RouteDef(Routes.signUpPage, page: SignUpPage), + RouteDef(Routes.homePage, page: HomePage), + RouteDef(Routes.splashPage, page: SplashPage), + ]; + @override + Map get pagesMap => _pagesMap; + final _pagesMap = { + LoginPage: (data) { + return MaterialPageRoute( + builder: (context) => LoginPage(), + settings: data, + ); + }, + SignUpPage: (data) { + return MaterialPageRoute( + builder: (context) => const SignUpPage(), + settings: data, + ); + }, + HomePage: (data) { + return MaterialPageRoute( + builder: (context) => const HomePage(), + settings: data, + ); + }, + SplashPage: (data) { + return MaterialPageRoute( + builder: (context) => const SplashPage(), + settings: data, + ); + }, + }; +} + +/// ************************************************************************ +/// Navigation helper methods extension +/// ************************************************************************* + +extension RouterExtendedNavigatorStateX on ExtendedNavigatorState { + Future pushLoginPage() => push(Routes.loginPage); + + Future pushSignUpPage() => push(Routes.signUpPage); + + Future pushHomePage() => push(Routes.homePage); + + Future pushSplashPage() => push(Routes.splashPage); +} diff --git a/lib/repository/auth_repository.dart b/lib/repository/auth_repository.dart new file mode 100644 index 0000000..2fbf467 --- /dev/null +++ b/lib/repository/auth_repository.dart @@ -0,0 +1,54 @@ +import 'dart:convert' as convert; + +import 'package:ecommerce_flutter/injection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:injectable/injectable.dart'; +import 'package:rx_shared_preferences/rx_shared_preferences.dart'; + +@lazySingleton +class AuthRepository { + final http.Client httpClient; + + static const BASE_URL = "https://reqres.in/api/"; + + AuthRepository({this.httpClient}); + + Future login({ + @required String email, + @required String password, + }) async { + final loginUrl = "${BASE_URL}login"; + http.Response response = await this + .httpClient + .post(loginUrl, body: {'email': email, 'password': password}); + if (response.statusCode == 200) { + final Map jsonDecode = + convert.jsonDecode(response.body) as Map; + return jsonDecode['token']; + } else { + return null; + } + } + + Future signup({ + @required String email, + @required String password, + }) async { + final registerUrl = "${BASE_URL}register"; + http.Response response = await this + .httpClient + .post(registerUrl, body: {'email': email, 'password': password}); + if (response.statusCode == 200) { + final Map jsonDecode = + convert.jsonDecode(response.body) as Map; + return jsonDecode['token']; + } else { + return null; + } + } + + logout() { + getIt().dispose(); + } +} diff --git a/lib/utils/apis.dart b/lib/utils/apis.dart index 6358e19..fa6f7a6 100644 --- a/lib/utils/apis.dart +++ b/lib/utils/apis.dart @@ -6,5 +6,6 @@ Note: */ abstract class API { - static const BASE_URL = ""; + static const BASE_URL = "https://reqres.in/api/"; + static const REGISTER_URL = "${BASE_URL}register"; } diff --git a/lib/utils/injectable_modules.dart b/lib/utils/injectable_modules.dart new file mode 100644 index 0000000..8a2a6ad --- /dev/null +++ b/lib/utils/injectable_modules.dart @@ -0,0 +1,16 @@ +import 'package:http/http.dart' as http; +import 'package:injectable/injectable.dart'; +import 'package:rx_shared_preferences/rx_shared_preferences.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +@module +abstract class InjectableModules { + @lazySingleton + RxSharedPreferences get rxSharedPreference => RxSharedPreferences( + SharedPreferences.getInstance(), + const DefaultLogger(), + ); + + @lazySingleton + http.Client get httpClient => http.Client(); +} diff --git a/pubspec.lock b/pubspec.lock index f628010..0eff25c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,27 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.39.17" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" async: dependency: transitive description: @@ -8,6 +29,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.5.0-nullsafety.1" + auto_route: + dependency: "direct main" + description: + name: auto_route + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.7" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.8" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" boolean_selector: dependency: transitive description: @@ -15,6 +57,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0-nullsafety.1" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.11" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.2" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.1" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.0" characters: dependency: transitive description: @@ -29,6 +127,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0-nullsafety.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -36,6 +148,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0-nullsafety.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.1" collection: dependency: transitive description: @@ -43,6 +162,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0-nullsafety.3" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.2" cupertino_icons: dependency: "direct main" description: @@ -50,6 +190,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.6" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.5" fake_async: dependency: transitive description: @@ -57,16 +211,154 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0-nullsafety.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.11" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.6" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + formz: + dependency: "direct main" + description: + name: formz + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.4" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+4" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" + injectable: + dependency: "direct main" + description: + name: injectable + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + injectable_generator: + dependency: "direct dev" + description: + name: injectable_generator + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" matcher: dependency: transitive description: @@ -81,6 +373,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0-nullsafety.3" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" path: dependency: transitive description: @@ -88,11 +415,172 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0-nullsafety.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+1" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.2" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.13" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2+2" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + rx_shared_preferences: + dependency: "direct main" + description: + name: rx_shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.24.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.12" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2+2" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+10" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2+7" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.9" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+1" source_span: dependency: transitive description: @@ -114,6 +602,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0-nullsafety.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" string_scanner: dependency: transitive description: @@ -135,6 +630,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.19-nullsafety.2" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+2" typed_data: dependency: transitive description: @@ -149,5 +651,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0-nullsafety.3" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+15" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" sdks: dart: ">=2.10.0-110 <2.11.0" + flutter: ">=1.16.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 882f606..5fb92ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,20 +1,8 @@ name: ecommerce_flutter description: A new Flutter project. -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.0.0+1 environment: @@ -24,53 +12,24 @@ dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + formz: ^0.3.1 + http: ^0.12.2 + get_it: ^4.0.0 + equatable: ^1.2.5 + auto_route: ^0.6.7 + injectable: ^1.0.4 + flutter_bloc: ^6.0.6 cupertino_icons: ^1.0.0 + shared_preferences: ^0.5.12 + rx_shared_preferences: ^1.3.1 dev_dependencies: flutter_test: sdk: flutter + build_runner: + injectable_generator: ^1.0.4 + auto_route_generator: ^0.6.8 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart index 7ff3728..4e3831c 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,15 +5,14 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. +import 'package:ecommerce_flutter/pages/core/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:ecommerce_flutter/main.dart'; - void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(App()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);