diff --git a/README.md b/README.md index e84d000..b299403 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,3 @@ # FlutterCourse It is the repsitory from mobile development subject -Here you can see screens from Part 1: - - - Hello World - -![image](https://user-images.githubusercontent.com/56874600/133886250-08d76635-ee0b-4282-82f4-7e32cd980041.png) - - - Randomly generated word - -![image](https://user-images.githubusercontent.com/56874600/133886245-8ac9457a-6969-4417-a518-7c8493bbf88d.png) - - - Randomly generated word with different size - -![image](https://user-images.githubusercontent.com/56874600/133886240-54bed9d7-ef3e-4b03-ac8b-de9f0f1b988e.png) - -And code result video you can see above diff --git a/android/app/build.gradle b/android/app/build.gradle index c9f7074..27d3afa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -43,7 +43,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.hometask_flutter_v2" + applicationId "com.example.hometask_v_3" minSdkVersion 16 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index d5a4d52..427941d 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.example.hometask_v_3"> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 81844b9..951cb18 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + package="com.example.hometask_v_3"> + package="com.example.hometask_v_3"> diff --git a/android/hometask_v_3_android.iml b/android/hometask_v_3_android.iml new file mode 100644 index 0000000..1899969 --- /dev/null +++ b/android/hometask_v_3_android.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index c63568d..c12e17d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -291,7 +291,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.example.hometaskFlutterV2; + PRODUCT_BUNDLE_IDENTIFIER = com.example.hometaskV3; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -415,7 +415,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.example.hometaskFlutterV2; + PRODUCT_BUNDLE_IDENTIFIER = com.example.hometaskV3; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -434,7 +434,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.example.hometaskFlutterV2; + PRODUCT_BUNDLE_IDENTIFIER = com.example.hometaskV3; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 19c2041..bcbbd54 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -11,7 +11,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - hometask_flutter_v2 + hometask_v_3 CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/common/theme.dart b/lib/common/theme.dart new file mode 100644 index 0000000..95b9fd4 --- /dev/null +++ b/lib/common/theme.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +final appTheme = ThemeData( + primarySwatch: Colors.red, + textTheme: const TextTheme( + headline1: TextStyle( + fontFamily: 'Corben', + fontWeight: FontWeight.w700, + fontSize: 24, + color: Colors.black, + ), + ), +); diff --git a/lib/main.dart b/lib/main.dart index c8f117c..010a02b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,114 +1,39 @@ -import 'package:english_words/english_words.dart'; -import 'package:flutter/material.dart'; - -void main() => runApp(MyApp()); - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Startup Name Generator', - theme: ThemeData( - primaryColor: Colors.white, - ), - home: RandomWords(), - ); - } -} - -class _RandomWordsState extends State { - final _suggestions = []; - final _saved = {}; - final _biggerFont = const TextStyle(fontSize: 18.0); - Widget _buildSuggestions() { - return ListView.builder( - padding: const EdgeInsets.all(16.0), - itemBuilder: (context, i) { - if (i.isOdd) return const Divider(); - - final index = i ~/ 2; - if (index >= _suggestions.length) { - _suggestions.addAll(generateWordPairs().take(10)); - } - return _buildRow(_suggestions[index]); - }); - } - - Widget _buildRow(WordPair pair) { - final alreadySaved = _saved.contains(pair); - return ListTile( - title: Text( - pair.asPascalCase, - style: _biggerFont, - ), - trailing: Icon( - alreadySaved ? Icons.favorite : Icons.favorite_border, - color: alreadySaved ? Colors.red : null, - semanticLabel: alreadySaved ? 'Remove from saved' : 'Save', - ), - onTap: () { - setState(() { - if (alreadySaved) { - _saved.remove(pair); - } else { - _saved.add(pair); - } - }); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Startup Name Generator'), - actions: [ - IconButton( - icon: const Icon(Icons.list), - onPressed: _pushSaved, - tooltip: 'Saved Suggestions', - ), - ], - ), - body: _buildSuggestions(), - ); - } - - void _pushSaved() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - final tiles = _saved.map( - (pair) { - return ListTile( - title: Text( - pair.asPascalCase, - style: _biggerFont, - ), - ); - }, - ); - final divided = tiles.isNotEmpty - ? ListTile.divideTiles( - context: context, - tiles: tiles, - ).toList() - : []; - - return Scaffold( - appBar: AppBar( - title: const Text('Saved Suggestions'), - ), - body: ListView(children: divided), - ); - }, - ), - ); - } -} - -class RandomWords extends StatefulWidget { - @override - State createState() => _RandomWordsState(); -} +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:hometask_v_3/common/theme.dart'; +import 'package:hometask_v_3/models/cart_methods.dart'; +import 'package:hometask_v_3/models/catalog_list.dart'; +import 'package:hometask_v_3/screens/cart.dart'; +import 'package:hometask_v_3/screens/catalog.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + Provider(create: (context) => CatalogModel()), + ChangeNotifierProxyProvider( + create: (context) => CartModel(), + update: (context, catalog, cart) { + if (cart == null) throw ArgumentError.notNull('cart'); + cart.catalog = catalog; + return cart; + }, + ), + ], + child: MaterialApp( + title: 'Provider Demo', + theme: appTheme, + initialRoute: '/', + routes: { + '/': (context) => MyCatalog(), + '/cart': (context) => MyCart(), + }, + ), + ); + } +} diff --git a/lib/models/cart_methods.dart b/lib/models/cart_methods.dart new file mode 100644 index 0000000..efeb329 --- /dev/null +++ b/lib/models/cart_methods.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; +import 'package:hometask_v_3/models/catalog_list.dart'; + +class CartModel extends ChangeNotifier { + late CatalogModel _catalog; + + final List _itemIds = []; + + CatalogModel get catalog => _catalog; + + set catalog(CatalogModel newCatalog) { + _catalog = newCatalog; + notifyListeners(); + } + + List get items => _itemIds.map((id) => _catalog.getById(id)).toList(); + + int get totalPrice => + items.fold(0, (total, current) => total + current.price); + + void add(Item item) { + _itemIds.add(item.id); + notifyListeners(); + } + + void remove(Item item) { + _itemIds.remove(item.id); + notifyListeners(); + } +} diff --git a/lib/models/catalog_list.dart b/lib/models/catalog_list.dart new file mode 100644 index 0000000..337ebd2 --- /dev/null +++ b/lib/models/catalog_list.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +class CatalogModel { + static List itemNames = [ + 'Code Smell', + 'Control Flow', + 'Interpreter', + 'Recursion', + 'Sprint', + 'Heisenbug', + 'Spaghetti', + 'Hydra Code', + 'Off-By-One', + 'Scope', + 'Callback', + 'Closure', + 'Automata', + 'Bit Shift', + 'Currying', + ]; + + Item getById(int id) => Item(id, itemNames[id % itemNames.length]); + + Item getByPosition(int position) { + return getById(position); + } +} + +@immutable +class Item { + final int id; + final String name; + final Color color; + final int price = 42; + + Item(this.id, this.name) + : color = Colors.primaries[id % Colors.primaries.length]; + + @override + int get hashCode => id; + + @override + bool operator ==(Object other) => other is Item && other.id == id; +} diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart new file mode 100644 index 0000000..db56303 --- /dev/null +++ b/lib/screens/cart.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:hometask_v_3/models/cart_methods.dart'; + +class MyCart extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Cart'), + backgroundColor: Colors.red, + ), + body: Container( + color: Colors.white, + child: Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(32), + child: _CartList(), + ), + ), + const Divider(height: 4, color: Colors.black), + _CartTotal() + ], + ), + ), + ); + } +} + +class _CartList extends StatelessWidget { + @override + Widget build(BuildContext context) { + var itemNameStyle = Theme.of(context).textTheme.headline6; + + var cart = context.watch(); + + return ListView.builder( + itemCount: cart.items.length, + itemBuilder: (context, index) => ListTile( + trailing: IconButton( + icon: const Icon(Icons.remove_circle_outline), + onPressed: () { + cart.remove(cart.items[index]); + }, + ), + title: Text( + cart.items[index].name, + style: itemNameStyle, + ), + ), + ); + } +} + +class _CartTotal extends StatelessWidget { + @override + Widget build(BuildContext context) { + var hugeStyle = + Theme.of(context).textTheme.headline1!.copyWith(fontSize: 48); + + return SizedBox( + height: 200, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Consumer( + builder: (context, cart, child) => + Text('\UAH ${cart.totalPrice}', style: hugeStyle)), + const SizedBox(width: 24), + TextButton( + onPressed: () { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar(content: Text('COMING SOON'))); + }, + style: TextButton.styleFrom(primary: Colors.green), + child: const Text('BUY'), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/catalog.dart b/lib/screens/catalog.dart new file mode 100644 index 0000000..09fc0ea --- /dev/null +++ b/lib/screens/catalog.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:hometask_v_3/models/cart_methods.dart'; +import 'package:hometask_v_3/models/catalog_list.dart'; + +class MyCatalog extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + _MyAppBar(), + const SliverToBoxAdapter(child: SizedBox(height: 12)), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => ItemOfList(index)), + ), + ], + ), + ); + } +} + +class _AddButton extends StatelessWidget { + final Item item; + + const _AddButton({required this.item, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + var isInCart = context.select( + (cart) => cart.items.contains(item), + ); + + return TextButton( + onPressed: isInCart + ? null + : () { + var cart = context.read(); + cart.add(item); + }, + child: isInCart + ? const Icon(Icons.check) + : const Icon( + Icons.monetization_on, + color: Colors.green, + ), + ); + } +} + +class _MyAppBar extends StatelessWidget { + @override + Widget build(BuildContext context) { + return SliverAppBar( + title: const Text('Catalog'), + floating: true, + actions: [ + IconButton( + icon: const Icon(Icons.shopping_cart), + onPressed: () => Navigator.pushNamed(context, '/cart'), + ), + ], + ); + } +} + +class ItemOfList extends StatelessWidget { + final int index; + + const ItemOfList(this.index, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + var item = context.select( + (catalog) => catalog.getByPosition(index), + ); + var textTheme = Theme.of(context).textTheme.headline6; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: LimitedBox( + maxHeight: 48, + child: Row( + children: [ + const SizedBox(width: 24), + Expanded( + child: Text(item.name, style: textTheme), + ), + const SizedBox(width: 24), + _AddButton(item: item), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 9ba7a7f..295dff0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: hometask_flutter_v2 +name: hometask_v_3 description: A new Flutter project. # The following line prevents the package from being accidentally published to @@ -35,6 +35,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 english_words: ^4.0.0 + provider: ^6.0.0 dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart index 4835132..301ff17 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,12 +8,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:hometask_flutter_v2/main.dart'; +import 'package:hometask_v_3/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); diff --git a/web/index.html b/web/index.html index eafd8e9..16b3cff 100644 --- a/web/index.html +++ b/web/index.html @@ -23,10 +23,10 @@ - + - hometask_flutter_v2 + hometask_v_3 diff --git a/web/manifest.json b/web/manifest.json index 28c805f..b10c9ff 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "hometask_flutter_v2", - "short_name": "hometask_flutter_v2", + "name": "hometask_v_3", + "short_name": "hometask_v_3", "start_url": ".", "display": "standalone", "background_color": "#0175C2",