diff --git a/app/mobile/lib/data/models/character_model.dart b/app/mobile/lib/data/models/character_model.dart index 0db96ed6..3bc1c8ff 100644 --- a/app/mobile/lib/data/models/character_model.dart +++ b/app/mobile/lib/data/models/character_model.dart @@ -16,7 +16,7 @@ class Character { characterId: json['characterId'], name: json['name'], description: json['description'], - gameId: json['gameId'], + gameId: json['gameID'], ); } @@ -25,7 +25,7 @@ class Character { 'characterId': characterId, 'title': name, 'description': description, - 'gameId': gameId, + 'gameID': gameId, }; } } diff --git a/app/mobile/lib/data/models/dto/game/character_create_request.dart b/app/mobile/lib/data/models/dto/game/character_create_request.dart new file mode 100644 index 00000000..6ffd6661 --- /dev/null +++ b/app/mobile/lib/data/models/dto/game/character_create_request.dart @@ -0,0 +1,37 @@ +import 'package:mobile/data/models/dto/base_dto_object.dart'; +import 'package:mobile/utils/service_validation_util.dart'; + +class CharacterCreateRequest extends BaseDTOObject { + String name; + String description; + + + CharacterCreateRequest({ + required this.name, + required this.description, + + }); + + @override + void validate() { + ValidationUtil.validate(name, ValidationPolicy.stringNotEmptyValidation()); + ValidationUtil.validate( + description, ValidationPolicy.stringNotEmptyValidation()); + } + + factory CharacterCreateRequest.fromJson(Map json) => + CharacterCreateRequest( + name: json["name"], + description: json["description"], + ); + + @override + Map toJson() => { + "name": name, + "description": description, + }; + + @override + CharacterCreateRequest fromJson(Map json) => + CharacterCreateRequest.fromJson(json); +} \ No newline at end of file diff --git a/app/mobile/lib/data/models/dto/lfg/lfg_create_dto_request.dart b/app/mobile/lib/data/models/dto/lfg/lfg_create_dto_request.dart new file mode 100644 index 00000000..659c23a6 --- /dev/null +++ b/app/mobile/lib/data/models/dto/lfg/lfg_create_dto_request.dart @@ -0,0 +1,61 @@ +import 'package:mobile/data/models/dto/base_dto_object.dart'; +import 'package:mobile/utils/service_validation_util.dart'; + +class LFGCreateDTORequest extends BaseDTOObject { + String title; + String description; + String requiredPlatform; + String requiredLanguage; + bool micCamRequirement; + int memberCapacity; + int? gameId; + List tags; + + LFGCreateDTORequest({ + required this.title, + required this.description, + required this.requiredPlatform, + required this.requiredLanguage, + required this.micCamRequirement, + required this.memberCapacity, + required this.gameId, + required this.tags, + }); + + @override + void validate() { + ValidationUtil.validate(title, ValidationPolicy.stringNotEmptyValidation()); + ValidationUtil.validate( + description, ValidationPolicy.stringNotEmptyValidation()); + } + + factory LFGCreateDTORequest.fromJson(Map json) => + LFGCreateDTORequest( + title: json["title"], + description: json["description"], + requiredPlatform: json["requiredPlatform"], + requiredLanguage: json["requiredLanguage"], + micCamRequirement: json["micCamRequirement"], + memberCapacity: json["memberCapacity"], + gameId: json["gameId"], + tags: json["tags"] != null + ? List.from(json["tags"].map((x) => x)) + : [], + ); + + @override + Map toJson() => { + "title": title, + "description": description, + "requiredPlatform": requiredPlatform, + "requiredLanguage": requiredLanguage, + "micCamRequirement": micCamRequirement, + "memberCapacity": memberCapacity, + "gameId": gameId, + "tags": tags, + }; + + @override + LFGCreateDTORequest fromJson(Map json) => + LFGCreateDTORequest.fromJson(json); +} diff --git a/app/mobile/lib/data/models/dto/lfg/lfg_response.dart b/app/mobile/lib/data/models/dto/lfg/lfg_response.dart new file mode 100644 index 00000000..e0dbd2a6 --- /dev/null +++ b/app/mobile/lib/data/models/dto/lfg/lfg_response.dart @@ -0,0 +1,25 @@ +import 'package:mobile/data/models/character_model.dart'; +import 'package:mobile/data/models/dto/base_dto_object.dart'; +import 'package:mobile/data/models/game_model.dart'; +import 'package:mobile/data/models/lfg_model.dart'; + +class LFGDTOResponse extends BaseDTOObject { + LFG? lfg; + + LFGDTOResponse({ + this.lfg, + }); + + @override + void validate() {} + + factory LFGDTOResponse.fromJson(Map json) => + LFGDTOResponse(lfg: LFG.fromJson(json)); + + @override + Map toJson() => lfg!.toJson(); + + @override + LFGDTOResponse fromJson(Map json) => + LFGDTOResponse.fromJson(json); +} diff --git a/app/mobile/lib/data/models/dto/lfg/mutliple_lfg_dto_request.dart b/app/mobile/lib/data/models/dto/lfg/mutliple_lfg_dto_request.dart new file mode 100644 index 00000000..2495d930 --- /dev/null +++ b/app/mobile/lib/data/models/dto/lfg/mutliple_lfg_dto_request.dart @@ -0,0 +1,33 @@ +import 'package:mobile/data/models/dto/base_dto_object.dart'; +import 'package:mobile/data/models/dto/game/game_response.dart'; +import 'package:mobile/data/models/dto/lfg/lfg_response.dart'; + +class MultipleLFGAsDTO extends BaseDTOObject { + List? lfgs; + + MultipleLFGAsDTO({ + this.lfgs, + }); + + @override + void validate() { + for (var lfg in lfgs!) { + lfg.validate(); + } + } + + factory MultipleLFGAsDTO.fromJson(Map json) => + MultipleLFGAsDTO( + lfgs: List.from( + json["response"].map((x) => LFGDTOResponse.fromJson(x))), + ); + + @override + Map toJson() => { + "response": List.from(lfgs!.map((x) => x.toJson())), + }; + + @override + MultipleLFGAsDTO fromJson(Map json) => + MultipleLFGAsDTO.fromJson(json); +} diff --git a/app/mobile/lib/data/models/game_model.dart b/app/mobile/lib/data/models/game_model.dart index a26d6a81..d326cca3 100644 --- a/app/mobile/lib/data/models/game_model.dart +++ b/app/mobile/lib/data/models/game_model.dart @@ -44,7 +44,8 @@ class Game { this.developers, this.similarGameList = const [], this.relatedPosts = const [], - this.status + this.status, + this.characters, }); factory Game.fromJson(Map json) { @@ -64,7 +65,8 @@ class Game { averageRating: json['averageRating'], creationDate: json['creationDate'], gamePicture: json['gamePicture'], - status: json['status'] + status: json['status'], + characters: json['characters'] != null ? List.from(json["characters"].map((x) => Character.fromJson(x))) : [] ); } @@ -85,6 +87,7 @@ class Game { 'creationDate': creationDate, 'gamePicture': gamePicture, 'status': status, + 'characters': characters, }; } } diff --git a/app/mobile/lib/data/models/lfg_model.dart b/app/mobile/lib/data/models/lfg_model.dart index 4352eeee..a5b1daec 100644 --- a/app/mobile/lib/data/models/lfg_model.dart +++ b/app/mobile/lib/data/models/lfg_model.dart @@ -23,7 +23,6 @@ class LFG extends Content { dislikes = 0, comments = 0, List commentList = const [], - this.requiredPlatform, this.requiredLanguage, this.micCamRequirement, @@ -44,10 +43,52 @@ class LFG extends Content { tags: tags, relatedGameId: relatedGameId, ); - + Future loadLfgSocialData() async { LFGService lfgService = LFGService(); likeIds = await lfgService.getLikedUsers(id); dislikeIds = await lfgService.getDislikedUsers(id); } + + factory LFG.fromJson(Map json) { + print(json); + return LFG( + id: json['lfgId'], + title: json['title'], + description: json['description'], + ownerUserId: json["user"]["userId"], + ownerUsername: json["user"]["username"], + ownerProfileImage: json["user"]["profilePicture"] ?? "", + creationDate: DateTime.parse(json['creationDate']), + tags: json["tags"] != null + ? List.from(json["tags"].map((x) => x["name"])) + : [], + relatedGameId: + json['relatedGame'] != null ? json['relatedGame']["gameId"] : null, + requiredPlatform: json['requiredPlatform'], + requiredLanguage: json['requiredLanguage'], + micCamRequirement: json['micCamRequirement'], + memberCapacity: json['memberCapacity']); + } + + Map toJson() { + return { + "id": id, + "description": content, + "title": title, + "ownerUserId": ownerUserId, + "ownerUsername": ownerUsername, + "ownerProfileImage": ownerProfileImage, + "tags": tags, + "relatedGameId": relatedGameId, + "likes": likes, + "dislikes": dislikes, + "comments": comments, + "commentList": commentList, + "requiredPlatform": requiredPlatform, + "requiredLanguage": requiredLanguage, + "micCamRequirement": micCamRequirement, + "memberCapacity": memberCapacity + }; + } } diff --git a/app/mobile/lib/data/services/game_service.dart b/app/mobile/lib/data/services/game_service.dart index d31db950..4fd53cb0 100644 --- a/app/mobile/lib/data/services/game_service.dart +++ b/app/mobile/lib/data/services/game_service.dart @@ -4,7 +4,9 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:http_parser/http_parser.dart'; import 'package:mobile/constants/network_constants.dart'; +import 'package:mobile/data/models/dto/content/post_report_dto_request.dart'; import 'package:mobile/data/models/dto/empty_response.dart'; +import 'package:mobile/data/models/dto/game/character_create_request.dart'; import 'package:mobile/data/models/dto/game/game_create_dto_request.dart'; import 'package:mobile/data/models/dto/game/game_request.dart'; import 'package:mobile/data/models/dto/game/game_response.dart'; @@ -32,8 +34,9 @@ class GameService { genres: ["Adventure, Role-playing(RPG)"], developers: "CD Projekt RED", releaseYear: 2015, - platforms: - [" Xbox One, PlayStation 4, PlayStation 5, PC (Microsoft Windows), Nintendo Switch, Xbox Series X|S"], + platforms: [ + " Xbox One, PlayStation 4, PlayStation 5, PC (Microsoft Windows), Nintendo Switch, Xbox Series X|S" + ], playerNumber: "Single Player", universe: "Fantasy", mechanics: "Third-person", @@ -45,7 +48,7 @@ class GameService { title: "League of Legends", gamePicture: "https://cdn.ntvspor.net/047bed7cbad44a3dae8bdd7b643ab253.jpg?crop=158,0,782,624&w=800&h=800&mode=crop", - genres: ["MOBA", "Role-playing(RPG)", "Strategy"], + genres: ["MOBA", "Role-playing(RPG)", "Strategy"], developers: "Riot Games", releaseYear: 2009, ), @@ -56,7 +59,7 @@ class GameService { title: "Call of Duty: WWII", gamePicture: "https://upload.wikimedia.org/wikipedia/tr/8/85/Call_of_Duty_WIII_Kapak_Resmi.jpg", - genres: ["Shooter"], + genres: ["Shooter"], developers: "Sledgehammer Games", releaseYear: 2017, ), @@ -121,8 +124,9 @@ Celeste has left a lasting impact on the indie gaming scene, inspiring other dev title: "Baldur's Gate 3", gamePicture: "https://image.api.playstation.com/vulcan/ap/rnd/202302/2321/ba706e54d68d10a0eb6ab7c36cdad9178c58b7fb7bb03d28.png", - genres: - ["Adventure, Role-playing (RPG), Strategy, Tactical, Turn-based strategy (TBS)"], + genres: [ + "Adventure, Role-playing (RPG), Strategy, Tactical, Turn-based strategy (TBS)" + ], developers: "Larian Studios", releaseYear: 2020, ), @@ -153,9 +157,10 @@ Celeste has left a lasting impact on the indie gaming scene, inspiring other dev ); if (response.success) { - List games = response.responseConverted!.games!.map((e) => e.game!) - .where((game) => game.status == "APPROVED") - .toList(); + List games = response.responseConverted!.games! + .map((e) => e.game!) + .where((game) => game.status == "APPROVED") + .toList(); return games; } else { throw Exception('Failed to load games'); @@ -226,16 +231,17 @@ Celeste has left a lasting impact on the indie gaming scene, inspiring other dev String sessionID = manager.getString(SharedKeys.sessionId); String fileName = file!.path.split('/').last; - FormData formData = FormData.fromMap({ - "request": await MultipartFile.fromString( - jsonEncode(gameCreateDTORequest.toJson()), - contentType: MediaType.parse('application/json'), - ), - "image": await MultipartFile.fromFile(file.path, filename: fileName), - }, - ListFormat.multiCompatible, + FormData formData = FormData.fromMap( + { + "request": await MultipartFile.fromString( + jsonEncode(gameCreateDTORequest.toJson()), + contentType: MediaType.parse('application/json'), + ), + "image": await MultipartFile.fromFile(file.path, filename: fileName), + }, + ListFormat.multiCompatible, ); - + Response response = await Dio().post( service.options.baseUrl + "/game", data: formData, @@ -251,7 +257,7 @@ Celeste has left a lasting impact on the indie gaming scene, inspiring other dev } Future rateGame(int gameid, int score) async { - final response = + final response = await service.sendRequestSafe( "/game/$gameid/rating/$score", null, @@ -345,16 +351,17 @@ Celeste has left a lasting impact on the indie gaming scene, inspiring other dev } String sessionID = manager.getString(SharedKeys.sessionId); - FormData formData = FormData.fromMap({ - "request": await MultipartFile.fromString( - jsonEncode(gameCreateDTORequest.toJson()), - contentType: MediaType.parse('application/json'), - ), - "image": null, - }, - ListFormat.multiCompatible, + FormData formData = FormData.fromMap( + { + "request": await MultipartFile.fromString( + jsonEncode(gameCreateDTORequest.toJson()), + contentType: MediaType.parse('application/json'), + ), + "image": null, + }, + ListFormat.multiCompatible, ); - + Response response = await Dio().post( service.options.baseUrl + "/game/$gameid", data: formData, @@ -368,4 +375,86 @@ Celeste has left a lasting impact on the indie gaming scene, inspiring other dev return response.data['gameId']; } + + Future reportGame(int gameid, String reason, String description ) async { + + PostReportDTORequest gameReportDTORequest = PostReportDTORequest( + reason: "$reason : $description", + ); + + final response = + await service.sendRequestSafe( + "/game/$gameid/report", + gameReportDTORequest, + EmptyResponse(), + 'POST', + ); + + if (response.success) { + return true; + } else { + throw Exception('Failed to report game'); + } + } + + Future createCharacter(int gameid, String name, String description ) async { + + CharacterCreateRequest characterCreateRequest = CharacterCreateRequest( + name: name, + description: description + ); + + final response = + await service.sendRequestSafe( + "/character/$gameid", + characterCreateRequest, + EmptyResponse(), + 'POST', + ); + + if (response.success) { + return true; + } else { + throw Exception('Failed to create character'); + } + } + + Future updateCharacter(int gameid, int characterId, String name, String description ) async { + + CharacterCreateRequest characterCreateRequest = CharacterCreateRequest( + name: name, + description: description + ); + + final response = + await service.sendRequestSafe( + "/character/$gameid/$characterId", + characterCreateRequest, + EmptyResponse(), + 'POST', + ); + + if (response.success) { + return true; + } else { + throw Exception('Failed to update character'); + } + } + + Future deleteCharacter(int gameid, int characterId, String name, String description ) async { + + final response = + await service.sendRequestSafe( + "/character/$gameid/$characterId", + null, + EmptyResponse(), + 'DELETE', + ); + + if (response.success) { + return true; + } else { + throw Exception('Failed to delete character'); + } + } } diff --git a/app/mobile/lib/data/services/lfg_service.dart b/app/mobile/lib/data/services/lfg_service.dart index 54f99075..ee9d0b92 100644 --- a/app/mobile/lib/data/services/lfg_service.dart +++ b/app/mobile/lib/data/services/lfg_service.dart @@ -1,14 +1,21 @@ +import 'package:dio/dio.dart'; import 'package:mobile/constants/network_constants.dart'; import 'package:mobile/data/models/comment_model.dart'; +import 'package:mobile/data/models/dto/content/single_content_dto_response.dart'; +import 'package:mobile/data/models/dto/empty_response.dart'; +import 'package:mobile/data/models/dto/lfg/lfg_create_dto_request.dart'; +import 'package:mobile/data/models/dto/lfg/mutliple_lfg_dto_request.dart'; import 'package:mobile/data/models/lfg_model.dart'; +import 'package:mobile/data/models/service_response.dart'; import 'package:mobile/data/services/base_service.dart'; +import 'package:mobile/utils/shared_manager.dart'; class LFGService { static const String serverUrl = NetworkConstants.BASE_LOCAL_URL; final BaseNetworkService service = BaseNetworkService(); - static const String _getLFGs = "/lfg"; + static const String _getLFGs = "/lfg/all"; static const String _getLikedUsers = "/lfg/likes"; static const String _getDislikedUsers = "/lfg/dislikes"; static const String _getComments = "/lfg/comments"; @@ -112,7 +119,25 @@ class LFGService { ]; Future> getLFGs() async { - return getLfgDataList(); + if (NetworkConstants.useMockData) { + return getLfgDataList(); + } else { + + ServiceResponse response = + await service.sendRequestSafe( + _getLFGs, + EmptyResponse(), + MultipleLFGAsDTO(), + 'GET', + ); + if (response.success) { + List lfgs = + response.responseConverted!.lfgs!.map((e) => e.lfg!).toList(); + return lfgs; + } else { + throw Exception('Failed to load lfgs'); + } + } } Future getLFG(int lfgId) async { @@ -149,4 +174,63 @@ class LFGService { static List getLfgDataList() { return lfgList; } + + Future createLFG( + String title, + String description, + String requiredPlatform, + String requiredLanguage, + bool micCamRequirement, + int memberCapacity, + int? gameId, + List tags, + ) async { + if (NetworkConstants.useMockData) { + lfgList.add(LFG( + creationDate: DateTime.now().subtract(const Duration(hours: 5)), + id: lfgList.length + 1, + title: title, + description: description, + requiredPlatform: requiredPlatform, + requiredLanguage: requiredLanguage, + micCamRequirement: micCamRequirement, + memberCapacity: memberCapacity, + tags: ["tag1"], + ownerUserId: 5, + ownerUsername: 'GamerXplorer', + ownerProfileImage: '', + likes: 23, + dislikes: 2, + relatedGameId: 5, + comments: 8, + )); + return true; + } else { + LFGCreateDTORequest request = LFGCreateDTORequest( + title: title, + description: description, + requiredPlatform: requiredPlatform, + requiredLanguage: requiredLanguage, + micCamRequirement: micCamRequirement, + memberCapacity: memberCapacity, + gameId: gameId, + tags: tags, + ); + + Map jsonData = request.toJson(); + + final response = + await service.sendRequestSafe( + "/lfg", + request, + EmptyResponse(), + 'POST', + ); + if (response.success) { + return true; + } else { + throw Exception('Failed to create lfg.'); + } + } + } } diff --git a/app/mobile/lib/presentation/pages/game_wiki_page.dart b/app/mobile/lib/presentation/pages/game_wiki_page.dart index a533d692..9e5f603e 100644 --- a/app/mobile/lib/presentation/pages/game_wiki_page.dart +++ b/app/mobile/lib/presentation/pages/game_wiki_page.dart @@ -6,11 +6,13 @@ import 'package:mobile/data/models/post_model.dart'; import 'package:mobile/data/services/game_service.dart'; import 'package:mobile/data/services/post_service.dart'; import 'package:mobile/presentation/pages/game_page_create.dart'; +import 'package:mobile/presentation/pages/post/report_widget.dart'; import 'package:mobile/presentation/widgets/annotatable_image_widget.dart'; import 'package:mobile/presentation/widgets/app_bar_widget.dart'; import 'package:mobile/presentation/widgets/drawer_widget.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:mobile/presentation/widgets/form_widget.dart'; import 'package:mobile/presentation/widgets/game_card_widget.dart'; import 'package:mobile/presentation/widgets/markdown_widget.dart'; import 'package:mobile/presentation/widgets/post_card_widget.dart'; @@ -18,6 +20,7 @@ import 'package:autoscale_tabbarview/autoscale_tabbarview.dart'; import 'package:mobile/presentation/widgets/vertical_game_card_widget.dart'; import 'package:mobile/utils/cache_manager.dart'; import 'package:mobile/utils/shared_manager.dart'; +import 'package:mobile/utils/validation_utils.dart'; class GameWiki extends StatefulWidget { const GameWiki({super.key}); @@ -133,6 +136,11 @@ class _GameWikiPageState extends State static late Game game; + final TextEditingController nameController = TextEditingController(); + final TextEditingController descriptionController= TextEditingController(); + + final List controllerNames = ['Name', 'Description']; + Future loadGame(int gameId) async { Game game = await gameService.getGame(gameId); List postList = await postService.getPosts(); @@ -164,18 +172,29 @@ class _GameWikiPageState extends State width: 500, child: Column( children: [ + Text(game.title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700)), + Align( + alignment: Alignment.centerRight, + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return ReportWidgetForGame(gameid: game.gameId,); + }, + ); + }, + child: Icon(Icons.warning), + ), + ), Row( children: [ Expanded( - child: Column( + child: Column( children: [ - Text(game.title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700)), - const SizedBox( - height: 15, - ), const Align( alignment: Alignment.centerLeft, child: Text("Release Year: ", @@ -418,6 +437,69 @@ class _GameWikiPageState extends State ), ), ), + Card( + margin: + const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + child: SizedBox( + width: 500, + child: Column( + children: [ + const Align( + alignment: Alignment.center, + child: Text("Characters", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + ))), + Align( + alignment: Alignment.centerRight, + child: InkWell( + onTap: () { + _dialogBuilder(context); + }, + child:Icon(Icons.add), + )), + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + height: 75, + child: ListView( + // This next line does the trick. + scrollDirection: Axis.vertical, + children: [ + if (game.characters != null) + for (var i = 0; + i < game.characters!.length; + i++) + Row( + children: [ + RichText( + text: TextSpan( + style: TextStyle(color: Colors.black), + children: [ + TextSpan( + text: game.characters![i].name, + style: + TextStyle(fontWeight: FontWeight.w500), + ), + TextSpan( + text: ": ", + style: + TextStyle(fontWeight: FontWeight.w500), + ), + TextSpan( + text: game.characters![i].description, + ) + ], + )), ], + ), + ], + ), + ), + ], + )), + ), Card( margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), @@ -519,6 +601,71 @@ class _GameWikiPageState extends State } }); } + + Future _dialogBuilder(BuildContext context) { + final _formKey = GlobalKey(); + + return showDialog( + context: context, + builder: (context) => AlertDialog( + content: Stack( + clipBehavior: Clip.none, + children: [ + Positioned( + right: -40, + top: -40, + child: InkResponse( + onTap: () { + Navigator.of(context).pop(); + }, + child: const CircleAvatar( + backgroundColor: Colors.red, + child: Icon(Icons.close), + ), + ), + ), + Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: TextFormField( + controller: nameController, + decoration: InputDecoration(hintText: 'Character Name'), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextFormField( + minLines: 3, + maxLines: 5, + controller: descriptionController, + decoration: InputDecoration(hintText: 'Character Description'), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ElevatedButton( + child: const Text('Submit'), + onPressed: () { + gameService.createCharacter(game.gameId, nameController.text, descriptionController.text); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + } + Navigator.pop(context); + }, + ), + ) + ], + ), + ), + ], + ), + ) + ); + } } // class VerticalGameCard extends StatelessWidget { @@ -553,3 +700,5 @@ class _GameWikiPageState extends State // ); // } // } + + diff --git a/app/mobile/lib/presentation/pages/lfg_page_create.dart b/app/mobile/lib/presentation/pages/lfg_page_create.dart index 571c345b..4bd10535 100644 --- a/app/mobile/lib/presentation/pages/lfg_page_create.dart +++ b/app/mobile/lib/presentation/pages/lfg_page_create.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:mobile/constants/color_constants.dart'; +import 'package:mobile/data/models/game_model.dart'; import 'package:mobile/data/models/lfg_model.dart'; +import 'package:mobile/data/services/game_service.dart'; +import 'package:mobile/data/services/lfg_service.dart'; import 'package:mobile/presentation/widgets/button_widget.dart'; import 'package:mobile/presentation/widgets/tag_input.dart'; @@ -16,7 +19,7 @@ class LFGPageCreate extends StatefulWidget { class _LFGCreatePageState extends State { final _descriptionController = TextEditingController(); final _titleController = TextEditingController(); - var title = "Create Game Page"; + var title = "Create LFG Page"; var buttonLabel = "Create"; final List _platformList = [ "XBOX", @@ -26,6 +29,7 @@ class _LFGCreatePageState extends State { "EMPTY" ]; String? _selectedPlatform; + int? _selectedGame; final List _languageList = [ "TUR", "EN", @@ -40,7 +44,7 @@ class _LFGCreatePageState extends State { void initState() { super.initState(); if (widget.selectedLFG != null) { - title = "Update Game Page"; + title = "Update LFG Page"; buttonLabel = "Update"; _titleController.text = widget.selectedLFG!.title!; _descriptionController.text = widget.selectedLFG!.content!; @@ -52,8 +56,26 @@ class _LFGCreatePageState extends State { } } + Future> loadGameData() async { + return await GameService().getGames(); + } + @override Widget build(BuildContext context) { + return FutureBuilder( + future: loadGameData(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return buildLFGCreatePage(context, snapshot.data as List); + } else if (snapshot.hasError) { + return Text("${snapshot.error}"); + } + return const CircularProgressIndicator(); + }, + ); + } + + Widget buildLFGCreatePage(BuildContext context, List gameList) { return Scaffold( appBar: AppBar( title: Text(title), @@ -169,9 +191,26 @@ class _LFGCreatePageState extends State { ), TagInput(tags: _tags, tagController: _tagController), const SizedBox(height: 16), + buildGameDropdown(gameList), + const SizedBox(height: 16), Button( label: buttonLabel, - onPressed: () async {}, + onPressed: () async { + if (widget.selectedLFG == null) { + LFGService().createLFG( + _titleController.text, + _descriptionController.text, + _selectedPlatform!, + _selectedLanguage!, + micCamRequired, + int.parse(_capacityController.text), + _selectedGame, + _tags); + } else { + //update + } + Navigator.of(context).pop("create"); + }, ), ]), ), @@ -179,4 +218,36 @@ class _LFGCreatePageState extends State { ), ); } + + Widget buildGameDropdown(List gameList) { + return DropdownButtonFormField( + value: _selectedGame, + decoration: const InputDecoration( + hintText: "Select a game", + ), + items: gameList.map((game) { + return DropdownMenuItem( + value: game.gameId, + child: Row( + children: [ + Image.network(game.gamePicture, width: 50, height: 50), + const SizedBox(width: 16), + Text(game.title), + ], + ), + ); + }).toList(), + onChanged: (value) { + setState(() { + _selectedGame = value; + }); + }, + validator: (value) { + if (value == null) { + return "Please select a game"; + } + return null; + }, + ); + } } diff --git a/app/mobile/lib/presentation/pages/post/report_widget.dart b/app/mobile/lib/presentation/pages/post/report_widget.dart index 6a7041d9..bc632ff5 100644 --- a/app/mobile/lib/presentation/pages/post/report_widget.dart +++ b/app/mobile/lib/presentation/pages/post/report_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mobile/data/models/content_model.dart'; +import 'package:mobile/data/services/game_service.dart'; import 'package:mobile/data/services/post_service.dart'; class ReportWidget extends StatelessWidget { @@ -91,4 +92,93 @@ class ReportWidget extends StatelessWidget { ], ); } +} + + +class ReportWidgetForGame extends StatelessWidget { + final int gameid; + + ReportWidgetForGame({Key? key, required this.gameid }) : super(key: key); + + final _formKey = GlobalKey(); + final _reasonController = TextEditingController(); + final _descriptionController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text("Report"), + // increase size of the alert dialog + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + controller: _reasonController, + decoration: const InputDecoration( + hintText: "Reason", + ), + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter a reason"; + } + return null; + }, + ), + TextFormField( + controller: _descriptionController, + minLines: 3, + maxLines: 5, + decoration: const InputDecoration( + hintText: "Description", + ), + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter a description"; + } + return null; + }, + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text("Cancel"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text("Report"), + onPressed: () { + if (_formKey.currentState!.validate()) { + String reason = _reasonController.text; + String description = _descriptionController.text; + GameService().reportGame(gameid, reason, description); + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text("Report"), + content: const Text("Your report has been sent."), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + }, + ), + ], + ); + } } \ No newline at end of file diff --git a/app/mobile/lib/presentation/states/game_list_grid_view_state.dart b/app/mobile/lib/presentation/states/game_list_grid_view_state.dart index b7a05832..05e60bed 100644 --- a/app/mobile/lib/presentation/states/game_list_grid_view_state.dart +++ b/app/mobile/lib/presentation/states/game_list_grid_view_state.dart @@ -64,24 +64,6 @@ class GridViewState extends State { } else { return const CircularProgressIndicator(); } - }) - - /*Column(children: [ - Expanded( - child: GridView.count( - crossAxisCount: countValue, - childAspectRatio: (aspectWidth / aspectHeight), - children: itemList - .map((data) => GestureDetector( - onTap: () { - Navigator.pushNamed(context, "/game", - arguments: data.gameId); - }, - child: GameCard(game: data))) - .toList(), - ), - ) - ])*/ - ); + })); } } diff --git a/app/mobile/lib/presentation/states/lfg_list_grid_view_state.dart b/app/mobile/lib/presentation/states/lfg_list_grid_view_state.dart index f97b03f3..c7c8ed03 100644 --- a/app/mobile/lib/presentation/states/lfg_list_grid_view_state.dart +++ b/app/mobile/lib/presentation/states/lfg_list_grid_view_state.dart @@ -10,14 +10,16 @@ class GridViewState extends State { int countValue = 2; int aspectWidth = 2; int aspectHeight = 1; - List itemList = getImageDataList(); - - static List getImageDataList() { - return LFGService.lfgList; - } + final LFGService service = LFGService(); late bool isLoggedIn; + Future> loadLFGs() async { + List lfgList = await service.getLFGs(); + + return lfgList; + } + @override void initState() { super.initState(); @@ -58,8 +60,8 @@ class GridViewState extends State { ); } - @override - Widget build(BuildContext context) { +/* + Widget build2(BuildContext context) { return Scaffold( body: Column(children: [ Expanded( @@ -82,4 +84,39 @@ class GridViewState extends State { ) ])); } +*/ + @override + Widget build(BuildContext context) { + return Scaffold( + body: FutureBuilder( + future: Future.wait([loadLFGs()]), + builder: + (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + List lfgs = snapshot.data![0]; + return Column(children: [ + Expanded( + child: GridView.count( + crossAxisCount: countValue, + childAspectRatio: (aspectWidth / aspectHeight), + children: lfgs + .map((data) => GestureDetector( + onTap: () { + if (!isLoggedIn) { + Navigator.pushNamed(context, '/login'); + } else { + Navigator.pushNamed(context, "/group", + arguments: data.id); + } + }, + child: LFGCard(lfg: data))) + .toList(), + ), + ) + ]); + } else { + return const CircularProgressIndicator(); + } + })); + } } diff --git a/app/mobile/lib/presentation/widgets/lfg_card_widget.dart b/app/mobile/lib/presentation/widgets/lfg_card_widget.dart index d45a5a1c..182c0b17 100644 --- a/app/mobile/lib/presentation/widgets/lfg_card_widget.dart +++ b/app/mobile/lib/presentation/widgets/lfg_card_widget.dart @@ -25,7 +25,7 @@ class LFGCard extends StatelessWidget { child: SizedBox( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), - child: Text(lfg.title![0]), + child: Text(lfg.title!), ), )), ],