diff --git a/lib/presentation/core/routes/routes.dart b/lib/presentation/core/routes/routes.dart index e89f6719..bf7c5e97 100644 --- a/lib/presentation/core/routes/routes.dart +++ b/lib/presentation/core/routes/routes.dart @@ -88,13 +88,18 @@ GoRouter createRouter() => GoRouter( path: 'details', builder: (context, state) { try { - final json = state.extra as Map; + final extra = + state.extra as (Map, DateTime); return ScheduleDetailsPage( - lesson: LessonSchedulePart.fromJson(json), + lesson: LessonSchedulePart.fromJson(extra.$1), + selectedDate: extra.$2, ); } catch (e) { + final extra = + state.extra as (LessonSchedulePart, DateTime); return ScheduleDetailsPage( - lesson: state.extra as LessonSchedulePart, + lesson: extra.$1, + selectedDate: extra.$2, ); } }, diff --git a/lib/presentation/widgets/feedback_modal.dart b/lib/presentation/widgets/feedback_modal.dart index 91139222..68b403b6 100644 --- a/lib/presentation/widgets/feedback_modal.dart +++ b/lib/presentation/widgets/feedback_modal.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:rtu_mirea_app/presentation/widgets/forms/text_input.dart'; import 'package:sentry/sentry.dart'; import '../theme.dart'; @@ -125,47 +126,11 @@ class _FeedbackBottomModalSheetState extends State { ), ), const SizedBox(height: 8), - TextField( - decoration: InputDecoration( - errorText: _emailErrorText, - errorStyle: AppTextStyle.captionL.copyWith( - color: AppTheme.colors.colorful07, - ), - hintText: 'Введите email', - hintStyle: AppTextStyle.titleS.copyWith( - color: AppTheme.colors.deactive, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: AppTheme.colors.primary, - ), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: AppTheme.colors.colorful07, - ), - ), - disabledBorder: border, - enabledBorder: border, - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: AppTheme.colors.colorful07, - ), - ), - fillColor: AppTheme.colors.background01, - filled: true, - ), - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - style: AppTextStyle.titleS, + TextInput( + hintText: 'Введите email', controller: _emailController, + errorText: _emailErrorText, + keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 24), Text( @@ -175,48 +140,11 @@ class _FeedbackBottomModalSheetState extends State { ), ), const SizedBox(height: 8), - TextField( - keyboardType: TextInputType.multiline, - maxLines: 5, + TextInput( + hintText: 'Когда я нажимаю "Х" происходит "У"', controller: _textController, - decoration: InputDecoration( - hintText: 'Когда я нажимаю "Х" происходит "У"', - hintStyle: AppTextStyle.bodyL.copyWith( - color: AppTheme.colors.deactive, - ), - errorText: _textErrorText, - errorStyle: AppTextStyle.captionS.copyWith( - color: AppTheme.colors.colorful07, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: AppTheme.colors.primary, - ), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: AppTheme.colors.colorful07, - ), - ), - disabledBorder: border, - enabledBorder: border, - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: AppTheme.colors.colorful07, - ), - ), - fillColor: AppTheme.colors.background01, - filled: true, - ), - textInputAction: TextInputAction.done, - style: AppTextStyle.bodyL, + errorText: _textErrorText, + maxLines: 5, ), const SizedBox(height: 24), PrimaryButton( diff --git a/lib/presentation/widgets/forms/text_input.dart b/lib/presentation/widgets/forms/text_input.dart new file mode 100644 index 00000000..4f63909e --- /dev/null +++ b/lib/presentation/widgets/forms/text_input.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:rtu_mirea_app/presentation/theme.dart'; +import 'package:rtu_mirea_app/presentation/typography.dart'; + +class TextInput extends StatelessWidget { + const TextInput({ + Key? key, + this.hintText, + this.errorText, + this.controller, + this.keyboardType, + this.maxLines, + this.fillColor, + }) : super(key: key); + + final String? hintText; + final String? errorText; + final TextEditingController? controller; + final TextInputType? keyboardType; + final int? maxLines; + final Color? fillColor; + + @override + Widget build(BuildContext context) { + return TextField( + maxLines: maxLines, + decoration: InputDecoration( + errorText: errorText, + errorStyle: AppTextStyle.captionL.copyWith( + color: AppTheme.colors.colorful07, + ), + hintText: hintText, + hintStyle: AppTextStyle.titleS.copyWith( + color: AppTheme.colors.deactive, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: AppTheme.colors.primary, + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: AppTheme.colors.colorful07, + ), + ), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.transparent, + width: 0, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.transparent, + width: 0, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: AppTheme.colors.colorful07, + ), + ), + fillColor: fillColor ?? AppTheme.colors.background01, + filled: true, + ), + keyboardType: keyboardType, + textInputAction: TextInputAction.done, + style: AppTextStyle.titleS, + controller: controller, + ); + } +} diff --git a/lib/schedule/bloc/schedule_bloc.dart b/lib/schedule/bloc/schedule_bloc.dart index 50decbc0..f94399da 100644 --- a/lib/schedule/bloc/schedule_bloc.dart +++ b/lib/schedule/bloc/schedule_bloc.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:equatable/equatable.dart'; +import 'package:get/get.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:schedule_repository/schedule_repository.dart'; @@ -32,10 +33,44 @@ class ScheduleBloc extends HydratedBloc { on(_onSetSelectedSchedule); on(_onRemoveSavedSchedule); on(_onSetEmptyLessonsDisplaying); + on(_onSetLessonComment); } final ScheduleRepository _scheduleRepository; + Future _onSetLessonComment( + SetLessonComment event, + Emitter emit, + ) async { + final comment = state.comments.firstWhereOrNull( + (comment) => + event.comment.lessonDate == comment.lessonDate && + event.comment.lessonBells == comment.lessonBells, + ); + + if (event.comment.text.isEmpty && comment == null) { + return; + } + + List updatedComments = state.comments + .where( + (element) => + element.lessonDate != event.comment.lessonDate || + element.lessonBells != event.comment.lessonBells, + ) + .toList(); + + if (event.comment.text.isNotEmpty) { + updatedComments.add(event.comment); + } + + emit( + state.copyWith( + comments: updatedComments, + ), + ); + } + Future _onSetEmptyLessonsDisplaying( ScheduleSetEmptyLessonsDisplaying event, Emitter emit, diff --git a/lib/schedule/bloc/schedule_bloc.g.dart b/lib/schedule/bloc/schedule_bloc.g.dart index 01c04115..4bf2a0cf 100644 --- a/lib/schedule/bloc/schedule_bloc.g.dart +++ b/lib/schedule/bloc/schedule_bloc.g.dart @@ -54,6 +54,10 @@ ScheduleState _$ScheduleStateFromJson(Map json) => .toList() ?? const [], isMiniature: json['isMiniature'] as bool? ?? false, + comments: (json['comments'] as List?) + ?.map((e) => ScheduleComment.fromJson(e as Map)) + .toList() ?? + const [], showEmptyLessons: json['showEmptyLessons'] as bool? ?? false, selectedSchedule: const SelectedScheduleConverter() .fromJson(json['selectedSchedule'] as Map?), @@ -83,6 +87,7 @@ Map _$ScheduleStateToJson(ScheduleState instance) => r'$3': e.$3, }) .toList(), + 'comments': instance.comments, 'isMiniature': instance.isMiniature, 'showEmptyLessons': instance.showEmptyLessons, 'selectedSchedule': diff --git a/lib/schedule/bloc/schedule_event.dart b/lib/schedule/bloc/schedule_event.dart index 4c7d2a9b..bc547853 100644 --- a/lib/schedule/bloc/schedule_event.dart +++ b/lib/schedule/bloc/schedule_event.dart @@ -4,6 +4,17 @@ abstract class ScheduleEvent extends Equatable { const ScheduleEvent(); } +class SetLessonComment extends ScheduleEvent { + const SetLessonComment({ + required this.comment, + }); + + final ScheduleComment comment; + + @override + List get props => [comment]; +} + class ScheduleRequested extends ScheduleEvent { const ScheduleRequested({ required this.group, diff --git a/lib/schedule/bloc/schedule_state.dart b/lib/schedule/bloc/schedule_state.dart index 7245e640..f12fff0f 100644 --- a/lib/schedule/bloc/schedule_state.dart +++ b/lib/schedule/bloc/schedule_state.dart @@ -15,6 +15,7 @@ class ScheduleState extends Equatable { this.teachersSchedule = const [], this.groupsSchedule = const [], this.isMiniature = false, + this.comments = const [], this.showEmptyLessons = false, this.selectedSchedule, }); @@ -35,6 +36,10 @@ class ScheduleState extends Equatable { final List<(UID, Group, List)> groupsSchedule; + /// Comments attached to certain lessons at certain times (dates and + /// [LessonBells]). + final List comments; + /// Miniature display mode for lesson cards. final bool isMiniature; @@ -53,6 +58,7 @@ class ScheduleState extends Equatable { SelectedSchedule? selectedSchedule, bool? isMiniature, bool? showEmptyLessons, + List? comments, }) { return ScheduleState( status: status ?? this.status, @@ -62,6 +68,7 @@ class ScheduleState extends Equatable { selectedSchedule: selectedSchedule ?? this.selectedSchedule, isMiniature: isMiniature ?? this.isMiniature, showEmptyLessons: showEmptyLessons ?? this.showEmptyLessons, + comments: comments ?? this.comments, ); } @@ -76,6 +83,7 @@ class ScheduleState extends Equatable { isMiniature, selectedSchedule, showEmptyLessons, + comments, ]; } diff --git a/lib/schedule/models/models.dart b/lib/schedule/models/models.dart index 1576dc16..881a3f33 100644 --- a/lib/schedule/models/models.dart +++ b/lib/schedule/models/models.dart @@ -2,3 +2,4 @@ export 'selected_schedule.dart'; export 'selected_classroom_schedule.dart'; export 'selected_group_schedule.dart'; export 'selected_teacher_schedule.dart'; +export 'schedule_comment.dart'; diff --git a/lib/schedule/models/schedule_comment.dart b/lib/schedule/models/schedule_comment.dart new file mode 100644 index 00000000..dfa7a5bf --- /dev/null +++ b/lib/schedule/models/schedule_comment.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:university_app_server_api/client.dart'; + +part 'schedule_comment.g.dart'; + +/// {@template schedule_comment} +/// Comment for lesson. +/// {@endtemplate} +@immutable +@JsonSerializable() +class ScheduleComment extends Equatable { + /// {@macro schedule_comment} + const ScheduleComment({ + required this.subjectName, + required this.lessonDate, + required this.lessonBells, + required this.text, + }); + + /// {@macro from_json} + factory ScheduleComment.fromJson(Map json) => + _$ScheduleCommentFromJson(json); + + /// {@macro to_json} + Map toJson() => _$ScheduleCommentToJson(this); + + /// Comment id. + final String subjectName; + + /// Date when lesson is scheduled. + final DateTime lessonDate; + + final LessonBells lessonBells; + + /// Comment text. + final String text; + + ScheduleComment copyWith({ + String? subjectName, + DateTime? lessonDate, + LessonBells? lessonBells, + String? text, + }) { + return ScheduleComment( + subjectName: subjectName ?? this.subjectName, + lessonDate: lessonDate ?? this.lessonDate, + lessonBells: lessonBells ?? this.lessonBells, + text: text ?? this.text, + ); + } + + @override + List get props => [ + subjectName, + lessonDate, + lessonBells, + text, + ]; +} diff --git a/lib/schedule/models/schedule_comment.g.dart b/lib/schedule/models/schedule_comment.g.dart new file mode 100644 index 00000000..1eff9c20 --- /dev/null +++ b/lib/schedule/models/schedule_comment.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'schedule_comment.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ScheduleComment _$ScheduleCommentFromJson(Map json) => + ScheduleComment( + subjectName: json['subjectName'] as String, + lessonDate: DateTime.parse(json['lessonDate'] as String), + lessonBells: + LessonBells.fromJson(json['lessonBells'] as Map), + text: json['text'] as String, + ); + +Map _$ScheduleCommentToJson(ScheduleComment instance) => + { + 'subjectName': instance.subjectName, + 'lessonDate': instance.lessonDate.toIso8601String(), + 'lessonBells': instance.lessonBells, + 'text': instance.text, + }; diff --git a/lib/schedule/view/schedule_details_page.dart b/lib/schedule/view/schedule_details_page.dart index 2e39b620..e596438b 100644 --- a/lib/schedule/view/schedule_details_page.dart +++ b/lib/schedule/view/schedule_details_page.dart @@ -1,8 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get/get.dart'; import 'package:go_router/go_router.dart'; import 'package:latlong2/latlong.dart'; import 'package:rtu_mirea_app/presentation/theme.dart'; import 'package:rtu_mirea_app/presentation/typography.dart'; +import 'package:rtu_mirea_app/presentation/widgets/forms/text_input.dart'; +import 'package:rtu_mirea_app/schedule/models/models.dart'; import 'package:rtu_mirea_app/schedule/schedule.dart'; import 'package:university_app_server_api/client.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -10,10 +14,18 @@ import 'package:flutter_map_cancellable_tile_provider/flutter_map_cancellable_ti import 'package:url_launcher/url_launcher_string.dart'; class ScheduleDetailsPage extends StatefulWidget { - const ScheduleDetailsPage({super.key, required this.lesson}); + const ScheduleDetailsPage({ + super.key, + required this.lesson, + required this.selectedDate, + }); final LessonSchedulePart lesson; + /// The date in the [Calendar] where the lesson was selected to display the + /// details. + final DateTime selectedDate; + @override State createState() => _ScheduleDetailsPageState(); } @@ -25,6 +37,63 @@ class _ScheduleDetailsPageState extends State { tileProvider: CancellableNetworkTileProvider(), ); + late final TextEditingController _textController; + + @override + void initState() { + super.initState(); + _textController = TextEditingController(); + + final comment = _getComment(); + + if (comment != null) { + _textController.text = comment.text; + } + + _textController.addListener(() { + if (_textController.text.length > 500 && _textErrorText == null) { + setState(() { + _textErrorText = 'Слишком длинный комментарий'; + }); + } else if (_textController.text.length <= 500 && _textErrorText != null) { + setState(() { + _textErrorText = null; + }); + } + + final bloc = context.read(); + + bloc.add( + SetLessonComment( + comment: ScheduleComment( + subjectName: widget.lesson.subject, + lessonDate: widget.lesson.dates.first, + lessonBells: widget.lesson.lessonBells, + text: _textController.text, + ), + ), + ); + }); + } + + ScheduleComment? _getComment() { + final bloc = context.read(); + + return bloc.state.comments.firstWhereOrNull( + (comment) => + widget.lesson.dates.contains(comment.lessonDate) && + comment.lessonBells == widget.lesson.lessonBells, + ); + } + + @override + void dispose() { + _textController.dispose(); + super.dispose(); + } + + String? _textErrorText; + @override Widget build(BuildContext context) { return Scaffold( @@ -57,16 +126,34 @@ class _ScheduleDetailsPageState extends State { if (widget.lesson.groups != null && widget.lesson.groups!.isNotEmpty) { content.addAll([ _buildGroups(), - const Divider(), + const Divider(height: 16), ]); } if (widget.lesson.teachers.isNotEmpty) { content.addAll([ _buildTeachers(), + const Divider(height: 8), ]); } + content.addAll([ + ListTile( + title: Text('Комментарий'.toUpperCase()), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: TextInput( + hintText: 'Введите комментарий', + controller: _textController, + errorText: _textErrorText, + maxLines: 5, + fillColor: AppTheme.colors.background03, + ), + ), + ), + const Divider(), + ]); + return content; } @@ -79,7 +166,6 @@ class _ScheduleDetailsPageState extends State { ); } - // Build the lesson type ListTile _buildLessonType() { return ListTile( title: Text('Тип занятия'.toUpperCase()), @@ -103,7 +189,6 @@ class _ScheduleDetailsPageState extends State { ); } - // Build the classroom details List _buildClassroomDetails() { return widget.lesson.classrooms.map((classroom) { return Column( @@ -135,7 +220,6 @@ class _ScheduleDetailsPageState extends State { }).toList(); } - // Build the map for the classroom SizedBox _buildClassroomMap(Classroom classroom) { return SizedBox( height: 200, @@ -175,7 +259,6 @@ class _ScheduleDetailsPageState extends State { ); } - // Build the groups ListTile _buildGroups() { return ListTile( title: Text('Группы'.toUpperCase()), @@ -193,7 +276,6 @@ class _ScheduleDetailsPageState extends State { ); } - // Build the teachers ListTile _buildTeachers() { return ListTile( title: Padding( diff --git a/lib/schedule/view/schedule_page.dart b/lib/schedule/view/schedule_page.dart index 2a4ae37d..b763e176 100644 --- a/lib/schedule/view/schedule_page.dart +++ b/lib/schedule/view/schedule_page.dart @@ -26,7 +26,7 @@ class _SchedulePageState extends State { void initState() { super.initState(); _schedulePageController = PageController( - initialPage: Calendar.getPageIndex(DateTime.now()), + initialPage: Calendar.getPageIndex(Calendar.getNowWithoutTime()), ); } @@ -158,7 +158,15 @@ class _SchedulePageState extends State { horizontal: 16.0, vertical: 8.0, ), - child: LessonCard(lesson: lesson), + child: LessonCard( + lesson: lesson, + onTap: (lesson) { + context.go( + '/schedule/details', + extra: (lesson, day), + ); + }, + ), ); } @@ -182,7 +190,15 @@ class _SchedulePageState extends State { horizontal: 16.0, vertical: 8.0, ), - child: LessonCard(lesson: e), + child: LessonCard( + lesson: e, + onTap: (lesson) { + context.go( + '/schedule/details', + extra: (lesson, day), + ); + }, + ), ), ) .toList(), diff --git a/lib/schedule/widgets/calendar.dart b/lib/schedule/widgets/calendar.dart index 3a8b36e6..06b45bb8 100644 --- a/lib/schedule/widgets/calendar.dart +++ b/lib/schedule/widgets/calendar.dart @@ -30,19 +30,24 @@ class Calendar extends StatefulWidget { }).toList(); } + static DateTime getNowWithoutTime() { + final now = DateTime.now(); + return DateTime(now.year, now.month, now.day); + } + /// Get page index by date. Used to set up [PageController.initialPage]. static int getPageIndex(DateTime date) { // +1 because first day is 1, not 0 - return date.difference(firstCalendarDay).inDays + 1; + return date.difference(firstCalendarDay).inDays; } /// First day of calendar. Used to set up [TableCalendar.firstDay]. - static final DateTime firstCalendarDay = DateTime.now().subtract( + static final DateTime firstCalendarDay = getNowWithoutTime().subtract( const Duration(days: 365), ); /// Last day of calendar. Used to set up [TableCalendar.lastDay]. - static final DateTime lastCalendarDay = DateTime.now().add( + static final DateTime lastCalendarDay = getNowWithoutTime().add( const Duration(days: 365), ); @@ -74,7 +79,7 @@ class _CalendarState extends State { void initState() { super.initState(); - _focusedDay = Calendar.getDayInAvailableRange(DateTime.now()); + _focusedDay = Calendar.getDayInAvailableRange(Calendar.getNowWithoutTime()); _selectedPage = Calendar.getPageIndex(_focusedDay); @@ -95,7 +100,8 @@ class _CalendarState extends State { } }); - _selectedDay = Calendar.getDayInAvailableRange(DateTime.now()); + _selectedDay = + Calendar.getDayInAvailableRange(Calendar.getNowWithoutTime()); _selectedWeek = CalendarUtils.getCurrentWeek(); } @@ -228,7 +234,7 @@ class _CalendarState extends State { _focusedDay = Calendar.getDayInAvailableRange(focusedDay); }, onHeaderTapped: (date) { - final currentDate = DateTime.now(); + final currentDate = Calendar.getNowWithoutTime(); if (mounted) { setState(() { _focusedDay = Calendar.getDayInAvailableRange(currentDate); @@ -238,7 +244,7 @@ class _CalendarState extends State { _selectedPage = Calendar.getPageIndex(_selectedDay); }); if (widget.pageViewController.hasClients) { - widget.pageViewController.jumpToPage(_selectedPage - 1); + widget.pageViewController.jumpToPage(_selectedPage); } } }, diff --git a/lib/schedule/widgets/lesson_card.dart b/lib/schedule/widgets/lesson_card.dart index 85b9a8d9..ca456f16 100644 --- a/lib/schedule/widgets/lesson_card.dart +++ b/lib/schedule/widgets/lesson_card.dart @@ -2,21 +2,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:go_router/go_router.dart'; +import 'package:get/get.dart'; import 'package:rtu_mirea_app/presentation/typography.dart'; import 'package:rtu_mirea_app/presentation/theme.dart'; +import 'package:rtu_mirea_app/schedule/models/models.dart'; import 'package:university_app_server_api/client.dart'; import '../schedule.dart'; class LessonCard extends StatelessWidget { - final LessonSchedulePart lesson; - const LessonCard({ Key? key, required this.lesson, + this.onTap, }) : super(key: key); + final LessonSchedulePart lesson; + final void Function(LessonSchedulePart)? onTap; + static Color getColorByType(LessonType lessonType) { switch (lessonType) { case LessonType.lecture: @@ -78,6 +81,46 @@ class LessonCard extends StatelessWidget { .join(', '); } + Widget _buildCommentAlert(List comments) { + final comment = comments.firstWhereOrNull( + (comment) => + lesson.dates.contains(comment.lessonDate) && + comment.lessonBells == lesson.lessonBells, + ); + + if (comment == null) { + return const SizedBox(); + } + + return Container( + margin: const EdgeInsets.only(top: 8), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: AppTheme.colors.colorful01.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + const FaIcon( + FontAwesomeIcons.comment, + size: 16, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: Text( + comment.text, + style: AppTextStyle.body, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { final state = context.read().state; @@ -98,7 +141,7 @@ class LessonCard extends StatelessWidget { ), child: InkWell( onTap: () { - context.go('/schedule/details', extra: lesson); + onTap?.call(lesson); }, child: Container( constraints: const BoxConstraints(minHeight: 75), @@ -239,19 +282,25 @@ class LessonCard extends StatelessWidget { height: 4, ), Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - FaIcon( - FontAwesomeIcons.userTie, - size: 12, - color: AppTheme.colors.deactive, - ), - const SizedBox( - width: 9, + Padding( + padding: const EdgeInsets.only( + left: 2, right: 7, top: 3), + child: FaIcon( + FontAwesomeIcons.userTie, + size: 12, + color: AppTheme.colors.deactive, + ), ), - Text( - lesson.teachers.map((e) => e.name).join(', '), - style: AppTextStyle.body.copyWith( - color: AppTheme.colors.deactive), + Expanded( + child: Text( + lesson.teachers + .map((e) => e.name) + .join(', '), + style: AppTextStyle.body.copyWith( + color: AppTheme.colors.deactive), + ), ), ], ), @@ -270,6 +319,7 @@ class LessonCard extends StatelessWidget { return ch; }, ), + _buildCommentAlert(state.comments), ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index bff9312b..2b45876d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ publish_to: 'none' # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.4.1+51 +version: 1.4.2+52 environment: sdk: ">=3.1.1"