diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000000000000000000000000000000000..04ad12ce18161f0ec5272153caaaf01a313ad436 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // 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_app", + "cwd": "flutter_app", + "request": "launch", + "type": "dart" + }, + { + "name": "flutter_app (profile mode)", + "cwd": "flutter_app", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "flutter_app (release mode)", + "cwd": "flutter_app", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/flutter_app/devtools_options.yaml b/flutter_app/devtools_options.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fa0b357c4f4a29c3de7c5abfa47ba9ea8e52dd92 --- /dev/null +++ b/flutter_app/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/flutter_app/lib/main.dart b/flutter_app/lib/main.dart index dbb75a02105f10188e1c76deb857d04f5e345b7b..8d9dbbe8b9e6fcafe59b80e0ce91d8f270ccdcfe 100644 --- a/flutter_app/lib/main.dart +++ b/flutter_app/lib/main.dart @@ -1,9 +1,19 @@ +import 'dart:convert'; + import 'package:english_words/english_words.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; +import 'package:http/http.dart' as http; void main() { - runApp(MyApp()); + + runApp( + ChangeNotifierProvider( + create: (context) => MyAppState(), + child: const MyApp(), + ), + ); } class MyApp extends StatelessWidget { @@ -11,15 +21,26 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => MyAppState(), - child: MaterialApp( - title: 'Namer App', - theme: ThemeData( - useMaterial3: true, - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange), - ), - home: MyHomePage(), + var appState = context.watch<MyAppState>(); + return MaterialApp( + title: 'Namer App', + theme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange), + ), + home: FutureBuilder<List<WordPair>>( + future: appState.futureFavorites, + builder: (context, snapshot) { + if(snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if(snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}'),); + } else if(snapshot.hasData) { + return MyHomePage(); + } else { + return const Center(child: Text('No favorites yer.')); + } + } ), ); } @@ -27,27 +48,227 @@ class MyApp extends StatelessWidget { class MyAppState extends ChangeNotifier { var current = WordPair.random(); + List<WordPair> favorites = []; + late Future<List<WordPair>> futureFavorites; + + MyAppState() { + futureFavorites = fetchFavorites(); + } + + void getNext() { + current = WordPair.random(); + notifyListeners(); + } + + Future<List<WordPair>> fetchFavorites() async { + final response = await http.get( + Uri.parse('http://localhost:3000/likes') + ); + List<WordPair> list = []; + var jsonData = jsonDecode(response.body); + for (Map<String, dynamic> obj in jsonData) { + List<String> res = obj["id"].split('_'); + list.add(WordPair(res[0], res[1])); + } + + favorites = [...list]; + notifyListeners(); + return list; + } + + Future<void> toggleFavorite() async{ + var uri = "http://localhost:3000/likes/${current.first}_${current.second}"; + if (favorites.contains(current)) { + final http.Response resp = await http.delete(Uri.parse(uri)); + if(resp.statusCode != 200) { + return; + } + favorites.remove(current); + favorites = [...favorites]; + } else { + print("1"); + final http.Response resp = await http.post( + Uri.parse(uri), + headers: <String, String> { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: jsonEncode(<String, dynamic>{ + 'collectionId': "${current.first}_${current.second}", + 'like' : true, + }) + ); + print("2"); + if(resp.statusCode != 200) { + return; + } + + favorites = [...favorites, current]; + } + print(favorites); + notifyListeners(); + } +} + +class MyHomePage extends StatefulWidget { + @override + State<MyHomePage> createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State<MyHomePage> { + + var selectedIndex = 0; + + @override + Widget build(BuildContext context) { + Widget page; + switch (selectedIndex) { + case 0: + page = GeneratorPage(); + break; + case 1: + page = FavoritesPage(); + break; + default: + throw UnimplementedError('no widget for $selectedIndex'); + } + + return LayoutBuilder( + builder: (context, Constraints) { + return Scaffold( + body: Row( + children: [ + SafeArea( + child: NavigationRail( + extended: Constraints.maxWidth >= 600, + destinations: [ + NavigationRailDestination( + icon: Icon(Icons.home), + label: Text('Home'), + ), + NavigationRailDestination( + icon: Icon(Icons.favorite), + label: Text('Favorites'), + ), + ], + selectedIndex: selectedIndex, + onDestinationSelected: (value) { + setState(() { + selectedIndex = value; + }); + }, + ), + ), + Expanded( + child: Container( + color: Theme.of(context).colorScheme.primaryContainer, + child: page, + ), + ), + ], + ), + ); + } + ); + } } -class MyHomePage extends StatelessWidget { +class GeneratorPage extends StatelessWidget { @override Widget build(BuildContext context) { var appState = context.watch<MyAppState>(); + var pair = appState.current; + + IconData icon; + if (appState.favorites.contains(pair)) { + icon = Icons.favorite; + } else { + icon = Icons.favorite_border; + } - return Scaffold( - body: Column( + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('A random AWESOME idea:'), - Text(appState.current.asLowerCase), - // - ElevatedButton( - onPressed: () { - print('button pressed!'); - }, - child: Text('Next'), + BigCard(pair: pair), + SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () { + appState.toggleFavorite(); + }, + icon: Icon(icon), + label: Text('Like'), + ), + SizedBox(width: 10), + ElevatedButton( + onPressed: () { + appState.getNext(); + }, + child: Text('Next'), + ), + ], ), ], ), ); } +} + +class BigCard extends StatelessWidget { + const BigCard({ + super.key, + required this.pair, + }); + + final WordPair pair; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final style = theme.textTheme.displayMedium!.copyWith( + color: theme.colorScheme.onPrimary, + ); + + return Card( + color: theme.colorScheme.primary, + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + pair.asLowerCase, + style: style, + semanticsLabel: "${pair.first} ${pair.second}", + ), + ), + ); + } +} + +class FavoritesPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + var appState = context.watch<MyAppState>(); + + if (appState.favorites.isEmpty) { + return Center( + child: Text('No favorites yet.'), + ); + } + + return ListView( + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: Text('You have ' + '${appState.favorites.length} favorites:'), + ), + for (var pair in appState.favorites) + ListTile( + leading: Icon(Icons.favorite), + title: Text(pair.asLowerCase), + ), + ], + ); + } } \ No newline at end of file diff --git a/flutter_app/pubspec.lock b/flutter_app/pubspec.lock index 71b08a2aef65c91982ed1499ee69fb73e764f717..004e83aed0a26f8f994de363cea269e384dcad5b 100644 --- a/flutter_app/pubspec.lock +++ b/flutter_app/pubspec.lock @@ -75,6 +75,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" leak_tracker: dependency: transitive description: @@ -208,6 +224,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -224,6 +248,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.5.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/flutter_app/pubspec.yaml b/flutter_app/pubspec.yaml index d03029e081c75077de445b6f1e9e008ed0e0d00d..addb77587c34a8ea276fb55a5224cbac7a2e829b 100644 --- a/flutter_app/pubspec.yaml +++ b/flutter_app/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: english_words: ^4.0.0 provider: ^6.0.0 + http: ^1.2.2 dev_dependencies: flutter_test: