From 60198526d772a60dab2c4be7460f584bede12780 Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Tue, 19 Mar 2024 14:24:43 +0100 Subject: [PATCH 1/9] feat: add template board model --- lib/models/trello_board_template.dart | 32 +++++++++++++++++++++++++++ lib/repositories/api.dart | 20 ++++++++++++++++- lib/views/board/workspace_view.dart | 21 +++++++++++------- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/lib/models/trello_board_template.dart b/lib/models/trello_board_template.dart index e69de29..96c40bc 100644 --- a/lib/models/trello_board_template.dart +++ b/lib/models/trello_board_template.dart @@ -0,0 +1,32 @@ +class TrelloBoardTemplate { + + final String id; + final String name; + final String description; + String? backgroundImage; + String? backgroundColor; + final int viewCount; + final int copyCount; + + TrelloBoardTemplate({ + required this.id, + required this.name, + required this.description, + this.backgroundImage, + this.backgroundColor, + required this.viewCount, + required this.copyCount, + }); + + factory TrelloBoardTemplate.fromJson(Map json) { + return TrelloBoardTemplate( + id: json['id'], + name: json['name'], + description: json['desc'], + backgroundImage: json['prefs']['backgroundImage'], + backgroundColor: json['prefs']['backgroundColor'], + viewCount: json['prefs']['viewCount'], + copyCount: json['prefs']['copyCount'], + ); + } +} \ No newline at end of file diff --git a/lib/repositories/api.dart b/lib/repositories/api.dart index 04884c1..d5d4fbb 100644 --- a/lib/repositories/api.dart +++ b/lib/repositories/api.dart @@ -4,6 +4,7 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:trelltech/models/board.dart'; +import 'package:trelltech/models/trello_board_template.dart'; import 'package:trelltech/models/trello_organization.dart'; import '../models/trello_card.dart'; @@ -279,9 +280,26 @@ Future getWorkspace(String apiKey, String? token, String wor if (response.statusCode == 200) { var data = jsonDecode(response.body); - print(data); return TrelloOrganization.fromJson(data); } else { throw Exception('Failed to load workspace'); } +} + +/// Fetches the templates of boards in the gallery from Trello. +/// +/// This function calls the Trello API to fetch the templates of boards in the gallery. +/// It requires the user's API key and token. +/// Returns a list of board templates. +Future> getBoardTemplates(String apiKey, String token) async { + final response = await http.get( + Uri.parse('https://api.trello.com/1/boards/templates/gallery?key=$apiKey&token=$token'), + ); + + if (response.statusCode == 200) { + var boardTemplatesJson = jsonDecode(response.body) as List; + return boardTemplatesJson.map((boardTemplate) => TrelloBoardTemplate.fromJson(boardTemplate)).toList(); + } else { + throw Exception('Failed to load board templates'); + } } \ No newline at end of file diff --git a/lib/views/board/workspace_view.dart b/lib/views/board/workspace_view.dart index 3afb539..6a3ac65 100644 --- a/lib/views/board/workspace_view.dart +++ b/lib/views/board/workspace_view.dart @@ -91,6 +91,17 @@ class WorkspaceViewState extends State { return await PaletteGenerator.fromImageProvider(provider); } + Future _addNewBoard() async { + var result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => CreateBoardScreen()), + ); + + if (result == 'boardCreated') { + await refreshData(); + } + } + Future _getBgColor(String? color, String? imageUrl) async { if (color != null) { String colorHex = color!.split('#')[1]; @@ -141,7 +152,7 @@ class WorkspaceViewState extends State { "Vous n'avez actuellement aucun tableau de créé pour cette organisation. Veuillez cliquer pour en ajouter un", iconData: Icons.dashboard, onTap: () { - print('Tableau clicked'); + _addNewBoard(); }, isMasculine: true, ) @@ -157,13 +168,7 @@ class WorkspaceViewState extends State { bottomNavigationBar: MenuWidget(), floatingActionButton: FloatingActionButton( onPressed: () async { - var result = await Navigator.push( - context, - MaterialPageRoute(builder: (context) => CreateBoardScreen()), - ); - if (result == 'organizationCreated') { - refreshData(); - } + _addNewBoard(); }, backgroundColor: const Color(0xFF0D1B50), foregroundColor: Colors.white, From 490a9f4714f410998e8981a217f6d709252de308 Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Tue, 19 Mar 2024 16:30:08 +0100 Subject: [PATCH 2/9] feat: :sparkles: add creation of boards --- android/app/google-services.json | 29 ++ google-services.json | 29 ++ ios/firebase_app_id_file.json | 7 + lib/models/trello_board_template.dart | 7 +- lib/repositories/api.dart | 145 +++++++--- lib/repositories/authentification.dart | 13 +- lib/views/board/board_create_view.dart | 358 +++++++++++++++++++++---- lib/views/board/workspace_view.dart | 44 +-- 8 files changed, 523 insertions(+), 109 deletions(-) create mode 100644 android/app/google-services.json create mode 100644 google-services.json create mode 100644 ios/firebase_app_id_file.json diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..0e2363a --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "87151672734", + "project_id": "trelltech-5a6ad", + "storage_bucket": "trelltech-5a6ad.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:87151672734:android:42139890d5d5705fc42a8f", + "android_client_info": { + "package_name": "com.dev600.nan2.trelltech" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCwW21JzPiNAG1Hfa6btTwE0muge-GbBVg" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/google-services.json b/google-services.json new file mode 100644 index 0000000..dfde0f9 --- /dev/null +++ b/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "87151672734", + "project_id": "trelltech-5a6ad", + "storage_bucket": "trelltech-5a6ad.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:87151672734:android:caa5026e4ffad71dc42a8f", + "android_client_info": { + "package_name": "com.dev600.nan2.trelltech" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCwW21JzPiNAG1Hfa6btTwE0muge-GbBVg" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/ios/firebase_app_id_file.json b/ios/firebase_app_id_file.json new file mode 100644 index 0000000..caaeef7 --- /dev/null +++ b/ios/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:87151672734:ios:25efc20cf9c69ebac42a8f", + "FIREBASE_PROJECT_ID": "trelltech-5a6ad", + "GCM_SENDER_ID": "87151672734" +} \ No newline at end of file diff --git a/lib/models/trello_board_template.dart b/lib/models/trello_board_template.dart index 96c40bc..9643e8a 100644 --- a/lib/models/trello_board_template.dart +++ b/lib/models/trello_board_template.dart @@ -1,5 +1,4 @@ class TrelloBoardTemplate { - final String id; final String name; final String description; @@ -25,8 +24,8 @@ class TrelloBoardTemplate { description: json['desc'], backgroundImage: json['prefs']['backgroundImage'], backgroundColor: json['prefs']['backgroundColor'], - viewCount: json['prefs']['viewCount'], - copyCount: json['prefs']['copyCount'], + viewCount: json['templateGallery']['stats']['viewCount'], + copyCount: json['templateGallery']['stats']['copyCount'], ); } -} \ No newline at end of file +} diff --git a/lib/repositories/api.dart b/lib/repositories/api.dart index d5d4fbb..408c516 100644 --- a/lib/repositories/api.dart +++ b/lib/repositories/api.dart @@ -30,7 +30,8 @@ Future getTrelloClientID(String apiKey, String token) async { Future getMember(String apiKey, String token, String memberId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/members/$memberId?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/members/$memberId?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -40,9 +41,11 @@ Future getMember(String apiKey, String token, String memberId) async { } } -Future getMembersFromCard(String apiKey, String token, String cardId) async { +Future getMembersFromCard( + String apiKey, String token, String cardId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/cards/$cardId/members?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/cards/$cardId/members?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -52,9 +55,11 @@ Future getMembersFromCard(String apiKey, String token, String cardId) a } } -Future getMembersFromBoard(String apiKey, String token, String boardId) async { +Future getMembersFromBoard( + String apiKey, String token, String boardId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/boards/$boardId/members?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/boards/$boardId/members?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -64,9 +69,11 @@ Future getMembersFromBoard(String apiKey, String token, String boardId) } } -Future addMemberToCard(String apiKey, String token, String cardId, String memberId) async { +Future addMemberToCard( + String apiKey, String token, String cardId, String memberId) async { final response = await http.post( - Uri.parse('https://api.trello.com/1/cards/$cardId/idMembers?key=$apiKey&token=$token&value=$memberId'), + Uri.parse( + 'https://api.trello.com/1/cards/$cardId/idMembers?key=$apiKey&token=$token&value=$memberId'), ); if (response.statusCode != 200) { @@ -74,14 +81,50 @@ Future addMemberToCard(String apiKey, String token, String cardId, Stri } } +/// Create a new board on Trello. +/// This function calls the Trello API to create a new board. +/// It requires the user's API key, token, and the name of the board. +/// Returns the ID of the new board. +/// Throws an exception if the request fails. +Future createBoard(String apiKey, String token, String name, + String description, String workspaceId, String? templateId) async { + if (templateId != null) { + final response = await http.post( + Uri.parse( + 'https://api.trello.com/1/boards?key=$apiKey&token=$token&name=$name&desc=$description&idBoardSource=$templateId&idOrganization=$workspaceId'), + ); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return data['id']; + } else { + throw Exception('Failed to create board'); + } + } else { + final response = await http.post( + Uri.parse( + 'https://api.trello.com/1/boards?key=$apiKey&token=$token&name=$name&desc=$description&idOrganization=$workspaceId'), + ); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return data['id']; + } else { + throw Exception('Failed to create board'); + } + } +} + /// Fetches the user's boards from Trello. /// /// This function calls the Trello API to fetch the user's boards. /// It requires the user's API key, token, and the workspace ID. /// Returns a list of boards. -Future> getBoards(String apiKey, String token, String workspaceId) async { +Future> getBoards( + String apiKey, String token, String workspaceId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/organizations/$workspaceId/boards?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/organizations/$workspaceId/boards?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -93,9 +136,11 @@ Future> getBoards(String apiKey, String token, String workspaceId) a } } -Future updateBoard(String apiKey, String token, String boardId, Board board) async { +Future updateBoard( + String apiKey, String token, String boardId, Board board) async { final response = await http.put( - Uri.parse('https://api.trello.com/1/boards/$boardId?key=$apiKey&token=$token&name=${board.name}'), + Uri.parse( + 'https://api.trello.com/1/boards/$boardId?key=$apiKey&token=$token&name=${board.name}'), ); if (response.statusCode != 200) { @@ -108,9 +153,11 @@ Future updateBoard(String apiKey, String token, String boardId, Board boar /// This function calls the Trello API to fetch the user's workspaces. /// It requires the user's API key, token, and client ID. /// Returns a list of workspaces. -Future> getWorkspaces(String apiKey, String? token, String? clientId) async { +Future> getWorkspaces( + String apiKey, String? token, String? clientId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/members/$clientId/organizations?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/members/$clientId/organizations?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -126,9 +173,11 @@ Future> getWorkspaces(String apiKey, String? token, String? client /// This function calls the Trello API to fetch the lists of a specific board. /// It requires the user's API key, token, and the board's ID. /// Returns a list of lists. -Future> getLists(String apiKey, String token, String boardId) async { +Future> getLists( + String apiKey, String token, String boardId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/boards/$boardId/lists?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/boards/$boardId/lists?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -138,9 +187,11 @@ Future> getLists(String apiKey, String token, String boardId) asyn } } -Future updateList(String apiKey, String token, String listId, TrelloList list) async { +Future updateList( + String apiKey, String token, String listId, TrelloList list) async { final response = await http.put( - Uri.parse('https://api.trello.com/1/lists/$listId?key=$apiKey&token=$token&name=${list.name}'), + Uri.parse( + 'https://api.trello.com/1/lists/$listId?key=$apiKey&token=$token&name=${list.name}'), ); if (response.statusCode != 200) { @@ -153,9 +204,11 @@ Future updateList(String apiKey, String token, String listId, TrelloList l /// This function calls the Trello API to fetch the cards of a specific list. /// It requires the user's API key, token, and the list's ID. /// Returns a list of cards. -Future> getCards(String apiKey, String token, String listId) async { +Future> getCards( + String apiKey, String token, String listId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/lists/$listId/cards?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/lists/$listId/cards?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -169,9 +222,11 @@ Future> getCards(String apiKey, String token, String listId) async /// /// This function calls the Trello API to create a new card in a specific list. /// It requires the user's API key, token, the list's ID, and the name of the card. -Future createCard(String apiKey, String token, String listId, String name) async { +Future createCard( + String apiKey, String token, String listId, String name) async { final response = await http.post( - Uri.parse('https://api.trello.com/1/cards?key=$apiKey&token=$token&idList=$listId&name=$name'), + Uri.parse( + 'https://api.trello.com/1/cards?key=$apiKey&token=$token&idList=$listId&name=$name'), ); if (response.statusCode != 200) { @@ -185,7 +240,8 @@ Future createCard(String apiKey, String token, String listId, String name) /// It requires the user's API key, token, and the card's ID. Future deleteCard(String apiKey, String token, String cardId) async { final response = await http.delete( - Uri.parse('https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -205,9 +261,11 @@ Future deleteCard(String apiKey, String token, String cardId) async { /// /// This function calls the Trello API to update the name of a specific card. /// It requires the user's API key, token, the card's ID, and the new name of the card. -Future updateCard(String apiKey, String token, String cardId, TrelloCard card) async { +Future updateCard( + String apiKey, String token, String cardId, TrelloCard card) async { final response = await http.put( - Uri.parse('https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token&desc=${card.desc}&name=${card.name}'), + Uri.parse( + 'https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token&desc=${card.desc}&name=${card.name}'), ); if (response.statusCode != 200) { @@ -219,9 +277,11 @@ Future updateCard(String apiKey, String token, String cardId, TrelloCard c /// /// This function calls the Trello API to move a specific card to a different list. /// It requires the user's API key, token, the card's ID, and the ID of the list to move the card to. -Future moveCard(String apiKey, String token, String cardId, String listId) async { +Future moveCard( + String apiKey, String token, String cardId, String listId) async { final response = await http.put( - Uri.parse('https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token&idList=$listId'), + Uri.parse( + 'https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token&idList=$listId'), ); if (response.statusCode != 200) { @@ -235,7 +295,8 @@ Future moveCard(String apiKey, String token, String cardId, String listId) /// It requires the user's API key, token, and the name of the workspace. Future createWorkspace(String apiKey, String token, String name) async { final response = await http.post( - Uri.parse('https://api.trello.com/1/organizations?key=$apiKey&token=$token&displayName=$name'), + Uri.parse( + 'https://api.trello.com/1/organizations?key=$apiKey&token=$token&displayName=$name'), ); if (response.statusCode != 200) { @@ -247,9 +308,11 @@ Future createWorkspace(String apiKey, String token, String name) async { /// /// This function calls the Trello API to delete a specific workspace. /// It requires the user's API key, token, and the workspace's ID. -Future deleteWorkspace(String apiKey, String token, String workspaceId) async { +Future deleteWorkspace( + String apiKey, String token, String workspaceId) async { final response = await http.delete( - Uri.parse('https://api.trello.com/1/organizations/$workspaceId?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/organizations/$workspaceId?key=$apiKey&token=$token'), ); if (response.statusCode != 200) { @@ -257,9 +320,11 @@ Future deleteWorkspace(String apiKey, String token, String workspaceId) as } } -Future updateWorkspace(String apiKey, String? token, String workspaceId, TrelloOrganization organization) async { +Future updateWorkspace(String apiKey, String? token, String workspaceId, + TrelloOrganization organization) async { final response = await http.put( - Uri.parse('https://api.trello.com/1/organizations/$workspaceId?key=$apiKey&token=$token&displayName=${organization.displayName}&desc=${organization.description}'), + Uri.parse( + 'https://api.trello.com/1/organizations/$workspaceId?key=$apiKey&token=$token&displayName=${organization.displayName}&desc=${organization.description}'), ); if (response.statusCode != 200) { @@ -273,9 +338,11 @@ Future updateWorkspace(String apiKey, String? token, String workspaceId, T /// /// This function calls the Trello API to update the name of a specific workspace. /// It requires the user's API key, token, the workspace's ID, and the new name of the workspace. -Future getWorkspace(String apiKey, String? token, String workspaceId) async { +Future getWorkspace( + String apiKey, String? token, String workspaceId) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/organizations/$workspaceId?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/organizations/$workspaceId?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { @@ -291,15 +358,19 @@ Future getWorkspace(String apiKey, String? token, String wor /// This function calls the Trello API to fetch the templates of boards in the gallery. /// It requires the user's API key and token. /// Returns a list of board templates. -Future> getBoardTemplates(String apiKey, String token) async { +Future> getBoardTemplates( + String apiKey, String? token) async { final response = await http.get( - Uri.parse('https://api.trello.com/1/boards/templates/gallery?key=$apiKey&token=$token'), + Uri.parse( + 'https://api.trello.com/1/boards/templates/gallery?key=$apiKey&token=$token'), ); if (response.statusCode == 200) { var boardTemplatesJson = jsonDecode(response.body) as List; - return boardTemplatesJson.map((boardTemplate) => TrelloBoardTemplate.fromJson(boardTemplate)).toList(); + return boardTemplatesJson + .map((boardTemplate) => TrelloBoardTemplate.fromJson(boardTemplate)) + .toList(); } else { throw Exception('Failed to load board templates'); } -} \ No newline at end of file +} diff --git a/lib/repositories/authentification.dart b/lib/repositories/authentification.dart index 24dcc3f..2d1f668 100644 --- a/lib/repositories/authentification.dart +++ b/lib/repositories/authentification.dart @@ -9,7 +9,6 @@ import 'package:trelltech/repositories/api.dart'; import 'package:trelltech/views/dashboard/dashboard_view.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; - /// Authenticate the user with Trello. /// /// This function initiates the authentication process with Trello, obtaining @@ -34,12 +33,13 @@ Future authenticateWithTrello(BuildContext context) async { }); // Verify that the Trello API key is present - if(trelloAPIKey == null || trelloAPPName == null) { + if (trelloAPIKey == null || trelloAPPName == null) { if (kDebugMode) { print('TRELLO_API_KEY or TRELLO_APP_NAME not found in the .env file'); } FToast().showToast( - child: const Text('Une erreur est survenue lors de l\'authentification. Veuillez contacter le support avec le code d\'erreur ATH_NT_FND'), + child: const Text( + 'Une erreur est survenue lors de l\'authentification. Veuillez contacter le support avec le code d\'erreur ATH_NT_FND'), gravity: ToastGravity.BOTTOM, toastDuration: const Duration(seconds: 3), ); @@ -75,7 +75,7 @@ Future authenticateWithTrello(BuildContext context) async { // Redirect the user to the dashboard page once authenticated (and remove the possibility to go back) Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (context) => DashboardView()), - (Route route) => false, + (Route route) => false, ); return accessToken; } @@ -102,7 +102,8 @@ Future disconnect(BuildContext context) async { /// @return True if the user is connected, false otherwise. Future isConnected() async { final prefs = await SharedPreferences.getInstance(); - return prefs.containsKey('accessToken') && prefs.getString('accessToken') != null; + return prefs.containsKey('accessToken') && + prefs.getString('accessToken') != null; } /// Get the access token of the user. @@ -119,4 +120,4 @@ Future getAccessToken() async { Future getClientID() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('clientID'); -} \ No newline at end of file +} diff --git a/lib/views/board/board_create_view.dart b/lib/views/board/board_create_view.dart index 37b00a2..bf0b303 100644 --- a/lib/views/board/board_create_view.dart +++ b/lib/views/board/board_create_view.dart @@ -1,14 +1,18 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:trelltech/models/board.dart'; +import 'package:trelltech/models/trello_board_template.dart'; import 'package:trelltech/models/trello_organization.dart'; import 'package:trelltech/repositories/api.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:trelltech/repositories/authentification.dart'; class CreateBoardScreen extends StatefulWidget { - const CreateBoardScreen({Key? key}) : super(key: key); + String organizationId; + CreateBoardScreen({super.key, required this.organizationId}); @override _CreateBoardScreenState createState() => _CreateBoardScreenState(); @@ -19,6 +23,8 @@ class _CreateBoardScreenState extends State { late TextEditingController _nameController; late TextEditingController _descriptionController; late bool _isLoading = false; + String _selectedOption = 'tableau_vierge'; + TrelloBoardTemplate? _selectedTemplate; @override void initState() { @@ -53,7 +59,13 @@ class _CreateBoardScreenState extends State { } try { - await createWorkspace(apiKey, accessToken, _nameController.text); + await createBoard( + apiKey, + accessToken, + _nameController.text, + _descriptionController.text, + widget.organizationId, + _selectedTemplate?.id); Fluttertoast.showToast( msg: AppLocalizations.of(context)!.boardCreated, @@ -64,7 +76,7 @@ class _CreateBoardScreenState extends State { textColor: Colors.white, fontSize: 16.0, ); - Navigator.pop(context, 'organizationCreated'); + Navigator.pop(context, 'boardCreated'); } catch (e) { Fluttertoast.showToast( msg: AppLocalizations.of(context)!.boardCreationFailed, @@ -83,6 +95,103 @@ class _CreateBoardScreenState extends State { } } + void _showTemplatePicker() async { + final templates = await getBoardTemplates( + dotenv.env['TRELLO_API_KEY']!, await getAccessToken()); + + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (BuildContext bc) { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.9, + child: Padding( + padding: + const EdgeInsets.only(top: 16.0), // Ajoutez une marge en haut + child: ListView.builder( + itemCount: templates.length, + itemBuilder: (context, index) { + return Card( + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + templates[index].backgroundImage != null + ? Ink.image( + image: NetworkImage( + templates[index].backgroundImage!), + fit: BoxFit.cover, + height: 240, + child: InkWell( + onTap: () { + setState(() { + _selectedTemplate = templates[index]; + }); + Navigator.of(context).pop(); + }, + ), + ) + : Container( + color: Color(int.parse( + 'FF${templates[index].backgroundColor?.replaceAll('#', '') ?? ''}', + radix: 16)), + height: 240, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + templates[index].name, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + Row( + children: [ + const Icon( + Icons.remove_red_eye, + color: Colors.black, + ), + Text( + '${templates[index].viewCount}', + style: const TextStyle( + wordSpacing: 2, + color: Colors.black, + ), + ), + const SizedBox(width: 16.0), + const Icon( + Icons.copy, + color: Colors.black, + ), + Text( + '${templates[index].copyCount}', + style: const TextStyle( + color: Colors.black, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + ); + }, + ); + } @override Widget build(BuildContext context) { @@ -93,50 +202,209 @@ class _CreateBoardScreenState extends State { body: _isLoading ? const Center(child: CircularProgressIndicator()) : Padding( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextFormField( - controller: _nameController, - decoration: InputDecoration( - labelText: AppLocalizations.of(context)!.boardName, - border: const OutlineInputBorder(), - ), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.requiredField; - } - return null; - }, - ), - const SizedBox(height: 16.0), - TextFormField( - controller: _descriptionController, - decoration: InputDecoration( - labelText: AppLocalizations.of(context)!.boardDescription, - border: const OutlineInputBorder(), - ), - maxLines: 4, - ), - const SizedBox(height: 16.0), - ElevatedButton( - onPressed: _submitForm, - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, backgroundColor: Color(0xFF1C39A1), - ), - child: Text( - AppLocalizations.of(context)!.boardCreated, - style: const TextStyle(fontSize: 16.0), + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + controller: _nameController, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)!.boardName, + border: const OutlineInputBorder(), + ), + validator: (value) { + if (value!.isEmpty) { + return AppLocalizations.of(context)!.requiredField; + } + return null; + }, + ), + const SizedBox(height: 16.0), + TextFormField( + controller: _descriptionController, + decoration: InputDecoration( + labelText: + AppLocalizations.of(context)!.boardDescription, + border: const OutlineInputBorder(), + ), + maxLines: 4, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _selectedOption = 'tableau_vierge'; + _selectedTemplate = null; + }); + }, + child: AspectRatio( + aspectRatio: 1.0, + child: Card( + shape: RoundedRectangleBorder( + side: BorderSide( + color: _selectedOption == 'tableau_vierge' + ? Colors.blue + : Colors.grey, + width: 2.0, + ), + borderRadius: BorderRadius.circular(15.0), + ), + child: const Padding( + padding: EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.insert_chart, + size: 50.0, + ), + Text('Tableau vierge'), + ], + ), + ), + ), + ), + ), + ), + _selectedTemplate != null + ? Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _selectedOption = 'template'; + }); + _showTemplatePicker(); + }, + child: AspectRatio( + aspectRatio: 1.0, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(15.0), + side: BorderSide( + color: _selectedOption == 'template' + ? Colors.blue + : Colors.grey, + width: 2.0, + ), + ), + child: Stack( + fit: StackFit.expand, + children: [ + ClipRRect( + borderRadius: + BorderRadius.circular(15.0), + child: Image.network( + _selectedTemplate! + .backgroundImage!, + fit: BoxFit.cover, + ), + ), + ClipRRect( + borderRadius: + BorderRadius.circular(15.0), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 5.0, sigmaY: 5.0), + child: Container( + color: + Colors.black.withOpacity(0), + child: ClipRRect( + borderRadius: + BorderRadius.circular( + 15.0), + child: Image.network( + _selectedTemplate! + .backgroundImage!, + fit: BoxFit.cover, + ), + ), + ), + ), + ), + Center( + child: Text( + _selectedTemplate!.name, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + ), + ) + : Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _selectedOption = 'template'; + _selectedTemplate = null; + }); + _showTemplatePicker(); + }, + child: AspectRatio( + aspectRatio: 1.0, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(15.0), + side: BorderSide( + color: _selectedOption == 'template' + ? Colors.blue + : Colors.grey, + width: 2.0, + ), + ), + child: const Padding( + padding: EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.dashboard_customize, + size: 50.0, + ), + Text( + 'Basé sur un template', + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 16.0), + ElevatedButton( + onPressed: _submitForm, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: const Color(0xFF1C39A1), + ), + child: Text( + AppLocalizations.of(context)!.boardCreated, + style: const TextStyle(fontSize: 16.0), + ), + ), + ], ), ), - ], - ), - ), - ), + ), ); } } - diff --git a/lib/views/board/workspace_view.dart b/lib/views/board/workspace_view.dart index 6a3ac65..0d1a74a 100644 --- a/lib/views/board/workspace_view.dart +++ b/lib/views/board/workspace_view.dart @@ -47,6 +47,7 @@ class WorkspaceViewState extends State { super.initState(); _initialize(); } + Future refreshData() async { _initialize(); } @@ -94,7 +95,9 @@ class WorkspaceViewState extends State { Future _addNewBoard() async { var result = await Navigator.push( context, - MaterialPageRoute(builder: (context) => CreateBoardScreen()), + MaterialPageRoute( + builder: (context) => + CreateBoardScreen(organizationId: widget.workspaceId)), ); if (result == 'boardCreated') { @@ -125,9 +128,8 @@ class WorkspaceViewState extends State { future: bgColorFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { return RefreshIndicator( - onRefresh: refreshData, - child: _buildWorkspaceView(snapshot.data ?? Colors.grey) - ); + onRefresh: refreshData, + child: _buildWorkspaceView(snapshot.data ?? Colors.grey)); }, ); } @@ -137,7 +139,10 @@ class WorkspaceViewState extends State { appBar: AppBar( title: Text(organization?.displayName ?? 'Loading...'), actions: [ - CustomPopupMenuButton(organisationId: widget.workspaceId, boards: widget.boards, state: this), + CustomPopupMenuButton( + organisationId: widget.workspaceId, + boards: widget.boards, + state: this), ], ), body: Center( @@ -147,15 +152,15 @@ class WorkspaceViewState extends State { Flexible( child: widget.boards.isEmpty ? EmptyBoardWidget( - itemType: 'Tableau', - message: - "Vous n'avez actuellement aucun tableau de créé pour cette organisation. Veuillez cliquer pour en ajouter un", - iconData: Icons.dashboard, - onTap: () { - _addNewBoard(); - }, - isMasculine: true, - ) + itemType: 'Tableau', + message: + "Vous n'avez actuellement aucun tableau de créé pour cette organisation. Veuillez cliquer pour en ajouter un", + iconData: Icons.dashboard, + onTap: () { + _addNewBoard(); + }, + isMasculine: true, + ) : _buildBoardList(), ), ElevatedButton( @@ -290,15 +295,20 @@ class CustomCardContent extends StatelessWidget { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), - image: board.bgColor == null && board.bgImage != null && board.bgImage!.isNotEmpty + image: board.bgColor == null && + board.bgImage != null && + board.bgImage!.isNotEmpty ? DecorationImage( image: CachedNetworkImageProvider(board.bgImage!), fit: BoxFit.cover, ) : null, color: board.bgColor != null - ? Color(int.parse(board.bgColor!.split('#')[1], radix: 16)).withOpacity(1) - : (board.bgImage != null && board.bgImage!.isNotEmpty ? null : bgColor), + ? Color(int.parse(board.bgColor!.split('#')[1], radix: 16)) + .withOpacity(1) + : (board.bgImage != null && board.bgImage!.isNotEmpty + ? null + : bgColor), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, From bd51c934f33637af74506257ff57a10791696d6e Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Tue, 19 Mar 2024 16:36:14 +0100 Subject: [PATCH 3/9] feat: :lipstick: Add searchbar --- lib/views/board/board_create_view.dart | 199 +++++++++++++++---------- 1 file changed, 117 insertions(+), 82 deletions(-) diff --git a/lib/views/board/board_create_view.dart b/lib/views/board/board_create_view.dart index bf0b303..856a9fe 100644 --- a/lib/views/board/board_create_view.dart +++ b/lib/views/board/board_create_view.dart @@ -99,95 +99,130 @@ class _CreateBoardScreenState extends State { final templates = await getBoardTemplates( dotenv.env['TRELLO_API_KEY']!, await getAccessToken()); + String filter = ''; + showModalBottomSheet( context: context, isScrollControlled: true, builder: (BuildContext bc) { - return SizedBox( - height: MediaQuery.of(context).size.height * 0.9, - child: Padding( - padding: - const EdgeInsets.only(top: 16.0), // Ajoutez une marge en haut - child: ListView.builder( - itemCount: templates.length, - itemBuilder: (context, index) { - return Card( - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Column( - children: [ - templates[index].backgroundImage != null - ? Ink.image( - image: NetworkImage( - templates[index].backgroundImage!), - fit: BoxFit.cover, - height: 240, - child: InkWell( - onTap: () { - setState(() { - _selectedTemplate = templates[index]; - }); - Navigator.of(context).pop(); - }, - ), - ) - : Container( - color: Color(int.parse( - 'FF${templates[index].backgroundColor?.replaceAll('#', '') ?? ''}', - radix: 16)), - height: 240, - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - templates[index].name, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 24, - ), - ), - Row( - children: [ - const Icon( - Icons.remove_red_eye, - color: Colors.black, - ), - Text( - '${templates[index].viewCount}', - style: const TextStyle( - wordSpacing: 2, - color: Colors.black, + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.9, + child: Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + onChanged: (value) { + setState(() { + filter = value; + }); + }, + decoration: InputDecoration( + labelText: "Rechercher", + hintText: "Rechercher", + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(25.0)), + ), + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: templates.length, + itemBuilder: (context, index) { + return templates[index].name.contains(filter) + ? Card( + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - ), - const SizedBox(width: 16.0), - const Icon( - Icons.copy, - color: Colors.black, - ), - Text( - '${templates[index].copyCount}', - style: const TextStyle( - color: Colors.black, + child: Column( + children: [ + templates[index].backgroundImage != null + ? Ink.image( + image: NetworkImage( + templates[index] + .backgroundImage!), + fit: BoxFit.cover, + height: 240, + child: InkWell( + onTap: () { + setState(() { + _selectedTemplate = + templates[index]; + }); + Navigator.of(context).pop(); + }, + ), + ) + : Container( + color: Color(int.parse( + 'FF${templates[index].backgroundColor?.replaceAll('#', '') ?? ''}', + radix: 16)), + height: 240, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + templates[index].name, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + Row( + children: [ + const Icon( + Icons.remove_red_eye, + color: Colors.black, + ), + Text( + '${templates[index].viewCount}', + style: const TextStyle( + wordSpacing: 2, + color: Colors.black, + ), + ), + const SizedBox(width: 16.0), + const Icon( + Icons.copy, + color: Colors.black, + ), + Text( + '${templates[index].copyCount}', + style: const TextStyle( + color: Colors.black, + ), + ), + ], + ), + ], + ), + ), + ], ), - ), - ], - ), - ], - ), + ) + : Container(); + }, ), - ], - ), - ); - }, - ), - ), + ), + ], + ), + ), + ); + }, ); }, ); From 5be876b631fd97af7ffcd055ed031866816668e9 Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Tue, 19 Mar 2024 16:36:55 +0100 Subject: [PATCH 4/9] refactor: flutter tips --- lib/views/board/board_create_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/board/board_create_view.dart b/lib/views/board/board_create_view.dart index 856a9fe..fb3ae72 100644 --- a/lib/views/board/board_create_view.dart +++ b/lib/views/board/board_create_view.dart @@ -121,7 +121,7 @@ class _CreateBoardScreenState extends State { filter = value; }); }, - decoration: InputDecoration( + decoration: const InputDecoration( labelText: "Rechercher", hintText: "Rechercher", prefixIcon: Icon(Icons.search), @@ -366,7 +366,7 @@ class _CreateBoardScreenState extends State { child: Text( _selectedTemplate!.name, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, From f133a7ec652aff8552809929547ed4ffccf549d0 Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Tue, 19 Mar 2024 16:44:08 +0100 Subject: [PATCH 5/9] fix: :ambulance: fix debug problem how take local varable wbut we have a function to get the variable --- lib/repositories/api.dart | 10 +- lib/views/board/board_view.dart | 457 ++++++++++++++++---------------- 2 files changed, 229 insertions(+), 238 deletions(-) diff --git a/lib/repositories/api.dart b/lib/repositories/api.dart index 408c516..84c6b10 100644 --- a/lib/repositories/api.dart +++ b/lib/repositories/api.dart @@ -137,7 +137,7 @@ Future> getBoards( } Future updateBoard( - String apiKey, String token, String boardId, Board board) async { + String apiKey, String? token, String boardId, Board board) async { final response = await http.put( Uri.parse( 'https://api.trello.com/1/boards/$boardId?key=$apiKey&token=$token&name=${board.name}'), @@ -174,7 +174,7 @@ Future> getWorkspaces( /// It requires the user's API key, token, and the board's ID. /// Returns a list of lists. Future> getLists( - String apiKey, String token, String boardId) async { + String apiKey, String? token, String boardId) async { final response = await http.get( Uri.parse( 'https://api.trello.com/1/boards/$boardId/lists?key=$apiKey&token=$token'), @@ -188,7 +188,7 @@ Future> getLists( } Future updateList( - String apiKey, String token, String listId, TrelloList list) async { + String apiKey, String? token, String listId, TrelloList list) async { final response = await http.put( Uri.parse( 'https://api.trello.com/1/lists/$listId?key=$apiKey&token=$token&name=${list.name}'), @@ -205,7 +205,7 @@ Future updateList( /// It requires the user's API key, token, and the list's ID. /// Returns a list of cards. Future> getCards( - String apiKey, String token, String listId) async { + String apiKey, String? token, String listId) async { final response = await http.get( Uri.parse( 'https://api.trello.com/1/lists/$listId/cards?key=$apiKey&token=$token'), @@ -238,7 +238,7 @@ Future createCard( /// /// This function calls the Trello API to delete a specific card. /// It requires the user's API key, token, and the card's ID. -Future deleteCard(String apiKey, String token, String cardId) async { +Future deleteCard(String apiKey, String? token, String cardId) async { final response = await http.delete( Uri.parse( 'https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token'), diff --git a/lib/views/board/board_view.dart b/lib/views/board/board_view.dart index ca1f211..8543a10 100644 --- a/lib/views/board/board_view.dart +++ b/lib/views/board/board_view.dart @@ -10,12 +10,12 @@ import 'package:trelltech/models/board.dart'; import 'package:trelltech/models/trello_card.dart'; import 'package:trelltech/models/trello_list.dart'; import 'package:trelltech/repositories/api.dart'; +import 'package:trelltech/repositories/authentification.dart'; import 'package:trelltech/widgets/card_widget.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:trelltech/widgets/empty_widget.dart'; import 'package:trelltech/widgets/menu_widget.dart'; - class BoardView extends StatefulWidget { final Board board; @@ -26,7 +26,6 @@ class BoardView extends StatefulWidget { } class BoardViewState extends State { - String? accessToken; late CarouselController carouselController; bool _editList = false; @@ -38,292 +37,284 @@ class BoardViewState extends State { } Future _initialize() async { - await _getAccessToken(); setState(() {}); } - Future _getAccessToken() async { - final prefs = await SharedPreferences.getInstance(); - accessToken = prefs.getString('accessToken'); - } - Future> _getLists() async { var apiKey = dotenv.env['TRELLO_API_KEY']; - var lists = await getLists(apiKey!, accessToken!, widget.board.id); + var lists = + await getLists(apiKey!, await getAccessToken(), widget.board.id!); List listList = - lists.map((item) => TrelloList.fromJson(item)).toList(); + lists.map((item) => TrelloList.fromJson(item)).toList(); return listList; } Future> _getCardsByList(String trelloListId) async { var apiKey = dotenv.env['TRELLO_API_KEY']; - var cards = await getCards(apiKey!, accessToken!, trelloListId); + var cards = await getCards(apiKey!, await getAccessToken(), trelloListId); List listCard = - cards.map((item) => TrelloCard.fromJson(item)).toList(); + cards.map((item) => TrelloCard.fromJson(item)).toList(); return listCard; } Future _updateList(String name, String listId) async { - TrelloList list = TrelloList( - id: listId, - name: name, + id: listId, + name: name, ); var apiKey = dotenv.env['TRELLO_API_KEY']; - await updateList(apiKey!, accessToken!, listId, list); + await updateList(apiKey!, await getAccessToken(), listId, list); setState(() {}); } Future _updateBoard(String name) async { - Board board = Board( - id: widget.board.id, - name: name, - idOrganization: widget.board.idOrganization, - closed: widget.board.closed, - pinned: widget.board.pinned, - url: widget.board.url, - shortUrl: widget.board.shortUrl, + id: widget.board.id, + name: name, + idOrganization: widget.board.idOrganization, + closed: widget.board.closed, + pinned: widget.board.pinned, + url: widget.board.url, + shortUrl: widget.board.shortUrl, ); var apiKey = dotenv.env['TRELLO_API_KEY']; - await updateBoard(apiKey!, accessToken!, widget.board.id, board); + await updateBoard(apiKey!, await getAccessToken(), widget.board.id, board); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: - TextFormField( - onChanged: (String value) { - setState(() { - _updateBoard(value); - }); - }, - decoration: const InputDecoration( - border: InputBorder.none, - ), - initialValue: widget.board.name, - style: const TextStyle( - color: Colors.black, - fontSize: 20, - ) - )), - body: Container( - decoration: BoxDecoration( - image: widget.board.bgImage != null - ? DecorationImage( - image: NetworkImage(widget.board.bgImage!), - fit: BoxFit.cover, - ) - : null, - color: widget.board.bgColor != null - ? Color(int.parse('0xff${widget.board.bgColor!.split('#')[1]}')) - : null, - ), - child: FutureBuilder( - future: _getLists(), - builder: (context, AsyncSnapshot> snapshot) { - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.data!.isEmpty) { - return EmptyBoardWidget( - itemType: 'Listes', - message: + appBar: AppBar( + title: TextFormField( + onChanged: (String value) { + setState(() { + _updateBoard(value); + }); + }, + decoration: const InputDecoration( + border: InputBorder.none, + ), + initialValue: widget.board.name, + style: const TextStyle( + color: Colors.black, + fontSize: 20, + ))), + body: Container( + decoration: BoxDecoration( + image: widget.board.bgImage != null + ? DecorationImage( + image: NetworkImage(widget.board.bgImage!), + fit: BoxFit.cover, + ) + : null, + color: widget.board.bgColor != null + ? Color(int.parse('0xff${widget.board.bgColor!.split('#')[1]}')) + : null, + ), + child: FutureBuilder( + future: _getLists(), + builder: (context, AsyncSnapshot> snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.data!.isEmpty) { + return EmptyBoardWidget( + itemType: 'Listes', + message: "Vous n'avez actuellement aucune liste dans ce tableau", - iconData: Icons.list, - witheColor: true, - onTap: () { + iconData: Icons.list, + witheColor: true, + onTap: () { print('Tableau clicked'); }, - isMasculine: false, + isMasculine: false, ); - } else { - return CarouselSlider.builder( - itemCount: snapshot.data?.length, - options: CarouselOptions( - autoPlay: false, - enlargeCenterPage: false, - height: MediaQuery.of(context).size.height), - itemBuilder: - (BuildContext context, int item, int pageViewIndex) { - return Card( - margin: const EdgeInsets.fromLTRB(15, 30, 15, 30), - child: Column(children: [ - Expanded( - flex: 1, + } else { + return CarouselSlider.builder( + itemCount: snapshot.data?.length, + options: CarouselOptions( + autoPlay: false, + enlargeCenterPage: false, + height: MediaQuery.of(context).size.height), + itemBuilder: + (BuildContext context, int item, int pageViewIndex) { + return Card( + margin: const EdgeInsets.fromLTRB(15, 30, 15, 30), + child: Column(children: [ + Expanded( + flex: 1, + child: Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10)), + color: Color(0xff162B62)), child: Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10)), - color: Color(0xff162B62)), - child: Container( - margin: const EdgeInsets.only( - right: 20, left: 20), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: TextFormField( - onChanged: (String value) { - _updateList(value, snapshot - .data![item].id); - }, - decoration: const InputDecoration( - border: InputBorder.none, - ), - initialValue: snapshot.data![item].name.toUpperCase(), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - ) - ), - ), - IconButton( - icon: const Icon(Icons.edit), - color: Colors.white, - onPressed: () => { - setState(() { - _editList = !_editList; - }) - }, - ) - ]), - ), + margin: const EdgeInsets.only( + right: 20, left: 20), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextFormField( + onChanged: (String value) { + _updateList(value, + snapshot.data![item].id); + }, + decoration: const InputDecoration( + border: InputBorder.none, + ), + initialValue: snapshot + .data![item].name + .toUpperCase(), + style: const TextStyle( + color: Colors.white, + fontSize: 20, + )), + ), + IconButton( + icon: const Icon(Icons.edit), + color: Colors.white, + onPressed: () => { + setState(() { + _editList = !_editList; + }) + }, + ) + ]), ), ), - Expanded( - flex: 8, - child: FutureBuilder( - future: _getCardsByList( - snapshot.data![item].id), - builder: (context, - AsyncSnapshot> - snapshot) { - if (!snapshot.hasData) { - return const Center( - child: - CircularProgressIndicator()); - } else { - return ListView.builder( - itemCount: snapshot.data?.length, - itemBuilder: - (BuildContext context, - int index) { - return Row( - children: [ - Expanded( - flex: 7, - child: Card( - margin: - const EdgeInsets.all(10), + ), + Expanded( + flex: 8, + child: FutureBuilder( + future: _getCardsByList( + snapshot.data![item].id), + builder: (context, + AsyncSnapshot> + snapshot) { + if (!snapshot.hasData) { + return const Center( + child: CircularProgressIndicator()); + } else { + return ListView.builder( + itemCount: snapshot.data?.length, + itemBuilder: (BuildContext context, + int index) { + return Row( + children: [ + Expanded( + flex: 7, + child: Card( + margin: const EdgeInsets + .all(10), color: Colors.grey[200], child: ListTile( title: Text(snapshot - .data![index].name), + .data![index] + .name), onTap: () => showModalBottomSheet( isScrollControlled: true, - context: context, + context: + context, builder: (BuildContext context) { return CardWidget( - card: snapshot - .data![ - index]); - }).whenComplete( () { - setState(() { - _getCardsByList( - snapshot - .data![ - index] - .id); - }); - })) - ), - ), - _editList - ? Expanded( - flex: 3, - child: Card( - color: Colors - .redAccent, - child: IconButton( - icon: const Icon( - Icons.delete, color: Colors.white,), - onPressed: () => { - // _deleteCard(snapshot.data![index].id), + card: + snapshot.data![index]); + }).whenComplete(() { setState(() { - deleteCard( - dotenv.env['TRELLO_API_KEY']!, - accessToken!, - snapshot.data![index].id); - _editList = false; - }) - }, + _getCardsByList( + snapshot + .data![index] + .id); + }); + }))), + ), + _editList + ? Expanded( + flex: 3, + child: Card( + color: Colors + .redAccent, + child: IconButton( + icon: const Icon( + Icons.delete, + color: Colors + .white, ), + onPressed: () => { + // _deleteCard(snapshot.data![index].id), + setState( + () async { + deleteCard( + dotenv.env[ + 'TRELLO_API_KEY']!, + await getAccessToken(), + snapshot + .data![ + index] + .id); + _editList = + false; + }) + }, ), - ) - : const SizedBox() - ], - ); - }); - } - })), - Expanded( - flex: 1, - child: Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10)), - color: Color(0xff162B62)), - child: Center( - child: Container( - margin: - const EdgeInsets.only(left: 10), - child: Row( - children: [ - const Icon( - Icons.add, - color: Colors.white, - ), - const SizedBox( - width: 8, - ), - Expanded( - child: TextField( - style: const TextStyle( - color: Colors.white), - decoration: - InputDecoration.collapsed( - hintText: - AppLocalizations.of( - context)! - .addCard, - hintStyle: const TextStyle( - color: Colors.white70), - ), + ), + ) + : const SizedBox() + ], + ); + }); + } + })), + Expanded( + flex: 1, + child: Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)), + color: Color(0xff162B62)), + child: Center( + child: Container( + margin: const EdgeInsets.only(left: 10), + child: Row( + children: [ + const Icon( + Icons.add, + color: Colors.white, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: TextField( + style: const TextStyle( + color: Colors.white), + decoration: + InputDecoration.collapsed( + hintText: AppLocalizations.of( + context)! + .addCard, + hintStyle: const TextStyle( + color: Colors.white70), ), ), - ], - ), + ), + ], ), - ) - ) - ) - ]) - ); - } - ); - } + ), + ))) + ])); + }); } - ), - ), - bottomNavigationBar: MenuWidget(), // Here is where you add the MenuWidget + }), + ), + bottomNavigationBar: MenuWidget(), // Here is where you add the MenuWidget ); } -} \ No newline at end of file +} From 031493fe82c12f0ec62caba8d047a68780b4ad17 Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Tue, 19 Mar 2024 17:22:17 +0100 Subject: [PATCH 6/9] refactor: :recycle: delete code dont need --- lib/views/board/workspace_view.dart | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/views/board/workspace_view.dart b/lib/views/board/workspace_view.dart index 0d1a74a..0651155 100644 --- a/lib/views/board/workspace_view.dart +++ b/lib/views/board/workspace_view.dart @@ -48,10 +48,6 @@ class WorkspaceViewState extends State { _initialize(); } - Future refreshData() async { - _initialize(); - } - Future _initialize() async { accessToken = (await getAccessToken())!; @@ -101,13 +97,13 @@ class WorkspaceViewState extends State { ); if (result == 'boardCreated') { - await refreshData(); + await _initialize(); } } Future _getBgColor(String? color, String? imageUrl) async { if (color != null) { - String colorHex = color!.split('#')[1]; + String colorHex = color.split('#')[1]; int colorInt = int.parse(colorHex, radix: 16); int colorBinary = 0xFF000000 + colorInt; return Color(colorBinary); @@ -128,7 +124,7 @@ class WorkspaceViewState extends State { future: bgColorFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { return RefreshIndicator( - onRefresh: refreshData, + onRefresh: _initialize, child: _buildWorkspaceView(snapshot.data ?? Colors.grey)); }, ); @@ -378,7 +374,7 @@ class CustomPopupMenuButton extends StatelessWidget { organisationId: organisationId, boards: boards), ), - ).then((value) => state.refreshData()); + ).then((value) => state._initialize()); }, ), ListTile( From d0ae0458e1241a2db875200210dced20ea5054da Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Wed, 20 Mar 2024 17:42:53 +0100 Subject: [PATCH 7/9] fix: :bug: When click on template When click on template it pop the bottomsheet and show now in the boardview create the template selected --- lib/main.dart | 3 +-- lib/views/board/board_create_view.dart | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index bff73eb..8b20fbe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,7 +12,6 @@ Future main() async { runApp(const MyApp()); } - class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -35,4 +34,4 @@ class MyApp extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/views/board/board_create_view.dart b/lib/views/board/board_create_view.dart index fb3ae72..657f99a 100644 --- a/lib/views/board/board_create_view.dart +++ b/lib/views/board/board_create_view.dart @@ -95,7 +95,7 @@ class _CreateBoardScreenState extends State { } } - void _showTemplatePicker() async { + void _showTemplatePicker(Function(TrelloBoardTemplate) onSelect) async { final templates = await getBoardTemplates( dotenv.env['TRELLO_API_KEY']!, await getAccessToken()); @@ -153,10 +153,7 @@ class _CreateBoardScreenState extends State { height: 240, child: InkWell( onTap: () { - setState(() { - _selectedTemplate = - templates[index]; - }); + onSelect(templates[index]); Navigator.of(context).pop(); }, ), @@ -313,7 +310,11 @@ class _CreateBoardScreenState extends State { setState(() { _selectedOption = 'template'; }); - _showTemplatePicker(); + _showTemplatePicker((template) { + setState(() { + _selectedTemplate = template; + }); + }); }, child: AspectRatio( aspectRatio: 1.0, @@ -386,7 +387,11 @@ class _CreateBoardScreenState extends State { _selectedOption = 'template'; _selectedTemplate = null; }); - _showTemplatePicker(); + _showTemplatePicker((template) { + setState(() { + _selectedTemplate = template; + }); + }); }, child: AspectRatio( aspectRatio: 1.0, From 9a6ef88a60f099aa6891d088f78b8188d45865e3 Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Wed, 20 Mar 2024 17:48:10 +0100 Subject: [PATCH 8/9] feat: :sparkles: Redirect to the board created --- lib/repositories/api.dart | 4 ++-- lib/views/board/board_create_view.dart | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/repositories/api.dart b/lib/repositories/api.dart index 84c6b10..e10d68c 100644 --- a/lib/repositories/api.dart +++ b/lib/repositories/api.dart @@ -86,7 +86,7 @@ Future addMemberToCard( /// It requires the user's API key, token, and the name of the board. /// Returns the ID of the new board. /// Throws an exception if the request fails. -Future createBoard(String apiKey, String token, String name, +Future createBoard(String apiKey, String token, String name, String description, String workspaceId, String? templateId) async { if (templateId != null) { final response = await http.post( @@ -96,7 +96,7 @@ Future createBoard(String apiKey, String token, String name, if (response.statusCode == 200) { var data = jsonDecode(response.body); - return data['id']; + return Board.fromJson(data); } else { throw Exception('Failed to create board'); } diff --git a/lib/views/board/board_create_view.dart b/lib/views/board/board_create_view.dart index 657f99a..fcfe115 100644 --- a/lib/views/board/board_create_view.dart +++ b/lib/views/board/board_create_view.dart @@ -9,6 +9,7 @@ import 'package:trelltech/models/trello_organization.dart'; import 'package:trelltech/repositories/api.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:trelltech/repositories/authentification.dart'; +import 'package:trelltech/views/board/board_view.dart'; class CreateBoardScreen extends StatefulWidget { String organizationId; @@ -59,7 +60,7 @@ class _CreateBoardScreenState extends State { } try { - await createBoard( + Board board = await createBoard( apiKey, accessToken, _nameController.text, @@ -76,7 +77,12 @@ class _CreateBoardScreenState extends State { textColor: Colors.white, fontSize: 16.0, ); - Navigator.pop(context, 'boardCreated'); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => BoardView(board: board), + ), + ); } catch (e) { Fluttertoast.showToast( msg: AppLocalizations.of(context)!.boardCreationFailed, From d4ab924ae30a5744b6c542ef5f3079091a906344 Mon Sep 17 00:00:00 2001 From: "Alexandre.T" Date: Wed, 20 Mar 2024 21:23:50 +0100 Subject: [PATCH 9/9] fix: null accesstoken --- lib/repositories/api.dart | 8 +-- lib/widgets/card_widget.dart | 103 ++++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 47 deletions(-) diff --git a/lib/repositories/api.dart b/lib/repositories/api.dart index e10d68c..a170a80 100644 --- a/lib/repositories/api.dart +++ b/lib/repositories/api.dart @@ -42,7 +42,7 @@ Future getMember(String apiKey, String token, String memberId) async { } Future getMembersFromCard( - String apiKey, String token, String cardId) async { + String apiKey, String? token, String cardId) async { final response = await http.get( Uri.parse( 'https://api.trello.com/1/cards/$cardId/members?key=$apiKey&token=$token'), @@ -56,7 +56,7 @@ Future getMembersFromCard( } Future getMembersFromBoard( - String apiKey, String token, String boardId) async { + String apiKey, String? token, String boardId) async { final response = await http.get( Uri.parse( 'https://api.trello.com/1/boards/$boardId/members?key=$apiKey&token=$token'), @@ -70,7 +70,7 @@ Future getMembersFromBoard( } Future addMemberToCard( - String apiKey, String token, String cardId, String memberId) async { + String apiKey, String? token, String cardId, String memberId) async { final response = await http.post( Uri.parse( 'https://api.trello.com/1/cards/$cardId/idMembers?key=$apiKey&token=$token&value=$memberId'), @@ -262,7 +262,7 @@ Future deleteCard(String apiKey, String? token, String cardId) async { /// This function calls the Trello API to update the name of a specific card. /// It requires the user's API key, token, the card's ID, and the new name of the card. Future updateCard( - String apiKey, String token, String cardId, TrelloCard card) async { + String apiKey, String? token, String cardId, TrelloCard card) async { final response = await http.put( Uri.parse( 'https://api.trello.com/1/cards/$cardId?key=$apiKey&token=$token&desc=${card.desc}&name=${card.name}'), diff --git a/lib/widgets/card_widget.dart b/lib/widgets/card_widget.dart index e46cc5f..15e4158 100644 --- a/lib/widgets/card_widget.dart +++ b/lib/widgets/card_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:trelltech/repositories/api.dart'; +import 'package:trelltech/repositories/authentification.dart'; import '../models/trello_card.dart'; @@ -15,7 +16,6 @@ class CardWidget extends StatefulWidget { } class _CardWidgetState extends State { - String? accessToken; String? selectedItem; List> membersList = []; @@ -26,8 +26,7 @@ class _CardWidgetState extends State { } Future _initialize() async { - await _getAccessToken(); - if (mounted) { // Check if widget is mounted before calling setState + if (mounted) { await _getMembersBoard(); setState(() {}); } @@ -35,7 +34,8 @@ class _CardWidgetState extends State { Future _getMembersBoard() async { var apiKey = dotenv.env['TRELLO_API_KEY']; - var members = await getMembersFromBoard(apiKey!, accessToken!, widget.card.idBoard); + var members = await getMembersFromBoard( + apiKey!, await getAccessToken(), widget.card.idBoard); if (mounted) { setState(() { members.forEach((member) { @@ -45,24 +45,20 @@ class _CardWidgetState extends State { } } - Future _getAccessToken() async { - final prefs = await SharedPreferences.getInstance(); - accessToken = prefs.getString('accessToken'); - } - Future _getMembers(String cardId) async { var apiKey = dotenv.env['TRELLO_API_KEY']; - var members = await getMembersFromCard(apiKey!, accessToken!, cardId); + var members = + await getMembersFromCard(apiKey!, await getAccessToken(), cardId); return members; } Future _addMember(String memberId) async { var apiKey = dotenv.env['TRELLO_API_KEY']; - await addMemberToCard(apiKey!, accessToken!, widget.card.id, memberId); + await addMemberToCard( + apiKey!, await getAccessToken(), widget.card.id, memberId); } Future _updateCard(String name, String desc) async { - TrelloCard card = TrelloCard( id: widget.card.id, name: name, @@ -72,7 +68,7 @@ class _CardWidgetState extends State { idMembers: widget.card.idMembers, ); var apiKey = dotenv.env['TRELLO_API_KEY']; - await updateCard(apiKey!, accessToken!, widget.card.id, card); + await updateCard(apiKey!, await getAccessToken(), widget.card.id, card); setState(() {}); } @@ -95,18 +91,17 @@ class _CardWidgetState extends State { children: [ Expanded( child: TextFormField( - onChanged: (String value) { - _updateCard(value, widget.card.desc); - }, - decoration: const InputDecoration( - border: InputBorder.none, - ), - initialValue: widget.card.name, - style: const TextStyle( - color: Color(0xFF1C39A1), - fontSize: 20, - ) - ), + onChanged: (String value) { + _updateCard(value, widget.card.desc); + }, + decoration: const InputDecoration( + border: InputBorder.none, + ), + initialValue: widget.card.name, + style: const TextStyle( + color: Color(0xFF1C39A1), + fontSize: 20, + )), ), IconButton( icon: const Icon( @@ -134,7 +129,8 @@ class _CardWidgetState extends State { ), ), labelText: 'Description', - labelStyle: TextStyle(color: Color(0xFF1C39A1), fontSize: 20), + labelStyle: + TextStyle(color: Color(0xFF1C39A1), fontSize: 20), border: OutlineInputBorder(), ), initialValue: widget.card.desc, @@ -151,7 +147,8 @@ class _CardWidgetState extends State { height: 50, child: FutureBuilder( future: _getMembers(widget.card.id), - builder: (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, + AsyncSnapshot snapshot) { if (snapshot.hasError) { return Container(); } else { @@ -169,18 +166,27 @@ class _CardWidgetState extends State { Expanded( child: ListView.builder( scrollDirection: Axis.horizontal, - itemCount: snapshot.data != null ? snapshot.data.length : 0, - itemBuilder: (BuildContext context, int index) { + itemCount: snapshot.data != null + ? snapshot.data.length + : 0, + itemBuilder: + (BuildContext context, int index) { return CircleAvatar( - backgroundImage: NetworkImage( - snapshot.data[index]['avatarUrl'] != null ? snapshot.data[index]['avatarUrl'] + '/50.png' : 'https://placehold.co/50.png' - ), + backgroundImage: NetworkImage(snapshot + .data[index] + ['avatarUrl'] != + null + ? snapshot.data[index] + ['avatarUrl'] + + '/50.png' + : 'https://placehold.co/50.png'), ); }, ), ), IconButton( - icon: const Icon(Icons.add, size: 25, color: Color(0xFF1C39A1)), + icon: const Icon(Icons.add, + size: 25, color: Color(0xFF1C39A1)), onPressed: () { showDialog( context: context, @@ -188,16 +194,23 @@ class _CardWidgetState extends State { return StatefulBuilder( builder: (context, setState) { return AlertDialog( - title: const Text('Add Member'), - content: DropdownButton( + title: + const Text('Add Member'), + content: + DropdownButton( value: selectedItem, - items: membersList.map((Map member) { - return DropdownMenuItem( + items: membersList.map( + (Map + member) { + return DropdownMenuItem< + String>( value: member["id"], - child: Text(member["username"]), + child: Text( + member["username"]), ); }).toList(), - onChanged: (String? newValue) { + onChanged: + (String? newValue) { setState(() { selectedItem = newValue; }); @@ -207,9 +220,12 @@ class _CardWidgetState extends State { TextButton( child: const Text('Add'), onPressed: () { - if (selectedItem != null) { - _addMember(selectedItem!); - Navigator.of(context).pop(); + if (selectedItem != + null) { + _addMember( + selectedItem!); + Navigator.of(context) + .pop(); } }, ), @@ -226,7 +242,8 @@ class _CardWidgetState extends State { ), Row( children: [ - const Icon(Icons.calendar_today, size: 20, color: Color(0xFF1C39A1)), + const Icon(Icons.calendar_today, + size: 20, color: Color(0xFF1C39A1)), const SizedBox(width: 10), Text(widget.card.due ?? "No due date"), ],