diff --git a/lib/api/cube_api.dart b/lib/api/cube_api.dart index df1d375..9e62c3a 100644 --- a/lib/api/cube_api.dart +++ b/lib/api/cube_api.dart @@ -3,6 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; final cubeApi = Provider((ref) { return CubeApi.create( - baseUrl: Uri.tryParse('https://api.cube.nlabs.su/'), + baseUrl: Uri.tryParse('http://2qkhm0fz-test.api.cube.nlabs.su'), ); }); diff --git a/lib/features/analytics/analytical_logger_mixin.dart b/lib/features/analytics/analytical_logger_mixin.dart index 687e9c9..f5dc696 100644 --- a/lib/features/analytics/analytical_logger_mixin.dart +++ b/lib/features/analytics/analytical_logger_mixin.dart @@ -6,11 +6,11 @@ import 'package:proxima_logger/proxima_logger.dart' as pr; import 'package:cube_system/features/analytics/log_type.dart'; -import 'package:cube_system/core/utils.dart'; +import 'package:cube_system/features/analytics/logger.dart'; mixin AnalyticalLoggerMixin on pr.ProximaLogger { Future event(String name, [Map? attributes]) async { - if (kIsMobile) { + if (shouldReportEvent) { await AppMetrica.reportEventWithMap(name, attributes); } } diff --git a/lib/features/analytics/logger.dart b/lib/features/analytics/logger.dart index 1df3365..ffb7635 100644 --- a/lib/features/analytics/logger.dart +++ b/lib/features/analytics/logger.dart @@ -1,6 +1,6 @@ import 'package:appmetrica_plugin/appmetrica_plugin.dart'; import 'package:cube_system/core/utils.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:proxima_logger/proxima_logger.dart' as pr; import 'package:cube_system/features/analytics/analytical_logger_mixin.dart'; @@ -9,6 +9,8 @@ import 'package:cube_system/features/analytics/log_type.dart'; final logger = AppLogger(); +final shouldReportEvent = kIsMobile && !kDebugMode; + class AppLogger extends pr.ProximaLogger with AnalyticalLoggerMixin { AppLogger() : super( @@ -27,7 +29,7 @@ class AppLogger extends pr.ProximaLogger with AnalyticalLoggerMixin { ); void error(Object error, StackTrace stackTrace) { - if (kIsMobile) { + if (shouldReportEvent) { AppMetrica.reportError( errorDescription: AppMetricaErrorDescription.fromObjectAndStackTrace( error, @@ -44,7 +46,7 @@ class AppLogger extends pr.ProximaLogger with AnalyticalLoggerMixin { } void flutterError(FlutterErrorDetails details) { - if (kIsMobile) { + if (shouldReportEvent) { AppMetrica.reportError( message: null, errorDescription: diff --git a/lib/features/landing_page/ui/widgets/landing_welcome_page.dart b/lib/features/landing_page/ui/widgets/landing_welcome_page.dart index 659a985..7cc3ba2 100644 --- a/lib/features/landing_page/ui/widgets/landing_welcome_page.dart +++ b/lib/features/landing_page/ui/widgets/landing_welcome_page.dart @@ -144,7 +144,7 @@ class _LandingWelcomePageState extends ConsumerState ], ), Text( - "Расписание", + 'Расписание', style: context.textStyles.largeTitle.copyWith( fontSize: 32, color: context.colors.text, @@ -156,7 +156,7 @@ class _LandingWelcomePageState extends ConsumerState ), const SizedBox(height: 48), Text( - "Организуйте свою академическую жизнь c современным электронным расписанием: актуальность, легкий доступ и приятный дизайн для студентов и преподавателей.", + 'Cовременный сервис учебного расписания для студентов, преподавателей и всех желающих, предоставляющий удобный и быстрый доступ, где бы вы ни находились.', style: context.textStyles.subTitle.copyWith( fontSize: 16, ), diff --git a/lib/features/settings/managers/settings_manager.dart b/lib/features/settings/managers/settings_manager.dart index e69de29..8b13789 100644 --- a/lib/features/settings/managers/settings_manager.dart +++ b/lib/features/settings/managers/settings_manager.dart @@ -0,0 +1 @@ + diff --git a/lib/features/settings/models/app_settings/app_settings.g.dart b/lib/features/settings/models/app_settings/app_settings.g.dart index 82f8cea..4272770 100644 --- a/lib/features/settings/models/app_settings/app_settings.g.dart +++ b/lib/features/settings/models/app_settings/app_settings.g.dart @@ -103,13 +103,10 @@ const _$AppLessonColorsModeEnumMap = { }; const _$LessonCardLessonTypePositionEnumMap = { - LessonCardLessonTypePosition.afterBottomLeftBlock: 'afterBottomLeftBlock', - LessonCardLessonTypePosition.afterTopLeftBlock: 'afterTopLeftBlock', - LessonCardLessonTypePosition.bottomLeft: 'bottomLeft', - LessonCardLessonTypePosition.topRight: 'topRight', + LessonCardLessonTypePosition.onIndicator: 'onIndicator', LessonCardLessonTypePosition.bottomRight: 'bottomRight', + LessonCardLessonTypePosition.afterBottomLeftBlock: 'afterBottomLeftBlock', LessonCardLessonTypePosition.beforeBottomRightBlock: 'beforeBottomRightBlock', - LessonCardLessonTypePosition.onIndicator: 'onIndicator', LessonCardLessonTypePosition.none: 'none', }; diff --git a/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.dart b/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.dart index dea9a0f..d0bbc0c 100644 --- a/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.dart +++ b/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.dart @@ -6,36 +6,24 @@ part 'lesson_card_lesson_type_position.g.dart'; @HiveType(typeId: 10) enum LessonCardLessonTypePosition { @HiveField(0) - @JsonValue('afterBottomLeftBlock') - afterBottomLeftBlock, + @JsonValue('onIndicator') + onIndicator, @HiveField(1) - @JsonValue('afterTopLeftBlock') - afterTopLeftBlock, - @HiveField(2) - @JsonValue('bottomLeft') - bottomLeft, - @HiveField(3) - @JsonValue('topRight') - topRight, - @HiveField(4) @JsonValue('bottomRight') bottomRight, - @HiveField(5) + @HiveField(2) + @JsonValue('afterBottomLeftBlock') + afterBottomLeftBlock, + @HiveField(3) @JsonValue('beforeBottomRightBlock') beforeBottomRightBlock, - @HiveField(6) - @JsonValue('onIndicator') - onIndicator, - @HiveField(7) + @HiveField(4) @JsonValue('none') none; bool get isAfterBottomLeftBlock => this == LessonCardLessonTypePosition.afterBottomLeftBlock; - bool get isAfterTopLeftBlock => - this == LessonCardLessonTypePosition.afterTopLeftBlock; - bool get isBottomLeft => this == LessonCardLessonTypePosition.bottomLeft; - bool get isTopRight => this == LessonCardLessonTypePosition.topRight; + bool get isBottomRight => this == LessonCardLessonTypePosition.bottomRight; bool get isBeforeBottomRightBlock => this == LessonCardLessonTypePosition.beforeBottomRightBlock; diff --git a/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.g.dart b/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.g.dart index 1783e96..c75ef71 100644 --- a/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.g.dart +++ b/lib/features/settings/models/lesson_card_lesson_type_position/lesson_card_lesson_type_position.g.dart @@ -17,52 +17,37 @@ class LessonCardLessonTypePositionAdapter LessonCardLessonTypePosition read(BinaryReader reader) { switch (reader.readByte()) { case 0: - return LessonCardLessonTypePosition.afterBottomLeftBlock; + return LessonCardLessonTypePosition.onIndicator; case 1: - return LessonCardLessonTypePosition.afterTopLeftBlock; + return LessonCardLessonTypePosition.bottomRight; case 2: - return LessonCardLessonTypePosition.bottomLeft; + return LessonCardLessonTypePosition.afterBottomLeftBlock; case 3: - return LessonCardLessonTypePosition.topRight; - case 4: - return LessonCardLessonTypePosition.bottomRight; - case 5: return LessonCardLessonTypePosition.beforeBottomRightBlock; - case 6: - return LessonCardLessonTypePosition.onIndicator; - case 7: + case 4: return LessonCardLessonTypePosition.none; default: - return LessonCardLessonTypePosition.afterBottomLeftBlock; + return LessonCardLessonTypePosition.onIndicator; } } @override void write(BinaryWriter writer, LessonCardLessonTypePosition obj) { switch (obj) { - case LessonCardLessonTypePosition.afterBottomLeftBlock: + case LessonCardLessonTypePosition.onIndicator: writer.writeByte(0); break; - case LessonCardLessonTypePosition.afterTopLeftBlock: + case LessonCardLessonTypePosition.bottomRight: writer.writeByte(1); break; - case LessonCardLessonTypePosition.bottomLeft: + case LessonCardLessonTypePosition.afterBottomLeftBlock: writer.writeByte(2); break; - case LessonCardLessonTypePosition.topRight: - writer.writeByte(3); - break; - case LessonCardLessonTypePosition.bottomRight: - writer.writeByte(4); - break; case LessonCardLessonTypePosition.beforeBottomRightBlock: - writer.writeByte(5); - break; - case LessonCardLessonTypePosition.onIndicator: - writer.writeByte(6); + writer.writeByte(3); break; case LessonCardLessonTypePosition.none: - writer.writeByte(7); + writer.writeByte(4); break; } } diff --git a/lib/features/settings/ui/widgets/settings_page_lesson_card_lesson_type_position.dart b/lib/features/settings/ui/widgets/settings_page_lesson_card_lesson_type_position.dart index 8bd607d..d395f39 100644 --- a/lib/features/settings/ui/widgets/settings_page_lesson_card_lesson_type_position.dart +++ b/lib/features/settings/ui/widgets/settings_page_lesson_card_lesson_type_position.dart @@ -21,13 +21,15 @@ class _SettingsPageLessonCardLessonTypePosition extends ConsumerWidget { settingsNotifier.editLessonCardLessonTypePosition(value), items: const [ AppRadioSelectorItem( - title: Text('Снизу справа'), - description: Text('Выбор по умолчанию'), - value: LessonCardLessonTypePosition.bottomRight, + title: Text('На идикаторе'), + description: Text( + 'Выбор по умолчанию. Вертикальный текст на левом индикаторе.', + ), + value: LessonCardLessonTypePosition.onIndicator, ), AppRadioSelectorItem( - title: Text('Снизу слева'), - value: LessonCardLessonTypePosition.bottomLeft, + title: Text('Снизу справа'), + value: LessonCardLessonTypePosition.bottomRight, ), AppRadioSelectorItem( title: Text('После нижнего левого блока'), @@ -37,19 +39,6 @@ class _SettingsPageLessonCardLessonTypePosition extends ConsumerWidget { title: Text('Перед нижним правым блоком'), value: LessonCardLessonTypePosition.beforeBottomRightBlock, ), - AppRadioSelectorItem( - title: Text('Сверху справа'), - value: LessonCardLessonTypePosition.topRight, - ), - AppRadioSelectorItem( - title: Text('После верхнего левого блока'), - value: LessonCardLessonTypePosition.afterTopLeftBlock, - ), - AppRadioSelectorItem( - title: Text('На идикаторе'), - description: Text('Вертикальное расположение слева'), - value: LessonCardLessonTypePosition.onIndicator, - ), AppRadioSelectorItem( title: Text('Отсутствует'), description: Text('Впрочем, действительно, можно и без него'), diff --git a/lib/features/timetable_page/features/lesson_card/providers/current_lesson_time_to_end_provider.dart b/lib/features/timetable_page/features/lesson_card/providers/current_lesson_time_to_end_provider.dart index 787fec1..face12c 100644 --- a/lib/features/timetable_page/features/lesson_card/providers/current_lesson_time_to_end_provider.dart +++ b/lib/features/timetable_page/features/lesson_card/providers/current_lesson_time_to_end_provider.dart @@ -6,7 +6,7 @@ import 'package:cube_system/features/timetable_page/state_holders/lessons/curren final currentLessonTimeToEndProvider = Provider((ref) { final currentDateTime = ref.watch(currentDateTimeQuick); - final activeLesson = ref.watch(currentLesson); + final activeLesson = ref.watch(currentLessonStateHolder); if (activeLesson == null) return null; diff --git a/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_progress_value_provider.dart b/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_progress_value_provider.dart index 61dff2e..cc22aaf 100644 --- a/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_progress_value_provider.dart +++ b/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_progress_value_provider.dart @@ -17,8 +17,8 @@ final nextLessonTimeToStartProgressValueProvider = Provider((ref) { final timeLeft = ref.read(nextLessonTimeToStartProvider)?.duration; if (timeLeft == null) return 1; - final lessonNext = ref.watch(nextLesson); - final lessonLast = ref.watch(lastLesson); + final lessonNext = ref.watch(nextLessonStateHolder); + final lessonLast = ref.watch(lastLessonStateHolder); if (lessonNext == null || lessonLast == null) return 1; diff --git a/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_provider.dart b/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_provider.dart index 9c8592d..90adaac 100644 --- a/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_provider.dart +++ b/lib/features/timetable_page/features/lesson_card/providers/next_lesson_time_to_start_provider.dart @@ -16,13 +16,13 @@ final nextLessonTimeToStartProvider = Provider((ref) { if (condition.isNever) return null; if (condition.isOnlyRecessBetweenLessons) { - final last = ref.watch(lastLesson); - final next = ref.watch(nextLesson); + final last = ref.watch(lastLessonStateHolder); + final next = ref.watch(nextLessonStateHolder); if (last == null || next == null) return null; if (next.number - last.number != 1) return null; } - final next = ref.watch(nextLesson); + final next = ref.watch(nextLessonStateHolder); if (next == null) return null; diff --git a/lib/features/timetable_page/features/lesson_card/ui/lesson_card.dart b/lib/features/timetable_page/features/lesson_card/ui/lesson_card.dart index 58a4ae2..be64194 100644 --- a/lib/features/timetable_page/features/lesson_card/ui/lesson_card.dart +++ b/lib/features/timetable_page/features/lesson_card/ui/lesson_card.dart @@ -2,7 +2,6 @@ import 'dart:math'; import 'package:cube_system/features/settings/state_holders/app_settings_state_holder.dart'; import 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/recess_card.dart'; -import 'package:cube_system/features/timetable_page/managers/timetable_page_manager.dart'; import 'package:cube_system/features/timetable_page/state_holders/selected_timetable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -24,12 +23,16 @@ import 'package:cube_system/features/settings/state_holders/app_lesson_colors.da import 'package:cube_system/features/timetable_page/state_holders/lessons/next_lesson.dart'; +import 'package:cube_system/features/timetable_page/managers/timetable_lessons_manager.dart'; + part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_body.dart'; part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_footer.dart'; part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_header.dart'; -part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_icons.dart'; +part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_icon.dart'; +part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_chip.dart'; +part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_time_to_end.dart'; +part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel.dart'; part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_indicator.dart'; -part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_left.dart'; part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/next_lesson_time_to_start_progress_bar.dart'; part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_lesson_type_chip.dart'; part 'package:cube_system/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_to_start.dart'; @@ -46,7 +49,6 @@ class LessonCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final manager = ref.watch(timetablePageManager); return ProviderScope( overrides: [ _lessonInLessonCard.overrideWithValue(lesson), @@ -54,15 +56,15 @@ class LessonCard extends ConsumerWidget { child: Column( children: [ const LessonCardRecess( - margin: EdgeInsets.only(bottom: 12, top: 8), + margin: EdgeInsets.only(bottom: 12, top: 9), ), const LessonCardTimeToStart( - margin: EdgeInsets.only(bottom: 8, top: 4), + margin: EdgeInsets.only(bottom: 9, top: 4), ), Stack( children: [ Container( - margin: const EdgeInsets.only(top: 8), + margin: const EdgeInsets.only(top: 9), decoration: BoxDecoration( borderRadius: BorderRadius.circular(7), color: context.colors.background, @@ -84,7 +86,6 @@ class LessonCard extends ConsumerWidget { const LessonCardIndicator(), Expanded( child: InkWell( - onTap: manager.findLastCurrentNextLesson, child: Column( children: [ Padding( @@ -125,7 +126,7 @@ class LessonCard extends ConsumerWidget { const Positioned( top: 0, right: 8, - child: LessonCardIcons(), + child: LessonCardInfoPanel(), ), ], ), diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel.dart new file mode 100644 index 0000000..80ef88d --- /dev/null +++ b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel.dart @@ -0,0 +1,56 @@ +part of 'package:cube_system/features/timetable_page/features/lesson_card/ui/lesson_card.dart'; + +class LessonCardInfoPanel extends ConsumerWidget { + const LessonCardInfoPanel({super.key}); + + static final _random = Random(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final lesson = ref.watch(_lessonInLessonCard); + + final isRemotely = lesson.isRemotely; + + final isCollision = lesson.isCollision; + + final isCancelled = lesson.isCancelled; + + // final isCollision = _random.nextInt(100) < 25; + + // final isRemotely = _random.nextInt(100) < 25; + + // final isCancelled = _random.nextInt(100) < 0; + + final presentImportantNote = _random.nextInt(100) < 0; + + final numberOfNotes = _random.nextInt(100) < 0 ? _random.nextInt(7) : 0; + + return Row( + children: [ + if (isRemotely) + const LessonCardInfoPanelIcon( + icon: Icon(Icons.home), + ), + if (presentImportantNote) + const LessonCardInfoPanelIcon( + icon: Icon(Icons.priority_high_rounded), + ), + if (numberOfNotes > 0) + LessonCardInfoPanelIcon( + icon: Text('$numberOfNotes'), + ), + const LessonCardInfoPanelTimeToEnd(), + if (isCollision) + const LessonCardInfoPanelChip( + text: Text('Коллизия'), + isDestructive: true, + ), + if (isCancelled) + const LessonCardInfoPanelChip( + text: Text('Отменено'), + isDestructive: true, + ), + ], + ); + } +} diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_chip.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_chip.dart new file mode 100644 index 0000000..afd3edb --- /dev/null +++ b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_chip.dart @@ -0,0 +1,44 @@ +part of 'package:cube_system/features/timetable_page/features/lesson_card/ui/lesson_card.dart'; + +class LessonCardInfoPanelChip extends ConsumerWidget { + final Widget text; + final bool isDestructive; + final double minWidth; + final Color? color; + + const LessonCardInfoPanelChip({ + super.key, + required this.text, + this.isDestructive = false, + this.minWidth = 0, + this.color, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final backgroundColor = isDestructive + ? context.colors.destructive + : color ?? + ref.watch(appLessonColorByLesson(ref.read(_lessonInLessonCard))); + + return Container( + margin: const EdgeInsets.only(right: 4), + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6), + constraints: BoxConstraints(minWidth: minWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(16)), + ), + child: Center( + child: Padding( + padding: const EdgeInsets.only(top: 0.5, left: 0), + child: DefaultTextStyle( + style: context.textStyles.chipLabel + .copyWith(color: context.colors.white), + child: text, + ), + ), + ), + ); + } +} diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_icon.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_icon.dart new file mode 100644 index 0000000..0101dd4 --- /dev/null +++ b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_icon.dart @@ -0,0 +1,50 @@ +part of 'package:cube_system/features/timetable_page/features/lesson_card/ui/lesson_card.dart'; + +class LessonCardInfoPanelIcon extends ConsumerWidget { + final Widget icon; + final bool isDestructive; + final Color? color; + + const LessonCardInfoPanelIcon({ + super.key, + required this.icon, + this.isDestructive = false, + this.color, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final backgroundColor = isDestructive + ? context.colors.destructive + : color ?? + ref.watch(appLessonColorByLesson(ref.read(_lessonInLessonCard))); + + return Container( + margin: const EdgeInsets.only(right: 4), + width: 18, + height: 18, + decoration: BoxDecoration( + color: backgroundColor, + shape: BoxShape.circle, + ), + child: Center( + child: DefaultTextStyle( + style: context.textStyles.chipLabel + .copyWith(color: context.colors.white), + child: IconTheme( + data: Theme.of(context).iconTheme.copyWith( + size: 13.5, + color: context.colors.white, + ), + child: Padding( + padding: icon is Text + ? const EdgeInsets.only(left: 0.4) + : const EdgeInsets.only(bottom: 1, right: 0.2), + child: icon, + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_time_to_end.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_time_to_end.dart new file mode 100644 index 0000000..c82040b --- /dev/null +++ b/lib/features/timetable_page/features/lesson_card/ui/widgets/info_panel/lesson_card_info_panel_time_to_end.dart @@ -0,0 +1,59 @@ +part of 'package:cube_system/features/timetable_page/features/lesson_card/ui/lesson_card.dart'; + +class LessonCardInfoPanelTimeToEnd extends ConsumerWidget { + const LessonCardInfoPanelTimeToEnd({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final lesson = ref.watch(_lessonInLessonCard); + + final currentLesson = ref.watch(currentLessonStateHolder); + + final manager = ref.read(timetableLessonsManager); + + final isActiveLessons = manager.isEquelLessons(lesson, currentLesson); + + if (!isActiveLessons) return const SizedBox(); + + return Consumer( + builder: (context, ref, _) { + ref.watch( + currentLessonTimeToEndProvider + .select((value) => value?.format().length), + ); + + final hasHours = + (ref.read(currentLessonTimeToEndProvider)?.hours ?? 0) > 0; + + return hasHours + ? const LessonCardInfoPanelChip( + minWidth: 66, + text: _LessonCardInfoPanelTimeToEndText(), + ) + : const LessonCardInfoPanelChip( + minWidth: 46, + text: _LessonCardInfoPanelTimeToEndText(), + ); + }, + ); + } +} + +class _LessonCardInfoPanelTimeToEndText extends ConsumerWidget { + const _LessonCardInfoPanelTimeToEndText(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Consumer( + builder: (context, ref, _) { + final timeToEnd = ref.watch(currentLessonTimeToEndProvider); + final timeToEndStr = timeToEnd?.format() ?? '00:00'; + return Text( + timeToEndStr, + style: context.textStyles.chipLabel + .copyWith(color: context.colors.white), + ); + }, + ); + } +} diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_footer.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_footer.dart index ffe2c49..008b3b3 100644 --- a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_footer.dart +++ b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_footer.dart @@ -57,11 +57,6 @@ class LessonCardFooter extends ConsumerWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (lessonTypePosition.isBottomLeft) - const Padding( - padding: EdgeInsets.only(left: 8), - child: LessonCardLessonTypeChip(), - ), if (teachersIsNotEmpty) Flexible( child: Padding( @@ -99,7 +94,7 @@ class LessonCardFooter extends ConsumerWidget { constraints: const BoxConstraints( maxWidth: 160, ), - padding: const EdgeInsets.only(top: 4, left: 8), + padding: const EdgeInsets.only(top: 4, left: 8, right: 6), child: Text( rigthText, style: context.textStyles.smallLabel.copyWith( @@ -112,7 +107,7 @@ class LessonCardFooter extends ConsumerWidget { ), if (lessonTypePosition.isBottomRight) const Padding( - padding: EdgeInsets.only(right: 4, left: 8), + padding: EdgeInsets.only(right: 4, left: 4), child: LessonCardLessonTypeChip(), ), ], diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_header.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_header.dart index 6d304b2..f3678f4 100644 --- a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_header.dart +++ b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_header.dart @@ -18,11 +18,6 @@ class LessonCardHeader extends ConsumerWidget { final color = ref.watch(appLessonColorByLesson(ref.read(_lessonInLessonCard))); - final lessonTypePosition = ref.watch( - appSettingsStateHolder - .select((value) => value.lessonCardLessonTypePosition), - ); - return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -53,22 +48,8 @@ class LessonCardHeader extends ConsumerWidget { color: context.colors.subduedText, ), ), - if (lessonTypePosition.isAfterTopLeftBlock) - const Padding( - padding: EdgeInsets.only( - left: 8, - ), - child: LessonCardLessonTypeChip(), - ), ], ), - if (lessonTypePosition.isTopRight) - const Padding( - padding: EdgeInsets.only( - left: 8, - ), - child: LessonCardLessonTypeChip(), - ), ], ); } diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_icons.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_icons.dart deleted file mode 100644 index 1e51182..0000000 --- a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_icons.dart +++ /dev/null @@ -1,111 +0,0 @@ -part of 'package:cube_system/features/timetable_page/features/lesson_card/ui/lesson_card.dart'; - -class LessonCardIcons extends ConsumerWidget { - const LessonCardIcons({super.key}); - - static final _random = Random(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final isRemotely = - ref.watch(_lessonInLessonCard.select((value) => value.isRemotely)); - - final isActiveLessons = - ref.watch(_lessonInLessonCard) == ref.watch(currentLesson); - - final color = - ref.watch(appLessonColorByLesson(ref.read(_lessonInLessonCard))); - - // final isRemotely = _random.nextInt(100) < 0; - - final presentImportantNote = _random.nextInt(100) < 0; - - final numberOfNotes = _random.nextInt(100) < 0 ? _random.nextInt(7) : 0; - - return Row( - children: [ - if (isRemotely) - Container( - margin: const EdgeInsets.only(right: 4), - width: 16, - height: 16, - decoration: BoxDecoration( - color: color, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.wifi_rounded, - size: 12, - color: Colors.white, - ), - ), - if (presentImportantNote) - Container( - margin: const EdgeInsets.only(right: 4), - width: 16, - height: 16, - decoration: BoxDecoration( - color: color, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.priority_high_rounded, - size: 12, - color: Colors.white, - ), - ), - if (numberOfNotes > 0) - Container( - margin: const EdgeInsets.only(right: 4), - width: 16, - height: 16, - decoration: BoxDecoration( - color: color, - shape: BoxShape.circle, - ), - child: Center( - child: Padding( - padding: const EdgeInsets.only(left: 0.5), - child: Text( - numberOfNotes.toString(), - style: context.textStyles.chipLabel - .copyWith(color: context.colors.white), - ), - ), - ), - ), - if (isActiveLessons) - Consumer( - builder: (context, ref, _) { - ref.watch( - currentLessonTimeToEndProvider - .select((value) => value?.format().length), - ); - - final hasHours = - (ref.read(currentLessonTimeToEndProvider)?.hours ?? 0) > 0; - - return Container( - margin: const EdgeInsets.only(right: 4), - padding: - const EdgeInsets.symmetric(vertical: 1.5, horizontal: 5), - constraints: BoxConstraints( - minWidth: hasHours ? 66 : 46, - ), - decoration: BoxDecoration( - color: color, - borderRadius: const BorderRadius.all(Radius.circular(16)), - ), - child: const Center( - child: Padding( - padding: EdgeInsets.only(top: 0.5, left: 0), - child: LessonCardTimeLeft(), - ), - ), - ); - }, - ), - ], - ); - } -} diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_left.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_left.dart deleted file mode 100644 index fef6a9e..0000000 --- a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_left.dart +++ /dev/null @@ -1,25 +0,0 @@ -part of 'package:cube_system/features/timetable_page/features/lesson_card/ui/lesson_card.dart'; - -class LessonCardTimeLeft extends ConsumerWidget { - const LessonCardTimeLeft({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final isActiveLessons = - ref.watch(_lessonInLessonCard) == ref.watch(currentLesson); - - if (!isActiveLessons) return const SizedBox(); - - return Consumer( - builder: (context, ref, _) { - final timeToEnd = ref.watch(currentLessonTimeToEndProvider); - final timeToEndStr = timeToEnd?.format() ?? '00:00'; - return Text( - timeToEndStr, - style: context.textStyles.chipLabel - .copyWith(color: context.colors.white), - ); - }, - ); - } -} diff --git a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_to_start.dart b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_to_start.dart index 8d43e3c..df20f74 100644 --- a/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_to_start.dart +++ b/lib/features/timetable_page/features/lesson_card/ui/widgets/lesson_card_time_to_start.dart @@ -15,10 +15,15 @@ class LessonCardTimeToStart extends ConsumerWidget { nextLessonTimeToStartProvider.select((value) => value == null), ); - final lessonNext = ref.watch(nextLesson); - final lessonCurrent = ref.watch(currentLesson); + final nextLesson = ref.watch(nextLessonStateHolder); - if (dontShow || lesson != lessonNext || lessonCurrent != null) { + final currentLesson = ref.watch(currentLessonStateHolder); + + final manager = ref.read(timetableLessonsManager); + + final isActiveLessons = manager.isEquelLessons(lesson, nextLesson); + + if (dontShow || !isActiveLessons || currentLesson != null) { return const SizedBox(); } diff --git a/lib/features/timetable_page/managers/lesson_convertor.dart b/lib/features/timetable_page/managers/lesson_convertor.dart index 6af5143..1b6141b 100644 --- a/lib/features/timetable_page/managers/lesson_convertor.dart +++ b/lib/features/timetable_page/managers/lesson_convertor.dart @@ -24,8 +24,9 @@ class LessonConvertor { }); Lesson lessonByLessonFullNamesInDb({ - required LessonFullNamesInDb lesson, + required LessonFullInDb lesson, int emptyLessonsBefore = 0, + bool isCollision = false, }) { final number = lesson.number; @@ -84,11 +85,13 @@ class LessonConvertor { typeShortName: lesson.type.shortName, disciplineName: lesson.discipline?.name, place: lesson.place?.name, - groupNames: lesson.groupNames, - teacherNames: lesson.teacherNames, + groupNames: lesson.groups.map((e) => e.name).toList(), + teacherNames: lesson.teachers?.map((e) => e.shortName).toList() ?? [], isElective: lesson.isElective, isRemotely: lesson.isRemotely, isEvent: isEvent, + isCollision: isCollision, + isCancelled: lesson.status == LessonStatus.value_3, defaultColor: color, emptyLessonsBefore: emptyLessonsBefore, ); diff --git a/lib/features/timetable_page/managers/timetable_lessons_manager.dart b/lib/features/timetable_page/managers/timetable_lessons_manager.dart index 58e85c4..41ebd68 100644 --- a/lib/features/timetable_page/managers/timetable_lessons_manager.dart +++ b/lib/features/timetable_page/managers/timetable_lessons_manager.dart @@ -31,9 +31,9 @@ final timetableLessonsManager = Provider((ref) { events: ref.watch(timetablePageEvents.notifier), currentDateTime: ref.watch(currentDateTimeQuick.notifier), selectedDate: ref.watch(selectedDate.notifier), - currentLesson: ref.watch(currentLesson.notifier), - nextLesson: ref.watch(nextLesson.notifier), - lastLesson: ref.watch(lastLesson.notifier), + currentLesson: ref.watch(currentLessonStateHolder.notifier), + nextLesson: ref.watch(nextLessonStateHolder.notifier), + lastLesson: ref.watch(lastLessonStateHolder.notifier), ); }); @@ -73,29 +73,31 @@ class TimetableLessonsManager { events.change(SplayTreeMap()); } - Future> _getLessons({ + Future> _getLessons({ required DateTime startDate, required DateTime endDate, }) async { - final format = DateFormat('yyyy-MM-dd'); - final timetable = selectedTimetable.state; if (timetable == null) return []; - final lessonResponse = await api.apiLessonsGet( - fullData: true, - groups: timetable.type == TimetableType.group ? [timetable.id] : null, - teachers: timetable.type == TimetableType.teacher ? [timetable.id] : null, - places: timetable.type == TimetableType.place ? [timetable.id] : null, - startDate: format.format(startDate), - endDate: format.format(endDate), + final format = DateFormat('yyyy-MM-dd'); + + final startDateStr = format.format(startDate); + final endDateStr = format.format(endDate); + + final lessonResponse = await api.apiTimetableLessonsViewerGet( + group: timetable.type == TimetableType.group ? [timetable.id] : null, + teacher: timetable.type == TimetableType.teacher ? [timetable.id] : null, + place: timetable.type == TimetableType.place ? [timetable.id] : null, + startDate: startDateStr, + endDate: endDateStr, ); - return lessonResponse.body!; + return lessonResponse.body!.data; } - void _setLessons(List lessons) { + void _setLessons(List lessons) { TimetableLessons timetableMap = SplayTreeMap.of(timetableLessons.state.cast()); @@ -103,15 +105,41 @@ class TimetableLessonsManager { timetableMap[lesson.date] = []; } + // for (int i = 0; i < lessons.length; i++) { + // if (lessons[i].date.day == DateTime.now().day) { + // lessons.insert( + // i + 1, + // lessons[i].copyWith( + // place: lessons[i].place?.copyWith( + // name: 'fwefweof', + // ), + // ), + // ); + // break; + // } + // } + for (int i = 0; i < lessons.length; i++) { final lesson = lessons[i]; int emptyLessonsBefore = 0; - if (i > 0 && lessons[i - 1].date == lesson.date) { + + final equalPrevious = i > 0 && + lessons[i - 1].number == lesson.number && + lessons[i - 1].date == lesson.date; + final equalNext = i < lessons.length - 1 && + lessons[i + 1].number == lesson.number && + lessons[i + 1].date == lesson.date; + + final isCollision = equalPrevious || equalNext; + + if (i > 0 && lessons[i - 1].date == lesson.date && !isCollision) { emptyLessonsBefore = lesson.number - lessons[i - 1].number - 1; } + final l = lessonConvertor.lessonByLessonFullNamesInDb( lesson: lesson, emptyLessonsBefore: emptyLessonsBefore, + isCollision: isCollision, ); timetableMap[lesson.date]!.add(l); @@ -187,4 +215,9 @@ class TimetableLessonsManager { } } } + + bool isEquelLessons(Lesson? one, Lesson? two) { + return one?.dateTimings.startDateTime == two?.dateTimings.startDateTime && + one?.number == two?.number; + } } diff --git a/lib/features/timetable_page/state_holders/lessons/current_lesson.dart b/lib/features/timetable_page/state_holders/lessons/current_lesson.dart index 6a6c0da..8541336 100644 --- a/lib/features/timetable_page/state_holders/lessons/current_lesson.dart +++ b/lib/features/timetable_page/state_holders/lessons/current_lesson.dart @@ -1,6 +1,9 @@ import 'package:cube_system/models/lesson/lesson.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -final currentLesson = StateProvider((ref) { +// TODO: Объеденить current, next, last в один stateHolder +// TODO: Возможно, стоит хранить не Lesson, a LessonDateTimings и LessonTimings + +final currentLessonStateHolder = StateProvider((ref) { return null; }); diff --git a/lib/features/timetable_page/state_holders/lessons/last_lesson.dart b/lib/features/timetable_page/state_holders/lessons/last_lesson.dart index e5cc722..e2cc33f 100644 --- a/lib/features/timetable_page/state_holders/lessons/last_lesson.dart +++ b/lib/features/timetable_page/state_holders/lessons/last_lesson.dart @@ -2,6 +2,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cube_system/models/lesson/lesson.dart'; -final lastLesson = StateProvider((ref) { +final lastLessonStateHolder = StateProvider((ref) { return null; }); diff --git a/lib/features/timetable_page/state_holders/lessons/next_lesson.dart b/lib/features/timetable_page/state_holders/lessons/next_lesson.dart index 6868f4a..9497853 100644 --- a/lib/features/timetable_page/state_holders/lessons/next_lesson.dart +++ b/lib/features/timetable_page/state_holders/lessons/next_lesson.dart @@ -2,6 +2,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cube_system/models/lesson/lesson.dart'; -final nextLesson = StateProvider((ref) { +final nextLessonStateHolder = StateProvider((ref) { return null; }); diff --git a/lib/features/timetable_page/ui/event_pages/ksrs_event_page.dart b/lib/features/timetable_page/ui/event_pages/ksrs_event_page.dart index 753bcb9..9776ebd 100644 --- a/lib/features/timetable_page/ui/event_pages/ksrs_event_page.dart +++ b/lib/features/timetable_page/ui/event_pages/ksrs_event_page.dart @@ -11,8 +11,8 @@ class KsrsEventPage extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return AppEventPage( picture: Assets.brooklyn.rest3.svg(), - title: 'В этот день пар нет', - subTitle: 'Можно отдохнуть', + title: 'КСРС', + subTitle: 'Контролируемая самостоятельная работа студентов', ); } } diff --git a/lib/features/timetable_page/ui/widgets/timetable_page_day.dart b/lib/features/timetable_page/ui/widgets/timetable_page_day.dart index c39f60d..1725c12 100644 --- a/lib/features/timetable_page/ui/widgets/timetable_page_day.dart +++ b/lib/features/timetable_page/ui/widgets/timetable_page_day.dart @@ -76,9 +76,22 @@ class TimetablePageDay extends ConsumerWidget { physics: const BouncingScrollPhysics( parent: AlwaysScrollableScrollPhysics(), ), - itemCount: lessons.length, - itemBuilder: (context, index) => LessonCard(lessons[index]), - separatorBuilder: (context, index) => const SizedBox(height: 12), + itemCount: lessons.length + 1, + itemBuilder: (context, index) { + if (index == lessons.length) { + return const Padding( + padding: EdgeInsets.only(top: 9), + // child: TimetablePageMessageCard(), + child: SizedBox(), + ); + } + final lesson = lessons[index]; + return LessonCard( + key: ValueKey(lesson), + lesson, + ); + }, + separatorBuilder: (context, index) => const SizedBox(height: 11), ); } } diff --git a/lib/features/timetable_page/ui/widgets/timetable_page_header.dart b/lib/features/timetable_page/ui/widgets/timetable_page_header.dart index 82ad544..fc1603d 100644 --- a/lib/features/timetable_page/ui/widgets/timetable_page_header.dart +++ b/lib/features/timetable_page/ui/widgets/timetable_page_header.dart @@ -1,172 +1,40 @@ -import 'package:cube_system/features/timetable_page/features/week_timeline/state_holders/week_timeline_shown_week_date.dart'; -import 'package:cube_system/features/timetable_page/managers/timetable_page_manager.dart'; -import 'package:cube_system/features/date_time_contol/state_holders/current_date_time_state_holders.dart'; -import 'package:cube_system/features/timetable_page/state_holders/selected_timetable.dart'; -import 'package:cube_system/models/timetable/timetable_type.dart'; -import 'package:cube_system/core/extensions.dart'; -import 'package:cube_system/styles/app_theme_context_extension.dart'; +import 'package:cube_system/features/timetable_page/ui/widgets/timetable_page_header_back_button.dart'; +import 'package:cube_system/features/timetable_page/ui/widgets/timetable_page_header_date.dart'; +import 'package:cube_system/features/timetable_page/ui/widgets/timetable_page_header_timetable_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; - -import 'package:intl/intl.dart'; - -import 'package:cube_system/features/timetable_page/features/week_timeline/providers/week_timeline_offset_back_button_direction_provider.dart'; - -import 'package:cube_system/features/timetable_page/features/week_timeline/models/week_timeline_offset_back_button_direction.dart'; class TimetablePageHeader extends ConsumerWidget { const TimetablePageHeader({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final manager = ref.watch(timetablePageManager); - - final timetable = ref.watch(selectedTimetableStateHolder); - - final date = ref.watch(currentDate); - final weekDate = ref.watch(weekTimelineShownWeekDate); - - final weekOffsetButton = - ref.watch(weekTimelineOffsetBackButtonDirectionProvider); - - final labelTextStyle = - timetable == null || timetable.type == TimetableType.teacher - ? context.textStyles.label - : context.textStyles.largeTitle; - - final String weekLabel; - - if (weekDate.weekNumber % 2 == 1) { - weekLabel = 'Числитель'; - } else { - weekLabel = 'Знаменатель'; - } - return SizedBox( height: 62, child: Row( crossAxisAlignment: CrossAxisAlignment.center, - children: [ + children: const [ Expanded( flex: 10, child: Padding( - padding: const EdgeInsets.only(left: 8, top: 4), - child: InkWell( - onTap: () { - context.go('/timetable/search'); - }, - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: Padding( - padding: const EdgeInsets.only(left: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - children: [ - Text( - timetable?.label ?? 'Расписание', - style: labelTextStyle, - ), - SizedBox( - height: 16, - child: Text( - String.fromCharCode( - Icons.arrow_drop_down_rounded.codePoint, - ), - style: labelTextStyle.copyWith( - fontFamily: - Icons.arrow_drop_down_rounded.fontFamily, - package: - Icons.arrow_drop_down_rounded.fontPackage, - fontSize: 20, - height: 0.95, - ), - ), - ), - ], - ), - const SizedBox(height: 1), - Text( - timetable?.type.label ?? 'Не выбрано', - style: context.textStyles.smallSubTitle.copyWith( - fontSize: 10, - color: context.colors.subduedText, - ), - ), - ], - ), - ), - ), + padding: EdgeInsets.only(left: 8, top: 4), + child: TimetablePageHeaderTimetableInfo(), ), ), Expanded( flex: 7, child: Padding( - padding: const EdgeInsets.only(left: 4, top: 4, right: 4), - child: InkWell( - onTap: () {}, - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - DateFormat('MMMM', 'ru').format(weekDate).capitalize(), - style: context.textStyles.label, - textAlign: TextAlign.center, - ), - const SizedBox(height: 1), - if (date.year != weekDate.year) - Text( - DateFormat('yyyy', 'ru').format(weekDate).capitalize(), - style: context.textStyles.label.copyWith(), - textAlign: TextAlign.center, - ), - Text( - weekLabel, - style: context.textStyles.smallSubTitle.copyWith( - fontSize: 10, - color: context.colors.subduedText, - ), - textAlign: TextAlign.center, - ), - ], - ), - ), + padding: EdgeInsets.only(left: 4, top: 4, right: 4), + child: TimetablePageHeaderDate(), ), ), Expanded( flex: 10, child: Align( alignment: Alignment.centerRight, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only(right: 8), - child: weekOffsetButton != - WeekTimelineOffsetBackButtonDirection.stay - ? InkWell( - onTap: () => - manager.selectDate(date.add(Duration.zero)), - borderRadius: - const BorderRadius.all(Radius.circular(99)), - child: Container( - padding: const EdgeInsets.all(8), - child: Icon( - weekOffsetButton == - WeekTimelineOffsetBackButtonDirection - .forward - ? Icons.arrow_forward_ios_rounded - : Icons.arrow_back_ios_new_rounded, - size: 20, - ), - ), - ) - : null, - ), - ], + child: Padding( + padding: EdgeInsets.only(right: 8), + child: TimetablePageHeaderBackButton(), ), ), ) diff --git a/lib/features/timetable_page/ui/widgets/timetable_page_header_back_button.dart b/lib/features/timetable_page/ui/widgets/timetable_page_header_back_button.dart new file mode 100644 index 0000000..d9902ec --- /dev/null +++ b/lib/features/timetable_page/ui/widgets/timetable_page_header_back_button.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:cube_system/features/date_time_contol/state_holders/current_date_time_state_holders.dart'; + +import 'package:cube_system/features/timetable_page/features/week_timeline/models/week_timeline_offset_back_button_direction.dart'; +import 'package:cube_system/features/timetable_page/features/week_timeline/providers/week_timeline_offset_back_button_direction_provider.dart'; +import 'package:cube_system/features/timetable_page/managers/timetable_page_manager.dart'; + +class TimetablePageHeaderBackButton extends ConsumerWidget { + const TimetablePageHeaderBackButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final manager = ref.watch(timetablePageManager); + + final date = ref.watch(currentDate); + + final weekOffsetButton = + ref.watch(weekTimelineOffsetBackButtonDirectionProvider); + + return weekOffsetButton != WeekTimelineOffsetBackButtonDirection.stay + ? InkWell( + onTap: () => manager.selectDate(date.add(Duration.zero)), + borderRadius: const BorderRadius.all(Radius.circular(99)), + child: Container( + padding: const EdgeInsets.all(8), + child: Icon( + weekOffsetButton == + WeekTimelineOffsetBackButtonDirection.forward + ? Icons.arrow_forward_ios_rounded + : Icons.arrow_back_ios_new_rounded, + size: 20, + ), + ), + ) + : const SizedBox(); + } +} diff --git a/lib/features/timetable_page/ui/widgets/timetable_page_header_date.dart b/lib/features/timetable_page/ui/widgets/timetable_page_header_date.dart new file mode 100644 index 0000000..e15809e --- /dev/null +++ b/lib/features/timetable_page/ui/widgets/timetable_page_header_date.dart @@ -0,0 +1,63 @@ +import 'package:cube_system/core/extensions.dart'; +import 'package:cube_system/styles/app_theme_context_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; + +import 'package:cube_system/features/date_time_contol/state_holders/current_date_time_state_holders.dart'; +import 'package:cube_system/features/timetable_page/features/week_timeline/state_holders/week_timeline_shown_week_date.dart'; + +import 'package:cube_system/features/timetable_page/state_holders/selected_timetable.dart'; + +class TimetablePageHeaderDate extends ConsumerWidget { + const TimetablePageHeaderDate({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final date = ref.watch(currentDate); + final weekDate = ref.watch(weekTimelineShownWeekDate); + + final String weekLabel; + + if (weekDate.weekNumber % 2 == 1) { + weekLabel = 'Числитель'; + } else { + weekLabel = 'Знаменатель'; + } + + final isGroup = ref.watch( + selectedTimetableStateHolder + .select((value) => value?.type.isGroup ?? false), + ); + + return InkWell( + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + DateFormat('MMMM', 'ru').format(weekDate).capitalize(), + style: context.textStyles.label, + textAlign: TextAlign.center, + ), + const SizedBox(height: 1), + if (date.year != weekDate.year) + Text( + DateFormat('yyyy', 'ru').format(weekDate).capitalize(), + style: context.textStyles.label.copyWith(), + textAlign: TextAlign.center, + ), + if (isGroup) + Text( + weekLabel, + style: context.textStyles.smallSubTitle.copyWith( + fontSize: 10, + color: context.colors.subduedText, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } +} diff --git a/lib/features/timetable_page/ui/widgets/timetable_page_header_timetable_info.dart b/lib/features/timetable_page/ui/widgets/timetable_page_header_timetable_info.dart new file mode 100644 index 0000000..45ae383 --- /dev/null +++ b/lib/features/timetable_page/ui/widgets/timetable_page_header_timetable_info.dart @@ -0,0 +1,69 @@ +import 'package:cube_system/styles/app_theme_context_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import 'package:cube_system/models/timetable/timetable_type.dart'; +import 'package:cube_system/features/timetable_page/state_holders/selected_timetable.dart'; + +class TimetablePageHeaderTimetableInfo extends ConsumerWidget { + const TimetablePageHeaderTimetableInfo({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final timetable = ref.watch(selectedTimetableStateHolder); + + final labelTextStyle = + timetable == null || timetable.type == TimetableType.teacher + ? context.textStyles.label + : context.textStyles.largeTitle; + + return InkWell( + onTap: () { + context.go('/timetable/search'); + }, + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Padding( + padding: const EdgeInsets.only(left: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Flexible( + child: Text( + timetable?.label ?? 'Расписание', + style: labelTextStyle, + ), + ), + SizedBox( + height: 16, + child: Text( + String.fromCharCode( + Icons.arrow_drop_down_rounded.codePoint, + ), + style: labelTextStyle.copyWith( + fontFamily: Icons.arrow_drop_down_rounded.fontFamily, + package: Icons.arrow_drop_down_rounded.fontPackage, + fontSize: 20, + height: 0.95, + ), + ), + ), + ], + ), + const SizedBox(height: 1), + Text( + timetable?.type.label ?? 'Не выбрано', + style: context.textStyles.smallSubTitle.copyWith( + fontSize: 10, + color: context.colors.subduedText, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/timetable_page/ui/widgets/timetable_page_message_card.dart b/lib/features/timetable_page/ui/widgets/timetable_page_message_card.dart new file mode 100644 index 0000000..cd5ebf0 --- /dev/null +++ b/lib/features/timetable_page/ui/widgets/timetable_page_message_card.dart @@ -0,0 +1,92 @@ +import 'package:cube_system/styles/app_theme_context_extension.dart'; +import 'package:cube_system/ui/ui_kit/app_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class TimetablePageMessageCard extends ConsumerWidget { + const TimetablePageMessageCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(7), + color: context.colors.background, + boxShadow: [ + BoxShadow( + color: context.colors.shadow, + blurRadius: 12, + offset: const Offset(0, 1), + ) + ], + ), + padding: const EdgeInsets.only(left: 12, right: 4, top: 4, bottom: 12), + child: Material( + type: MaterialType.transparency, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8), + Text( + 'Информация о ближайших выходных днях', + style: context.textStyles.subTitle.copyWith( + // color: context.colors.subduedText, + ), + ), + const SizedBox(height: 8), + Text( + '''Студенты и работники КубГУ отдыхают: +• 25 апреля +• 1, 8 и 9 мая''', + style: context.textStyles.smallSubTitle.copyWith( + color: context.colors.subduedText, + ), + ), + // const SizedBox(height: 8), + // Text( + // 'Кому интересно, приходите 26 апреля в 14.10 в ауд. А424. Для записи на семинар заполните форму.', + // style: context.textStyles.smallSubTitle.copyWith( + // color: context.colors.subduedText, + // ), + // ), + // const SizedBox(height: 7), + // Text( + // 'https://forms.gle/3hRh6kQajuYBjbcW9', + // style: context.textStyles.smallSubTitle.copyWith( + // color: context.colors.primary, + // decoration: TextDecoration.underline, + // ), + // ), + const SizedBox(height: 12), + AppButton( + height: 32, + isExpanded: true, + style: AppButtonStyle.secondary, + onTap: () {}, + text: 'Понятно', + ) + ], + ), + ), + InkWell( + onTap: () {}, + borderRadius: BorderRadius.circular(99), + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.close_rounded, + color: context.colors.text, + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/timetable_search_page/managers/timetable_search_page_manager.dart b/lib/features/timetable_search_page/managers/timetable_search_page_manager.dart index 8ef4ad3..c691590 100644 --- a/lib/features/timetable_search_page/managers/timetable_search_page_manager.dart +++ b/lib/features/timetable_search_page/managers/timetable_search_page_manager.dart @@ -4,6 +4,7 @@ import 'package:chopper/chopper.dart'; import 'package:cube_system/api/cube_api.dart'; import 'package:cube_system/features/analytics/logger.dart'; import 'package:cube_system/features/timetable_page/managers/timetable_page_manager.dart'; +import 'package:cube_system/features/timetable_search_page/models/timetable_search_group_info.dart'; import 'package:cube_system/features/timetable_search_page/state_holders/timetable_search_page_search_controller.dart'; import 'package:cube_system/features/timetable_search_page/state_holders/timetable_search_page_search_focus.dart'; import 'package:cube_system/features/timetable_search_page/state_holders/timetable_search_page_timetables.dart'; @@ -17,6 +18,8 @@ import 'package:cube_system/features/timetable_search_page/state_holders/timetab import 'package:cube_system/features/timetable_search_page/state_holders/timetable_search_page_timer.dart'; +import 'package:cube_system/features/timetable_search_page/models/timetable_search_info.dart'; + final timetableSearchPageManager = Provider((ref) { return TimetableSearchPageManager( api: ref.watch(cubeApi), @@ -33,7 +36,7 @@ class TimetableSearchPageManager { final CubeApi api; final TimetablePageManager timetablePageManager; - final StateController> timetables; + final StateController> timetables; final StateController searchContoller; final StateController searchFocus; final StateController event; @@ -69,8 +72,8 @@ class TimetableSearchPageManager { } } - Future selectTimetable(TimetableInfo timetable) async { - await timetablePageManager.selectTimetable(timetable); + Future selectTimetable(TimetableSearchInfo timetable) async { + await timetablePageManager.selectTimetable(timetable.info); } Future instantSearch() => @@ -102,10 +105,11 @@ class TimetableSearchPageManager { return; } - final Response response; + final TimetableAutocomplete res; try { - response = await api.apiLessonsAutocompleteGet(q: query); + final response = await api.apiTimetableEntitiesGet(q: query); + res = response.body!; } catch (e) { event.state = TimetableSearchEventType.error; return; @@ -116,36 +120,47 @@ class TimetableSearchPageManager { return; } - final res = response.body!; - - final List timetablesList = []; + final List timetablesList = []; for (final group in res.groups) { timetablesList.add( - TimetableInfo( - id: group.id, - label: group.name, - type: TimetableType.group, + TimetableSearchInfo( + info: TimetableInfo( + id: group.id, + label: group.name, + type: TimetableType.group, + ), + groupInfo: TimetableSearchGroupInfo( + course: group.course, + faculty: group.faculty.shortName, + directionCipher: group.direction.cipher, + degreeStudy: group.direction.degreeStudy.value ?? '', + ), ), ); } for (final teacher in res.teachers) { timetablesList.add( - TimetableInfo( - id: teacher.id, + TimetableSearchInfo( label: teacher.fullName, - type: TimetableType.teacher, + info: TimetableInfo( + id: teacher.id, + label: teacher.shortName, + type: TimetableType.teacher, + ), ), ); } for (final place in res.places) { timetablesList.add( - TimetableInfo( - id: place.id, - label: place.name, - type: TimetableType.place, + TimetableSearchInfo( + info: TimetableInfo( + id: place.id, + label: place.name, + type: TimetableType.place, + ), ), ); } diff --git a/lib/features/timetable_search_page/models/timetable_search_group_info.dart b/lib/features/timetable_search_page/models/timetable_search_group_info.dart new file mode 100644 index 0000000..fdc3223 --- /dev/null +++ b/lib/features/timetable_search_page/models/timetable_search_group_info.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'timetable_search_group_info.freezed.dart'; + +@freezed +class TimetableSearchGroupInfo with _$TimetableSearchGroupInfo { + factory TimetableSearchGroupInfo({ + required int course, + required String faculty, + required String directionCipher, + required String degreeStudy, + }) = _TimetableSearchGroupInfo; +} diff --git a/lib/features/timetable_search_page/models/timetable_search_group_info.freezed.dart b/lib/features/timetable_search_page/models/timetable_search_group_info.freezed.dart new file mode 100644 index 0000000..a5e0052 --- /dev/null +++ b/lib/features/timetable_search_page/models/timetable_search_group_info.freezed.dart @@ -0,0 +1,197 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'timetable_search_group_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$TimetableSearchGroupInfo { + int get course => throw _privateConstructorUsedError; + String get faculty => throw _privateConstructorUsedError; + String get directionCipher => throw _privateConstructorUsedError; + String get degreeStudy => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TimetableSearchGroupInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TimetableSearchGroupInfoCopyWith<$Res> { + factory $TimetableSearchGroupInfoCopyWith(TimetableSearchGroupInfo value, + $Res Function(TimetableSearchGroupInfo) then) = + _$TimetableSearchGroupInfoCopyWithImpl<$Res, TimetableSearchGroupInfo>; + @useResult + $Res call( + {int course, String faculty, String directionCipher, String degreeStudy}); +} + +/// @nodoc +class _$TimetableSearchGroupInfoCopyWithImpl<$Res, + $Val extends TimetableSearchGroupInfo> + implements $TimetableSearchGroupInfoCopyWith<$Res> { + _$TimetableSearchGroupInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? course = null, + Object? faculty = null, + Object? directionCipher = null, + Object? degreeStudy = null, + }) { + return _then(_value.copyWith( + course: null == course + ? _value.course + : course // ignore: cast_nullable_to_non_nullable + as int, + faculty: null == faculty + ? _value.faculty + : faculty // ignore: cast_nullable_to_non_nullable + as String, + directionCipher: null == directionCipher + ? _value.directionCipher + : directionCipher // ignore: cast_nullable_to_non_nullable + as String, + degreeStudy: null == degreeStudy + ? _value.degreeStudy + : degreeStudy // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_TimetableSearchGroupInfoCopyWith<$Res> + implements $TimetableSearchGroupInfoCopyWith<$Res> { + factory _$$_TimetableSearchGroupInfoCopyWith( + _$_TimetableSearchGroupInfo value, + $Res Function(_$_TimetableSearchGroupInfo) then) = + __$$_TimetableSearchGroupInfoCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int course, String faculty, String directionCipher, String degreeStudy}); +} + +/// @nodoc +class __$$_TimetableSearchGroupInfoCopyWithImpl<$Res> + extends _$TimetableSearchGroupInfoCopyWithImpl<$Res, + _$_TimetableSearchGroupInfo> + implements _$$_TimetableSearchGroupInfoCopyWith<$Res> { + __$$_TimetableSearchGroupInfoCopyWithImpl(_$_TimetableSearchGroupInfo _value, + $Res Function(_$_TimetableSearchGroupInfo) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? course = null, + Object? faculty = null, + Object? directionCipher = null, + Object? degreeStudy = null, + }) { + return _then(_$_TimetableSearchGroupInfo( + course: null == course + ? _value.course + : course // ignore: cast_nullable_to_non_nullable + as int, + faculty: null == faculty + ? _value.faculty + : faculty // ignore: cast_nullable_to_non_nullable + as String, + directionCipher: null == directionCipher + ? _value.directionCipher + : directionCipher // ignore: cast_nullable_to_non_nullable + as String, + degreeStudy: null == degreeStudy + ? _value.degreeStudy + : degreeStudy // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_TimetableSearchGroupInfo implements _TimetableSearchGroupInfo { + _$_TimetableSearchGroupInfo( + {required this.course, + required this.faculty, + required this.directionCipher, + required this.degreeStudy}); + + @override + final int course; + @override + final String faculty; + @override + final String directionCipher; + @override + final String degreeStudy; + + @override + String toString() { + return 'TimetableSearchGroupInfo(course: $course, faculty: $faculty, directionCipher: $directionCipher, degreeStudy: $degreeStudy)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_TimetableSearchGroupInfo && + (identical(other.course, course) || other.course == course) && + (identical(other.faculty, faculty) || other.faculty == faculty) && + (identical(other.directionCipher, directionCipher) || + other.directionCipher == directionCipher) && + (identical(other.degreeStudy, degreeStudy) || + other.degreeStudy == degreeStudy)); + } + + @override + int get hashCode => + Object.hash(runtimeType, course, faculty, directionCipher, degreeStudy); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_TimetableSearchGroupInfoCopyWith<_$_TimetableSearchGroupInfo> + get copyWith => __$$_TimetableSearchGroupInfoCopyWithImpl< + _$_TimetableSearchGroupInfo>(this, _$identity); +} + +abstract class _TimetableSearchGroupInfo implements TimetableSearchGroupInfo { + factory _TimetableSearchGroupInfo( + {required final int course, + required final String faculty, + required final String directionCipher, + required final String degreeStudy}) = _$_TimetableSearchGroupInfo; + + @override + int get course; + @override + String get faculty; + @override + String get directionCipher; + @override + String get degreeStudy; + @override + @JsonKey(ignore: true) + _$$_TimetableSearchGroupInfoCopyWith<_$_TimetableSearchGroupInfo> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/timetable_search_page/models/timetable_search_info.dart b/lib/features/timetable_search_page/models/timetable_search_info.dart new file mode 100644 index 0000000..6ccc1f3 --- /dev/null +++ b/lib/features/timetable_search_page/models/timetable_search_info.dart @@ -0,0 +1,15 @@ +import 'package:cube_system/features/timetable_search_page/models/timetable_search_group_info.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'package:cube_system/models/timetable/timetable_info.dart'; + +part 'timetable_search_info.freezed.dart'; + +@freezed +class TimetableSearchInfo with _$TimetableSearchInfo { + factory TimetableSearchInfo({ + String? label, + required TimetableInfo info, + TimetableSearchGroupInfo? groupInfo, + }) = _TimetableSearchInfo; +} diff --git a/lib/features/timetable_search_page/models/timetable_search_info.freezed.dart b/lib/features/timetable_search_page/models/timetable_search_info.freezed.dart new file mode 100644 index 0000000..eb31f1b --- /dev/null +++ b/lib/features/timetable_search_page/models/timetable_search_info.freezed.dart @@ -0,0 +1,199 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'timetable_search_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$TimetableSearchInfo { + String? get label => throw _privateConstructorUsedError; + TimetableInfo get info => throw _privateConstructorUsedError; + TimetableSearchGroupInfo? get groupInfo => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TimetableSearchInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TimetableSearchInfoCopyWith<$Res> { + factory $TimetableSearchInfoCopyWith( + TimetableSearchInfo value, $Res Function(TimetableSearchInfo) then) = + _$TimetableSearchInfoCopyWithImpl<$Res, TimetableSearchInfo>; + @useResult + $Res call( + {String? label, TimetableInfo info, TimetableSearchGroupInfo? groupInfo}); + + $TimetableInfoCopyWith<$Res> get info; + $TimetableSearchGroupInfoCopyWith<$Res>? get groupInfo; +} + +/// @nodoc +class _$TimetableSearchInfoCopyWithImpl<$Res, $Val extends TimetableSearchInfo> + implements $TimetableSearchInfoCopyWith<$Res> { + _$TimetableSearchInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? label = freezed, + Object? info = null, + Object? groupInfo = freezed, + }) { + return _then(_value.copyWith( + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String?, + info: null == info + ? _value.info + : info // ignore: cast_nullable_to_non_nullable + as TimetableInfo, + groupInfo: freezed == groupInfo + ? _value.groupInfo + : groupInfo // ignore: cast_nullable_to_non_nullable + as TimetableSearchGroupInfo?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $TimetableInfoCopyWith<$Res> get info { + return $TimetableInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $TimetableSearchGroupInfoCopyWith<$Res>? get groupInfo { + if (_value.groupInfo == null) { + return null; + } + + return $TimetableSearchGroupInfoCopyWith<$Res>(_value.groupInfo!, (value) { + return _then(_value.copyWith(groupInfo: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$_TimetableSearchInfoCopyWith<$Res> + implements $TimetableSearchInfoCopyWith<$Res> { + factory _$$_TimetableSearchInfoCopyWith(_$_TimetableSearchInfo value, + $Res Function(_$_TimetableSearchInfo) then) = + __$$_TimetableSearchInfoCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? label, TimetableInfo info, TimetableSearchGroupInfo? groupInfo}); + + @override + $TimetableInfoCopyWith<$Res> get info; + @override + $TimetableSearchGroupInfoCopyWith<$Res>? get groupInfo; +} + +/// @nodoc +class __$$_TimetableSearchInfoCopyWithImpl<$Res> + extends _$TimetableSearchInfoCopyWithImpl<$Res, _$_TimetableSearchInfo> + implements _$$_TimetableSearchInfoCopyWith<$Res> { + __$$_TimetableSearchInfoCopyWithImpl(_$_TimetableSearchInfo _value, + $Res Function(_$_TimetableSearchInfo) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? label = freezed, + Object? info = null, + Object? groupInfo = freezed, + }) { + return _then(_$_TimetableSearchInfo( + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String?, + info: null == info + ? _value.info + : info // ignore: cast_nullable_to_non_nullable + as TimetableInfo, + groupInfo: freezed == groupInfo + ? _value.groupInfo + : groupInfo // ignore: cast_nullable_to_non_nullable + as TimetableSearchGroupInfo?, + )); + } +} + +/// @nodoc + +class _$_TimetableSearchInfo implements _TimetableSearchInfo { + _$_TimetableSearchInfo({this.label, required this.info, this.groupInfo}); + + @override + final String? label; + @override + final TimetableInfo info; + @override + final TimetableSearchGroupInfo? groupInfo; + + @override + String toString() { + return 'TimetableSearchInfo(label: $label, info: $info, groupInfo: $groupInfo)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_TimetableSearchInfo && + (identical(other.label, label) || other.label == label) && + (identical(other.info, info) || other.info == info) && + (identical(other.groupInfo, groupInfo) || + other.groupInfo == groupInfo)); + } + + @override + int get hashCode => Object.hash(runtimeType, label, info, groupInfo); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_TimetableSearchInfoCopyWith<_$_TimetableSearchInfo> get copyWith => + __$$_TimetableSearchInfoCopyWithImpl<_$_TimetableSearchInfo>( + this, _$identity); +} + +abstract class _TimetableSearchInfo implements TimetableSearchInfo { + factory _TimetableSearchInfo( + {final String? label, + required final TimetableInfo info, + final TimetableSearchGroupInfo? groupInfo}) = _$_TimetableSearchInfo; + + @override + String? get label; + @override + TimetableInfo get info; + @override + TimetableSearchGroupInfo? get groupInfo; + @override + @JsonKey(ignore: true) + _$$_TimetableSearchInfoCopyWith<_$_TimetableSearchInfo> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/timetable_search_page/state_holders/timetable_search_page_timetables.dart b/lib/features/timetable_search_page/state_holders/timetable_search_page_timetables.dart index cb25d7e..d9b1e22 100644 --- a/lib/features/timetable_search_page/state_holders/timetable_search_page_timetables.dart +++ b/lib/features/timetable_search_page/state_holders/timetable_search_page_timetables.dart @@ -1,6 +1,8 @@ -import 'package:cube_system/models/timetable/timetable_info.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -final timetableSearchPageTimetables = StateProvider>((ref) { +import 'package:cube_system/features/timetable_search_page/models/timetable_search_info.dart'; + +final timetableSearchPageTimetables = + StateProvider>((ref) { return []; }); diff --git a/lib/features/timetable_search_page/ui/timetable_search_page.dart b/lib/features/timetable_search_page/ui/timetable_search_page.dart index 590fe2d..9a678a1 100644 --- a/lib/features/timetable_search_page/ui/timetable_search_page.dart +++ b/lib/features/timetable_search_page/ui/timetable_search_page.dart @@ -25,10 +25,18 @@ class _TimetableSearchPage extends ConsumerStatefulWidget { } class _TimetableSearchPageState extends ConsumerState<_TimetableSearchPage> { + @override + void initState() { + super.initState(); + + Future(() { + ref.read(timetableSearchPageManager).handleWelcome(); + }); + } + @override Widget build(BuildContext context) { final manager = ref.watch(timetableSearchPageManager); - manager.handleWelcome(); return Scaffold( body: SafeArea( diff --git a/lib/features/timetable_search_page/ui/widgets/timetable_card.dart b/lib/features/timetable_search_page/ui/widgets/timetable_card.dart deleted file mode 100644 index 5a81c24..0000000 --- a/lib/features/timetable_search_page/ui/widgets/timetable_card.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:cube_system/models/timetable/timetable_info.dart'; -import 'package:cube_system/styles/app_theme_context_extension.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'package:cube_system/models/timetable/timetable_type.dart'; - -class TimetableCard extends ConsumerWidget { - final TimetableInfo timetable; - - final VoidCallback onTap; - - const TimetableCard({ - required this.timetable, - required this.onTap, - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final Color color; - final Widget icon; - - switch (timetable.type) { - case TimetableType.group: - color = const Color(0xFF1aaf5c); - icon = Icon( - Icons.people_alt_rounded, - color: context.colors.white, - size: 18, - ); - break; - case TimetableType.teacher: - color = const Color(0xFF488aff); - icon = Icon( - Icons.person, - color: context.colors.white, - size: 18, - ); - break; - case TimetableType.place: - color = const Color(0xFFfa601f); - icon = Padding( - padding: const EdgeInsets.only(left: 1, top: 1), - child: Icon( - Icons.sell, - color: context.colors.white, - size: 18, - ), - ); - break; - } - - return Container( - decoration: BoxDecoration( - color: context.colors.background, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: context.colors.shadow, - blurRadius: 12, - offset: const Offset(0, 2), - ) - ], - ), - clipBehavior: Clip.antiAlias, - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: onTap, - child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - Container( - height: 32, - width: 32, - margin: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: color.withOpacity(0.75), - shape: BoxShape.circle, - ), - child: icon, - ), - const SizedBox(width: 8), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - timetable.label, - style: context.textStyles.label, - ), - const SizedBox(height: 1), - Text( - timetable.type.label, - style: context.textStyles.smallSubTitle.copyWith( - color: context.colors.subduedText, - ), - ), - ], - ), - ) - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/features/timetable_search_page/ui/widgets/timetable_search_info_card.dart b/lib/features/timetable_search_page/ui/widgets/timetable_search_info_card.dart new file mode 100644 index 0000000..718b84a --- /dev/null +++ b/lib/features/timetable_search_page/ui/widgets/timetable_search_info_card.dart @@ -0,0 +1,152 @@ +import 'package:cube_system/styles/app_theme_context_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:cube_system/models/timetable/timetable_type.dart'; + +import 'package:cube_system/features/timetable_search_page/models/timetable_search_info.dart'; + +class TimetableSearchInfoCard extends ConsumerWidget { + final TimetableSearchInfo timetable; + + final VoidCallback onTap; + + const TimetableSearchInfoCard({ + required this.timetable, + required this.onTap, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final Color color; + final Widget icon; + + switch (timetable.info.type) { + case TimetableType.group: + color = const Color(0xFF1aaf5c); + icon = Icon( + Icons.people_alt_rounded, + color: context.colors.white, + size: 18, + ); + break; + case TimetableType.teacher: + color = const Color(0xFF488aff); + icon = Icon( + Icons.person, + color: context.colors.white, + size: 18, + ); + break; + case TimetableType.place: + color = const Color(0xFFfa601f); + icon = Padding( + padding: const EdgeInsets.only(left: 1, top: 1), + child: Icon( + Icons.sell, + color: context.colors.white, + size: 18, + ), + ); + break; + } + + return Container( + decoration: BoxDecoration( + color: context.colors.background, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: context.colors.shadow, + blurRadius: 12, + offset: const Offset(0, 2), + ) + ], + ), + clipBehavior: Clip.antiAlias, + child: Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: SizedBox( + height: 38, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + Container( + height: 32, + width: 32, + margin: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: color.withOpacity(0.75), + shape: BoxShape.circle, + ), + child: icon, + ), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + timetable.label ?? timetable.info.label, + style: context.textStyles.label, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 1), + Text( + timetable.info.type.label, + style: + context.textStyles.smallSubTitle.copyWith( + color: context.colors.subduedText, + ), + ), + ], + ), + ), + ], + ), + ), + if (timetable.info.type.isGroup) + Flexible( + child: Padding( + padding: const EdgeInsets.only(right: 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + timetable.groupInfo!.faculty, + style: context.textStyles.smallSubTitle.copyWith( + color: context.colors.subduedText, + ), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 1), + Text( + '${timetable.groupInfo!.degreeStudy}, ${timetable.groupInfo!.directionCipher}', + style: context.textStyles.smallSubTitle.copyWith( + color: context.colors.subduedText, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/timetable_search_page/ui/widgets/timetable_search_page_body.dart b/lib/features/timetable_search_page/ui/widgets/timetable_search_page_body.dart index 938a270..ea7a183 100644 --- a/lib/features/timetable_search_page/ui/widgets/timetable_search_page_body.dart +++ b/lib/features/timetable_search_page/ui/widgets/timetable_search_page_body.dart @@ -1,6 +1,6 @@ import 'package:cube_system/features/timetable_search_page/state_holders/timetable_search_page_event.dart'; import 'package:cube_system/features/timetable_search_page/ui/event_pages/no_found_search_event_page.dart'; -import 'package:cube_system/features/timetable_search_page/ui/widgets/timetable_card.dart'; +import 'package:cube_system/features/timetable_search_page/ui/widgets/timetable_search_info_card.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -53,7 +53,7 @@ class TimetableSearchPageBody extends ConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), itemBuilder: (context, index) { final timetable = timetables[index]; - return TimetableCard( + return TimetableSearchInfoCard( timetable: timetable, onTap: () async { manager.selectTimetable(timetable); diff --git a/lib/main.dart b/lib/main.dart index 8d9dac7..2b741e0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,19 +25,18 @@ void main() async { await dotenv.load(); await Future.wait([ - if (kIsMobile) - AppMetrica.activate( - AppMetricaConfig(dotenv.env['APP_METRICA_API_KEY']!), - ), + AppMetrica.activate( + AppMetricaConfig(dotenv.env['APP_METRICA_API_KEY']!), + ), HiveInitializer.init(), Future.delayed(const Duration(milliseconds: 2500)) ]); }, builder: (context, snapshot, _) { - if (snapshot.connectionState == ConnectionState.done) { - return const ProviderScope(child: MainApp()); + if (snapshot.connectionState != ConnectionState.done) { + return const AppSplash(); } - return const AppSplash(); + return const ProviderScope(child: MainApp()); }, ), onFlutterError: logger.flutterError, diff --git a/lib/models/lesson/lesson.dart b/lib/models/lesson/lesson.dart index bdfbd1d..3ce2cd4 100644 --- a/lib/models/lesson/lesson.dart +++ b/lib/models/lesson/lesson.dart @@ -25,6 +25,8 @@ class Lesson with _$Lesson { @HiveField(9) required bool isRemotely, @HiveField(10) required bool isElective, @HiveField(11) required bool isEvent, + @HiveField(14, defaultValue: false) required bool isCollision, + @HiveField(15, defaultValue: false) required bool isCancelled, @HiveField(12) required Color defaultColor, @HiveField(13) required int emptyLessonsBefore, }) = _Lesson; diff --git a/lib/models/lesson/lesson.freezed.dart b/lib/models/lesson/lesson.freezed.dart index 1aeb7fb..5853bc3 100644 --- a/lib/models/lesson/lesson.freezed.dart +++ b/lib/models/lesson/lesson.freezed.dart @@ -40,6 +40,10 @@ mixin _$Lesson { bool get isElective => throw _privateConstructorUsedError; @HiveField(11) bool get isEvent => throw _privateConstructorUsedError; + @HiveField(14, defaultValue: false) + bool get isCollision => throw _privateConstructorUsedError; + @HiveField(15, defaultValue: false) + bool get isCancelled => throw _privateConstructorUsedError; @HiveField(12) Color get defaultColor => throw _privateConstructorUsedError; @HiveField(13) @@ -67,6 +71,8 @@ abstract class $LessonCopyWith<$Res> { @HiveField(9) bool isRemotely, @HiveField(10) bool isElective, @HiveField(11) bool isEvent, + @HiveField(14, defaultValue: false) bool isCollision, + @HiveField(15, defaultValue: false) bool isCancelled, @HiveField(12) Color defaultColor, @HiveField(13) int emptyLessonsBefore}); @@ -99,6 +105,8 @@ class _$LessonCopyWithImpl<$Res, $Val extends Lesson> Object? isRemotely = null, Object? isElective = null, Object? isEvent = null, + Object? isCollision = null, + Object? isCancelled = null, Object? defaultColor = null, Object? emptyLessonsBefore = null, }) { @@ -151,6 +159,14 @@ class _$LessonCopyWithImpl<$Res, $Val extends Lesson> ? _value.isEvent : isEvent // ignore: cast_nullable_to_non_nullable as bool, + isCollision: null == isCollision + ? _value.isCollision + : isCollision // ignore: cast_nullable_to_non_nullable + as bool, + isCancelled: null == isCancelled + ? _value.isCancelled + : isCancelled // ignore: cast_nullable_to_non_nullable + as bool, defaultColor: null == defaultColor ? _value.defaultColor : defaultColor // ignore: cast_nullable_to_non_nullable @@ -198,6 +214,8 @@ abstract class _$$_LessonCopyWith<$Res> implements $LessonCopyWith<$Res> { @HiveField(9) bool isRemotely, @HiveField(10) bool isElective, @HiveField(11) bool isEvent, + @HiveField(14, defaultValue: false) bool isCollision, + @HiveField(15, defaultValue: false) bool isCancelled, @HiveField(12) Color defaultColor, @HiveField(13) int emptyLessonsBefore}); @@ -229,6 +247,8 @@ class __$$_LessonCopyWithImpl<$Res> Object? isRemotely = null, Object? isElective = null, Object? isEvent = null, + Object? isCollision = null, + Object? isCancelled = null, Object? defaultColor = null, Object? emptyLessonsBefore = null, }) { @@ -281,6 +301,14 @@ class __$$_LessonCopyWithImpl<$Res> ? _value.isEvent : isEvent // ignore: cast_nullable_to_non_nullable as bool, + isCollision: null == isCollision + ? _value.isCollision + : isCollision // ignore: cast_nullable_to_non_nullable + as bool, + isCancelled: null == isCancelled + ? _value.isCancelled + : isCancelled // ignore: cast_nullable_to_non_nullable + as bool, defaultColor: null == defaultColor ? _value.defaultColor : defaultColor // ignore: cast_nullable_to_non_nullable @@ -310,6 +338,8 @@ class _$_Lesson implements _Lesson { @HiveField(9) required this.isRemotely, @HiveField(10) required this.isElective, @HiveField(11) required this.isEvent, + @HiveField(14, defaultValue: false) required this.isCollision, + @HiveField(15, defaultValue: false) required this.isCancelled, @HiveField(12) required this.defaultColor, @HiveField(13) required this.emptyLessonsBefore}) : _groupNames = groupNames, @@ -364,6 +394,12 @@ class _$_Lesson implements _Lesson { @HiveField(11) final bool isEvent; @override + @HiveField(14, defaultValue: false) + final bool isCollision; + @override + @HiveField(15, defaultValue: false) + final bool isCancelled; + @override @HiveField(12) final Color defaultColor; @override @@ -372,7 +408,7 @@ class _$_Lesson implements _Lesson { @override String toString() { - return 'Lesson(number: $number, timings: $timings, dateTimings: $dateTimings, type: $type, typeShortName: $typeShortName, disciplineName: $disciplineName, place: $place, groupNames: $groupNames, teacherNames: $teacherNames, isRemotely: $isRemotely, isElective: $isElective, isEvent: $isEvent, defaultColor: $defaultColor, emptyLessonsBefore: $emptyLessonsBefore)'; + return 'Lesson(number: $number, timings: $timings, dateTimings: $dateTimings, type: $type, typeShortName: $typeShortName, disciplineName: $disciplineName, place: $place, groupNames: $groupNames, teacherNames: $teacherNames, isRemotely: $isRemotely, isElective: $isElective, isEvent: $isEvent, isCollision: $isCollision, isCancelled: $isCancelled, defaultColor: $defaultColor, emptyLessonsBefore: $emptyLessonsBefore)'; } @override @@ -399,6 +435,10 @@ class _$_Lesson implements _Lesson { (identical(other.isElective, isElective) || other.isElective == isElective) && (identical(other.isEvent, isEvent) || other.isEvent == isEvent) && + (identical(other.isCollision, isCollision) || + other.isCollision == isCollision) && + (identical(other.isCancelled, isCancelled) || + other.isCancelled == isCancelled) && (identical(other.defaultColor, defaultColor) || other.defaultColor == defaultColor) && (identical(other.emptyLessonsBefore, emptyLessonsBefore) || @@ -420,6 +460,8 @@ class _$_Lesson implements _Lesson { isRemotely, isElective, isEvent, + isCollision, + isCancelled, defaultColor, emptyLessonsBefore); @@ -444,6 +486,8 @@ abstract class _Lesson implements Lesson { @HiveField(9) required final bool isRemotely, @HiveField(10) required final bool isElective, @HiveField(11) required final bool isEvent, + @HiveField(14, defaultValue: false) required final bool isCollision, + @HiveField(15, defaultValue: false) required final bool isCancelled, @HiveField(12) required final Color defaultColor, @HiveField(13) required final int emptyLessonsBefore}) = _$_Lesson; @@ -484,6 +528,12 @@ abstract class _Lesson implements Lesson { @HiveField(11) bool get isEvent; @override + @HiveField(14, defaultValue: false) + bool get isCollision; + @override + @HiveField(15, defaultValue: false) + bool get isCancelled; + @override @HiveField(12) Color get defaultColor; @override diff --git a/lib/models/lesson/lesson.g.dart b/lib/models/lesson/lesson.g.dart index 56613c3..e3729c4 100644 --- a/lib/models/lesson/lesson.g.dart +++ b/lib/models/lesson/lesson.g.dart @@ -31,6 +31,8 @@ class LessonAdapter extends TypeAdapter<_$_Lesson> { isRemotely: fields[9] as bool, isElective: fields[10] as bool, isEvent: fields[11] as bool, + isCollision: fields[14] == null ? false : fields[14] as bool, + isCancelled: fields[15] == null ? false : fields[15] as bool, defaultColor: fields[12] as Color, emptyLessonsBefore: fields[13] as int, ); @@ -39,7 +41,7 @@ class LessonAdapter extends TypeAdapter<_$_Lesson> { @override void write(BinaryWriter writer, _$_Lesson obj) { writer - ..writeByte(14) + ..writeByte(16) ..writeByte(0) ..write(obj.number) ..writeByte(1) @@ -60,6 +62,10 @@ class LessonAdapter extends TypeAdapter<_$_Lesson> { ..write(obj.isElective) ..writeByte(11) ..write(obj.isEvent) + ..writeByte(14) + ..write(obj.isCollision) + ..writeByte(15) + ..write(obj.isCancelled) ..writeByte(12) ..write(obj.defaultColor) ..writeByte(13) diff --git a/lib/models/timetable/timetable_type.dart b/lib/models/timetable/timetable_type.dart index 7a3d63e..542dd16 100644 --- a/lib/models/timetable/timetable_type.dart +++ b/lib/models/timetable/timetable_type.dart @@ -20,4 +20,8 @@ enum TimetableType { } return null; } + + bool get isGroup => this == TimetableType.group; + bool get isTeacher => this == TimetableType.teacher; + bool get isPlace => this == TimetableType.place; } diff --git a/lib/styles/app_colors/app_colors.dart b/lib/styles/app_colors/app_colors.dart index 865ace8..579c045 100644 --- a/lib/styles/app_colors/app_colors.dart +++ b/lib/styles/app_colors/app_colors.dart @@ -11,6 +11,7 @@ part 'app_colors.tailor.dart'; class _$AppColors { static List primary = [const Color(0xFF38a1ff)]; static List background = [const Color(0xFFF6F7FF)]; + static List destructive = [Colors.red]; static List text = [const Color(0xFF2B2B2B)]; static List hintText = [const Color.fromARGB(255, 161, 161, 161)]; diff --git a/lib/styles/app_colors/app_colors.tailor.dart b/lib/styles/app_colors/app_colors.tailor.dart index 906291c..4cd6d14 100644 --- a/lib/styles/app_colors/app_colors.tailor.dart +++ b/lib/styles/app_colors/app_colors.tailor.dart @@ -12,6 +12,7 @@ class AppColors extends ThemeExtension { const AppColors({ required this.primary, required this.background, + required this.destructive, required this.text, required this.hintText, required this.subduedText, @@ -24,6 +25,7 @@ class AppColors extends ThemeExtension { final Color primary; final Color background; + final Color destructive; final Color text; final Color hintText; final Color subduedText; @@ -36,6 +38,7 @@ class AppColors extends ThemeExtension { static final AppColors light = AppColors( primary: _$AppColors.primary[0], background: _$AppColors.background[0], + destructive: _$AppColors.destructive[0], text: _$AppColors.text[0], hintText: _$AppColors.hintText[0], subduedText: _$AppColors.subduedText[0], @@ -54,6 +57,7 @@ class AppColors extends ThemeExtension { AppColors copyWith({ Color? primary, Color? background, + Color? destructive, Color? text, Color? hintText, Color? subduedText, @@ -66,6 +70,7 @@ class AppColors extends ThemeExtension { return AppColors( primary: primary ?? this.primary, background: background ?? this.background, + destructive: destructive ?? this.destructive, text: text ?? this.text, hintText: hintText ?? this.hintText, subduedText: subduedText ?? this.subduedText, @@ -83,6 +88,7 @@ class AppColors extends ThemeExtension { return AppColors( primary: Color.lerp(primary, other.primary, t)!, background: Color.lerp(background, other.background, t)!, + destructive: Color.lerp(destructive, other.destructive, t)!, text: Color.lerp(text, other.text, t)!, hintText: Color.lerp(hintText, other.hintText, t)!, subduedText: Color.lerp(subduedText, other.subduedText, t)!, @@ -102,6 +108,8 @@ class AppColors extends ThemeExtension { const DeepCollectionEquality().equals(primary, other.primary) && const DeepCollectionEquality() .equals(background, other.background) && + const DeepCollectionEquality() + .equals(destructive, other.destructive) && const DeepCollectionEquality().equals(text, other.text) && const DeepCollectionEquality().equals(hintText, other.hintText) && const DeepCollectionEquality() @@ -121,6 +129,7 @@ class AppColors extends ThemeExtension { runtimeType, const DeepCollectionEquality().hash(primary), const DeepCollectionEquality().hash(background), + const DeepCollectionEquality().hash(destructive), const DeepCollectionEquality().hash(text), const DeepCollectionEquality().hash(hintText), const DeepCollectionEquality().hash(subduedText), diff --git a/lib/ui/main_app.dart b/lib/ui/main_app.dart index 7838092..69604ba 100644 --- a/lib/ui/main_app.dart +++ b/lib/ui/main_app.dart @@ -10,6 +10,7 @@ import 'package:cube_system/features/navigation/router/app_router.dart'; import 'package:cube_system/styles/app_colors/app_colors.dart'; import 'package:cube_system/styles/app_text_styles/app_text_styles.dart'; import 'package:cube_system/styles/app_theme.dart'; +import 'package:upgrader/upgrader.dart'; class MainApp extends ConsumerWidget { const MainApp({super.key}); @@ -25,10 +26,15 @@ class MainApp extends ConsumerWidget { builder: (context) => MaterialApp.router( debugShowCheckedModeBanner: false, locale: DevicePreview.locale(context), - builder: (context, child) => Scaffold( - resizeToAvoidBottomInset: false, - body: FadeAnimatedWidget( - child: DevicePreview.appBuilder(context, child), + builder: (context, child) => UpgradeAlert( + upgrader: Upgrader( + countryCode: 'RU', + ), + child: Scaffold( + resizeToAvoidBottomInset: false, + body: FadeAnimatedWidget( + child: DevicePreview.appBuilder(context, child), + ), ), ), scrollBehavior: AppScrollBehavior(), diff --git a/lib/ui/ui_kit/app_button.dart b/lib/ui/ui_kit/app_button.dart index 054c179..50fd859 100644 --- a/lib/ui/ui_kit/app_button.dart +++ b/lib/ui/ui_kit/app_button.dart @@ -72,34 +72,37 @@ class AppButton extends StatelessWidget { ); } - return DecoratedBox( - decoration: BoxDecoration( - boxShadow: [ - if (style.isPrimary && onTap != null) - BoxShadow( - color: context.colors.primary.withOpacity(0.3), - offset: const Offset(0, 1), - blurRadius: 8, - ) - ], - borderRadius: BorderRadius.circular(8), - ), - child: GestureDetector( - onTap: isDisabled ? onDisabledTap : null, - child: TextButton( - onPressed: onTap, - style: buttonStyle, - child: rightIcon == null - ? Text(text ?? '') - : Stack( - children: [ - Center(child: Text(text ?? '')), - Align( - alignment: Alignment.centerRight, - child: rightIcon!, - ), - ], - ), + return SizedBox( + height: height, + child: DecoratedBox( + decoration: BoxDecoration( + boxShadow: [ + if (style.isPrimary && onTap != null) + BoxShadow( + color: context.colors.primary.withOpacity(0.3), + offset: const Offset(0, 1), + blurRadius: 8, + ) + ], + borderRadius: BorderRadius.circular(8), + ), + child: GestureDetector( + onTap: isDisabled ? onDisabledTap : null, + child: TextButton( + onPressed: onTap, + style: buttonStyle, + child: rightIcon == null + ? Text(text ?? '') + : Stack( + children: [ + Center(child: Text(text ?? '')), + Align( + alignment: Alignment.centerRight, + child: rightIcon!, + ), + ], + ), + ), ), ), ); diff --git a/lib/ui/widgets/fade_animated_widget.dart b/lib/ui/widgets/fade_animated_widget.dart index 08f9be5..e2a72ef 100644 --- a/lib/ui/widgets/fade_animated_widget.dart +++ b/lib/ui/widgets/fade_animated_widget.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; class FadeAnimatedWidget extends StatefulWidget { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 802712b..3e13a20 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import device_info_plus import package_info_plus import path_provider_foundation import rive_common @@ -13,6 +14,7 @@ import url_launcher_macos import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 98207da..f8c3595 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -297,6 +297,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903 + url: "https://pub.dev" + source: hosted + version: "8.2.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" + source: hosted + version: "7.0.0" device_preview: dependency: "direct main" description: @@ -661,6 +677,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + os_detect: + dependency: transitive + description: + name: os_detect + sha256: faf3bcf39515e64da8ff76b2f2805b20a6ff47ae515393e535f8579ff91d6b7f + url: "https://pub.dev" + source: hosted + version: "2.0.1" package_config: dependency: transitive description: @@ -1107,6 +1131,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + upgrader: + dependency: "direct main" + description: + name: upgrader + sha256: "3c61e3a99184e60947d2b2d93c1b5243a98a634aa6cc852d49dfaaeb9f42a7b5" + url: "https://pub.dev" + source: hosted + version: "6.3.0" url_launcher: dependency: "direct main" description: @@ -1211,6 +1243,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + version: + dependency: transitive + description: + name: version + sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" + url: "https://pub.dev" + source: hosted + version: "3.0.2" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d425ff4..f689be4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: cube_system description: Modern electronic timetable publish_to: 'none' -version: 0.7.0+3 +version: 0.8.0+4 environment: sdk: '>=2.19.2 <3.0.0' @@ -44,6 +44,7 @@ dependencies: package_info_plus: ^3.1.0 flutter_dotenv: ^5.0.2 appmetrica_plugin: ^1.1.0 + upgrader: ^6.3.0