From 30db638877a43cc919e7a1787f5188bdaef2a346 Mon Sep 17 00:00:00 2001 From: "Serik.Uvaissov" Date: Wed, 20 May 2020 19:11:17 +0600 Subject: [PATCH] MVVM with Provider --- .vscode/launch.json | 13 +++ assets/images/icon_large.png | Bin 0 -> 23213 bytes assets/images/logo.png | Bin 0 -> 11797 bytes assets/images/splash.png | Bin 0 -> 7728 bytes assets/lang/en.json | 4 + assets/lang/ru.json | 21 ++++ lib/app_routes.dart | 7 -- lib/core/base/base_model.dart | 5 + lib/core/base/base_service.dart | 13 +++ lib/core/base/base_view_model.dart | 49 ++++++++ lib/core/locator.dart | 19 +++ lib/core/logger.dart | 43 +++++++ lib/core/models/user.dart | 23 ++++ lib/core/providers.dart | 30 +++++ lib/core/route_names.dart | 3 + lib/core/router.dart | 61 ++++++++++ lib/core/services/ApiService.dart | 37 ++++++ lib/core/services/authentication_service.dart | 35 ++++++ lib/core/services/navigator_service.dart | 35 ++++++ lib/features/home/avatars_stack.dart | 31 ----- lib/features/home/game_list_item.dart | 72 ------------ lib/features/home/home_page.dart | 33 ------ lib/features/menu/bottom_nav_bar.dart | 84 ------------- lib/features/menu/main_menu.dart | 31 ----- lib/features/newgame/new_game.dart | 34 ------ lib/features/stub_screen.dart | 16 --- lib/main.dart | 108 +++-------------- lib/model/Court.dart | 6 - lib/model/Game.dart | 12 -- lib/model/Player.dart | 5 - lib/redux/actions.dart | 44 ------- lib/redux/app_state.dart | 46 -------- lib/redux/navigation_middleware.dart | 27 ----- lib/redux/reducers/app_reducer.dart | 14 --- lib/redux/reducers/games_reducer.dart | 11 -- lib/redux/reducers/loading_reducer.dart | 5 - lib/redux/reducers/navigation_reducer.dart | 24 ---- lib/redux/selectors.dart | 4 - lib/route_aware_widget.dart | 46 -------- lib/views/home/home_view.dart | 41 +++++++ lib/views/home/home_view_model.dart | 25 ++++ lib/views/login/login_view.dart | 22 ++++ lib/views/login/login_view_model.dart | 7 ++ pubspec.lock | 110 ++++++++++++++---- pubspec.yaml | 67 ++++------- test/widget_test.dart | 2 +- 46 files changed, 617 insertions(+), 708 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 assets/images/icon_large.png create mode 100644 assets/images/logo.png create mode 100644 assets/images/splash.png create mode 100644 assets/lang/en.json create mode 100644 assets/lang/ru.json delete mode 100644 lib/app_routes.dart create mode 100644 lib/core/base/base_model.dart create mode 100644 lib/core/base/base_service.dart create mode 100644 lib/core/base/base_view_model.dart create mode 100644 lib/core/locator.dart create mode 100644 lib/core/logger.dart create mode 100644 lib/core/models/user.dart create mode 100644 lib/core/providers.dart create mode 100644 lib/core/route_names.dart create mode 100644 lib/core/router.dart create mode 100644 lib/core/services/ApiService.dart create mode 100644 lib/core/services/authentication_service.dart create mode 100644 lib/core/services/navigator_service.dart delete mode 100644 lib/features/home/avatars_stack.dart delete mode 100644 lib/features/home/game_list_item.dart delete mode 100644 lib/features/home/home_page.dart delete mode 100644 lib/features/menu/bottom_nav_bar.dart delete mode 100644 lib/features/menu/main_menu.dart delete mode 100644 lib/features/newgame/new_game.dart delete mode 100644 lib/features/stub_screen.dart delete mode 100644 lib/model/Court.dart delete mode 100644 lib/model/Game.dart delete mode 100644 lib/model/Player.dart delete mode 100644 lib/redux/actions.dart delete mode 100644 lib/redux/app_state.dart delete mode 100644 lib/redux/navigation_middleware.dart delete mode 100644 lib/redux/reducers/app_reducer.dart delete mode 100644 lib/redux/reducers/games_reducer.dart delete mode 100644 lib/redux/reducers/loading_reducer.dart delete mode 100644 lib/redux/reducers/navigation_reducer.dart delete mode 100644 lib/redux/selectors.dart delete mode 100644 lib/route_aware_widget.dart create mode 100644 lib/views/home/home_view.dart create mode 100644 lib/views/home/home_view_model.dart create mode 100644 lib/views/login/login_view.dart create mode 100644 lib/views/login/login_view_model.dart diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3287bb6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Flutter", + "request": "launch", + "type": "dart" + } + ] +} \ No newline at end of file diff --git a/assets/images/icon_large.png b/assets/images/icon_large.png new file mode 100644 index 0000000000000000000000000000000000000000..eabb25a33fbfed10da853c8508a8806065d62674 GIT binary patch literal 23213 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 OSJ5`U^+wb2@&5r~1H7gH literal 0 HcmV?d00001 diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..eec3735221b812a9d1ca1d0537bc8136dff8ef2c GIT binary patch literal 11797 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 literal 0 HcmV?d00001 diff --git a/assets/images/splash.png b/assets/images/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..e7f4164e5bcf6006879135bbae3028b0dd5a5b66 GIT binary patch literal 7728 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 S?gE>*Ad|Zmcgk-)eD#0V4~j toMap(); +} \ No newline at end of file diff --git a/lib/core/base/base_service.dart b/lib/core/base/base_service.dart new file mode 100644 index 0000000..03b422c --- /dev/null +++ b/lib/core/base/base_service.dart @@ -0,0 +1,13 @@ +import 'package:logger/logger.dart'; + +import '../logger.dart'; + +class BaseService { + Logger log; + + BaseService({String title}) { + this.log = getLogger( + title ?? this.runtimeType.toString(), + ); + } +} \ No newline at end of file diff --git a/lib/core/base/base_view_model.dart b/lib/core/base/base_view_model.dart new file mode 100644 index 0000000..630c082 --- /dev/null +++ b/lib/core/base/base_view_model.dart @@ -0,0 +1,49 @@ +import 'package:flutter/foundation.dart'; +import 'package:logger/logger.dart'; +import '../logger.dart'; + +class BaseViewModel extends ChangeNotifier { + String _title; + bool _busy; + Logger log; + bool _isDisposed = false; + + BaseViewModel({ + bool busy = false, + String title, + }) : _busy = busy, + _title = title { + log = getLogger(title ?? this.runtimeType.toString()); + } + + bool get busy => this._busy; + bool get isDisposed => this._isDisposed; + String get title => _title ?? this.runtimeType.toString(); + + set busy(bool busy) { + log.i( + 'busy: ' + '$title is entering ' + '${busy ? 'busy' : 'free'} state', + ); + this._busy = busy; + notifyListeners(); + } + + @override + void notifyListeners() { + if (!isDisposed) { + super.notifyListeners(); + } else { + log.w('notifyListeners: Notify listeners called after ' + '${title ?? this.runtimeType.toString()} has been disposed'); + } + } + + @override + void dispose() { + log.i('dispose'); + _isDisposed = true; + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/core/locator.dart b/lib/core/locator.dart new file mode 100644 index 0000000..fba2128 --- /dev/null +++ b/lib/core/locator.dart @@ -0,0 +1,19 @@ +import '../core/services/ApiService.dart'; + +import '../core/logger.dart'; +import '../core/services/navigator_service.dart'; +import 'package:get_it/get_it.dart'; +import 'package:logger/logger.dart'; + +GetIt locator = GetIt.instance; + +class LocatorInjector { + static Logger _log = getLogger('LocatorInjector'); + + static Future setupLocator() async { + _log.d('Initializing Navigator Service'); + locator.registerLazySingleton(() => NavigatorService()); + _log.d('Initializing Api Service'); + locator.registerLazySingleton(() => ApiService()); + } +} \ No newline at end of file diff --git a/lib/core/logger.dart b/lib/core/logger.dart new file mode 100644 index 0000000..620f8fc --- /dev/null +++ b/lib/core/logger.dart @@ -0,0 +1,43 @@ +import 'dart:developer' as prefix0; +import 'package:logger/logger.dart'; + +class SimpleLogPrinter extends LogPrinter { + static int counter = 0; + final String className; + + SimpleLogPrinter(this.className); + + @override + List log(LogEvent event) { + prefix0.log( + event.message, + time: DateTime.now(), + level: () { + switch (event.level) { + case Level.verbose: + return 0; + case Level.debug: + return 500; + case Level.info: + return 0; + case Level.warning: + return 1500; + case Level.error: + return 2000; + case Level.wtf: + return 2000; + default: + return 2000; + } + }(), + name: className, + error: event.error, + sequenceNumber: counter += 1, + ); + return []; + } +} + +Logger getLogger(String className) { + return Logger(printer: SimpleLogPrinter(className)); +} \ No newline at end of file diff --git a/lib/core/models/user.dart b/lib/core/models/user.dart new file mode 100644 index 0000000..b632465 --- /dev/null +++ b/lib/core/models/user.dart @@ -0,0 +1,23 @@ +class User { + final String id; + final String fullName; + final String email; + final String userRole; + + User({this.id, this.fullName, this.email, this.userRole}); + + User.fromData(Map data) + : id = data['id'], + fullName = data['fullName'], + email = data['email'], + userRole = data['userRole']; + + Map toJson() { + return { + 'id': id, + 'fullName': fullName, + 'email': email, + 'userRole': userRole, + }; + } +} diff --git a/lib/core/providers.dart b/lib/core/providers.dart new file mode 100644 index 0000000..92bf4ad --- /dev/null +++ b/lib/core/providers.dart @@ -0,0 +1,30 @@ +import 'package:provider/single_child_widget.dart'; + +import '../core/locator.dart'; +import '../core/services/navigator_service.dart'; +import 'package:provider/provider.dart'; + +import 'services/ApiService.dart'; +import 'services/authentication_service.dart'; + +class ProviderInjector { + static List providers = [ + ..._independentServices, + ..._dependentServices, + ..._consumableServices, + ]; + + static List _independentServices = [ + Provider.value(value: locator()), + Provider.value(value: locator()), + ]; + + static List _dependentServices = [ + ProxyProvider( + update: (context, api, authenticationService) => + AuthenticationService(api: api), + ) + ]; + + static List _consumableServices = []; +} \ No newline at end of file diff --git a/lib/core/route_names.dart b/lib/core/route_names.dart new file mode 100644 index 0000000..cd86836 --- /dev/null +++ b/lib/core/route_names.dart @@ -0,0 +1,3 @@ +const String LoginViewRoute = "LoginView"; +const String HomeViewRoute = "HomeView"; +// Generate the views here diff --git a/lib/core/router.dart b/lib/core/router.dart new file mode 100644 index 0000000..e6ee8c7 --- /dev/null +++ b/lib/core/router.dart @@ -0,0 +1,61 @@ +import './route_names.dart'; +import 'package:aman_kassa_flutter/views/home/home_view.dart'; +import 'package:aman_kassa_flutter/views/login/login_view.dart'; +import 'package:flutter/material.dart'; + +Route generateRoute(RouteSettings settings) { + switch (settings.name) { + case LoginViewRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: LoginView(), + ); + case HomeViewRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: HomeView(), + ); + // case AddAndEditViewRoute: + // var documentToEdit = settings.arguments as Document; + // return SlideRightRoute(widget:AddAndEditView( + // edittingDocument: documentToEdit, + // )); + default: + return MaterialPageRoute( + builder: (_) => Scaffold( + body: Center( + child: Text('No route defined for ${settings.name}')), + )); + } +} + +PageRoute _getPageRoute({String routeName, Widget viewToShow}) { + return MaterialPageRoute( + settings: RouteSettings( + name: routeName, + ), + builder: (_) => viewToShow); +} + +class SlideRightRoute extends PageRouteBuilder { + final Widget widget; + SlideRightRoute({this.widget}) + : super( + pageBuilder: (BuildContext context, Animation animation, + Animation secondaryAnimation) { + return widget; + }, + transitionsBuilder: (BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return new SlideTransition( + position: new Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: child, + ); + }, + ); +} diff --git a/lib/core/services/ApiService.dart b/lib/core/services/ApiService.dart new file mode 100644 index 0000000..43b5e32 --- /dev/null +++ b/lib/core/services/ApiService.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +import 'package:aman_kassa_flutter/core/base/base_service.dart'; +import 'package:aman_kassa_flutter/core/models/user.dart'; +import 'package:http/http.dart' as http; + +/// The service responsible for networking requests +class ApiService extends BaseService { + static const endpoint = 'https://jsonplaceholder.typicode.com'; + + var client = new http.Client(); + + Future getUserProfile(int userId) async { + // Get user profile for id + var response = await client.get('$endpoint/users/$userId'); + + // Convert and return + return User.fromData(json.decode(response.body)); + } + + // Future> getPostsForUser(int userId) async { + // var posts = List(); + // // Get user posts for id + // var response = await client.get('$endpoint/posts?userId=$userId'); + + // // parse into List + // var parsed = json.decode(response.body) as List; + + // // loop and convert each item to Post + // for (var post in parsed) { + // posts.add(Post.fromJson(post)); + // } + + // return posts; + // } + +} \ No newline at end of file diff --git a/lib/core/services/authentication_service.dart b/lib/core/services/authentication_service.dart new file mode 100644 index 0000000..2e2063f --- /dev/null +++ b/lib/core/services/authentication_service.dart @@ -0,0 +1,35 @@ + +import 'package:aman_kassa_flutter/core/base/base_service.dart'; +import 'package:aman_kassa_flutter/core/models/user.dart'; +import 'package:flutter/foundation.dart'; + +import 'ApiService.dart'; + +class AuthenticationService extends BaseService { + + final ApiService _api; + + AuthenticationService({ApiService api}) : _api = api; + + User _currentUser; + User get currentUser => _currentUser; + + Future loginWithEmail({ + @required String email, + @required String password, + }) async { + try { + User result = await _api.getUserProfile(123); + _currentUser = result; + return result != null; + } catch (e) { + return e.message; + } + } + + // Future isUserLoggedIn() async { + // var user = await _firebaseAuth.currentUser(); + // await _populateCurrentUser(user); + // return user != null; + // } +} diff --git a/lib/core/services/navigator_service.dart b/lib/core/services/navigator_service.dart new file mode 100644 index 0000000..7e94494 --- /dev/null +++ b/lib/core/services/navigator_service.dart @@ -0,0 +1,35 @@ +import '../../core/base/base_service.dart'; +import 'package:flutter/material.dart'; + +class NavigatorService extends BaseService { + final GlobalKey navigatorKey = GlobalKey(); + + Future navigateToPage(MaterialPageRoute pageRoute) async { + log.i('navigateToPage: pageRoute: ${pageRoute.settings.name}'); + if (navigatorKey.currentState == null) { + log.e('navigateToPage: Navigator State is null'); + return null; + } + return navigatorKey.currentState.push(pageRoute); + } + + Future navigateToPageWithReplacement( + MaterialPageRoute pageRoute) async { + log.i('navigateToPageWithReplacement: ' + 'pageRoute: ${pageRoute.settings.name}'); + if (navigatorKey.currentState == null) { + log.e('navigateToPageWithReplacement: Navigator State is null'); + return null; + } + return navigatorKey.currentState.pushReplacement(pageRoute); + } + + void pop([T result]) { + log.i('goBack:'); + if (navigatorKey.currentState == null) { + log.e('goBack: Navigator State is null'); + return; + } + navigatorKey.currentState.pop(result); + } +} \ No newline at end of file diff --git a/lib/features/home/avatars_stack.dart b/lib/features/home/avatars_stack.dart deleted file mode 100644 index b977875..0000000 --- a/lib/features/home/avatars_stack.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; - -class AvatarsStack extends StatelessWidget { - final List names; - - AvatarsStack(this.names); - - @override - Widget build(BuildContext context) { - final avatars = List(); - final max = names.length - 1 > 8 ? 8 : names.length - 1; - for (num i = max; i >= 0; i--) { - avatars.add(Positioned( - right: (5.0 + (25 - i) * i), - child: CircleAvatar( - backgroundColor: (i % 2 == 0 ? Colors.yellow : Colors.grey), - radius: 20, - child: Text(names[i].substring(0, 1), - style: TextStyle(fontSize: 30)), - ), - )); - } - - return Container( - height: 40, - child: Stack( - children: avatars, - ), - ); - } -} diff --git a/lib/features/home/game_list_item.dart b/lib/features/home/game_list_item.dart deleted file mode 100644 index 36f18be..0000000 --- a/lib/features/home/game_list_item.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:aman_kassa_flutter/features/home/avatars_stack.dart'; -import 'package:aman_kassa_flutter/model/Game.dart'; - -class GameListItem extends StatelessWidget { - final Game game; - - const GameListItem(this.game, {Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.all(5.0), - child: Center( - child: Card( - child: IntrinsicHeight( - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - padding: EdgeInsets.all(10.0), - decoration: BoxDecoration( - color: Colors.grey.shade900, - borderRadius: BorderRadius.horizontal( - left: Radius.circular(4.0), - right: Radius.elliptical(15.0, 25.0))), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - 'DateFormat', - style: TextStyle(color: Colors.white, fontSize: 20), - ), - ), - Text( - 'DateToday', - style: TextStyle(color: Colors.white), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text( - '${game.where}', - style: TextStyle(fontSize: 20), - ), - Row( - children: [ - Text('aaa') - ], - ), - ], - ), - ), - Expanded( - child: AvatarsStack( - game.players.map((player) => player.name).toList())), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/features/home/home_page.dart b/lib/features/home/home_page.dart deleted file mode 100644 index 685afde..0000000 --- a/lib/features/home/home_page.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:aman_kassa_flutter/features/home/game_list_item.dart'; -import 'package:aman_kassa_flutter/features/menu/main_menu.dart'; -//import 'package:aman_kassa_flutter/testdata/test_data.dart'; - -class HomePage extends StatefulWidget { - HomePage({Key key}) : super(key: key); - - @override - _HomePageState createState() => _HomePageState(); -} - -class _HomePageState extends State { - List _games; - - _HomePageState() { - //var games = [TestData.getRandomGames(5); - // games.sort((a, b) => a.date.compareTo(b.date)); - // _games = games.map((game) => GameListItem(game)) - // .toList()]; - _games=[]; - } - - Widget _getBody() => ListView.builder( - itemBuilder: (BuildContext context, int index) => _games[index], - itemCount: _games.length, - ); - - @override - Widget build(BuildContext context) { - return MainMenu(_getBody()); - } -} diff --git a/lib/features/menu/bottom_nav_bar.dart b/lib/features/menu/bottom_nav_bar.dart deleted file mode 100644 index 84463a9..0000000 --- a/lib/features/menu/bottom_nav_bar.dart +++ /dev/null @@ -1,84 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:redux/redux.dart'; -import 'package:aman_kassa_flutter/app_routes.dart'; -import 'package:aman_kassa_flutter/redux/actions.dart'; -import 'package:aman_kassa_flutter/redux/app_state.dart'; -import 'package:aman_kassa_flutter/redux/selectors.dart'; - -class BottomNavBar extends StatelessWidget { - Widget _addPadding(Widget child) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: child, - ); - - Widget _getMenuItem(BuildContext context, - {Icon icon, String routeName, @required _ViewModel vm}) { - if (!vm.route.contains(routeName)) - return _addPadding( - IconButton(icon: icon, onPressed: () => vm.navigate(routeName))); - else - return _addPadding(IconButton( - icon: icon, - onPressed: () => vm.navigate(routeName), - color: Theme.of(context).accentColor.withOpacity(0.7))); - } - - @override - Widget build(BuildContext context) { - return BottomAppBar( - notchMargin: 8, - color: Theme.of(context).primaryColor, - shape: CircularNotchedRectangle(), - child: StoreConnector( - distinct: true, - converter: (store) => _ViewModel.fromStore(store), - builder: (context, vm) => Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _getMenuItem(context, - icon: Icon(Icons.home), routeName: AppRoutes.home, vm: vm), - _getMenuItem(context, - icon: Icon(Icons.history), - routeName: AppRoutes.history, - vm: vm), - SizedBox(width: 16), - _getMenuItem(context, - icon: Icon(Icons.monetization_on), - routeName: AppRoutes.money, - vm: vm), - _getMenuItem(context, - icon: Icon(Icons.supervised_user_circle), - routeName: AppRoutes.profile, - vm: vm), - ], - ), - ), - ); - } -} - -class _ViewModel { - final List route; - final Function(String) navigate; - - _ViewModel({@required this.route, @required this.navigate}); - - static _ViewModel fromStore(Store store) { - return _ViewModel( - route: currentRoute(store.state), - navigate: (routeName) => - store.dispatch(NavigateReplaceAction(routeName))); - } - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _ViewModel && - runtimeType == other.runtimeType && - route == other.route; - - @override - int get hashCode => route.hashCode; -} diff --git a/lib/features/menu/main_menu.dart b/lib/features/menu/main_menu.dart deleted file mode 100644 index 14922a3..0000000 --- a/lib/features/menu/main_menu.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:aman_kassa_flutter/app_routes.dart'; -import 'package:aman_kassa_flutter/features/menu/bottom_nav_bar.dart'; -import 'package:aman_kassa_flutter/redux/actions.dart'; -import 'package:aman_kassa_flutter/redux/app_state.dart'; - -class MainMenu extends StatelessWidget { - final Widget body; - - MainMenu(this.body); - - Widget _getInfoBarWorkaround() => - PreferredSize(child: Container(), preferredSize: Size(0.0, 0.0)); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: _getInfoBarWorkaround(), - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - bottomNavigationBar: BottomNavBar(), - floatingActionButton: FloatingActionButton( - onPressed: () => StoreProvider.of(context) - .dispatch(NavigatePushAction(AppRoutes.addGame)), - tooltip: 'Add new game', - child: Icon(Icons.add), - ), - body: body, - ); - } -} diff --git a/lib/features/newgame/new_game.dart b/lib/features/newgame/new_game.dart deleted file mode 100644 index 2eba8be..0000000 --- a/lib/features/newgame/new_game.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; - -class NewGame extends StatelessWidget { - Widget _getBody(BuildContext context) => Center( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text('New Game', style: TextStyle(fontSize: 20, color: Colors.white),), - ), - RaisedButton( - child: Text('Show Alert'), - onPressed: () { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0))), - title: Text('Alert Title'), - content: Text('Content of alert.'))); - }) - ], - ), - )); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Add Game')), - body: _getBody(context), - ); - } -} diff --git a/lib/features/stub_screen.dart b/lib/features/stub_screen.dart deleted file mode 100644 index 907f877..0000000 --- a/lib/features/stub_screen.dart +++ /dev/null @@ -1,16 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:aman_kassa_flutter/features/menu/main_menu.dart'; - -class StubScreen extends StatelessWidget { - - Widget _getBody() => Center( - child: Text('Stub Screen'), - ); - - @override - Widget build(BuildContext context) { - return MainMenu(_getBody()); - } - -} diff --git a/lib/main.dart b/lib/main.dart index 6c00a6c..881541a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,98 +1,28 @@ +import 'package:aman_kassa_flutter/views/login/login_view.dart'; + +import 'core/locator.dart'; +import 'core/providers.dart'; +import 'core/router.dart'; +import 'core/services/navigator_service.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:redux/redux.dart'; -import 'package:aman_kassa_flutter/app_routes.dart'; -import 'package:aman_kassa_flutter/features/home/home_page.dart'; -import 'package:aman_kassa_flutter/features/newgame/new_game.dart'; -import 'package:aman_kassa_flutter/features/stub_screen.dart'; -import 'package:aman_kassa_flutter/redux/app_state.dart'; -import 'package:aman_kassa_flutter/redux/navigation_middleware.dart'; -import 'package:aman_kassa_flutter/redux/reducers/app_reducer.dart'; -import 'package:aman_kassa_flutter/route_aware_widget.dart'; +import 'package:provider/provider.dart'; +import 'views/home/home_view.dart'; -void main() => runApp(MyApp()); - -final GlobalKey navigatorKey = new GlobalKey(); - -class MyApp extends StatelessWidget { - final store = Store(appReducer, - initialState: AppState.loading(), - middleware: createNavigationMiddleware()); - - final theme = ThemeData( - primaryColor: Colors.grey.shade900, - //primaryColorLight: Colors.grey.shade800, - //primaryColorDark: Colors.black, - //scaffoldBackgroundColor: Colors.grey.shade800, -// textTheme: TextTheme( -// body1: TextStyle(color: Colors.white), -// display1: TextStyle(color: Colors.white), -// title: TextStyle(color: Colors.white), -// ), - //iconTheme: IconThemeData(color: Colors.white), - //accentColor: Colors.yellow[500], - ); - - MaterialPageRoute _getRoute(RouteSettings settings) { - switch (settings.name) { - case AppRoutes.home: - return MainRoute(HomePage(), settings: settings); - case AppRoutes.addGame: - return FabRoute(NewGame(), settings: settings); - default: - return MainRoute(StubScreen(), settings: settings); - } - } +void main() async { + await LocatorInjector.setupLocator(); + runApp(MainApplication()); +} +class MainApplication extends StatelessWidget { @override Widget build(BuildContext context) { - return StoreProvider( - store: store, + return MultiProvider( + providers: ProviderInjector.providers.toList(), child: MaterialApp( - navigatorKey: navigatorKey, - navigatorObservers: [routeObserver], - title: 'AppLocalizations.appTitle', - localizationsDelegates: [ - //AppLocalizationsDelegate(), - ], - theme: theme, - onGenerateRoute: (RouteSettings settings) => _getRoute(settings), + navigatorKey: locator().navigatorKey, + home: HomeView(), + onGenerateRoute: generateRoute, ), ); } -} - -class MainRoute extends MaterialPageRoute { - MainRoute(Widget widget, {RouteSettings settings}) - : super( - builder: (_) => RouteAwareWidget(child: widget), - settings: settings); - - @override - Widget buildTransitions(BuildContext context, Animation animation, - Animation secondaryAnimation, Widget child) { - if (settings.isInitialRoute) return child; - // Fades between routes. (If you don't want any animation, - // just return child.) - return FadeTransition(opacity: animation, child: child); - } -} - -class FabRoute extends MaterialPageRoute { - FabRoute(Widget widget, {RouteSettings settings}) - : super( - builder: (_) => RouteAwareWidget(child: widget), - settings: settings); - - @override - Widget buildTransitions(BuildContext context, Animation animation, - Animation secondaryAnimation, Widget child) { - if (settings.isInitialRoute) return child; - return SlideTransition( - position: new Tween( - begin: const Offset(0.0, 1.0), - end: Offset.zero, - ).animate(animation), - child: child); - } -} +} \ No newline at end of file diff --git a/lib/model/Court.dart b/lib/model/Court.dart deleted file mode 100644 index 9bf9906..0000000 --- a/lib/model/Court.dart +++ /dev/null @@ -1,6 +0,0 @@ -class Court { - final num number; - final bool wasReserved; - - Court({this.number, this.wasReserved}); -} diff --git a/lib/model/Game.dart b/lib/model/Game.dart deleted file mode 100644 index e10f158..0000000 --- a/lib/model/Game.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:aman_kassa_flutter/model/Court.dart'; -import 'package:aman_kassa_flutter/model/Player.dart'; - -class Game { - final String where; - final DateTime date; - final List courts; - final List players; - - Game({@required this.where, @required this.date, this.players, this.courts}); -} diff --git a/lib/model/Player.dart b/lib/model/Player.dart deleted file mode 100644 index 7ba20fd..0000000 --- a/lib/model/Player.dart +++ /dev/null @@ -1,5 +0,0 @@ -class Player { - final String name; - - Player(this.name); -} diff --git a/lib/redux/actions.dart b/lib/redux/actions.dart deleted file mode 100644 index 18d6ec5..0000000 --- a/lib/redux/actions.dart +++ /dev/null @@ -1,44 +0,0 @@ - - -import 'package:aman_kassa_flutter/model/Game.dart'; - -class AddGameAction { - final Game game; - - AddGameAction(this.game); - - @override - String toString() { - return 'AddGameAction{game: $game}'; - } -} - -class NavigateReplaceAction { - final String routeName; - - NavigateReplaceAction(this.routeName); - - @override - String toString() { - return 'MainMenuNavigateAction{routeName: $routeName}'; - } -} - -class NavigatePushAction { - final String routeName; - - NavigatePushAction(this.routeName); - - @override - String toString() { - return 'NavigatePushAction{routeName: $routeName}'; - } -} - -class NavigatePopAction { - - @override - String toString() { - return 'NavigatePopAction'; - } -} diff --git a/lib/redux/app_state.dart b/lib/redux/app_state.dart deleted file mode 100644 index 00e5a47..0000000 --- a/lib/redux/app_state.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:aman_kassa_flutter/app_routes.dart'; -import 'package:aman_kassa_flutter/model/Game.dart'; - -@immutable -class AppState { - final bool isLoading; - final List games; - final List route; - - AppState({ - this.isLoading = false, - this.games = const [], - this.route = const [AppRoutes.home], - }); - - factory AppState.loading() => AppState(isLoading: true); - - AppState copyWith({ - bool isLoading, - List games, - }) => - AppState( - isLoading: isLoading ?? this.isLoading, - games: games ?? this.games, - route: route ?? this.route - ); - - @override - int get hashCode => - isLoading.hashCode ^ games.hashCode ^ route.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is AppState && - runtimeType == other.runtimeType && - isLoading == other.isLoading && - games == other.games && - route == other.route; - - @override - String toString() { - return 'AppState{isLoading: $isLoading, games: $games, route: $route}'; - } -} diff --git a/lib/redux/navigation_middleware.dart b/lib/redux/navigation_middleware.dart deleted file mode 100644 index 709e880..0000000 --- a/lib/redux/navigation_middleware.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:redux/redux.dart'; -import 'package:aman_kassa_flutter/main.dart'; -import 'package:aman_kassa_flutter/redux/actions.dart'; -import 'package:aman_kassa_flutter/redux/app_state.dart'; - -List> createNavigationMiddleware() { - return [ - TypedMiddleware(_navigateReplace), - TypedMiddleware(_navigate), - ]; -} - -_navigateReplace(Store store, action, NextDispatcher next) { - final routeName = (action as NavigateReplaceAction).routeName; - if (store.state.route.last != routeName) { - navigatorKey.currentState.pushReplacementNamed(routeName); - } - next(action); //This need to be after name checks -} - -_navigate(Store store, action, NextDispatcher next) { - final routeName = (action as NavigatePushAction).routeName; - if (store.state.route.last != routeName) { - navigatorKey.currentState.pushNamed(routeName); - } - next(action); //This need to be after name checks -} diff --git a/lib/redux/reducers/app_reducer.dart b/lib/redux/reducers/app_reducer.dart deleted file mode 100644 index 224f23d..0000000 --- a/lib/redux/reducers/app_reducer.dart +++ /dev/null @@ -1,14 +0,0 @@ - - -import 'package:aman_kassa_flutter/redux/app_state.dart'; -import 'package:aman_kassa_flutter/redux/reducers/games_reducer.dart'; -import 'package:aman_kassa_flutter/redux/reducers/loading_reducer.dart'; -import 'package:aman_kassa_flutter/redux/reducers/navigation_reducer.dart'; - -AppState appReducer(AppState state, action) { - return AppState( - isLoading: loadingReducer(state.isLoading, action), - games: gamesReducer(state.games, action), - route: navigationReducer(state.route, action) - ); -} diff --git a/lib/redux/reducers/games_reducer.dart b/lib/redux/reducers/games_reducer.dart deleted file mode 100644 index 0b38fca..0000000 --- a/lib/redux/reducers/games_reducer.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:redux/redux.dart'; -import 'package:aman_kassa_flutter/model/Game.dart'; -import 'package:aman_kassa_flutter/redux/actions.dart'; - -final gamesReducer = combineReducers>([ - TypedReducer, AddGameAction>(_addGame), -]); - -List _addGame(List games, AddGameAction action) { - return List.from(games)..add(action.game); -} diff --git a/lib/redux/reducers/loading_reducer.dart b/lib/redux/reducers/loading_reducer.dart deleted file mode 100644 index f494b67..0000000 --- a/lib/redux/reducers/loading_reducer.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:redux/redux.dart'; - -final loadingReducer = combineReducers([ -]); - diff --git a/lib/redux/reducers/navigation_reducer.dart b/lib/redux/reducers/navigation_reducer.dart deleted file mode 100644 index 9239234..0000000 --- a/lib/redux/reducers/navigation_reducer.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:redux/redux.dart'; -import 'package:aman_kassa_flutter/redux/actions.dart'; - -final navigationReducer = combineReducers>([ - TypedReducer, NavigateReplaceAction>(_navigateReplace), - TypedReducer, NavigatePushAction>(_navigatePush), - TypedReducer, NavigatePopAction>(_navigatePop), -]); - -List _navigateReplace( - List route, NavigateReplaceAction action) => - [action.routeName]; - -List _navigatePush(List route, NavigatePushAction action) { - var result = List.from(route); - result.add(action.routeName); - return result; -} - -List _navigatePop(List route, NavigatePopAction action) { - var result = List.from(route); - result.removeLast(); - return result; -} diff --git a/lib/redux/selectors.dart b/lib/redux/selectors.dart deleted file mode 100644 index e3761cd..0000000 --- a/lib/redux/selectors.dart +++ /dev/null @@ -1,4 +0,0 @@ - -import './app_state.dart'; - -List currentRoute(AppState state) => state.route; diff --git a/lib/route_aware_widget.dart b/lib/route_aware_widget.dart deleted file mode 100644 index 678feb4..0000000 --- a/lib/route_aware_widget.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:aman_kassa_flutter/redux/actions.dart'; -import 'package:aman_kassa_flutter/redux/app_state.dart'; - -final RouteObserver routeObserver = RouteObserver(); - -class RouteAwareWidget extends StatefulWidget { - final Widget child; - - RouteAwareWidget({this.child}); - - State createState() => RouteAwareWidgetState(child: child); -} - -class RouteAwareWidgetState extends State with RouteAware { - final Widget child; - - RouteAwareWidgetState({this.child}); - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - routeObserver.subscribe(this, ModalRoute.of(context)); - } - - @override - void dispose() { - routeObserver.unsubscribe(this); - super.dispose(); - } - - @override - void didPush() { - // Route was pushed onto navigator and is now topmost route. - } - - @override - void didPopNext() { - // Covering route was popped off the navigator. - StoreProvider.of(context).dispatch(NavigatePopAction()); - } - - @override - Widget build(BuildContext context) => Container(child: child); -} diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart new file mode 100644 index 0000000..122697a --- /dev/null +++ b/lib/views/home/home_view.dart @@ -0,0 +1,41 @@ +library home_view; + +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'home_view_model.dart'; + +class HomeView extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => HomeViewModel(), + onModelReady: (viewModel) { + //viewModel.busy = true; + }, + builder: (context, viewModel, child) { + return buildWaitingLogo(context, viewModel, child); + }); + } + + Widget buildWaitingLogo( + BuildContext context, HomeViewModel viewModel, Widget child) => + new Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 300, + height: 100, + child: Image.asset('assets/images/icon_large.png'), + ), + CircularProgressIndicator( + strokeWidth: 3, + valueColor: AlwaysStoppedAnimation( + Colors.yellow[300], + ), + ) + ], + ), + )); +} diff --git a/lib/views/home/home_view_model.dart b/lib/views/home/home_view_model.dart new file mode 100644 index 0000000..4a6710d --- /dev/null +++ b/lib/views/home/home_view_model.dart @@ -0,0 +1,25 @@ +import 'package:aman_kassa_flutter/core/base/base_view_model.dart'; +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/route_names.dart'; +import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; +import 'package:aman_kassa_flutter/views/login/login_view.dart'; +import 'package:flutter/material.dart'; + +class HomeViewModel extends BaseViewModel { + final NavigatorService _navigationService = locator(); + int _counter; + + HomeViewModel({int counter = 0}) : this._counter = counter; + + int get counter => this._counter; + set counter(int value) { + this._counter = value; + notifyListeners(); + } + + void increment() => this.counter += 1; + + void goToLogin() { + _navigationService.navigateToPage(MaterialPageRoute(builder: (context) => LoginView())); + } +} \ No newline at end of file diff --git a/lib/views/login/login_view.dart b/lib/views/login/login_view.dart new file mode 100644 index 0000000..6545751 --- /dev/null +++ b/lib/views/login/login_view.dart @@ -0,0 +1,22 @@ +library login_view; + +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'login_view_model.dart'; + +class LoginView extends StatelessWidget { + @override + Widget build(BuildContext context) { + //LoginViewModel viewModel = LoginViewModel(); + return ViewModelBuilder.reactive( + viewModelBuilder: () => LoginViewModel(), + onModelReady: (viewModel) { + // Do something once your viewModel is initialized + }, + builder: (context, viewModel, child) { + return Scaffold( + body: Center(child: Text('LoginMobile')), + ); + }); + } +} diff --git a/lib/views/login/login_view_model.dart b/lib/views/login/login_view_model.dart new file mode 100644 index 0000000..087f76d --- /dev/null +++ b/lib/views/login/login_view_model.dart @@ -0,0 +1,7 @@ +import 'package:aman_kassa_flutter/core/base/base_view_model.dart'; + +class LoginViewModel extends BaseViewModel { + LoginViewModel(); + + // Add ViewModel specific code here +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 84dfaf5..9caeade 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,42 +7,42 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.13" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.4.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" convert: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" cupertino_icons: dependency: "direct main" description: @@ -64,30 +64,58 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_redux: - dependency: "direct main" - description: - name: flutter_redux - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" image: dependency: transitive description: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.12" + logger: + dependency: "direct main" + description: + name: logger + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.1" matcher: dependency: transitive description: @@ -102,6 +130,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.8" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" + observable_ish: + dependency: transitive + description: + name: observable_ish + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" path: dependency: transitive description: @@ -123,20 +165,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.4.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.2" + provider_architecture: + dependency: "direct main" + description: + name: provider_architecture + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1+1" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" - redux: + version: "2.1.3" + responsive_builder: dependency: "direct main" description: - name: redux + name: responsive_builder url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "0.1.9" sky_engine: dependency: transitive description: flutter @@ -148,7 +204,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" stack_trace: dependency: transitive description: @@ -156,6 +212,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.3" + stacked: + dependency: "direct main" + description: + name: stacked + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" stream_channel: dependency: transitive description: @@ -183,7 +246,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" typed_data: dependency: transitive description: @@ -204,6 +267,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "3.6.1" sdks: - dart: ">=2.4.0 <3.0.0" + dart: ">=2.7.0 <3.0.0" + flutter: ">=1.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 46769ae..9d452e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,59 +1,40 @@ name: aman_kassa_flutter description: A new Flutter project. - -# 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: - sdk: ">=2.1.0 <3.0.0" - + sdk: '>=2.3.0 <3.0.0' dependencies: - flutter: - sdk: flutter - - redux: ^3.0.0 - flutter_redux: ^0.5.0 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 - + flutter: + sdk: flutter + cupertino_icons: ^0.1.2 + stacked : ^1.5.2 + provider_architecture: ^1.0.3 + responsive_builder: ^0.1.4 + provider: ^4.1.2 + logger: ^0.9.1 + get_it: ^3.0.3 + equatable: ^1.1.1 + http: ^0.12.1 dev_dependencies: - flutter_test: - sdk: flutter - - -# 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_test: + sdk: flutter flutter: + uses-material-design: true - # 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 - + assets: + - assets/images/logo.png + - assets/images/icon_large.png + - assets/lang/en.json + - assets/lang/ru.json + # 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 @@ -72,4 +53,4 @@ flutter: # weight: 700 # # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + # see https://flutter.dev/custom-fonts/#from-packages \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart index 1a601cb..858b009 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:aman_kassa_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(MainApplication()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);