diff --git a/uni/assets/images/book_placeholder.png b/uni/assets/images/book_placeholder.png new file mode 100644 index 000000000..a2ebec47c Binary files /dev/null and b/uni/assets/images/book_placeholder.png differ diff --git a/uni/lib/controller/fetchers/book_fetcher.dart b/uni/lib/controller/fetchers/book_fetcher.dart new file mode 100644 index 000000000..3c46fa06e --- /dev/null +++ b/uni/lib/controller/fetchers/book_fetcher.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:uni/controller/networking/network_router.dart'; + +class BookThumbFetcher { + Future fetchBookThumb(String isbn) async { + final url = Uri.https( + 'www.googleapis.com', + '/books/v1/volumes', + {'q': '+isbn:$isbn'}, + ); + + final response = + await http.get(Uri.decodeComponent(url.toString()).toUri()); + + final bookInformation = ((json.decode(response.body) + as Map)['items'] as List) + .first as Map; + + final thumbnailURL = + ((bookInformation['volumeInfo'] as Map)['imageLinks'] + as Map)['thumbnail']; + + return thumbnailURL.toString(); + } +} diff --git a/uni/lib/controller/fetchers/course_units_fetcher/course_units_info_fetcher.dart b/uni/lib/controller/fetchers/course_units_fetcher/course_units_info_fetcher.dart index 1c54029e3..4bf08d56a 100644 --- a/uni/lib/controller/fetchers/course_units_fetcher/course_units_info_fetcher.dart +++ b/uni/lib/controller/fetchers/course_units_fetcher/course_units_info_fetcher.dart @@ -5,6 +5,7 @@ import 'package:uni/controller/parsers/parser_course_unit_info.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/entities/course_units/course_unit_directory.dart'; import 'package:uni/model/entities/course_units/course_unit_sheet.dart'; +import 'package:uni/model/entities/course_units/sheet.dart'; import 'package:uni/model/entities/session.dart'; class CourseUnitsInfoFetcher implements SessionDependantFetcher { @@ -27,6 +28,19 @@ class CourseUnitsInfoFetcher implements SessionDependantFetcher { return parseCourseUnitSheet(response); } + Future fetchSheet( + Session session, + int occurId, + ) async { + final url = '${getEndpoints(session)[0]}mob_ucurr_geral.perfil'; + final response = await NetworkRouter.getWithCookies( + url, + {'pv_ocorrencia_id': occurId.toString()}, + session, + ); + return parseSheet(response); + } + Future> fetchCourseUnitFiles( Session session, int occurId, @@ -34,9 +48,7 @@ class CourseUnitsInfoFetcher implements SessionDependantFetcher { final url = '${getEndpoints(session)[0]}mob_ucurr_geral.conteudos'; final response = await NetworkRouter.getWithCookies( url, - { - 'pv_ocorrencia_id': occurId.toString(), - }, + {'pv_ocorrencia_id': occurId.toString()}, session, ); return parseFiles(response, session); diff --git a/uni/lib/controller/parsers/parser_course_unit_info.dart b/uni/lib/controller/parsers/parser_course_unit_info.dart index 01b689da5..51025c96b 100644 --- a/uni/lib/controller/parsers/parser_course_unit_info.dart +++ b/uni/lib/controller/parsers/parser_course_unit_info.dart @@ -7,6 +7,7 @@ import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/entities/course_units/course_unit_directory.dart'; import 'package:uni/model/entities/course_units/course_unit_file.dart'; import 'package:uni/model/entities/course_units/course_unit_sheet.dart'; +import 'package:uni/model/entities/course_units/sheet.dart'; import 'package:uni/model/entities/session.dart'; Future> parseFiles( @@ -44,6 +45,59 @@ Future> parseFiles( return dirs; } +Future parseSheet(http.Response response) async { + final json = jsonDecode(response.body) as Map; + + final professors = getCourseUnitProfessors( + (json['ds'] as List) + .map((element) => element as Map) + .toList(), + ); + + final regents = (json['responsabilidades'] as List).map((element) { + return Professor.fromJson(element as Map); + }).toList(); + + final books = (json['bibliografia'] as List) + .map((element) => element as Map) + .toList() + .map((element) { + return Book( + title: element['titulo'].toString(), + isbn: element['isbn'].toString(), + ); + }).toList(); + + return Sheet( + professors: professors, + content: json['conteudo'].toString(), + evaluation: json['for_avaliacao'].toString(), + regents: regents, + books: books, + ); +} + +List getCourseUnitProfessors(List> ds) { + final professors = []; + for (final map in ds) { + for (final docente in map['docentes'] as List) { + final professor = Professor( + code: (docente as Map)['doc_codigo'].toString(), + name: shortName(docente['nome'].toString()), + classes: [map['tipo'].toString()], + ); + if (professors.contains(professor)) { + professors[professors.indexWhere((element) => element == professor)] + .classes + .add(map['tipo'].toString()); + } else { + professors.add(professor); + } + } + } + return professors; +} + Future parseCourseUnitSheet(http.Response response) async { final document = parse(response.body); final titles = document.querySelectorAll('#conteudoinner h3'); @@ -121,3 +175,8 @@ String _htmlAfterElement(String body, String elementOuterHtml) { final index = body.indexOf(elementOuterHtml) + elementOuterHtml.length; return body.substring(index, body.indexOf('

', index)); } + +String shortName(String name) { + final splitName = name.split(' '); + return '${splitName.first} ${splitName.last}'; +} diff --git a/uni/lib/model/entities/course_units/sheet.dart b/uni/lib/model/entities/course_units/sheet.dart new file mode 100644 index 000000000..d33f77696 --- /dev/null +++ b/uni/lib/model/entities/course_units/sheet.dart @@ -0,0 +1,58 @@ +import 'dart:io'; + +import 'package:uni/controller/parsers/parser_course_unit_info.dart'; + +class Sheet { + Sheet({ + required this.professors, + required this.content, + required this.evaluation, + required this.regents, + required this.books, + }); + List professors; + List regents; + String content; + String evaluation; + List books; +} + +class Book { + Book({required this.title, required this.isbn}); + + String title; + String isbn; +} + +class Professor { + Professor({ + required this.code, + required this.name, + required this.classes, + this.picture, + }); + + factory Professor.fromJson(Map json) { + return Professor( + code: json['codigo'].toString(), + name: shortName(json['nome'].toString()), + classes: [], + ); + } + + File? picture; + String code; + String name; + List classes; + + @override + bool operator ==(Object other) { + if (other is Professor) { + return other.code == code; + } + return false; + } + + @override + int get hashCode => code.hashCode; +} diff --git a/uni/lib/model/providers/lazy/course_units_info_provider.dart b/uni/lib/model/providers/lazy/course_units_info_provider.dart index 69d653bfd..19ace085d 100644 --- a/uni/lib/model/providers/lazy/course_units_info_provider.dart +++ b/uni/lib/model/providers/lazy/course_units_info_provider.dart @@ -5,12 +5,12 @@ import 'package:uni/controller/fetchers/course_units_fetcher/course_units_info_f import 'package:uni/model/entities/course_units/course_unit.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/entities/course_units/course_unit_directory.dart'; -import 'package:uni/model/entities/course_units/course_unit_sheet.dart'; +import 'package:uni/model/entities/course_units/sheet.dart'; import 'package:uni/model/entities/session.dart'; import 'package:uni/model/providers/state_provider_notifier.dart'; import 'package:uni/model/providers/state_providers.dart'; -typedef SheetsMap = Map; +typedef SheetsMap = Map; typedef ClassesMap = Map>; typedef FilesMap = Map>; @@ -25,7 +25,7 @@ class CourseUnitsInfoProvider initialState: Tuple3({}, {}, {}), ); - UnmodifiableMapView get courseUnitsSheets => + UnmodifiableMapView get courseUnitsSheets => UnmodifiableMapView(state!.item1); UnmodifiableMapView> @@ -38,9 +38,8 @@ class CourseUnitsInfoProvider CourseUnit courseUnit, Session session, ) async { - state!.item1[courseUnit] = await CourseUnitsInfoFetcher() - .fetchCourseUnitSheet(session, courseUnit.occurrId); - notifyListeners(); + state!.item1[courseUnit] = + await CourseUnitsInfoFetcher().fetchSheet(session, courseUnit.occurrId); } Future fetchCourseUnitClasses( diff --git a/uni/lib/view/common_widgets/generic_animated_expandable.dart b/uni/lib/view/common_widgets/generic_animated_expandable.dart new file mode 100644 index 000000000..fc5a4da6f --- /dev/null +++ b/uni/lib/view/common_widgets/generic_animated_expandable.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class AnimatedExpandable extends StatefulWidget { + const AnimatedExpandable({ + required this.firstChild, + required this.secondChild, + super.key, + }); + + final Widget firstChild; + final Widget secondChild; + + @override + State createState() { + return AnimatedExpandableState(); + } +} + +class AnimatedExpandableState extends State { + bool _expanded = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + setState(() { + _expanded = !_expanded; + }); + }, + child: AnimatedCrossFade( + firstCurve: Curves.easeInOutCubic, + secondCurve: Curves.easeInOut, + sizeCurve: Curves.easeInOut, + duration: const Duration(milliseconds: 500), + firstChild: widget.firstChild, + secondChild: widget.secondChild, + crossFadeState: + _expanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + ), + ); + } +} diff --git a/uni/lib/view/common_widgets/generic_expandable.dart b/uni/lib/view/common_widgets/generic_expandable.dart new file mode 100644 index 000000000..a22275c78 --- /dev/null +++ b/uni/lib/view/common_widgets/generic_expandable.dart @@ -0,0 +1,40 @@ +import 'package:expandable/expandable.dart'; +import 'package:flutter/material.dart'; + +class GenericExpandable extends StatelessWidget { + const GenericExpandable({ + required this.title, + required this.content, + super.key, + }); + + final String title; + final Widget content; + + @override + Widget build(BuildContext context) { + return ExpandablePanel( + header: Align( + alignment: Alignment.centerLeft, + child: Text(title, style: const TextStyle(fontSize: 20)), + ), + collapsed: ShaderMask( + shaderCallback: (bounds) => const LinearGradient( + colors: [Colors.black, Colors.transparent], + stops: [0.7, 1.0], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(bounds), + blendMode: BlendMode.dstIn, + child: ClipRect( + child: Align( + alignment: Alignment.topCenter, + heightFactor: 0.25, + child: content, + ), + ), + ), + expanded: content, + ); + } +} diff --git a/uni/lib/view/course_unit_info/course_unit_info.dart b/uni/lib/view/course_unit_info/course_unit_info.dart index 1e52ee7cf..339c12560 100644 --- a/uni/lib/view/course_unit_info/course_unit_info.dart +++ b/uni/lib/view/course_unit_info/course_unit_info.dart @@ -112,8 +112,7 @@ class CourseUnitDetailPageViewState final sheet = context .read() .courseUnitsSheets[widget.courseUnit]; - - if (sheet == null || sheet.sections.isEmpty) { + if (sheet == null) { return Center( child: Text( S.of(context).no_info, diff --git a/uni/lib/view/course_unit_info/widgets/course_unit_sheet.dart b/uni/lib/view/course_unit_info/widgets/course_unit_sheet.dart index 7a408ced0..f5cdb1fd5 100644 --- a/uni/lib/view/course_unit_info/widgets/course_unit_sheet.dart +++ b/uni/lib/view/course_unit_info/widgets/course_unit_sheet.dart @@ -1,106 +1,246 @@ -import 'dart:math'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; -import 'package:html/dom.dart' as dom; -import 'package:uni/model/entities/course_units/course_unit_sheet.dart'; -import 'package:uni/view/course_unit_info/widgets/course_unit_info_card.dart'; +import 'package:provider/provider.dart'; +import 'package:uni/controller/fetchers/book_fetcher.dart'; +import 'package:uni/model/entities/course_units/sheet.dart'; +import 'package:uni/model/providers/startup/profile_provider.dart'; +import 'package:uni/model/providers/startup/session_provider.dart'; +import 'package:uni/view/common_widgets/generic_animated_expandable.dart'; +import 'package:uni/view/common_widgets/generic_expandable.dart'; class CourseUnitSheetView extends StatelessWidget { const CourseUnitSheetView(this.courseUnitSheet, {super.key}); - final CourseUnitSheet courseUnitSheet; + final Sheet courseUnitSheet; @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.only(left: 10, right: 10), - child: ListView( - children: courseUnitSheet.sections.entries - .map((e) => _buildCard(e.key, e.value)) - .toList(), + padding: const EdgeInsets.only(left: 20, right: 20), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Regentes', + style: TextStyle(fontSize: 20), + ), + buildRegentsRow(context, courseUnitSheet.regents), + const Text( + 'Docentes', + style: TextStyle(fontSize: 20), + ), + AnimatedExpandable( + firstChild: + buildProfessorsRow(context, courseUnitSheet.professors), + secondChild: + buildExpandedProfessors(context, courseUnitSheet.professors), + ), + _buildCard('Programa', courseUnitSheet.content), + _buildCard('Avaliação', courseUnitSheet.evaluation), + const Opacity( + opacity: 0.25, + child: Divider(color: Colors.grey), + ), + const Text( + 'Bibliografia', + style: TextStyle(fontSize: 20), + ), + if (courseUnitSheet.books.isNotEmpty) + buildBooksRow(context, courseUnitSheet.books), + ], + ), ), ); } +} + +Widget buildRegentsRow(BuildContext context, List regents) { + final session = context.read().state!; + return SizedBox( + height: (regents.length * 80) + 20, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ...regents.asMap().entries.map((regent) { + final idx = regent.key; + return Padding( + padding: EdgeInsets.only(bottom: idx == regents.length - 1 ? 0 : 5), + child: Row( + children: [ + FutureBuilder( + builder: (context, snapshot) => _buildAvatar(snapshot, 40), + future: ProfileProvider.fetchOrGetCachedProfilePicture( + session, + studentNumber: int.parse(regent.value.code), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 10, + right: 10, + ), + child: Text( + regent.value.name, + style: const TextStyle(fontSize: 17), + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + }), + ], + ), + ); +} + +Widget buildProfessorsRow(BuildContext context, List professors) { + final session = context.read().state!; + return SizedBox( + height: 55, + width: double.infinity, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + ...professors.asMap().entries.map((professor) { + final idx = professor.key; + return FutureBuilder( + builder: (context, snapshot) => Transform.translate( + offset: Offset(-10.0 * idx, 0), + child: _buildAvatar(snapshot, 20), + ), + future: ProfileProvider.fetchOrGetCachedProfilePicture( + session, + studentNumber: int.parse(professor.value.code), + ), + ); + }), + ], + ), + ), + ); +} + +Widget buildExpandedProfessors( + BuildContext context, + List professors, +) { + final session = context.read().state!; + return SizedBox( + height: (professors.length * 45) + 10, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ...professors.asMap().entries.map((professor) { + final idx = professor.key; + return Padding( + padding: + EdgeInsets.only(bottom: idx == professors.length - 1 ? 0 : 5), + child: Row( + children: [ + FutureBuilder( + builder: (context, snapshot) => _buildAvatar(snapshot, 20), + future: ProfileProvider.fetchOrGetCachedProfilePicture( + session, + studentNumber: int.parse(professor.value.code), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 10, + ), + child: Text( + professor.value.name, + style: const TextStyle(fontSize: 14), + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + }), + ], + ), + ); +} - CourseUnitInfoCard _buildCard( - String sectionTitle, - String sectionContent, - ) { - return CourseUnitInfoCard( - sectionTitle, - HtmlWidget( - sectionContent, - customWidgetBuilder: (element) { - if (element.className == 'informa' || element.className == 'limpar') { - return Container(); - } - if (element.localName == 'table') { - try { - element = _preprocessTable(element); - final tBody = element.children - .firstWhere((element) => element.localName == 'tbody'); - final rows = tBody.children; +Widget buildBooksRow(BuildContext context, List books) { + return SizedBox( + height: 500, + width: double.infinity, + child: Wrap( + alignment: WrapAlignment.spaceBetween, + children: [ + ...books.asMap().entries.map((book) { + return FutureBuilder( + builder: (context, snapshot) { return Padding( - padding: const EdgeInsets.only(bottom: 10), - child: Table( - border: TableBorder.all(), - children: rows - .map( - (e) => TableRow( - children: e.children - .sublist(0, min(4, e.children.length)) - .map( - (e) => TableCell( - child: Padding( - padding: const EdgeInsets.all(8), - child: HtmlWidget( - e.outerHtml, - ), - ), - ), - ) - .toList(), - ), - ) - .toList(), + padding: const EdgeInsets.only(left: 15, right: 15, top: 10), + child: Column( + children: [ + SizedBox( + width: 135, + height: 140, // adjust this value as needed + child: snapshot.data != null + ? Image(image: NetworkImage(snapshot.data!)) + : const Image( + image: AssetImage( + 'assets/images/book_placeholder.png', + ), + ), + ), + SizedBox( + width: 135, + child: Text( + book.value.title, + textAlign: TextAlign.center, + ), + ), + ], ), ); - } catch (_) { - return null; - } - } - return null; - }, - ), - ); - } + }, + future: BookThumbFetcher().fetchBookThumb(book.value.isbn), + ); + }), + ], + ), + ); +} - dom.Element _preprocessTable(dom.Element tableElement) { - final processedTable = tableElement.clone(true); - final tBody = tableElement.children - .firstWhere((element) => element.localName == 'tbody'); - final rows = tBody.children; +Widget _buildCard( + String sectionTitle, + dynamic sectionContent, +) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + children: [ + const Opacity( + opacity: 0.25, + child: Divider(color: Colors.grey), + ), + GenericExpandable( + content: HtmlWidget(sectionContent.toString()), + title: sectionTitle, + ), + ], + ), + ); +} - for (var i = 0; i < rows.length; i++) { - for (var j = 0; j < rows[i].children.length; j++) { - final cell = rows[i].children[j]; - if (cell.attributes['rowspan'] != null) { - final rowSpan = int.parse(cell.attributes['rowspan']!); - if (rowSpan <= 1) { - continue; - } - processedTable.children[0].children[i].children[j].innerHtml = ''; - for (var k = 1; k < rowSpan; k++) { - try { - processedTable.children[0].children[i + k].children - .insert(j, cell.clone(true)); - } catch (_) { - continue; - } - } - } - } - } - return processedTable; - } +Widget _buildAvatar(AsyncSnapshot snapshot, double radius) { + return CircleAvatar( + radius: radius, + backgroundImage: snapshot.hasData && snapshot.data != null + ? FileImage(snapshot.data!) as ImageProvider + : const AssetImage( + 'assets/images/profile_placeholder.png', + ), + ); } diff --git a/uni/pubspec.lock b/uni/pubspec.lock index dacf1d042..430f19b12 100644 --- a/uni/pubspec.lock +++ b/uni/pubspec.lock @@ -81,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f" + url: "https://pub.dev" + source: hosted + version: "0.1.18" battery_plus: dependency: "direct main" description: @@ -209,6 +217,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + chewie: + dependency: transitive + description: + name: chewie + sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144" + url: "https://pub.dev" + source: hosted + version: "1.7.5" ci: dependency: transitive description: @@ -377,6 +393,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.17" + expandable: + dependency: "direct main" + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" expansion_tile_card: dependency: "direct main" description: @@ -565,6 +589,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_widget_from_html: + dependency: "direct main" + description: + name: flutter_widget_from_html + sha256: "22c911b6ccf82b83e0c457d987bac4e703440fea0fc88dab24f4dfe995a5f33f" + url: "https://pub.dev" + source: hosted + version: "0.14.11" flutter_widget_from_html_core: dependency: "direct main" description: @@ -588,7 +620,55 @@ packages: sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "3.2.0" + fwfh_cached_network_image: + dependency: transitive + description: + name: fwfh_cached_network_image + sha256: "952aea958a5fda7d616cc297ba4bc08427e381459e75526fa375d6d8345630d3" + url: "https://pub.dev" + source: hosted + version: "0.14.2" + fwfh_chewie: + dependency: transitive + description: + name: fwfh_chewie + sha256: bbb036cd322ab77dc0edd34cbbf76181681f5e414987ece38745dc4f3d7408ed + url: "https://pub.dev" + source: hosted + version: "0.14.7" + fwfh_just_audio: + dependency: transitive + description: + name: fwfh_just_audio + sha256: "4962bc59cf8bbb0a77a55ff56a7b925612b0d8263bc2ede3636b9c86113cb493" + url: "https://pub.dev" + source: hosted + version: "0.14.2" + fwfh_svg: + dependency: transitive + description: + name: fwfh_svg + sha256: "3fd83926b7245d287f133a437ef430befd99d3b00ba8c600f26cc324af281f72" + url: "https://pub.dev" + source: hosted + version: "0.8.1" + fwfh_url_launcher: + dependency: transitive + description: + name: fwfh_url_launcher + sha256: "2a526c9819f74b4106ba2fba4dac79f0082deecd8d2c7011cd0471cb710e3eff" + url: "https://pub.dev" + source: hosted + version: "0.9.0+4" + fwfh_webview: + dependency: transitive + description: + name: fwfh_webview + sha256: b828bb5ddd4361a866cdb8f1b0de4f3348f332915ecf2f4215ba17e46c656adc + url: "https://pub.dev" + source: hosted + version: "0.14.8" glob: dependency: transitive description: @@ -700,7 +780,31 @@ packages: sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.8.1" + just_audio: + dependency: transitive + description: + name: just_audio + sha256: b607cd1a43bac03d85c3aaee00448ff4a589ef2a77104e3d409889ff079bf823 + url: "https://pub.dev" + source: hosted + version: "0.9.36" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1 + url: "https://pub.dev" + source: hosted + version: "4.2.2" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70" + url: "https://pub.dev" + source: hosted + version: "0.4.9" latlong2: dependency: "direct main" description: @@ -1515,6 +1619,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: transitive + description: + name: video_player + sha256: afc65f4b8bcb2c188f64a591f84fb471f4f2e19fc607c65fd8d2f8fedb3dec23 + url: "https://pub.dev" + source: hosted + version: "2.8.3" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2" + url: "https://pub.dev" + source: hosted + version: "2.4.12" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "8e9cb7fe94e49490e67bbc15149691792b58a0ade31b32e3f3688d104a0e057b" + url: "https://pub.dev" + source: hosted + version: "2.2.0" vm_service: dependency: transitive description: @@ -1523,6 +1667,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d + url: "https://pub.dev" + source: hosted + version: "1.1.4" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" + url: "https://pub.dev" + source: hosted + version: "1.1.0" watcher: dependency: transitive description: @@ -1555,6 +1715,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0" + url: "https://pub.dev" + source: hosted + version: "3.15.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + url: "https://pub.dev" + source: hosted + version: "2.10.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86" + url: "https://pub.dev" + source: hosted + version: "3.12.0" win32: dependency: transitive description: diff --git a/uni/pubspec.yaml b/uni/pubspec.yaml index 00e88634a..c1ae4072e 100644 --- a/uni/pubspec.yaml +++ b/uni/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: currency_text_input_formatter: ^2.1.5 diacritic: ^0.1.5 email_validator: ^2.0.1 + expandable: ^5.0.1 expansion_tile_card: ^3.0.0 flutter: sdk: flutter @@ -38,6 +39,7 @@ dependencies: flutter_markdown: ^0.6.0 flutter_secure_storage: ^9.0.0 flutter_svg: ^2.0.9 + flutter_widget_from_html: ^0.14.11 flutter_widget_from_html_core: ^0.14.11 html: ^0.15.0 http: ^1.1.0