diff --git a/ludos/mobile/assets/images/default_game.jpg b/ludos/mobile/assets/images/default_game.jpg new file mode 100644 index 00000000..e79ffc7e Binary files /dev/null and b/ludos/mobile/assets/images/default_game.jpg differ diff --git a/ludos/mobile/lib/create_game.dart b/ludos/mobile/lib/create_game.dart index 091cb1d9..5020d1e3 100644 --- a/ludos/mobile/lib/create_game.dart +++ b/ludos/mobile/lib/create_game.dart @@ -1,14 +1,12 @@ -import 'dart:convert'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; import 'helper/colors.dart'; import 'package:material_tag_editor/tag_editor.dart'; -import 'helper/APIService.dart'; +import 'create_game_second.dart'; -Widget getbox( - String hintText, TextEditingController controller, bool isMandatory) { +Widget getbox(String hintText, TextEditingController controller, + bool isMandatory, bool multiLine) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -31,15 +29,17 @@ Widget getbox( ), ], ), - SizedBox(height: 8.0), // Adjust the spacing as needed + const SizedBox(height: 8.0), // Adjust the spacing as needed TextFormField( controller: controller, style: const TextStyle(color: MyColors.red), + keyboardType: multiLine ? TextInputType.multiline : null, + maxLines: multiLine ? 5 : 1, decoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -49,7 +49,7 @@ Widget getbox( labelText: '', focusedBorder: OutlineInputBorder( borderSide: const BorderSide(color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), cursorColor: MyColors.lightBlue, @@ -59,7 +59,8 @@ Widget getbox( } class CreateGamePage extends StatefulWidget { - const CreateGamePage({Key? key}) : super(key: key); + final String? token; + const CreateGamePage({Key? key, required this.token}) : super(key: key); @override State createState() => _CreateGamePageState(); @@ -68,8 +69,17 @@ class CreateGamePage extends StatefulWidget { class _CreateGamePageState extends State { List predecessorValues = []; List successorValues = []; - List platformValues = []; List tagValues = []; + int selectdAgeRestriction = 0; + List ageRestrictions = [ + 'Early Childhood', + 'Everyone', + 'Everyone 10 and older', + 'Teen', + 'Mature', + 'Adults Only', + 'Rating Pending', + ]; _onDeletepr(index) { setState(() { @@ -83,201 +93,122 @@ class _CreateGamePageState extends State { }); } - _onDeletepl(index) { - setState(() { - platformValues.removeAt(index); - }); - } - _onDeletet(index) { setState(() { tagValues.removeAt(index); }); } + void _showDialog(Widget child) { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) => Container( + height: 216, + padding: const EdgeInsets.only(top: 6.0), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + color: CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: child, + ), + ), + ); + } + final TextEditingController titleController = TextEditingController(); final TextEditingController coverLinkController = TextEditingController(); - final TextEditingController systemRequirementsController = - TextEditingController(); final TextEditingController predecessorsController = TextEditingController(); final TextEditingController successorsController = TextEditingController(); - final TextEditingController gameGuideController = TextEditingController(); - final TextEditingController gameStoryController = TextEditingController(); - final TextEditingController platformsController = TextEditingController(); - final TextEditingController ageRestrictionController = - TextEditingController(); final TextEditingController gameBioController = TextEditingController(); final TextEditingController tagsController = TextEditingController(); - final TextEditingController releaseDateController = TextEditingController(); - final TextEditingController developerController = TextEditingController(); - final TextEditingController publisherController = TextEditingController(); - final TextEditingController triviaController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - backgroundColor: MyColors.orange, + backgroundColor: const Color(0xFF2f5b7a), centerTitle: true, title: const Text('Ludos'), - actions: [ - TextButton( - onPressed: () async { - http.Response token = await APIService().createGame( - titleController.text, - coverLinkController.text, - systemRequirementsController.text, - predecessorValues, - successorValues, - gameGuideController.text, - gameStoryController.text, - platformValues, - ageRestrictionController.text, - gameBioController.text, - tagValues, - releaseDateController.text, - developerController.text, - publisherController.text, - triviaController.text); - if (token.statusCode == 201) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Row( - children: [ - Icon( - Icons.check_circle_outline, - color: MyColors.blue, - ), - SizedBox(width: 8), - Text( - 'Your game is created successfully.', - style: TextStyle( - color: MyColors.blue, - fontSize: 16, - ), - ), - ], - ), - backgroundColor: MyColors.blue2, - duration: const Duration(seconds: 10), - action: SnackBarAction( - label: 'OK', - textColor: MyColors.blue, - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - ), - ), - ); - } else if (token.statusCode == 409) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: SizedBox( - width: MediaQuery.of(context).size.width, - child: Text( - json.decode(token.body)["message"], - style: const TextStyle( - color: MyColors.blue, - fontSize: 16, - ), - ), - ), - backgroundColor: MyColors.blue2, - duration: const Duration(seconds: 10), - action: SnackBarAction( - label: 'OK', - textColor: MyColors.blue, - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - ), - ), - ); - } - }, - child: const Text( - 'Save Game', - style: TextStyle(color: Colors.white), - ), - ), - ], ), - backgroundColor: MyColors.blue, + backgroundColor: MyColors.darkBlue, body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 50), - getbox("Title", titleController, true), const SizedBox(height: 20), - getbox("Coverlink", coverLinkController, false), + getbox("Title", titleController, true, false), const SizedBox(height: 20), - getbox( - "System Requirements", systemRequirementsController, false), + getbox("Coverlink", coverLinkController, true, false), const SizedBox(height: 20), - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - "Predecessors", - style: TextStyle( + getbox("Game Bio", gameBioController, true, true), + const SizedBox(height: 20), + const Row( + children: [ + Text( + '*', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + "Age Restriction", + style: TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 10), + Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.only(), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), color: MyColors.white, - fontWeight: FontWeight.bold, + border: Border.all( + color: Colors.white, + width: 2.0, + ), ), - ), - SizedBox(height: 8.0), - TagEditor( - length: predecessorValues.length, - controller: predecessorsController, - delimiters: const [',', ' '], - hasAddButton: true, - resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop - textStyle: const TextStyle( - color: MyColors.red, fontWeight: FontWeight.bold), - onSubmitted: (outstandingValue) { - setState(() { - predecessorValues.add(outstandingValue); - }); - }, - inputDecoration: InputDecoration( - filled: true, - fillColor: MyColors.white, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: Colors.white, - width: 2.0, + child: CupertinoButton( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + onPressed: () => _showDialog( + CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + itemExtent: 32.0, + scrollController: FixedExtentScrollController( + initialItem: selectdAgeRestriction, + ), + onSelectedItemChanged: (int selectedItem) { + setState(() { + selectdAgeRestriction = selectedItem; + }); + }, + children: List.generate(ageRestrictions.length, + (int index) { + return Center(child: Text(ageRestrictions[index])); + }), ), ), - labelStyle: const TextStyle( - color: MyColors.red, fontWeight: FontWeight.bold), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + child: Text( + ageRestrictions[selectdAgeRestriction], + style: const TextStyle(color: MyColors.blue), ), ), - onTagChanged: (newValue) { - setState(() { - predecessorValues.add(newValue); - }); - }, - tagBuilder: (context, index) => _Chip( - index: index, - label: predecessorValues[index], - onDeleted: _onDeletepr, - ), - // InputFormatters example, this disallow \ and / - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) - ], ), ]), const SizedBox(height: 20), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Successors", + "Tags", style: TextStyle( color: MyColors.white, fontWeight: FontWeight.bold, @@ -285,24 +216,23 @@ class _CreateGamePageState extends State { ), SizedBox(height: 8.0), TagEditor( - length: successorValues.length, - controller: successorsController, + length: tagValues.length, + controller: tagsController, delimiters: const [',', ' '], hasAddButton: true, resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop textStyle: const TextStyle( color: MyColors.red, fontWeight: FontWeight.bold), onSubmitted: (outstandingValue) { setState(() { - successorValues.add(outstandingValue); + tagValues.add(outstandingValue); }); }, inputDecoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -313,33 +243,28 @@ class _CreateGamePageState extends State { focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), onTagChanged: (newValue) { setState(() { - successorValues.add(newValue); + tagValues.add(newValue); }); }, tagBuilder: (context, index) => _Chip( index: index, - label: successorValues[index], - onDeleted: _onDeletes, + label: tagValues[index], + onDeleted: _onDeletet, ), - // InputFormatters example, this disallow \ and / inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) ], ), ]), const SizedBox(height: 20), - getbox("Game Guide", gameGuideController, false), - const SizedBox(height: 20), - getbox("Game Story", gameStoryController, false), - const SizedBox(height: 20), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Platforms", + "Predecessors", style: TextStyle( color: MyColors.white, fontWeight: FontWeight.bold, @@ -347,24 +272,23 @@ class _CreateGamePageState extends State { ), SizedBox(height: 8.0), TagEditor( - length: platformValues.length, - controller: platformsController, + length: predecessorValues.length, + controller: predecessorsController, delimiters: const [',', ' '], hasAddButton: true, resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop textStyle: const TextStyle( color: MyColors.red, fontWeight: FontWeight.bold), onSubmitted: (outstandingValue) { setState(() { - platformValues.add(outstandingValue); + predecessorValues.add(outstandingValue); }); }, inputDecoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -375,33 +299,28 @@ class _CreateGamePageState extends State { focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), onTagChanged: (newValue) { setState(() { - platformValues.add(newValue); + predecessorValues.add(newValue); }); }, tagBuilder: (context, index) => _Chip( index: index, - label: platformValues[index], - onDeleted: _onDeletepl, + label: predecessorValues[index], + onDeleted: _onDeletepr, ), - // InputFormatters example, this disallow \ and / inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) ], ), ]), const SizedBox(height: 20), - getbox("Age Restriction", ageRestrictionController, false), - const SizedBox(height: 20), - getbox("Game Bio", gameBioController, true), - const SizedBox(height: 20), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Tags", + "Successors", style: TextStyle( color: MyColors.white, fontWeight: FontWeight.bold, @@ -409,24 +328,23 @@ class _CreateGamePageState extends State { ), SizedBox(height: 8.0), TagEditor( - length: tagValues.length, - controller: tagsController, + length: successorValues.length, + controller: successorsController, delimiters: const [',', ' '], hasAddButton: true, resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop textStyle: const TextStyle( color: MyColors.red, fontWeight: FontWeight.bold), onSubmitted: (outstandingValue) { setState(() { - tagValues.add(outstandingValue); + successorValues.add(outstandingValue); }); }, inputDecoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -437,33 +355,58 @@ class _CreateGamePageState extends State { focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), onTagChanged: (newValue) { setState(() { - tagValues.add(newValue); + successorValues.add(newValue); }); }, tagBuilder: (context, index) => _Chip( index: index, - label: tagValues[index], - onDeleted: _onDeletet, + label: successorValues[index], + onDeleted: _onDeletes, ), - // InputFormatters example, this disallow \ and / inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) ], ), + const SizedBox( + height: 20, + ), + Center( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: MyColors.lightBlue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + ), + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => CreateGamePageSecond( + token: widget.token, + title: titleController.text, + coverLink: coverLinkController.text, + gameBio: gameBioController.text, + ageRestriction: + ageRestrictions[selectdAgeRestriction], + tags: tagValues, + predecessors: predecessorValues, + successors: successorValues), + )); + }, + child: const Text( + 'Next', + style: TextStyle( + color: MyColors.white, + fontSize: 16.0, + ), + ), + ), + ), ]), - const SizedBox(height: 20), - getbox("Release Date", releaseDateController, true), - const SizedBox(height: 20), - getbox("Developer", developerController, true), - const SizedBox(height: 20), - getbox("Publisher", publisherController, true), - const SizedBox(height: 20), - getbox("Trivia", triviaController, false), ], ), ), @@ -486,6 +429,7 @@ class _Chip extends StatelessWidget { @override Widget build(BuildContext context) { return Chip( + backgroundColor: MyColors.blue2, labelPadding: const EdgeInsets.only(left: 8.0), label: Text(label), deleteIcon: const Icon( diff --git a/ludos/mobile/lib/create_game_second.dart b/ludos/mobile/lib/create_game_second.dart new file mode 100644 index 00000000..15ca5f95 --- /dev/null +++ b/ludos/mobile/lib/create_game_second.dart @@ -0,0 +1,393 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:ludos_mobile_app/games_page.dart'; +import 'package:multi_dropdown/multiselect_dropdown.dart'; +import 'helper/colors.dart'; +import 'helper/APIService.dart'; + +Widget getbox(String hintText, TextEditingController controller, + bool isMandatory, bool multiLine) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (isMandatory) + const Text( + '*', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + hintText, + style: const TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8.0), // Adjust the spacing as needed + TextFormField( + controller: controller, + style: const TextStyle(color: MyColors.red), + keyboardType: multiLine ? TextInputType.multiline : null, + maxLines: multiLine ? 5 : 1, + decoration: InputDecoration( + filled: true, + fillColor: MyColors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(25.0), + borderSide: const BorderSide( + color: Colors.white, + width: 2.0, + ), + ), + hintText: '', + labelText: '', + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: MyColors.lightBlue, width: 2.0), + borderRadius: BorderRadius.circular(25.0), + ), + ), + cursorColor: MyColors.lightBlue, + ), + ], + ); +} + +String formatDateTime(DateTime dateTime) { + final months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + + String month = months[dateTime.month - 1]; + String day = dateTime.day.toString(); + String year = dateTime.year.toString(); + + return '$month $day, $year'; +} + +class CreateGamePageSecond extends StatefulWidget { + final String? token; + final String title; + final String coverLink; + final String gameBio; + final String ageRestriction; + final List tags; + final List predecessors; + final List successors; + + const CreateGamePageSecond( + {Key? key, + required this.token, + required this.title, + required this.coverLink, + required this.gameBio, + required this.ageRestriction, + required this.tags, + required this.predecessors, + required this.successors}) + : super(key: key); + + @override + State createState() => _CreateGamePageStateSecond(); +} + +class _CreateGamePageStateSecond extends State { + DateTime date = DateTime(2016, 10, 24); + List selectedOptions = []; + List platforms = [ + 'Android', + 'iOS', + 'Windows', + 'macOS', + 'Linux', + 'PlayStation', + 'Xbox', + 'Nintendo', + 'Board Game' + ]; + + void _showDialog(Widget child) { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) => Container( + height: 216, + padding: const EdgeInsets.only(top: 6.0), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + color: CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: child, + ), + ), + ); + } + + final TextEditingController systemRequirementsController = + TextEditingController(); + final TextEditingController gameGuideController = TextEditingController(); + final TextEditingController gameStoryController = TextEditingController(); + final TextEditingController developerController = TextEditingController(); + final TextEditingController publisherController = TextEditingController(); + final TextEditingController triviaController = TextEditingController(); + final MultiSelectController platformsController = MultiSelectController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: const Color(0xFF2f5b7a), + centerTitle: true, + title: const Text('Ludos'), + ), + backgroundColor: MyColors.darkBlue, + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 20), + getbox("Game Story", gameStoryController, true, true), + const SizedBox(height: 20), + getbox("Game Guide", gameGuideController, false, true), + const SizedBox(height: 20), + const Row( + children: [ + Text( + "Release Date", + style: TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.only(), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), + color: MyColors.white, + border: Border.all( + color: Colors.white, + width: 2.0, + ), + ), + child: SizedBox( + height: 30.0, + width: 200.0, + child: CupertinoButton( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + onPressed: () => _showDialog( + CupertinoDatePicker( + dateOrder: DatePickerDateOrder.dmy, + initialDateTime: date, + mode: CupertinoDatePickerMode.date, + use24hFormat: true, + onDateTimeChanged: (DateTime newDate) { + setState(() => date = newDate); + }, + ), + ), + child: Text( + '${date.day}.${date.month}.${date.year}', + style: const TextStyle( + color: MyColors.blue, + fontSize: 18.0, + ), + ), + ), + ), + ), + ]), + const SizedBox(height: 20), + getbox("System Requirements", systemRequirementsController, + true, false), + const SizedBox(height: 20), + const Row( + children: [ + Text( + '*', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + "Platforms", + style: TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 10), + MultiSelectDropDown( + hint: "", + borderRadius: 25.0, + showClearIcon: true, + controller: platformsController, + onOptionSelected: (options) {}, + options: const [ + ValueItem(label: 'Android'), + ValueItem(label: 'iOS'), + ValueItem(label: 'Windows'), + ValueItem(label: 'macOS'), + ValueItem(label: 'Linux'), + ValueItem(label: 'PlayStation'), + ValueItem(label: 'Xbox'), + ValueItem(label: 'Nintendo'), + ValueItem(label: 'Board Game'), + ], + selectionType: SelectionType.multi, + chipConfig: const ChipConfig( + wrapType: WrapType.wrap, + backgroundColor: MyColors.lightBlue), + dropdownHeight: 200, + optionTextStyle: const TextStyle(fontSize: 16), + selectedOptionTextColor: MyColors.blue, + selectedOptionIcon: const Icon( + Icons.check_circle, + color: MyColors.lightBlue, + ), + ), + const SizedBox(height: 20), + getbox("Developer", developerController, true, false), + const SizedBox(height: 20), + getbox("Game Publisher", publisherController, true, false), + const SizedBox(height: 20), + getbox("Trivia", triviaController, false, false), + const SizedBox(height: 20), + TextButton( + style: TextButton.styleFrom( + backgroundColor: MyColors.lightBlue, + ), + onPressed: () async { + http.Response token = await APIService().createGame( + widget.token, + widget.title, + widget.coverLink, + systemRequirementsController.text, + widget.predecessors, + widget.successors, + gameGuideController.text, + gameStoryController.text, + platformsController.selectedOptions + .map((item) => item.label) + .where((element) => element != null) + .map((e) => e!) + .toList(), + widget.ageRestriction, + widget.gameBio, + widget.tags, + formatDateTime(date), + developerController.text, + publisherController.text, + triviaController.text); + if (token.statusCode == 201) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon( + Icons.check_circle_outline, + color: MyColors.blue, + ), + SizedBox(width: 8), + Expanded( + child: Text( + 'Your game is created successfully. You will be redirected to the Games Page.', + style: TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + ], + ), + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 5), + action: SnackBarAction( + label: 'OK', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GamesPage(token: widget.token)), + ); + }, + ), + ), + ) + .closed + .then((reason) => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GamesPage(token: widget.token)), + )); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: SizedBox( + width: MediaQuery.of(context).size.width, + child: Text( + json.decode(token.body)["message"], + style: const TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 10), + action: SnackBarAction( + label: 'OK', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + }, + ), + ), + ); + } + }, + child: const Text( + 'Save Game', + style: TextStyle(color: Colors.white), + ), + ), + ]), + ), + ), + ); + } +} diff --git a/ludos/mobile/lib/games_page.dart b/ludos/mobile/lib/games_page.dart index 39a3aca3..28f80eeb 100644 --- a/ludos/mobile/lib/games_page.dart +++ b/ludos/mobile/lib/games_page.dart @@ -6,7 +6,8 @@ import 'helper/APIService.dart'; import 'create_game.dart'; class GamesPage extends StatefulWidget { - const GamesPage({Key? key}) : super(key: key); + final String? token; + const GamesPage({Key? key, required this.token}) : super(key: key); @override State createState() => _GamesPageState(); @@ -20,11 +21,12 @@ class _GamesPageState extends State { @override void initState() { super.initState(); - games = fetchData(); + games = fetchData(widget.token); } - Future> fetchData() async { - final response = await APIService().listGames(); + Future> fetchData(String? token) async { + //final userProvider = Provider.of(context, listen: false); + final response = await APIService().listGames(token); try { //print(json.decode(response.body)); if (response.statusCode == 200) { @@ -120,7 +122,7 @@ class _GamesPageState extends State { ), onPressed: () { Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const CreateGamePage(), + builder: (context) => CreateGamePage(token: widget.token), )); }, child: const Text( 'Create Game', diff --git a/ludos/mobile/lib/helper/APIService.dart b/ludos/mobile/lib/helper/APIService.dart index ae6a044a..88966d6c 100644 --- a/ludos/mobile/lib/helper/APIService.dart +++ b/ludos/mobile/lib/helper/APIService.dart @@ -39,7 +39,7 @@ class APIService { return response; } - Future createGame(String title, String coverLink, + Future createGame(String? authToken, String title, String coverLink, String systemRequirements, List predecessors, List successors, String gameGuide, String gameStory, List platforms, String ageRestriction, String gameBio, List tags, String releaseDate, String developer, @@ -62,7 +62,7 @@ class APIService { 'publisher': publisher, 'trivia': trivia, }); - final response = await http.post(uri, body: body, headers: {'content-type': "application/json"}); + final response = await http.post(uri, body: body, headers: {'content-type': "application/json", 'Authorization': 'Bearer $authToken'}); return response; } @@ -89,9 +89,9 @@ class APIService { return response; } - Future listGames() async { - var uri = Uri.parse("$baseURL/game?limit=10"); - final response = await http.get(uri, headers: {'content-type': "application/json"}); + Future listGames(String? authToken) async { + var uri = Uri.parse("$baseURL/game?limit=20"); + final response = await http.get(uri, headers: {'content-type': "application/json", 'Authorization': 'Bearer $authToken'}); return response; } diff --git a/ludos/mobile/lib/login_page.dart b/ludos/mobile/lib/login_page.dart index e9ce7bf9..ea6d0889 100644 --- a/ludos/mobile/lib/login_page.dart +++ b/ludos/mobile/lib/login_page.dart @@ -93,7 +93,7 @@ class LoginPageState extends State { onPressed: () async { (String?, int) token = await APIService() .login(emailController.text, passwordController.text); - print(token); + //print(token); if (token.$2 == 200) { Provider.of(context, listen: false) .setLoggedIn(true, emailController.text, token.$1); diff --git a/ludos/mobile/lib/main.dart b/ludos/mobile/lib/main.dart index 86c03f39..9bcb20cb 100644 --- a/ludos/mobile/lib/main.dart +++ b/ludos/mobile/lib/main.dart @@ -432,10 +432,53 @@ class Home extends StatelessWidget { builder: (context) => const CreateGamePage(key: null,), )); */ + if(userProvider.isLoggedIn){ Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const GamesPage(), + builder: (context) => GamesPage(token: userProvider.token), )); - + } + else{ + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon( + Icons.check_circle_outline, + color: MyColors.blue, + ), + SizedBox(width: 8), + Expanded( + child: Text( + 'Please log in to view games', + style: TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + ], + ), + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 5), + action: SnackBarAction( + label: 'Log In', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoginPage()), + ); + }, + ), + ), + ) + .closed + .then((reason) => {}); + } }, icon: const Icon(Icons.games)), IconButton( diff --git a/ludos/mobile/lib/reusable_widgets/game_summary.dart b/ludos/mobile/lib/reusable_widgets/game_summary.dart index bb69ede0..4db07bf1 100644 --- a/ludos/mobile/lib/reusable_widgets/game_summary.dart +++ b/ludos/mobile/lib/reusable_widgets/game_summary.dart @@ -28,16 +28,16 @@ class GameSummary extends StatefulWidget { @override State createState() => _GameSummaryState( - title: title, - averageRating: averageRating, - coverLink: coverLink, - numOfFollowers: numOfFollowers, - gameStory: gameStory, - tags: tags, - textColor: textColor, - backgroundColor: backgroundColor, - fontSize: fontSize, - ); + title: title, + averageRating: averageRating, + coverLink: coverLink, + numOfFollowers: numOfFollowers, + gameStory: gameStory, + tags: tags, + textColor: textColor, + backgroundColor: backgroundColor, + fontSize: fontSize, + ); } class _GameSummaryState extends State { @@ -69,11 +69,10 @@ class _GameSummaryState extends State { children: [ ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: backgroundColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), - ) - ), + backgroundColor: backgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + )), onPressed: () { // Handle button press for the specific game // Navigate to the game's profile page @@ -100,10 +99,17 @@ class _GameSummaryState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - coverLink, + FadeInImage( width: 100, height: 160, + image: NetworkImage(coverLink), + placeholder: const AssetImage( + 'assets/images/default_game.jpg', + ), + imageErrorBuilder: (context, error, stackTrace) { + return Image.asset('assets/images/default_game.jpg', + width: 100, height: 160, fit: BoxFit.fill); + }, fit: BoxFit.fill, ), ], @@ -112,55 +118,65 @@ class _GameSummaryState extends State { height: 160, width: 210, child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - const Icon(Icons.favorite, size: 20, color: MyColors.lightBlue), - Text( - "+$numOfFollowers favorites", - style: const TextStyle( - color: MyColors.lightBlue, - fontSize: 16.0, - ) - ) - ] - ), - Row( - children: List.generate( - 5, // Total number of stars - (index) { - double diff = 4.5 - index; - if (diff >= 1.0) { - // Full star - return const SingleRatingIcon(icon: Icons.star, size: 20, iconColor: MyColors.lightBlue, rating: 10.0); - } else if (diff >= 0.5) { - // Floating star - return SingleRatingIcon(icon: Icons.star, size: 20, iconColor: MyColors.lightBlue, rating: diff * 10.0); - } else { - // Empty star - return const SingleRatingIcon(icon: Icons.star, size: 20, iconColor: MyColors.lightBlue, rating: 0.0); - } - }, - ).toList(), - ), - ] - ), - const SizedBox(height: 10), - Text( - "Here will be brief game story.", - softWrap: true, - style: TextStyle( - color: MyColors.darkBlue, - fontSize: fontSize, + const Icon(Icons.favorite, + size: 20, color: MyColors.lightBlue), + Text("+$numOfFollowers favorites", + style: const TextStyle( + color: MyColors.lightBlue, + fontSize: 16.0, + )) + ]), + Row( + children: List.generate( + 5, // Total number of stars + (index) { + double diff = 4.5 - index; + if (diff >= 1.0) { + // Full star + return const SingleRatingIcon( + icon: Icons.star, + size: 20, + iconColor: MyColors.lightBlue, + rating: 10.0); + } else if (diff >= 0.5) { + // Floating star + return SingleRatingIcon( + icon: Icons.star, + size: 20, + iconColor: MyColors.lightBlue, + rating: diff * 10.0); + } else { + // Empty star + return const SingleRatingIcon( + icon: Icons.star, + size: 20, + iconColor: MyColors.lightBlue, + rating: 0.0); + } + }, + ).toList(), ), - ), - ], + ]), + const SizedBox(height: 10), + Text( + "Here will be brief game story.", + softWrap: true, + style: TextStyle( + color: MyColors.darkBlue, + fontSize: fontSize, + ), + ), + ], ), ), ], @@ -179,12 +195,12 @@ class _GameSummaryState extends State { textStyle: const TextStyle(color: MyColors.darkBlue), ), child: Text( - tag, - style: const TextStyle( - color: MyColors.darkBlue, - fontWeight: FontWeight.bold, - fontSize: 17.0, - ), + tag, + style: const TextStyle( + color: MyColors.darkBlue, + fontWeight: FontWeight.bold, + fontSize: 17.0, + ), ), ); }).toList(), diff --git a/ludos/mobile/pubspec.lock b/ludos/mobile/pubspec.lock index 02a5c1f2..a9cf92eb 100644 --- a/ludos/mobile/pubspec.lock +++ b/ludos/mobile/pubspec.lock @@ -155,6 +155,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + multi_dropdown: + dependency: "direct main" + description: + name: multi_dropdown + sha256: "2db9b41ae543a9277defc2b5ba95622acb72ed0fb49d6dead357a1639371bee3" + url: "https://pub.dev" + source: hosted + version: "2.1.0" nested: dependency: transitive description: diff --git a/ludos/mobile/pubspec.yaml b/ludos/mobile/pubspec.yaml index e5bb95b9..6fa84cbc 100644 --- a/ludos/mobile/pubspec.yaml +++ b/ludos/mobile/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: http: ^1.1.0 provider: ^5.0.0 material_tag_editor: 0.1.2 + multi_dropdown: ^2.1.0 # The following adds the Cupertino Icons font to your application.