diff --git a/assets/open_summer_of_code.png b/assets/open_summer_of_code.png new file mode 100644 index 0000000..a5d2a31 Binary files /dev/null and b/assets/open_summer_of_code.png differ diff --git a/assets/projects/osc/osc2021.json b/assets/projects/osoc/osoc2021.json similarity index 100% rename from assets/projects/osc/osc2021.json rename to assets/projects/osoc/osoc2021.json diff --git a/assets/projects/osc/osc2022.json b/assets/projects/osoc/osoc2022.json similarity index 100% rename from assets/projects/osc/osc2022.json rename to assets/projects/osoc/osoc2022.json diff --git a/lib/home_page.dart b/lib/home_page.dart index 4924df6..d79a35c 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -8,6 +8,7 @@ import 'package:opso/programs%20screen/google_season_of_docs_screen.dart'; import 'package:opso/programs%20screen/google_summer_of_code_screen.dart'; import 'package:opso/programs%20screen/linux_foundation.dart'; import 'package:opso/programs%20screen/major_league_hacking_fellowship.dart'; +import 'package:opso/programs%20screen/open_summer_of_code.dart'; import 'package:opso/programs%20screen/outreachy.dart'; import 'package:opso/programs%20screen/summer_of_bitcoin.dart'; import 'package:opso/programs%20screen/social_winter_of_code.dart'; @@ -98,6 +99,10 @@ class _HomePageState extends State { title: 'Social Winter of Code', imageAssetPath: 'assets/swoc.png', ), + Program( + title: 'Open Summer of Code', + imageAssetPath: 'assets/open_summer_of_code.png', + ), ]; @override @@ -356,17 +361,18 @@ class _HomePageState extends State { case 'Summer of Bitcoin': Navigator.pushNamed(context, "/summer_of_bitcoin"); - case 'Summer of Bitcoin': + case 'Open Summer of Code': Navigator.push( context, MaterialPageRoute( - builder: (context) => const SummerOfBitcoin(), + builder: (context) => const OpenSummerOfCode(), ), ); case 'Linux Foundation': Navigator.push(context, MaterialPageRoute(builder: (context) => const LinuxFoundation())); + default: break; } @@ -465,6 +471,10 @@ class ProgramSearchDelegate extends SearchDelegate { title: 'Social Winter of Code', imageAssetPath: 'assets/swoc.png', ), + Program( + title: 'Open Summer of Code', + imageAssetPath: 'assets/open_summer_of_code.png', + ), ]; @override diff --git a/lib/main.dart b/lib/main.dart index 87e2cd7..7d6c59e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:opso/programs%20screen/google_season_of_docs_screen.dart'; import 'package:opso/programs%20screen/google_summer_of_code_screen.dart'; import 'package:opso/programs%20screen/linux_foundation.dart'; import 'package:opso/programs%20screen/major_league_hacking_fellowship.dart'; +import 'package:opso/programs%20screen/open_summer_of_code.dart'; import 'package:opso/programs%20screen/outreachy.dart'; import 'package:opso/programs%20screen/summer_of_bitcoin.dart'; import 'package:opso/programs%20screen/social_winter_of_code.dart'; @@ -45,6 +46,7 @@ class OpSoApp extends StatelessWidget { "/google_season_of_docs": (context) => GoogleSeasonOfDocsScreen(), "/summer_of_bitcoin": (context) => const SummerOfBitcoin(), + "/open_summer_of_code": (context) => const OpenSummerOfCode(), "/outreachy": (context) => const OutReachy(), "/major_league_hacking_fellowship": (context) => const MajorLeagueHackingFellowship(), diff --git a/lib/modals/osoc_modal.dart b/lib/modals/osoc_modal.dart new file mode 100644 index 0000000..03bca90 --- /dev/null +++ b/lib/modals/osoc_modal.dart @@ -0,0 +1,83 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first, non_constant_identifier_names +import 'dart:convert'; + +class OsocModal { + String name = ""; + String image_url = ""; + String description = ""; + String project_url = ""; + int year = 0; + OsocModal({ + required this.name, + required this.image_url, + required this.description, + required this.project_url, + required this.year, + }); + + OsocModal copyWith({ + String? name, + String? image_url, + String? description, + String? project_url, + int? year, + }) { + return OsocModal( + name: name ?? this.name, + image_url: image_url ?? this.image_url, + description: description ?? this.description, + project_url: project_url ?? this.project_url, + year: year ?? this.year, + ); + } + + Map toMap() { + return { + 'name': name, + 'image_url': image_url, + 'description': description, + 'project_url': project_url, + 'year': year, + }; + } + + factory OsocModal.fromMap(Map map) { + return OsocModal( + name: map['name'] as String, + image_url: map['image_url'] as String, + description: map['description'] as String, + project_url: map['project_url'] as String, + year: map['year'] as int, + ); + } + + String toJson() => json.encode(toMap()); + + factory OsocModal.fromJson(String source) => + OsocModal.fromMap(json.decode(source) as Map); + + @override + String toString() { + return 'OsocModal(name: $name, image_url: $image_url, description: $description, project_url: $project_url, year: $year)'; + } + + @override + bool operator ==(covariant OsocModal other) { + if (identical(this, other)) return true; + + return other.name == name && + other.image_url == image_url && + other.description == description && + other.project_url == project_url && + other.year == year; + } + + @override + int get hashCode { + return name.hashCode ^ + image_url.hashCode ^ + description.hashCode ^ + project_url.hashCode ^ + year.hashCode; + } +} diff --git a/lib/programs screen/open_summer_of_code.dart b/lib/programs screen/open_summer_of_code.dart new file mode 100644 index 0000000..23ab1a1 --- /dev/null +++ b/lib/programs screen/open_summer_of_code.dart @@ -0,0 +1,326 @@ +import 'dart:convert'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:opso/modals/osoc_modal.dart'; +import 'package:opso/modals/sob_project_modal.dart'; +import 'package:opso/programs_info_pages/sob_info.dart'; +import 'package:opso/widgets/osoc_widget.dart'; +import 'package:opso/widgets/sob_project_widget.dart'; +import 'package:opso/widgets/year_button.dart'; + +import '../modals/book_mark_model.dart'; +import '../widgets/SearchandFilterWidget.dart'; + +class OpenSummerOfCode extends StatefulWidget { + const OpenSummerOfCode({super.key}); + + @override + State createState() => _OpenSummerOfCodeState(); +} + +class _OpenSummerOfCodeState extends State { + List osoc2022 = []; + List osoc2021 = []; + String currectPage = "/open_summer_of_code"; + String currentProject = "Open Summer of Code"; + bool isBookmarked = true; + int selectedYear = 2022; + + List projectList = []; + Future? getProjectFunction; + + Future initializeProjectLists() async { + var response = + await rootBundle.loadString('assets/projects/osoc/osoc2022.json'); + var jsonList = await json.decode(response); + for (var data in jsonList) { + osoc2022.add(OsocModal.fromMap(data)); + } + projectList = osoc2022; + print(projectList); + response = + await rootBundle.loadString('assets/projects/osoc/osoc2021.json'); + jsonList = await json.decode(response); + for (var data in jsonList) { + osoc2021.add(OsocModal.fromMap(data)); + } + } + + @override + void initState() { + getProjectFunction = initializeProjectLists(); + super.initState(); + _checkBookmarkStatus(); + } + + Future _checkBookmarkStatus() async { + bool bookmarkStatus = await HandleBookmark.isBookmarked(currentProject); + setState(() { + isBookmarked = bookmarkStatus; + }); + } + + void searchTag(String searchTag) { + projectList = projectList + .where((OsocModal element) => element.name.contains(searchTag)) + .toList(); + setState(() {}); + } + + void search(String searchText) { + if (searchText.isEmpty) { + switch (selectedYear) { + case 2021: + projectList = osoc2021; + break; + case 2022: + projectList = osoc2022; + break; + } + setState(() {}); + return; + } + searchText = searchText.toLowerCase(); + projectList = projectList + .where((OsocModal element) => + element.name.toLowerCase().contains(searchText) || + element.description.toLowerCase().contains(searchText)) + .toList(); + setState(() {}); + } + + // List languages = [ + // 'Rust miniscript', + // 'Core Lightning', + // 'LDK', + // 'Bitcoin Core', + // 'Alby', + // 'Eye of Satoshi', + // 'Ledger Bitcoin App', + // 'Galoy', + // 'Fedimint', + // 'VLS', + // 'StratumV2', + // 'bcoin', + // 'LND', + // 'Eclair' + // ]; + + Future _refresh() async { + osoc2021.clear(); + osoc2022.clear(); + await initializeProjectLists(); + + selectedYear = 2022; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + var height = MediaQuery.sizeOf(context).height; + var width = MediaQuery.sizeOf(context).width; + const ScreenUtilInit( + designSize: Size(360, 690), + ); + return RefreshIndicator( + onRefresh: _refresh, + child: Scaffold( + appBar: + AppBar(title: const Text('Summer of Bitcoin'), actions: [ + IconButton( + icon: (isBookmarked) + ? const Icon(Icons.bookmark_add_rounded) + : const Icon(Icons.bookmark_add_outlined), + onPressed: () { + setState(() { + isBookmarked = !isBookmarked; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + isBookmarked ? 'Bookmark added' : 'Bookmark removed'), + duration: const Duration( + seconds: 2), // Adjust the duration as needed + ), + ); + if (isBookmarked) { + print("Adding"); + HandleBookmark.addBookmark(currentProject, currectPage); + } 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) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: ScreenUtil().setWidth(46), + vertical: ScreenUtil().setHeight(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: EdgeInsets.symmetric( + vertical: ScreenUtil().setHeight(12), + horizontal: ScreenUtil().setWidth(20)), + ), + onFieldSubmitted: (value) { + search(value.trim()); + }, + onChanged: (value) { + if (value.isEmpty) { + search(value); + } + }, + ), + SizedBox(height: ScreenUtil().setHeight(20)), + SizedBox( + height: height * 0.1, + width: width, + child: GridView( + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.5 / 0.6, + crossAxisSpacing: 15, + mainAxisSpacing: 15, + ), + children: [ + YearButton( + year: "2021", + isEnabled: selectedYear == 2021 ? true : false, + onTap: () { + setState(() { + projectList = osoc2021; + selectedYear = 2021; + }); + }, + backgroundColor: selectedYear == 2021 + ? Colors.white + : const Color.fromRGBO(255, 183, 77, 1), + ), + YearButton( + year: "2022", + isEnabled: selectedYear == 2022 ? true : false, + onTap: () { + setState(() { + projectList = osoc2022; + selectedYear = 2022; + }); + }, + backgroundColor: selectedYear == 2022 + ? Colors.white + : const Color.fromRGBO(255, 183, 77, 1), + ), + ], + ), + ), + + // Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // children: [ + // const Text( + // 'Filter by Org:', + // style: TextStyle(fontWeight: FontWeight.w400), + // ), + // Padding( + // padding: EdgeInsets.all(ScreenUtil().setHeight(8)), + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // children: [ + // DropdownWidget( + // items: languages, + // hintText: 'Org', + // onChanged: (newValue) { + // setState(() { + // switch (selectedYear) { + // case 2021: + // projectList = sob2021; + // break; + // case 2022: + // projectList = sob2022; + // break; + // case 2023: + // projectList = sob2023; + // break; + // } + // searchTag(newValue); + // }); + // // Perform filtering based on selectedLanguage + // }, + // ), + // ], + // ), + // ) + // ], + // ), + // SizedBox( + // height: ScreenUtil().setHeight(20), + // ), + Expanded( + // width: width, + child: ListView.builder( + itemCount: projectList.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: OsocWidget( + modal: projectList[index], + height: ScreenUtil().screenHeight * 0.2, + width: ScreenUtil().screenWidth, + ), + ); + }, + ), + ), + ], + ), + ); + } else { + return const Center(child: Text("Some error occured")); + } + }), + ), + ); + } +} diff --git a/lib/widgets/osoc_widget.dart b/lib/widgets/osoc_widget.dart new file mode 100644 index 0000000..9e53aca --- /dev/null +++ b/lib/widgets/osoc_widget.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:opso/modals/osoc_modal.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class OsocWidget extends StatelessWidget { + final OsocModal modal; + final double height; + final double width; + const OsocWidget( + {super.key, required this.modal, this.height = 100, this.width = 100}); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final textColor = isDarkMode ? Colors.white : Colors.black; + final cardColor = isDarkMode ? Colors.grey.shade800 : Colors.white; + return GestureDetector( + onTap: () => launchUrl( + Uri.parse(modal.project_url), + ), + child: Container( + constraints: BoxConstraints( + minHeight: height, + ), + padding: const EdgeInsets.all(10), + width: width, + decoration: BoxDecoration( + color: cardColor, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SvgPicture.network( + modal.image_url, + height: height * 0.5, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + modal.name, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + textAlign: TextAlign.center, + ), + ), + Text(modal.description), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 69dd7cc..a8266ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -92,6 +92,7 @@ flutter: - assets/xyz_domain.png - assets/participation_certificate.png - assets/coding_ninja_swag.png + - assets/projects/osoc/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware