diff --git a/lib/programs screen/girl_script.dart b/lib/programs screen/girl_script.dart index da361be..9be23d7 100644 --- a/lib/programs screen/girl_script.dart +++ b/lib/programs screen/girl_script.dart @@ -8,6 +8,9 @@ import 'package:opso/widgets/year_button.dart'; import '../widgets/SearchandFilterWidget.dart'; +import 'package:multi_select_flutter/multi_select_flutter.dart'; + + class GSSOCScreen extends StatefulWidget { const GSSOCScreen({super.key}); @@ -18,53 +21,89 @@ class GSSOCScreen extends StatefulWidget { class _GSSOCScreenState extends State { - String currectPage = "/girl_script_summer_of_code"; + String currentPage = "/girl_script_summer_of_code"; String currentProject = "Girl Script Summer of Code"; List gssoc2024 = []; List gssoc2023 = []; - bool isBookmarked = true; List gssoc2022 = []; List gssoc2021 = []; + List allOrganizations = []; + List allLanguages = []; + List selectedOrganizations = ['All']; + List selectedLanguages = ['All']; int selectedYear = 2024; - String selectedOrg = "All"; - String selectedLanguage = "All"; + bool isBookmarked = true; List projectList = []; Future? getProjectFunction; Future initializeProjectLists() async { - String response = - await rootBundle.loadString('assets/projects/gssoc/gssoc2024.json'); - var jsonList = await json.decode(response); - for (var data in jsonList) { - gssoc2024.add(GssocProjectModal.getDataFromJson(data)); - } - projectList = List.from(gssoc2024); - response = - await rootBundle.loadString('assets/projects/gssoc/gssoc2023.json'); - jsonList = await json.decode(response); - for (var data in jsonList) { - gssoc2023.add(GssocProjectModal.getDataFromJson(data)); - } - response = - await rootBundle.loadString('assets/projects/gssoc/gssoc2022.json'); - jsonList = await json.decode(response); - for (var data in jsonList) { - gssoc2022.add(GssocProjectModal.getDataFromJson(data)); - } - response = - await rootBundle.loadString('assets/projects/gssoc/gssoc2021.json'); - jsonList = await json.decode(response); - for (var data in jsonList) { - gssoc2021.add(GssocProjectModal.getDataFromJson(data)); - } + await _loadProjects('assets/projects/gssoc/gssoc2024.json', gssoc2024); + await _loadProjects('assets/projects/gssoc/gssoc2023.json', gssoc2023); + await _loadProjects('assets/projects/gssoc/gssoc2022.json', gssoc2022); + await _loadProjects('assets/projects/gssoc/gssoc2021.json', gssoc2021); + + + // Populate all unique organizations and languages + allOrganizations = _extractUniqueValues((project) => project.hostedBy); + allLanguages = languages; + projectList = List.from(gssoc2024); // Default year + } + + + List languages = [ + 'All', + 'Js', + 'Python', + 'React', + 'Angular', + 'Bootstrap', + 'Firebase', + 'Node', + 'MongoDb', + 'Express', + 'Next', + 'CSS', + 'HTML', + 'JavaScript', + 'Flutter', + 'Dart' + ]; + + + Future _loadProjects(String path, List list) async { + String response = await rootBundle.loadString(path); + var jsonList = json.decode(response) as List; + list.addAll(jsonList.map((data) => GssocProjectModal.getDataFromJson(data)).toList()); + } + + + List _extractUniqueValues(String Function(GssocProjectModal) extractor) { + return { + 'All', + ...gssoc2024.map(extractor), + ...gssoc2023.map(extractor), + ...gssoc2022.map(extractor), + ...gssoc2021.map(extractor), + }.toList(); + } + + + List _extractUniqueLanguages(List Function(GssocProjectModal) extractor) { + final allLanguages = [ + for (var project in gssoc2024) ...extractor(project), + for (var project in gssoc2023) ...extractor(project), + for (var project in gssoc2022) ...extractor(project), + for (var project in gssoc2021) ...extractor(project), + ]; + return ['All', ...allLanguages.toSet()]; } @override void initState() { - getProjectFunction = initializeProjectLists(); super.initState(); + getProjectFunction = initializeProjectLists(); _checkBookmarkStatus(); } @@ -77,84 +116,53 @@ class _GSSOCScreenState extends State { } - void filterProjectsByTag(String tag) { - projectList = projectList.where((element) => element.techstack.contains(tag)).toList(); - _resetOrgIfNotValid(); - setState(() {}); - } + void filterProjects() { + projectList = _getProjectsByYear(); + + + if (!selectedLanguages.contains('All')) { + projectList = projectList.where((project) => project.techstack.any(selectedLanguages.contains)).toList(); + } - void filterProjectsBySearchText(String searchText) { - if (searchText.isEmpty) { - switch (selectedYear) { - case 2021: - projectList = gssoc2021; - break; - case 2022: - projectList = gssoc2022; - break; - case 2023: - projectList = gssoc2023; - break; - case 2024: - projectList = gssoc2024; - break; - } - _resetOrgIfNotValid(); - setState(() {}); - return; + if (!selectedOrganizations.contains('All')) { + projectList = projectList.where((project) => selectedOrganizations.contains(project.hostedBy)).toList(); } - projectList = projectList - .where( - (element) => - element.name.toLowerCase().contains(searchText.toLowerCase()) || - element.techstack.contains(searchText) || - element.hostedBy.toLowerCase().contains(searchText.toLowerCase()), - ) + + + // Update organization filter based on selected languages + allOrganizations = _extractUniqueValues((project) => project.hostedBy) + .where((organization) => projectList.any((project) => project.hostedBy == organization)) .toList(); - _resetOrgIfNotValid(); + allOrganizations.insert(0, 'All'); + + setState(() {}); } - void resetProjectsByLanguage() { + List _getProjectsByYear() { switch (selectedYear) { case 2021: - projectList = gssoc2021; - break; + return gssoc2021; case 2022: - projectList = gssoc2022; - break; + return gssoc2022; case 2023: - projectList = gssoc2023; - break; + return gssoc2023; case 2024: - projectList = gssoc2024; - break; - } - if (selectedLanguage != 'All') { - filterProjectsByTag(selectedLanguage); - } else { - _resetOrgIfNotValid(); - setState(() {}); - } - } - - - void _resetOrgIfNotValid() { - Set uniqueOrgs = projectList.map((e) => e.hostedBy).toSet(); - if (!uniqueOrgs.contains(selectedOrg) && selectedOrg != 'All') { - selectedOrg = 'All'; + return gssoc2024; + default: + return []; } } Future _refresh() async { + await initializeProjectLists(); setState(() { - initializeProjectLists(); selectedYear = 2024; - selectedOrg = 'All'; - selectedLanguage = 'All'; + selectedOrganizations = ['All']; + selectedLanguages = ['All']; }); } @@ -163,24 +171,8 @@ class _GSSOCScreenState extends State { Widget build(BuildContext context) { var height = MediaQuery.sizeOf(context).height; var width = MediaQuery.sizeOf(context).width; - List languages = [ - 'All', - 'Js', - 'Python', - 'React', - 'Angular', - 'Bootstrap', - 'Firebase', - 'Node', - 'MongoDb', - 'Express', - 'Next', - 'CSS', - 'HTML', - 'JavaScript', - 'Flutter', - 'Dart' - ]; + + return RefreshIndicator( onRefresh: _refresh, child: Scaffold( @@ -195,237 +187,216 @@ class _GSSOCScreenState extends State { }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text( - isBookmarked ? 'Bookmark added' : 'Bookmark removed'), - duration: const Duration( - seconds: 2), // Adjust the duration as needed + content: Text(isBookmarked ? 'Bookmark added' : 'Bookmark removed'), + duration: const Duration(seconds: 2), ), ); if (isBookmarked) { - print("Adding"); - HandleBookmark.addBookmark(currentProject, currectPage); + HandleBookmark.addBookmark(currentProject, currentPage); } else { - print("Deleting"); HandleBookmark.deleteBookmark(currentProject); } }, ) ]), body: FutureBuilder( - future: getProjectFunction, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.connectionState == ConnectionState.done) { - // Create a unique list of organizations - Set uniqueOrgs = projectList.map((e) => e.hostedBy).toSet(); - List orgsList = ["All", ...uniqueOrgs]; - - - // Reset selectedOrg if it's not in the filtered list - if (!orgsList.contains(selectedOrg)) { - selectedOrg = 'All'; - } - - - return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 46, vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextFormField( - decoration: InputDecoration( - filled: true, - hintText: 'Search', - suffixIcon: const Icon(Icons.search), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), - ), - ), - disabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), - ), - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), - ), - ), - contentPadding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 20.0), - ), - onFieldSubmitted: (value) { - print("value is $value"); - filterProjectsBySearchText(value.trim()); - }, - onChanged: (value) { - if (value.isEmpty) { - filterProjectsBySearchText(value); - } - }, - ), - const SizedBox(height: 20), - SizedBox( - height: height * 0.2, - width: width, - child: GridView( - physics: const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 1.5/0.6, - crossAxisCount: 2, - crossAxisSpacing: 15, - mainAxisSpacing: 15), - children: [ - YearButton( - year: "2021", - isEnabled: selectedYear == 2021 ? true : false, - onTap: () { - setState(() { - projectList = gssoc2021; - selectedYear = 2021; - }); - resetProjectsByLanguage(); - }, - backgroundColor: selectedYear == 2021 - ? Colors.white - : const Color.fromRGBO(255, 183, 77, 1), - ), - YearButton( - year: "2022", - isEnabled: selectedYear == 2022 ? true : false, - onTap: () { - setState(() { - projectList = gssoc2022; - selectedYear = 2022; - }); - resetProjectsByLanguage(); - }, - backgroundColor: selectedYear == 2022 - ? Colors.white - : const Color.fromRGBO(255, 183, 77, 1), - ), - YearButton( - year: "2023", - isEnabled: selectedYear == 2023 ? true : false, - onTap: () { - setState(() { - projectList = gssoc2023; - selectedYear = 2023; - }); - resetProjectsByLanguage(); - }, - backgroundColor: selectedYear == 2023 - ? Colors.white - : const Color.fromRGBO(255, 183, 77, 1), - ), - YearButton( - isEnabled: selectedYear == 2024 ? true : false, - year: "2024", - onTap: () { - setState(() { - projectList = gssoc2024; - selectedYear = 2024; - }); - resetProjectsByLanguage(); - }, - backgroundColor: selectedYear == 2024 - ? Colors.white - : const Color.fromRGBO(255, 183, 77, 1), - ), - ], - ), - ), - const SizedBox(height: 20), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - 'Filter by Language:', - style: TextStyle(fontWeight: FontWeight.w400), - ), - const SizedBox(width: 8), - DropdownWidget( - items: languages, - hintText: 'Language', - onChanged: (newValue) { - setState(() { - selectedLanguage = newValue; - resetProjectsByLanguage(); - }); - }, - ), - ], - ), - const SizedBox(height: 20), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - 'Filter by Name:', - style: TextStyle(fontWeight: FontWeight.w400), - ), - const SizedBox(width: 8), - Expanded( - child: DropdownButton( - value: selectedOrg, - hint: const Text('Organization'), - isExpanded: true, - items: orgsList - .map((org) => DropdownMenuItem( - child: Text(org), - value: org, - )) - .toList(), - onChanged: (newValue) { - setState(() { - selectedOrg = newValue!; - resetProjectsByLanguage(); - if (selectedOrg != 'All') { - filterProjectsBySearchText(selectedOrg); - } - }); - }, - ), - ), - ], - ), - const SizedBox(height: 20), - Expanded( - child: ListView.builder( - itemCount: projectList.length, - itemBuilder: (BuildContext context, int index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: GssocProjectWidget( - index: index + 1, - modal: projectList[index], - height: height * 0.2, - width: width, - ), - ); - }, - ), - ), - ], - ), - ); - } else { - return const Center(child: Text("Some error occurred")); - } - }), + future: getProjectFunction, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.connectionState == ConnectionState.done) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildSearchBar(), + const SizedBox(height: 20), + _buildYearButtons(), + const SizedBox(height: 20), + _buildMultiSelectField( + items: allLanguages, + selectedValues: selectedLanguages, + title: "Select Languages", + buttonText: "Filter by Language", + onConfirm: (results) { + setState(() { + selectedLanguages = results.isNotEmpty ? results : ['All']; + filterProjects(); + }); + }, + ), + const SizedBox(height: 20), + _buildMultiSelectField( + items: allOrganizations, + selectedValues: selectedOrganizations, + title: "Select Organizations", + buttonText: "Filter by Name", + onConfirm: (results) { + setState(() { + selectedOrganizations = results.isNotEmpty ? results : ['All']; + print("Selected Organizations: $selectedOrganizations"); + filterProjects(); + }); + }, + ), + const SizedBox(height: 20), + _buildProjectList(height, width), + ], + ), + ); + } else { + return const Center(child: Text("Some error occurred")); + } + }, + ), + ), + ); + } + + + Widget _buildSearchBar() { + return TextFormField( + decoration: InputDecoration( + filled: true, + hintText: 'Search', + suffixIcon: const Icon(Icons.search), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: Color(0xFFEEEEEE)), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: Color(0xFFEEEEEE)), + ), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: Color(0xFFEEEEEE)), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: Color(0xFFEEEEEE)), + ), + contentPadding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 20.0), + ), + onFieldSubmitted: (value) { + setState(() { + projectList = _getProjectsByYear() + .where((project) => project.name.toLowerCase().contains(value.toLowerCase())) + .toList(); + }); + }, + onChanged: (value) { + if (value.isEmpty) { + setState(() { + projectList = _getProjectsByYear(); + }); + } + }, + ); + } + + + Widget _buildYearButtons() { + return SizedBox( + height: MediaQuery.sizeOf(context).height * 0.2, + child: GridView( + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + childAspectRatio: 1.5 / 0.6, + crossAxisCount: 2, + crossAxisSpacing: 15, + mainAxisSpacing: 15, + ), + children: [ + YearButton( + year: "2021", + isEnabled: selectedYear == 2021, + onTap: () { + setState(() { + selectedYear = 2021; + filterProjects(); + }); + }, + backgroundColor: selectedYear == 2021 ? Colors.white : const Color.fromRGBO(255, 183, 77, 1), + ), + YearButton( + year: "2022", + isEnabled: selectedYear == 2022, + onTap: () { + setState(() { + selectedYear = 2022; + filterProjects(); + }); + }, + backgroundColor: selectedYear == 2022 ? Colors.white : const Color.fromRGBO(255, 183, 77, 1), + ), + YearButton( + year: "2023", + isEnabled: selectedYear == 2023, + onTap: () { + setState(() { + selectedYear = 2023; + filterProjects(); + }); + }, + backgroundColor: selectedYear == 2023 ? Colors.white : const Color.fromRGBO(255, 183, 77, 1), + ), + YearButton( + year: "2024", + isEnabled: selectedYear == 2024, + onTap: () { + setState(() { + selectedYear = 2024; + filterProjects(); + }); + }, + backgroundColor: selectedYear == 2024 ? Colors.white : const Color.fromRGBO(255, 183, 77, 1), + ), + ], + ), + ); + } + + + Widget _buildMultiSelectField({ + required List items, + required List selectedValues, + required String title, + required String buttonText, + required void Function(List) onConfirm, + }) { + bool isDarkMode = Theme.of(context).brightness == Brightness.dark; + return MultiSelectDialogField( + backgroundColor: isDarkMode ? Colors.grey.shade100 : Colors.white, + items: items.map((e) => MultiSelectItem(e, e)).toList(), + initialValue: selectedValues, + title: Text(title,style: TextStyle(color: isDarkMode ? Colors.black : Colors.black)), + buttonText: Text(buttonText), + onConfirm: onConfirm, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(10), + ), + ); + } + + + Widget _buildProjectList(double height, double width) { + return Expanded( + child: ListView.builder( + itemCount: projectList.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: GssocProjectWidget( + index: index + 1, + modal: projectList[index], + height: height * 0.2, + width: width, + ), + ); + }, ), ); } diff --git a/lib/programs screen/google_season_of_docs_screen.dart b/lib/programs screen/google_season_of_docs_screen.dart index 4039687..8852191 100644 --- a/lib/programs screen/google_season_of_docs_screen.dart +++ b/lib/programs screen/google_season_of_docs_screen.dart @@ -1,8 +1,7 @@ import 'dart:convert'; - - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:multi_select_flutter/multi_select_flutter.dart'; import 'package:opso/modals/book_mark_model.dart'; import 'package:opso/modals/gsod/gsod_modal_new.dart'; import 'package:opso/modals/gsod/gsod_modal_old.dart'; @@ -19,8 +18,8 @@ class GoogleSeasonOfDocsScreen extends StatefulWidget { class _GoogleSeasonOfDocsScreenState extends State { - String selectedOrganization = 'All'; - String selectedProposal = 'All'; + List selectedOrganizations = ['All']; + List selectedProposals = ['All']; String currentProgram = "Google Season of Docs"; bool isBookmarked = true; String currentPage = "/google_season_of_docs"; @@ -29,7 +28,6 @@ class _GoogleSeasonOfDocsScreenState extends State { List gsod2021 = []; List gsod2020 = []; List gsod2019 = []; - bool flag = true; int selectedYear = 2023; @@ -44,15 +42,13 @@ class _GoogleSeasonOfDocsScreenState extends State { for (var data in jsonList) { gsod2023.add(GsodModalNew.fromMap(data)); } - print(gsod2023.length); projectList = List.from(gsod2023); + + response = await rootBundle.loadString('assets/projects/gsod/gsod2022.json'); jsonList = await json.decode(response); - - for (var data in jsonList) { - print(data["organization_name"]); gsod2022.add(GsodModalNew.fromMap(data)); } @@ -60,17 +56,19 @@ class _GoogleSeasonOfDocsScreenState extends State { response = await rootBundle.loadString('assets/projects/gsod/gsod2021.json'); jsonList = await json.decode(response); - - for (var data in jsonList) { gsod2021.add(GsodModalNew.fromMap(data)); } + + response = await rootBundle.loadString('assets/projects/gsod/gsod2020.json'); jsonList = await json.decode(response); for (var data in jsonList) { gsod2020.add(GsodModalOld.fromMap(data)); } + + response = await rootBundle.loadString('assets/projects/gsod/gsod2019.json'); jsonList = await json.decode(response); @@ -117,30 +115,18 @@ class _GoogleSeasonOfDocsScreenState extends State { element.caseStudy .toLowerCase() .contains(searchText.toLowerCase()) || - element.docsPage - .toLowerCase() - .contains(searchText.toLowerCase()), + element.docsPage.toLowerCase().contains(searchText.toLowerCase()), ) .toList(); } else { projectList = projectList .where( (element) => - element.organization - .toLowerCase() - .contains(searchText.toLowerCase()) || - element.technicalWriter - .toLowerCase() - .contains(searchText.toLowerCase()) || - element.mentor - .toLowerCase() - .contains(searchText.toLowerCase()) || - element.project - .toLowerCase() - .contains(searchText.toLowerCase()) || - element.originalProjectProposal - .toLowerCase() - .contains(searchText.toLowerCase()) || + element.organization.toLowerCase().contains(searchText.toLowerCase()) || + element.technicalWriter.toLowerCase().contains(searchText.toLowerCase()) || + element.mentor.toLowerCase().contains(searchText.toLowerCase()) || + element.project.toLowerCase().contains(searchText.toLowerCase()) || + element.originalProjectProposal.toLowerCase().contains(searchText.toLowerCase()) || element.report.toLowerCase().contains(searchText.toLowerCase()), ) .toList(); @@ -154,11 +140,10 @@ class _GoogleSeasonOfDocsScreenState extends State { Future _refresh() async { - // Fetch data for the next year based on the currently selected year setState(() { initializeProjectLists(); - selectedOrganization = 'All'; - selectedProposal = 'All'; + selectedOrganizations = ['All']; + selectedProposals = ['All']; selectedYear = 2023; if (selectedYear > 2023) selectedYear = 2019; // Reset to the beginning if it exceeds 2023 }); @@ -186,53 +171,69 @@ class _GoogleSeasonOfDocsScreenState extends State { projectList = gsod2023; break; } - if (selectedOrganization != 'All') { - filterProjectsByTag(selectedOrganization); - } - setState(() { - _resetValueIfNotValid(); - }); + filterProjects(); } - void filterProjectsByTag(String tag) { - projectList = projectList - .where((element) => element.tags.contains(tag)) - .toList(); + void filterProjects() { + var filteredProjects = List.from(projectList); + print("!@# $selectedOrganizations"); + + + if (!selectedOrganizations.contains('All')) { + filteredProjects = filteredProjects.where((project) { + return selectedOrganizations.contains(project.organizationName); + }).toList(); + }else{ + filteredProjects = gsod2023; + } + + + if (!selectedProposals.contains('All')) { + filteredProjects = filteredProjects.where((project) { + return selectedProposals.contains(project.acceptedProjectProposal); + }).toList(); + } + + + + + + setState(() { + projectList = filteredProjects; _resetValueIfNotValid(); }); } void _resetValueIfNotValid() { - // Reset selectedOrganization if it is not valid - var validOrganizations = projectList - .map((project) => project.organizationName) - .toSet(); // Use a Set to ensure uniqueness - if (!validOrganizations.contains(selectedOrganization)) { - selectedOrganization = 'All'; + // Reset selectedOrganizations if they are not valid + var validOrganizations = projectList.map((project) => project.organizationName).toSet(); + selectedOrganizations = selectedOrganizations.where((org) => validOrganizations.contains(org) || org == 'All').toList(); + if (selectedOrganizations.isEmpty) { + selectedOrganizations = ['All']; } - // Reset selectedProposal if it is not valid - var validProposals = projectList - .map((project) => project.acceptedProjectProposal) - .toSet(); // Use a Set to ensure uniqueness - if (!validProposals.contains(selectedProposal)) { - selectedProposal = 'All'; + // Reset selectedProposals if they are not valid + var validProposals = projectList.map((project) => project.acceptedProjectProposal).toSet(); + selectedProposals = selectedProposals.where((proposal) => validProposals.contains(proposal) || proposal == 'All').toList(); + if (selectedProposals.isEmpty) { + selectedProposals = ['All']; } } @override Widget build(BuildContext context) { + bool isDarkMode = Theme.of(context).brightness == Brightness.dark; var height = MediaQuery.sizeOf(context).height; var width = MediaQuery.sizeOf(context).width; return RefreshIndicator( onRefresh: _refresh, child: Scaffold( - appBar: AppBar(title: const Text('OpSo'), actions: [ + appBar: AppBar(title: const Text('GSoD'), actions: [ IconButton( icon: (isBookmarked) ? const Icon(Icons.bookmark_add_rounded) @@ -243,16 +244,13 @@ class _GoogleSeasonOfDocsScreenState extends State { }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text( - isBookmarked ? 'Bookmark added' : 'Bookmark removed'), + content: Text(isBookmarked ? 'Bookmark added' : 'Bookmark removed'), duration: const Duration(seconds: 2), // Adjust the duration as needed ), ); if (isBookmarked) { - print("Adding"); HandleBookmark.addBookmark(currentProgram, currentPage); } else { - print("Deleting"); HandleBookmark.deleteBookmark(currentProgram); } }, @@ -264,227 +262,218 @@ class _GoogleSeasonOfDocsScreenState extends State { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.connectionState == ConnectionState.done) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 46, vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextFormField( - decoration: InputDecoration( - filled: true, - // fillColor: const Color(0xFFEEEEEE), - hintText: 'Search', - suffixIcon: const Icon(Icons.search), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 46, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + decoration: InputDecoration( + filled: true, + hintText: 'Search', + suffixIcon: const Icon(Icons.search), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: Color(0xFFEEEEEE), + ), ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: Color(0xFFEEEEEE), + ), ), - ), - disabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: Color(0xFFEEEEEE), + ), ), - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: Color(0xFFEEEEEE), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: Color(0xFFEEEEEE), + ), ), + contentPadding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 20.0), ), - contentPadding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 20.0), - ), - onFieldSubmitted: (value) { - print("value is $value"); - search(value.trim()); - }, - onChanged: (value) { - if (value.isEmpty) { - search(value); - } - }, - ), - const SizedBox(height: 20), - Container( - constraints: BoxConstraints( - maxHeight: height * 0.3, - ), - width: width, - child: GridView( - physics: const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - childAspectRatio: 1.5 / 0.6, - crossAxisSpacing: 15, - mainAxisSpacing: 15, - ), - children: [ - YearButton( - year: "2019", - isEnabled: selectedYear == 2019 ? true : false, - onTap: () { - setState(() { - projectList = gsod2019; - selectedYear = 2019; - _resetValueIfNotValid(); - }); - }, - backgroundColor: selectedYear == 2019 - ? Colors.white - : const Color.fromRGBO(249, 171, 0, 1), - ), - YearButton( - year: "2020", - isEnabled: selectedYear == 2020 ? true : false, - onTap: () { - setState(() { - projectList = gsod2020; - selectedYear = 2020; - _resetValueIfNotValid(); - }); - }, - backgroundColor: selectedYear == 2020 - ? Colors.white - : const Color.fromRGBO(249, 171, 0, 1), - ), - YearButton( - year: "2021", - isEnabled: selectedYear == 2021 ? true : false, - onTap: () { - setState(() { - projectList = gsod2021; - selectedYear = 2021; - _resetValueIfNotValid(); - }); - }, - backgroundColor: selectedYear == 2021 - ? Colors.white - : const Color.fromRGBO(249, 171, 0, 1), - ), - YearButton( - year: "2022", - isEnabled: selectedYear == 2022 ? true : false, - onTap: () { - setState(() { - projectList = gsod2022; - selectedYear = 2022; - _resetValueIfNotValid(); - }); - }, - backgroundColor: selectedYear == 2022 - ? Colors.white - : const Color.fromRGBO(249, 171, 0, 1), - ), - YearButton( - year: "2023", - isEnabled: selectedYear == 2023 ? true : false, - onTap: () { - setState(() { - projectList = gsod2023; - selectedYear = 2023; - _resetValueIfNotValid(); - }); - }, - backgroundColor: selectedYear == 2023 - ? Colors.white - : const Color.fromRGBO(249, 171, 0, 1), - ), - ], + onFieldSubmitted: (value) { + search(value.trim()); + }, + onChanged: (value) { + if (value.isEmpty) { + search(value); + } + }, ), - ), - const SizedBox( - height: 20, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - 'Filter by Organization:', - style: TextStyle(fontWeight: FontWeight.w400), + const SizedBox(height: 20), + Container( + constraints: BoxConstraints( + maxHeight: height * 0.3, ), - const SizedBox(width: 8), - Expanded( - child: DropdownButton( - value: selectedOrganization, - hint: const Text('Organization'), - isExpanded: true, - items: [ - DropdownMenuItem( - value: 'All', // Unique value for "All" - child: const Text('All'), - ), - ...projectList - .map((project) => project.organizationName) - .toSet() // Use a Set to ensure uniqueness - .map((organization) { - return DropdownMenuItem( - value: organization, - child: Text(organization), - ); - }).toList(), - ], - onChanged: (newValue) { - setState(() { - selectedOrganization = newValue!; - search(newValue); - }); - }, + width: width, + child: GridView( + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.5 / 0.6, + crossAxisSpacing: 15, + mainAxisSpacing: 15, ), + children: [ + YearButton( + year: "2019", + isEnabled: selectedYear == 2019, + onTap: () { + setState(() { + projectList = gsod2019; + selectedYear = 2019; + _resetValueIfNotValid(); + }); + }, + backgroundColor: selectedYear == 2019 + ? Colors.white + : const Color.fromRGBO(249, 171, 0, 1), + ), + YearButton( + year: "2020", + isEnabled: selectedYear == 2020, + onTap: () { + setState(() { + projectList = gsod2020; + selectedYear = 2020; + _resetValueIfNotValid(); + }); + }, + backgroundColor: selectedYear == 2020 + ? Colors.white + : const Color.fromRGBO(249, 171, 0, 1), + ), + YearButton( + year: "2021", + isEnabled: selectedYear == 2021, + onTap: () { + setState(() { + projectList = gsod2021; + selectedYear = 2021; + _resetValueIfNotValid(); + }); + }, + backgroundColor: selectedYear == 2021 + ? Colors.white + : const Color.fromRGBO(249, 171, 0, 1), + ), + YearButton( + year: "2022", + isEnabled: selectedYear == 2022, + onTap: () { + setState(() { + projectList = gsod2022; + selectedYear = 2022; + _resetValueIfNotValid(); + }); + }, + backgroundColor: selectedYear == 2022 + ? Colors.white + : const Color.fromRGBO(249, 171, 0, 1), + ), + YearButton( + year: "2023", + isEnabled: selectedYear == 2023, + onTap: () { + setState(() { + projectList = gsod2023; + selectedYear = 2023; + _resetValueIfNotValid(); + }); + }, + backgroundColor: selectedYear == 2023 + ? Colors.white + : const Color.fromRGBO(249, 171, 0, 1), + ), + ], ), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - 'Filter by Accepted Proposal:', - style: TextStyle(fontWeight: FontWeight.w400), + ), + const SizedBox(height: 20), + MultiSelectDialogField( + backgroundColor: isDarkMode ? Colors.grey.shade100 : Colors.white, + items: [ + MultiSelectItem('All', 'All'), + ...projectList + .map((project) => project.organizationName) + .toSet() + .map((organization) { + return MultiSelectItem(organization, organization); + }).toList(), + ], + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(10), ), - const SizedBox(width: 8), - Expanded( - child: DropdownButton( - value: selectedProposal, - hint: const Text('Proposal'), - isExpanded: true, - items: [ - DropdownMenuItem( - value: 'All', // Unique value for "All" - child: const Text('All'), - ), - ...projectList - .map((project) => - project.acceptedProjectProposal) - .toSet() // Use a Set to ensure uniqueness - .toList() - .map((proposal) { - return DropdownMenuItem( - value: proposal, - child: Text(proposal), - ); - }).toList(), - ], - onChanged: (newValue) { - setState(() { - selectedProposal = newValue!; - search(newValue); - }); - }, - ), + title: Text("Select Organizations", style: TextStyle(color: isDarkMode ? Colors.black : Colors.black)), + buttonText: const Text("Filter by Organization"), + onConfirm: (results) { + setState(() { + print("results are $results"); + if (results.contains('All')) { + selectedOrganizations = ['All']; + } else { + selectedOrganizations = results.isNotEmpty ? results : ['All']; + } + + + print("this is selected organization $selectedOrganizations"); + filterProjects(); + }); + }, + initialValue: selectedOrganizations, + ), + + + const SizedBox(height: 20), + MultiSelectDialogField( + backgroundColor: isDarkMode ? Colors.grey.shade100 : Colors.white, + items: [ + MultiSelectItem('All', 'All'), + ...projectList + .map((project) => project.acceptedProjectProposal) + .toSet() + .map((proposal) { + return MultiSelectItem(proposal, proposal); + }).toList(), + ], + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(10), ), - ], - ), - const SizedBox(height: 20), - Expanded( - child: ListView.builder( + title: Text("Select Proposals", style: TextStyle(color: isDarkMode ? Colors.black : Colors.black)), + buttonText: const Text("Filter by Accepted Proposal"), + onConfirm: (results) { + setState(() { + print("results are $results"); + if (results.contains('All')) { + selectedProposals = ['All']; + } else { + selectedProposals = results.isNotEmpty ? results : ['All']; + } + + + print("this is selected proposal $selectedProposals"); + filterProjects(); + }); + }, + initialValue: selectedProposals, + ), + + + const SizedBox(height: 20), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), itemCount: projectList.length, itemBuilder: (BuildContext context, int index) { return Padding( @@ -505,8 +494,8 @@ class _GoogleSeasonOfDocsScreenState extends State { ); }, ), - ), - ], + ], + ), ), ); } else { diff --git a/lib/programs screen/google_summer_of_code_screen.dart b/lib/programs screen/google_summer_of_code_screen.dart index 1329e69..4b3f1b2 100644 --- a/lib/programs screen/google_summer_of_code_screen.dart +++ b/lib/programs screen/google_summer_of_code_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:opso/widgets/gsoc/GsocProjectWidget.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:multi_select_flutter/multi_select_flutter.dart'; import '../modals/GSoC/Gsoc.dart'; import '../services/ApiService.dart'; import '../widgets/SearchandFilterWidget.dart'; @@ -15,7 +16,7 @@ class GoogleSummerOfCodeScreen extends StatefulWidget { class _GoogleSummerOfCodeScreenState extends State { - String selectedOrg = 'All'; + String selectedOrg = 'All'; // Ensure this is defined List gsoc2024 = []; List gsoc2023 = []; List gsoc2022 = []; @@ -39,6 +40,9 @@ class _GoogleSummerOfCodeScreenState extends State { 'Dart' ]; List orgList = []; + List selectedLanguages = ['All']; + List allOrganizations = []; + List selectedOrganizations = ['All']; late Future _dataFetchFuture; @@ -64,6 +68,7 @@ class _GoogleSummerOfCodeScreenState extends State { gsoc2023 = orgData2023.organizations ?? []; gsoc2024 = orgData2024.organizations ?? []; orgList = gsoc2024; // Default to the latest year + allOrganizations = ['All', ...orgList.map((org) => org.name!).toSet()]; }); } catch (e) { print('Error: $e'); @@ -71,27 +76,31 @@ class _GoogleSummerOfCodeScreenState extends State { } - void searchTag(String searchTag) { - setState(() { - selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch - orgList = _getOrganizationsByYear(selectedYear) - .where((element) => element.technologies?.contains(searchTag) == true || element.topics?.contains(searchTag) == true) - .toList(); - }); - } + void filterProjects() { + orgList = _getOrganizationsByYear(selectedYear); - void search(String searchText) { - setState(() { - selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch - if (searchText.isEmpty) { - orgList = _getOrganizationsByYear(selectedYear); - } else { - orgList = _getOrganizationsByYear(selectedYear) - .where((element) => element.name?.toLowerCase().contains(searchText.toLowerCase()) == true) - .toList(); - } - }); + if (!selectedLanguages.contains('All')) { + orgList = orgList.where((project) => project.technologies?.any(selectedLanguages.contains) == true).toList(); + } + + + if (!selectedOrganizations.contains('All')) { + orgList = orgList.where((project) => selectedOrganizations.contains(project.name)).toList(); + } + + + // Update organization filter based on selected languages + allOrganizations = [ + 'All', + ..._getOrganizationsByYear(selectedYear) + .where((org) => selectedLanguages.contains('All') || org.technologies?.any(selectedLanguages.contains) == true) + .map((org) => org.name!) + .toSet() + ]; + + + setState(() {}); } @@ -112,21 +121,25 @@ class _GoogleSummerOfCodeScreenState extends State { Future _refresh() async { + await getProjectData(); setState(() { - _dataFetchFuture = getProjectData(); selectedYear = 2024; - selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch + selectedLanguages = ['All']; + selectedOrganizations = ['All']; + filterProjects(); }); } - void resetProjectsByOrganization() { + // Add this method to the _GoogleSummerOfCodeScreenState class + void search(String searchText) { setState(() { - if (selectedOrg == 'All') { + selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch + if (searchText.isEmpty) { orgList = _getOrganizationsByYear(selectedYear); } else { orgList = _getOrganizationsByYear(selectedYear) - .where((element) => element.name == selectedOrg) + .where((element) => element.name?.toLowerCase().contains(searchText.toLowerCase()) == true) .toList(); } }); @@ -219,8 +232,9 @@ class _GoogleSummerOfCodeScreenState extends State { onTap: () { setState(() { selectedYear = 2021; - selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch - orgList = gsoc2021; + selectedLanguages = ['All']; + selectedOrganizations = ['All']; + filterProjects(); }); }, backgroundColor: selectedYear == 2021 @@ -233,8 +247,9 @@ class _GoogleSummerOfCodeScreenState extends State { onTap: () { setState(() { selectedYear = 2022; - selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch - orgList = gsoc2022; + selectedLanguages = ['All']; + selectedOrganizations = ['All']; + filterProjects(); }); }, backgroundColor: selectedYear == 2022 @@ -247,8 +262,9 @@ class _GoogleSummerOfCodeScreenState extends State { onTap: () { setState(() { selectedYear = 2023; - selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch - orgList = gsoc2023; + selectedLanguages = ['All']; + selectedOrganizations = ['All']; + filterProjects(); }); }, backgroundColor: selectedYear == 2023 @@ -261,8 +277,9 @@ class _GoogleSummerOfCodeScreenState extends State { onTap: () { setState(() { selectedYear = 2024; - selectedOrg = 'All'; // Reset selectedOrg to avoid mismatch - orgList = gsoc2024; + selectedLanguages = ['All']; + selectedOrganizations = ['All']; + filterProjects(); }); }, backgroundColor: selectedYear == 2024 @@ -272,64 +289,31 @@ class _GoogleSummerOfCodeScreenState extends State { ], ), ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Text( - 'Filter by Language:', - style: TextStyle(fontWeight: FontWeight.w400), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - DropdownWidget( - items: languages, - hintText: 'Language', - onChanged: (newValue) { - searchTag(newValue); - }, - ), - ], - ), - ) - ], + const SizedBox(height: 20), + _buildMultiSelectField( + items: languages, + selectedValues: selectedLanguages, + title: "Select Languages", + buttonText: "Filter by Language", + onConfirm: (results) { + setState(() { + selectedLanguages = results.isNotEmpty ? results : ['All']; + filterProjects(); + }); + }, ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - 'Filter by Organization:', - style: TextStyle(fontWeight: FontWeight.w400), - ), - const SizedBox(width: 8), - Expanded( - child: DropdownButton( - value: selectedOrg, - hint: const Text('Organization'), - isExpanded: true, - items: [ - 'All', - if (orgList.isNotEmpty) - ...orgList.map((org) => org.name!).toList() - ].map((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: (newValue) { - setState(() { - selectedOrg = newValue!; - resetProjectsByOrganization(); - }); - }, - ), - ), - ], + const SizedBox(height: 20), + _buildMultiSelectField( + items: allOrganizations, + selectedValues: selectedOrganizations, + title: "Select Organizations", + buttonText: "Filter by Organization", + onConfirm: (results) { + setState(() { + selectedOrganizations = results.isNotEmpty ? results : ['All']; + filterProjects(); + }); + }, ), const SizedBox(height: 20), Expanded( @@ -371,6 +355,29 @@ class _GoogleSummerOfCodeScreenState extends State { ), ); } + + + Widget _buildMultiSelectField({ + required List items, + required List selectedValues, + required String title, + required String buttonText, + required void Function(List) onConfirm, + }) { + bool isDarkMode = Theme.of(context).brightness == Brightness.dark; + return MultiSelectDialogField( + backgroundColor: isDarkMode ? Colors.grey.shade100 : Colors.white, + items: items.map((e) => MultiSelectItem(e, e)).toList(), + initialValue: selectedValues, + title: Text(title, style: TextStyle(color: isDarkMode ? Colors.black : Colors.black)), + buttonText: Text(buttonText), + onConfirm: onConfirm, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(10), + ), + ); + } } diff --git a/pubspec.lock b/pubspec.lock index 4a2f4ef..a29db0e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -304,6 +304,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.0" + multi_select_flutter: + dependency: "direct main" + description: + name: multi_select_flutter + sha256: "503857b415d390d29159df8a9d92d83c6aac17aaf1c307fb7bcfc77d097d20ed" + url: "https://pub.dev" + source: hosted + version: "4.1.3" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 18512c6..c11c04b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,7 +41,7 @@ dependencies: adaptive_theme: ^3.6.0 timeline_tile: ^2.0.0 flutter_svg: ^2.0.10+1 - + multi_select_flutter: ^4.1.3 dev_dependencies: awesome_notifications: ^0.9.3+1 flutter_launcher_icons: ^0.13.1