From 6e5ab6fbf989c9587e1d1824b4bd846c2c023593 Mon Sep 17 00:00:00 2001 From: Saina Amiri Moghadam Date: Sun, 28 Jan 2024 20:27:03 +0100 Subject: [PATCH] separate course card and small stream card --- .../course_view/components/course_card.dart | 201 +++------------ .../components/course_section.dart | 1 - .../components/live_stream_section.dart | 6 +- .../components/small_stream_card.dart | 232 ++++++++++++++++++ .../list_courses_view/courses_list_view.dart | 2 - .../pinned_courses_view.dart | 1 - 6 files changed, 267 insertions(+), 176 deletions(-) create mode 100644 lib/views/course_view/components/small_stream_card.dart diff --git a/lib/views/course_view/components/course_card.dart b/lib/views/course_view/components/course_card.dart index d2efd5b..1239abe 100644 --- a/lib/views/course_view/components/course_card.dart +++ b/lib/views/course_view/components/course_card.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; -import 'package:gocast_mobile/views/components/view_all_button.dart'; -import 'package:url_launcher/url_launcher.dart'; class CourseCard extends StatelessWidget { final String title; @@ -10,7 +8,6 @@ class CourseCard extends StatelessWidget { final VoidCallback onTap; final int courseId; - final bool isCourse; //true: course, false: stream //for displaying courses final bool? live; @@ -21,21 +18,12 @@ class CourseCard extends StatelessWidget { //for displaying livestreams final String? subtitle; - final String? roomName; - final String? roomNumber; - final String? viewerCount; - final String? path; const CourseCard({ super.key, - required this.isCourse, required this.title, this.subtitle, required this.tumID, - this.roomName, - this.roomNumber, - this.viewerCount, - this.path, required this.courseId, required this.onTap, this.live, @@ -70,66 +58,19 @@ class CourseCard extends StatelessWidget { ), child: ClipRRect( borderRadius: BorderRadius.circular(8.0), - child: isCourse - ? _buildCourseCard( - themeData, - cardWidth, - context, - course!, + child: _buildCourseCard( + themeData, + cardWidth, + context, + course!, onPinUnpin!, isPinned!, ) - : _buildStreamCard( - themeData, - cardWidth, - ), ), ), ); } - Widget _buildStreamCard(ThemeData themeData, double cardWidth) { - return Container( - width: cardWidth, - padding: const EdgeInsets.all(8.0), - color: themeData.cardColor, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildCourseTumID(), - _buildCourseViewerCount(themeData), - ], - ), - const SizedBox(height: 2), - Row( - children: [ - SizedBox( - width: 100, - child: _buildCourseImage(), - ), - const SizedBox(width: 15), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - _buildCourseTitle(themeData.textTheme), - _buildCourseSubtitle(themeData.textTheme), - const SizedBox(height: 15), - _buildLocation(), - ], - ), - ), - ], - ), - ], - ), - ); - } - Widget _buildCourseCard( ThemeData themeData, double cardWidth, @@ -224,114 +165,9 @@ class CourseCard extends StatelessWidget { false; } - Widget _buildCourseImage() { - if (path == null) return const SizedBox(); - return Stack( - children: [ - AspectRatio( - aspectRatio: 16 / 12, - child: ClipRRect( - borderRadius: BorderRadius.circular(8.0), - child: Image.asset( - path!, - fit: BoxFit.cover, - ), - ), - ), - ], - ); - } - - Widget _buildLocation() { - if (roomNumber == null) return const SizedBox(); - - final Uri url = Uri.parse('https://nav.tum.de/room/$roomNumber'); - - return InkWell( - onTap: () async { - if (await canLaunchUrl(url)) { - await launchUrl(url); - } else { - throw 'Could not launch $url'; - } - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - const Icon( - Icons.room, - size: 20, - ), - Text(roomName ?? "Location"), - Transform.scale( - scale: 0.6, // Adjust the scale factor as needed - child: ViewAllButton(onViewAll: () {}), - ), - ], - ), - ); - } - - Widget _buildCourseTumID() { - return Text( - tumID, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 14, - color: Colors.grey, - height: 0.9, - ), - ); - } - - Widget _buildCourseTitle(TextTheme textTheme) { - return Text( - title, - overflow: TextOverflow.ellipsis, - maxLines: 2, - softWrap: true, - style: textTheme.titleMedium?.copyWith( - fontSize: 16, - fontWeight: FontWeight.w700, - height: 1.2, - ) ?? - const TextStyle(), - ); - } - Widget _buildCourseSubtitle(TextTheme textTheme) { - if (subtitle == null) return const SizedBox(); - return Text( - subtitle!, //nullcheck already passed - overflow: TextOverflow.ellipsis, - style: textTheme.labelSmall?.copyWith( - fontSize: 14, - fontWeight: FontWeight.w400, - height: 1, - ) ?? - const TextStyle(), - ); - } - Widget _buildCourseViewerCount(ThemeData themeData) { - if (viewerCount == null) return const SizedBox(); - return Container( - decoration: BoxDecoration( - color: themeData.shadowColor.withOpacity(0.15), - borderRadius: BorderRadius.circular(4), - ), - padding: const EdgeInsets.all(3), - child: Text( - "${viewerCount!} viewers", - style: themeData.textTheme.labelSmall?.copyWith( - fontSize: 12, - height: 1, - ) ?? - const TextStyle(), - ), - ); - } Widget _buildCourseIsLive() { if (live == null) return const SizedBox(); @@ -382,4 +218,31 @@ class CourseCard extends StatelessWidget { return Colors.grey; } } + + Widget _buildCourseTitle(TextTheme textTheme) { + return Text( + title, + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + style: textTheme.titleMedium?.copyWith( + fontSize: 16, + fontWeight: FontWeight.w700, + height: 1.2, + ) ?? + const TextStyle(), + ); + } + + Widget _buildCourseTumID() { + return Text( + tumID, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + height: 0.9, + ), + ); + } } diff --git a/lib/views/course_view/components/course_section.dart b/lib/views/course_view/components/course_section.dart index 6ba466b..1ae8bdf 100644 --- a/lib/views/course_view/components/course_section.dart +++ b/lib/views/course_view/components/course_section.dart @@ -90,7 +90,6 @@ class CourseSection extends StatelessWidget { course: course, isPinned: isPinned, onPinUnpin: (course) => _togglePin(course, isPinned), - isCourse: true, title: course.name, tumID: course.tUMOnlineIdentifier, live: streams.any((stream) => stream.courseID == course.id), diff --git a/lib/views/course_view/components/live_stream_section.dart b/lib/views/course_view/components/live_stream_section.dart index 090c726..0921d30 100644 --- a/lib/views/course_view/components/live_stream_section.dart +++ b/lib/views/course_view/components/live_stream_section.dart @@ -4,8 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; import 'package:gocast_mobile/utils/constants.dart'; -import 'package:gocast_mobile/views/course_view/components/course_card.dart'; + import 'package:gocast_mobile/views/course_view/components/pulse_background.dart'; +import 'package:gocast_mobile/views/course_view/components/small_stream_card.dart'; import 'package:gocast_mobile/views/video_view/video_player.dart'; /// CourseSection @@ -86,8 +87,7 @@ class LiveStreamSection extends StatelessWidget { final course = courses.where((course) => course.id == stream.courseID).first; - return CourseCard( - isCourse: false, + return SmallStreamCard( title: stream.name, subtitle: course.name, tumID: course.tUMOnlineIdentifier, diff --git a/lib/views/course_view/components/small_stream_card.dart b/lib/views/course_view/components/small_stream_card.dart new file mode 100644 index 0000000..7110947 --- /dev/null +++ b/lib/views/course_view/components/small_stream_card.dart @@ -0,0 +1,232 @@ +import 'package:flutter/material.dart'; +import 'package:gocast_mobile/base/networking/api/gocast/api_v2.pb.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SmallStreamCard extends StatelessWidget { + final String title; + final String tumID; + final VoidCallback onTap; + final int courseId; + + //for displaying courses + final bool? live; + + final Course? course; + + //for displaying livestreams + final String? subtitle; + final String? roomName; + final String? roomNumber; + final String? viewerCount; + final String? path; + + const SmallStreamCard({ + super.key, + required this.title, + this.subtitle, + required this.tumID, + this.roomName, + this.roomNumber, + this.viewerCount, + this.path, + required this.courseId, + required this.onTap, + this.live, + this.course, + }); + + @override + Widget build(BuildContext context) { + ThemeData themeData = Theme.of(context); + + double cardWidth = MediaQuery.of(context).size.width >= 600 + ? MediaQuery.of(context).size.width * 0.4 + : MediaQuery.of(context).size.width * 0.9; + + return InkWell( + onTap: onTap, + child: Card( + elevation: 1, + shadowColor: themeData.shadowColor, + color: themeData.cardTheme.color, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + side: BorderSide( + color: themeData + .inputDecorationTheme.enabledBorder?.borderSide.color + .withOpacity(0.2) ?? + Colors.grey.withOpacity(0.2), + width: 1.0, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: _buildStreamCard( + themeData, + cardWidth, + ), + ), + ), + ); + } + + Widget _buildStreamCard(ThemeData themeData, double cardWidth) { + return Container( + width: cardWidth, + padding: const EdgeInsets.all(8.0), + color: themeData.cardColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildCourseTumID(), + _buildCourseViewerCount(themeData), + ], + ), + const SizedBox(height: 2), + Row( + children: [ + SizedBox( + width: 100, + child: _buildCourseImage(), + ), + const SizedBox(width: 15), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + _buildCourseTitle(themeData.textTheme), + _buildCourseSubtitle(themeData.textTheme), + const SizedBox(height: 15), + _buildLocation(themeData), + ], + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildCourseImage() { + return Stack( + children: [ + AspectRatio( + aspectRatio: 16 / 12, + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.asset( + path!, + fit: BoxFit.cover, + ), + ), + ), + ], + ); + } + + Widget _buildLocation(ThemeData themeData) { + if (roomNumber == null) return const SizedBox(); + + final Uri url = Uri.parse('https://nav.tum.de/room/$roomNumber'); + + return Align( + alignment: Alignment.centerRight, // Align to the right + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + border: Border.all( + color: themeData + .inputDecorationTheme.enabledBorder?.borderSide.color + .withOpacity(0.4) ?? + Colors.grey.withOpacity(0.4), + ), + borderRadius: BorderRadius.circular(5), + ), + child: InkWell( + onTap: () async { + if (await canLaunchUrl(url)) { + await launchUrl(url); + } else { + throw 'Could not launch $url'; + } + }, + child: const Row( + mainAxisSize: MainAxisSize.min, + // Constrain row size to its children + children: [ + Icon(Icons.room, size: 24), + SizedBox(width: 8), // Spacing before the arrow icon + Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ), + ); + } + + Widget _buildCourseTumID() { + return Text( + tumID, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + height: 0.9, + ), + ); + } + + Widget _buildCourseTitle(TextTheme textTheme) { + return Text( + title, + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + style: textTheme.titleMedium?.copyWith( + fontSize: 16, + fontWeight: FontWeight.w700, + height: 1.2, + ) ?? + const TextStyle(), + ); + } + + Widget _buildCourseSubtitle(TextTheme textTheme) { + if (subtitle == null) return const SizedBox(); + + return Text( + subtitle!, //nullcheck already passed + overflow: TextOverflow.ellipsis, + style: textTheme.labelSmall?.copyWith( + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1, + ) ?? + const TextStyle(), + ); + } + + Widget _buildCourseViewerCount(ThemeData themeData) { + if (viewerCount == null) return const SizedBox(); + return Container( + decoration: BoxDecoration( + color: themeData.shadowColor.withOpacity(0.15), + borderRadius: BorderRadius.circular(4), + ), + padding: const EdgeInsets.all(3), + child: Text( + "${viewerCount!} viewers", + style: themeData.textTheme.labelSmall?.copyWith( + fontSize: 12, + height: 1, + ) ?? + const TextStyle(), + ), + ); + } +} diff --git a/lib/views/course_view/list_courses_view/courses_list_view.dart b/lib/views/course_view/list_courses_view/courses_list_view.dart index 41f014e..f6a706f 100644 --- a/lib/views/course_view/list_courses_view/courses_list_view.dart +++ b/lib/views/course_view/list_courses_view/courses_list_view.dart @@ -74,10 +74,8 @@ class CoursesList extends ConsumerWidget { }, title: course.name, tumID: course.tUMOnlineIdentifier, - path: 'assets/images/course2.png', live: liveCourses.contains(course), courseId: course.id, - isCourse: true, onTap: () { Navigator.push( context, diff --git a/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart b/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart index a016c2d..4b07d88 100644 --- a/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart +++ b/lib/views/course_view/pinned_courses_view/pinned_courses_view.dart @@ -95,7 +95,6 @@ class PinnedCoursesState extends ConsumerState { isPinned: isPinned, onPinUnpin: (course) => _togglePin(course, isPinned), live: liveCourses.contains(course), - isCourse: true, title: course.name, courseId: course.id, subtitle: course.tUMOnlineIdentifier,