From b97924152c14b3a60382782eb965616ebcc67770 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Thu, 9 May 2024 00:08:28 +0200 Subject: [PATCH] MMOOOOAAAARRR --- lib/blocs.dart | 2 - lib/blocs/calendar_cubit.dart | 512 ------------------------ lib/blocs/liked_photos_cubit.dart | 105 ----- lib/blocs/sales_order_cubit.dart | 29 -- lib/main.dart | 5 - lib/routes.dart | 58 +-- lib/ui/screens.dart | 1 - lib/ui/screens/calendar_screen.dart | 383 ------------------ lib/ui/screens/liked_photos_screen.dart | 146 ------- lib/ui/widgets.dart | 2 - lib/ui/widgets/events.dart | 347 ---------------- lib/ui/widgets/sales_order_dialog.dart | 106 ----- test/unit/calendar_test.dart | 152 ------- test/widget/calendar_test.dart | 100 ----- 14 files changed, 3 insertions(+), 1945 deletions(-) delete mode 100644 lib/blocs/calendar_cubit.dart delete mode 100644 lib/blocs/liked_photos_cubit.dart delete mode 100644 lib/blocs/sales_order_cubit.dart delete mode 100644 lib/ui/screens/calendar_screen.dart delete mode 100644 lib/ui/screens/liked_photos_screen.dart delete mode 100644 lib/ui/widgets/events.dart delete mode 100644 lib/ui/widgets/sales_order_dialog.dart delete mode 100644 test/unit/calendar_test.dart delete mode 100644 test/widget/calendar_test.dart diff --git a/lib/blocs.dart b/lib/blocs.dart index 01c5ffcb2..265a64728 100644 --- a/lib/blocs.dart +++ b/lib/blocs.dart @@ -1,13 +1,11 @@ export 'blocs/album_cubit.dart'; export 'blocs/album_list_cubit.dart'; export 'blocs/auth_cubit.dart'; -export 'blocs/calendar_cubit.dart'; export 'blocs/detail_state.dart'; export 'blocs/food_cubit.dart'; export 'blocs/full_member_cubit.dart'; export 'blocs/list_state.dart'; export 'blocs/payment_user_cubit.dart'; export 'blocs/registration_fields_cubit.dart'; -export 'blocs/sales_order_cubit.dart'; export 'blocs/theme_cubit.dart'; export 'blocs/welcome_cubit.dart'; diff --git a/lib/blocs/calendar_cubit.dart b/lib/blocs/calendar_cubit.dart deleted file mode 100644 index 178c8ffac..000000000 --- a/lib/blocs/calendar_cubit.dart +++ /dev/null @@ -1,512 +0,0 @@ -import 'dart:async'; - -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:intl/intl.dart'; -import 'package:reaxit/api/api_repository.dart'; -import 'package:reaxit/api/exceptions.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/config.dart'; -import 'package:reaxit/models.dart'; - -/// Wrapper around a [BaseEvent] to be shown in the calendar. -/// This allows to split an event into multiple parts, to show on every day in an event -class CalendarEvent { - static final _timeFormatter = DateFormat('HH:mm'); - - final BaseEvent parentEvent; - final DateTime start; - final DateTime end; - final String label; - final int part; - final int totalParts; - - String get title => totalParts == 1 - ? parentEvent.title - : '${parentEvent.title} day $part/$totalParts'; - - int get pk => parentEvent.pk; - String get location => parentEvent.location; - - const CalendarEvent._({ - required this.parentEvent, - required this.start, - required this.end, - required this.label, - required this.part, - required this.totalParts, - }); - - static List splitEventIntoCalendarEvents(BaseEvent event) { - final localStart = event.start.toLocal(); - late final DateTime localEnd; - - // Prevent having a card for 'Until 00:00' when an event ends at midnight. - if (event.end.toLocal().hour == 0 && event.end.toLocal().minute == 0) { - localEnd = event.end.toLocal().subtract(const Duration(minutes: 1)); - } else { - localEnd = event.end.toLocal(); - } - - final startDate = DateTime( - localStart.year, - localStart.month, - localStart.day, - ); - - final endDate = DateTime( - localEnd.year, - localEnd.month, - localEnd.day, - ); - - final daySpan = endDate.difference(startDate).inDays + 1; - - final startTime = _timeFormatter.format(event.start.toLocal()); - final endTime = _timeFormatter.format(event.end.toLocal()); - - if (daySpan == 1) { - return [ - CalendarEvent._( - parentEvent: event, - start: event.start, - end: event.end, - label: '$startTime - $endTime | ${event.location}', - part: 1, - totalParts: 1, - ) - ]; - } else { - return [ - CalendarEvent._( - parentEvent: event, - start: event.start, - end: _addDays(startDate, 1), - label: 'From $startTime | ${event.location}', - part: 1, - totalParts: daySpan, - ), - for (var day in Iterable.generate(daySpan - 2, (i) => i + 2)) - CalendarEvent._( - parentEvent: event, - start: _addDays(startDate, day - 1), - end: _addDays(startDate, day), - label: 'All day | ${event.location}', - part: day, - totalParts: daySpan, - ), - CalendarEvent._( - parentEvent: event, - start: endDate, - end: event.end, - label: 'Until $endTime | ${event.location}', - part: daySpan, - totalParts: daySpan, - ), - ]; - } - } - - static DateTime _addDays(DateTime x, int days) => DateTime( - x.year, - x.month, - x.day + days, - ); - - bool get isFirstPart => part == 1; - bool get isLasttPart => part == totalParts; -} - -class CalendarState extends Equatable { - final DateTime now; - final DoubleListState events; - - /// The results to be shown in the up directoin. These are outdated if - /// `isLoading` is true. - List get resultsUp => events.resultsUp; - - /// The results to be shown in the up directoin. These are outdated if - /// `isLoading` is true. - List get resultsDown => events.resultsDown; - - /// A message describing why there are no results. - String? get message => events.message; - - /// Different results are being loaded. The results are outdated. - bool get isLoading => events.isLoading; - - /// More of the same results are being loaded in the up direction. The results - /// are not outdated. - bool get isLoadingMoreUp => events.isLoadingMoreUp; - - /// More of the same results are being loaded in the down direction. The results - /// are not outdated. - bool get isLoadingMoreDown => events.isLoadingMoreDown; - - /// The last results have been loaded in the up direction. There are no more - /// pages left. - bool get isDoneUp => events.isDoneUp; - - /// The last results have been loaded in the down direction. There are no more - /// pages left. - bool get isDoneDown => events.isDoneDown; - - bool get hasException => message != null; - - const CalendarState(this.now, this.events); - - @override - List get props => [now, events]; -} - -class CalendarCubit extends Cubit { - static const int firstPageSize = 20; - static const int pageSize = 5; - - final ApiRepository api; - - /// The last used search query. Can be set through `this.search(query)`. - String? _searchQuery; - - /// The last used search query. Can be set through `this.search(query)`. - String? get searchQuery => _searchQuery; - - /// A timer used to debounce calls to `this.load()` from `this.search()`. - Timer? _searchDebounceTimer; - - /// The offset to be used for the next paginated request. - int _nextOffset = 0; - int _nextPastOffset = 0; - - /// The time rounded down to the month used to split the events in "past" and - /// "future". Also used in the query. This is a final to avoid race conditions - DateTime get _splitTime => DateTime(_truthTime.year, _truthTime.month); - - // _truthTime is the time that we base "now" on for the calendar. - DateTime _truthTime = DateTime.now(); - - /// A list of events that have been removed from the previous results - /// in order to prevent them filling up the calendar before today. - /// These should be added in later calls to [moreUp()]. - List _remainingPastEvents = []; - - /// A list of events that have been removed from the previous results - /// in order to prevent them filling up the calendar further then where - /// the first not-loaded event will go later. These should be added in - /// later calls to [more()]. - List _remainingFutureEvents = []; - - /// Debouncetimer to fix things like load - Timer? _debounce; - - CalendarCubit(this.api) - : super(CalendarState(DateTime.now(), const DoubleListState.loading())); - - Future cachedLoad() async { - if (_debounce == null || !_debounce!.isActive) { - await load(); - } - } - - List filterDown(Iterable events) { - // Get the last non-parter event that will be shown on the calendar. - CalendarEvent lastIncludedEvent = events.lastWhere( - (event) => event.parentEvent is! PartnerEvent && event.isFirstPart, - orElse: () => events.last); - // Remove anything before - _remainingFutureEvents = events - .where((element) => lastIncludedEvent.start.isBefore(element.start)) - .toList(); - return events - .where((element) => !lastIncludedEvent.start.isBefore(element.start)) - .toList(); - } - - List filterUp(Iterable events) { - // Get the first non-parter event that will be shown on the calendar. - CalendarEvent lastIncludedEvent = events.firstWhere( - (event) => event.parentEvent is! PartnerEvent && event.isLasttPart, - orElse: () => events.first, - ); - // Remove anything before - _remainingPastEvents = events - .whereNot((element) => element.start.isBefore(lastIncludedEvent.start)) - .toList(); - return events - .where((element) => !element.start.isBefore(lastIncludedEvent.start)) - .toList(); - } - - Future load() async { - emit(CalendarState(DateTime.now(), const DoubleListState.loading())); - - _debounce = Timer(const Duration(minutes: 10), () => {}); - _truthTime = DateTime.now(); - - try { - final query = _searchQuery; - - // Get first page of events. - final futureEventsResponseFuture = api.getEvents( - start: _splitTime, - search: query, - ordering: 'start', - limit: firstPageSize, - offset: 0, - ); - // get -1st page - final pastEventsResponseFuture = api.getEvents( - end: _splitTime, - search: query, - ordering: '-end', - limit: firstPageSize, - offset: 0, - ); - - // Get all partner events. - final futurePartnerEventsResponseFuture = api.getPartnerEvents( - start: _splitTime, - search: query, - ordering: 'start', - ); - // Get all partner events. - final pastPartnerEventsResponseFuture = api.getPartnerEvents( - start: _splitTime, - search: query, - ordering: '-end', - ); - - // Wait for all the events to come in - final futureEventsResponse = await futureEventsResponseFuture; - final futurePartnerEventsResponse = - await futurePartnerEventsResponseFuture; - final pastEventsResponse = await pastEventsResponseFuture; - final pastPartnerEventsResponse = await pastPartnerEventsResponseFuture; - - // Discard result if _searchQuery has - // changed since the request was made. - if (query != _searchQuery) return; - - _remainingFutureEvents.clear(); - _remainingPastEvents.clear(); - - _nextOffset = futureEventsResponse.results.length; - _nextPastOffset = pastEventsResponse.results.length; - - final isDoneDown = - futureEventsResponse.results.length == futureEventsResponse.count; - final isDoneUp = - pastEventsResponse.results.length == pastEventsResponse.count; - - // Split multi-day events and merge the lists - List futureEvents = [ - ...futurePartnerEventsResponse.results - .expand(CalendarEvent.splitEventIntoCalendarEvents) - .where((element) => !element.start.isBefore(_splitTime)), - ...futureEventsResponse.results - .expand(CalendarEvent.splitEventIntoCalendarEvents) - .where((element) => !element.start.isBefore(_splitTime)), - ]; - - futureEvents.sort((a, b) => a.start.compareTo(b.start)); - - if (!isDoneDown) { - // Filter events that we don't want to show just yet - futureEvents = filterDown(futureEvents); - } - - // Split multi-day events and merge the lists - List pastEvents = [ - ...pastPartnerEventsResponse.results - .expand(CalendarEvent.splitEventIntoCalendarEvents) - .where((element) => element.start.isBefore(_splitTime)), - ...pastEventsResponse.results - .expand(CalendarEvent.splitEventIntoCalendarEvents) - .where((element) => element.start.isBefore(_splitTime)), - ]; - - // Move any events that started before _splitTime but ended after to the - // future events - pastEvents.sort((a, b) => a.start.compareTo(b.start)); - while ( - pastEvents.isNotEmpty && !pastEvents.last.end.isBefore(_splitTime)) { - futureEvents.add(pastEvents.removeLast()); - } - - // Remove the first partner events and day parts of events that could fill - // up the calendar further then where the first not-loaded event will go - // later. - if (!isDoneUp) { - pastEvents = filterUp(pastEvents); - } - - if (pastEvents.isEmpty && futureEvents.isEmpty) { - if (query?.isEmpty ?? true) { - emit(CalendarState(_truthTime, - const DoubleListState.failure(message: 'There are no events.'))); - } else { - emit(CalendarState( - _truthTime, - DoubleListState.failure( - message: 'There are no events found for "$query".', - ))); - } - } else { - emit(CalendarState( - _truthTime, - DoubleListState.success( - resultsUp: pastEvents, - resultsDown: futureEvents, - isDoneUp: isDoneUp, - isDoneDown: isDoneDown))); - } - } on ApiException catch (exception) { - emit(CalendarState( - _truthTime, DoubleListState.failure(message: exception.message))); - } - } - - Future more() async { - final oldState = state; - - // Ignore calls to `more()` if there is no data, or already more coming. - if (oldState.isDoneDown || - oldState.isLoading || - oldState.isLoadingMoreDown) { - return; - } - - emit(CalendarState( - _truthTime, oldState.events.copyWith(isLoadingMoreDown: true))); - try { - final query = _searchQuery; - final start = _splitTime; - - // Get next page of events. - final eventsResponse = await api.getEvents( - start: start, - search: query, - ordering: 'start', - limit: pageSize, - offset: _nextOffset, - ); - - // Discard result if _searchQuery has - // changed since the request was made. - if (query != _searchQuery) return; - - _nextOffset += eventsResponse.results.length; - - List newEvents = [ - ..._remainingFutureEvents, - ...eventsResponse.results - .expand( - CalendarEvent.splitEventIntoCalendarEvents, - ) - .where((element) => !element.start.isBefore(_splitTime)), - ]; - _remainingFutureEvents.clear(); - - // Sort only the new events, because the old events in - // `_state.result` are known to be complete and sorted. - newEvents.sort((a, b) => a.start.compareTo(b.start)); - - // Remove events we don't want to see yet, because we have not loaded up - // to there yet - final isDone = _nextOffset == eventsResponse.count; - if (!isDone) { - newEvents = filterDown(newEvents); - } - - final events = [ - ...oldState.resultsDown, - ...newEvents, - ]; - - emit(CalendarState( - _truthTime, oldState.events.copySuccessDown(events, isDone))); - } on ApiException catch (exception) { - emit(CalendarState( - _truthTime, DoubleListState.failure(message: exception.message))); - } - } - - Future moreUp() async { - final oldState = state; - // Ignore calls to `moreUp()` if there is no data, or already more coming. - if (oldState.isDoneUp || oldState.isLoading || oldState.isLoadingMoreUp) { - return; - } - - emit(CalendarState( - _truthTime, oldState.events.copyWith(isLoadingMoreUp: true))); - try { - final query = _searchQuery; - - // Get next page of events. - final eventsResponse = await api.getEvents( - end: _splitTime, - search: query, - ordering: '-end', - limit: pageSize, - offset: _nextPastOffset, - ); - - // Discard result if _searchQuery has - // changed since the request was made. - if (query != _searchQuery) return; - - _nextPastOffset += eventsResponse.results.length; - - List newEvents = [ - ..._remainingPastEvents, - ...eventsResponse.results - .expand( - CalendarEvent.splitEventIntoCalendarEvents, - ) - .where((element) => element.start.isBefore(_splitTime)), - ]; - _remainingPastEvents.clear(); - - // Sort only the new events, because the old events in - // `_state.result` are known to be complete and sorted. - newEvents.sort((a, b) => a.start.compareTo(b.start)); - - final isDoneUp = _nextPastOffset == eventsResponse.count; - if (!isDoneUp) { - newEvents = filterUp(newEvents); - } - - final events = [ - ...newEvents, - ...oldState.resultsUp, - ]; - - emit(CalendarState( - _truthTime, oldState.events.copySuccessUp(events, isDoneUp))); - } on ApiException catch (exception) { - emit(CalendarState( - _truthTime, DoubleListState.failure(message: exception.message))); - } - } - - /// Set this cubit's `searchQuery` and load the events for that query. - /// - /// Use `null` as argument to remove the search query. - void search(String? query) { - if (query != _searchQuery) { - _remainingFutureEvents = []; - _remainingPastEvents = []; - _searchQuery = query; - _searchDebounceTimer?.cancel(); - if (query?.isEmpty ?? false) { - /// Don't get results when the query is empty. - emit(CalendarState(_truthTime, - const DoubleListState.success(isDoneUp: true, isDoneDown: true))); - } else { - _searchDebounceTimer = Timer(Config.searchDebounceTime, load); - } - } - } -} diff --git a/lib/blocs/liked_photos_cubit.dart b/lib/blocs/liked_photos_cubit.dart deleted file mode 100644 index 4be97d531..000000000 --- a/lib/blocs/liked_photos_cubit.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:reaxit/api/api_repository.dart'; -import 'package:reaxit/api/exceptions.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/models.dart'; -import 'package:reaxit/ui/widgets/gallery.dart'; - -typedef LikedPhotosState = ListState; - -class LikedPhotosCubit extends Cubit - implements GalleryCubit { - static const int firstPageSize = 60; - static const int pageSize = 30; - - final ApiRepository api; - - int _nextOffset = 0; - - LikedPhotosCubit(this.api) - : super(const LikedPhotosState.loading(results: [])); - - Future load() async { - emit(state.copyWith(isLoading: true)); - try { - final photos = await api.getLikedPhotos( - limit: firstPageSize, - offset: 0, - ); - - final isDone = photos.results.length == photos.count; - - _nextOffset = firstPageSize; - - emit(LikedPhotosState.success( - results: photos.results, - isDone: isDone, - count: photos.count, - )); - } on ApiException catch (exception) { - emit(LikedPhotosState.failure(message: exception.message)); - } - } - - @override - Future more() async { - final oldState = state; - - // Ignore calls to `more()` if there is no data, or already more coming. - if (oldState.isDone || oldState.isLoading || oldState.isLoadingMore) return; - - emit(oldState.copyWith(isLoadingMore: true)); - try { - final photosResponse = await api.getLikedPhotos( - limit: pageSize, - offset: _nextOffset, - ); - - final photos = state.results + photosResponse.results; - final isDone = photos.length >= photosResponse.count; - - _nextOffset += pageSize; - - emit(LikedPhotosState.success( - results: photos, - isDone: isDone, - count: photosResponse.count, - )); - } on ApiException catch (exception) { - emit(LikedPhotosState.failure(message: exception.message)); - } - } - - @override - Future updateLike({required bool liked, required int index}) async { - assert(index < state.results.length); - if (state.isLoading) return; - - final oldState = state; - final oldPhoto = oldState.results[index]; - - if (oldPhoto.liked == liked) return; - - // Emit expected state after (un)liking. - AlbumPhoto newphoto = oldPhoto.copyWith( - liked: liked, - numLikes: oldPhoto.numLikes + (liked ? 1 : -1), - ); - - List newphotos = state.results; - newphotos[index] = newphoto; - - emit(state.copyWith(results: newphotos)); - - try { - await api.updateLiked(newphoto.pk, liked); - // If a photo is succesfully unliked, the offset should decrease by 1 - // so the next page is loaded correctly, and vice-versa. - _nextOffset += liked ? 1 : -1; - } on ApiException { - // Revert to state before (un)liking. - emit(oldState); - rethrow; - } - } -} diff --git a/lib/blocs/sales_order_cubit.dart b/lib/blocs/sales_order_cubit.dart deleted file mode 100644 index 7ef27d4a3..000000000 --- a/lib/blocs/sales_order_cubit.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:reaxit/api/api_repository.dart'; -import 'package:reaxit/api/exceptions.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/models.dart'; - -typedef SalesOrderState = DetailState; - -class SalesOrderCubit extends Cubit { - final ApiRepository api; - - SalesOrderCubit(this.api) : super(const LoadingState()); - - Future load(String pk) async { - emit(LoadingState.from(state)); - try { - final order = await api.claimSalesOrder(pk: pk); - emit(ResultState(order)); - } on ApiException catch (exception) { - emit(ErrorState( - exception.getMessage(notFound: 'The order does not exist.'), - )); - } - } - - Future paySalesOrder(String pk) async { - await api.thaliaPaySalesOrder(salesOrderPk: pk); - } -} diff --git a/lib/main.dart b/lib/main.dart index f4dbfef6b..8c7425d21 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -323,11 +323,6 @@ class _ThaliAppState extends State { WelcomeCubit(apiRepository)..load(), lazy: false, ), - BlocProvider( - create: (_) => - CalendarCubit(apiRepository)..cachedLoad(), - lazy: false, - ), BlocProvider( create: (_) => AlbumListCubit(apiRepository)..load(), diff --git a/lib/routes.dart b/lib/routes.dart index dc3b5f941..ca5826af9 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:reaxit/models.dart'; import 'package:reaxit/ui/screens.dart'; -import 'package:reaxit/ui/screens/liked_photos_screen.dart'; import 'package:reaxit/ui/widgets.dart'; /// Returns true if [uri] is a deep link that can be handled by the app. @@ -38,54 +37,11 @@ final List _deepLinkRegExps = [ final List routes = [ GoRoute( - path: '/', - name: 'welcome', - pageBuilder: (context, state) => CustomTransitionPage( - key: state.pageKey, - child: WelcomeScreen(), - transitionDuration: const Duration(milliseconds: 200), - transitionsBuilder: - (context, animation, secondaryAnimation, child) { - return FadeTransition( - opacity: animation.drive(CurveTween(curve: Curves.easeIn)), - child: child, - ); - }, - ), - routes: [ - GoRoute( - path: 'sales/order/:pk/pay', - name: 'sales-order-pay', - pageBuilder: (context, state) { - return CustomTransitionPage( - barrierColor: Colors.black54, - opaque: false, - transitionDuration: const Duration(milliseconds: 150), - transitionsBuilder: ( - context, - animation, - secondaryAnimation, - child, - ) { - return FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: Curves.easeOut, - ), - child: child, - ); - }, - child: SalesOrderDialog(pk: state.pathParameters['pk']!), - ); - }, - ), - ]), - GoRoute( - path: '/events', - name: 'calendar', + path: '/', + name: 'welcome', pageBuilder: (context, state) => CustomTransitionPage( key: state.pageKey, - child: CalendarScreen(), + child: WelcomeScreen(), transitionDuration: const Duration(milliseconds: 200), transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( @@ -127,14 +83,6 @@ final List routes = [ }, ), routes: [ - GoRoute( - path: 'liked-photos', - name: 'liked-photos', - pageBuilder: (context, state) => MaterialPage( - key: state.pageKey, - child: const LikedPhotosScreen(), - ), - ), GoRoute( path: ':albumSlug', name: 'album', diff --git a/lib/ui/screens.dart b/lib/ui/screens.dart index cfd5dc443..5acb8e5df 100644 --- a/lib/ui/screens.dart +++ b/lib/ui/screens.dart @@ -1,6 +1,5 @@ export 'screens/album_screen.dart'; export 'screens/albums_screen.dart'; -export 'screens/calendar_screen.dart'; export 'screens/food_screen.dart'; export 'screens/login_screen.dart'; export 'screens/registration_screen.dart'; diff --git a/lib/ui/screens/calendar_screen.dart b/lib/ui/screens/calendar_screen.dart deleted file mode 100644 index 410f104cd..000000000 --- a/lib/ui/screens/calendar_screen.dart +++ /dev/null @@ -1,383 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:intl/intl.dart'; -import 'package:reaxit/api/api_repository.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/models.dart'; -import 'package:reaxit/ui/widgets.dart'; - -class CalendarScreen extends StatefulWidget { - @override - State createState() => _CalendarScreenState(); -} - -class _CalendarScreenState extends State { - late ScrollController _controller; - late CalendarCubit _cubit; - GlobalKey todayKey = GlobalKey(); - GlobalKey thisMonthKey = GlobalKey(); - double? _todayOffset; - - @override - void initState() { - _cubit = BlocProvider.of(context); - _controller = ScrollController()..addListener(_scrollListener); - WidgetsBinding.instance.endOfFrame.then( - (_) => _scrollToToday(false), - ); - super.initState(); - } - - void _assureTodayOffset() { - if (_todayOffset == null && todayKey.currentContext != null) { - // Calculate the position the widget should be in to avoid being - // drawn under the header - final offset = thisMonthKey.currentContext!.size!.height; - RenderObject renderObject = todayKey.currentContext!.findRenderObject()!; - RenderAbstractViewport viewport = RenderAbstractViewport.of(renderObject); - _todayOffset = - viewport.getOffsetToReveal(renderObject, 0, rect: null).offset - - offset; - } - } - - void _scrollListener() { - if (_controller.position.pixels >= - _controller.position.maxScrollExtent - 300) { - _cubit.more(); - } - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - void openSearch() async { - final searchCubit = CalendarCubit( - RepositoryProvider.of(context), - ); - - await showSearch( - context: context, - delegate: CalendarSearchDelegate(searchCubit), - ); - - searchCubit.close(); - } - - void _scrollToToday(bool animate) { - if (_controller.hasClients) { - _assureTodayOffset(); - if (animate) { - _controller.animateTo( - _todayOffset ?? 0, - duration: const Duration(milliseconds: 500), - curve: Curves.ease, - ); - } else { - _controller.jumpTo(_todayOffset ?? 0); - } - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: ThaliaAppBar( - title: const Text('CALENDAR'), - collapsingActions: [ - IconAppbarAction( - 'SEARCH', - Icons.search, - openSearch, - ) - ], - ), - drawer: MenuDrawer(), - body: BlocBuilder( - builder: (context, calendarState) { - if (calendarState.hasException) { - return ErrorScrollView(calendarState.message!); - } else if (calendarState.isLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - todayKey = GlobalKey(); - thisMonthKey = GlobalKey(); - return CalendarScrollView( - key: const PageStorageKey('calendar'), - controller: _controller, - calendarState: calendarState, - loadMoreUp: _cubit.moreUp, - todayKey: todayKey, - thisMonthKey: thisMonthKey, - now: calendarState.now, - ); - } - }, - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () => _scrollToToday(true), - icon: const Icon(Icons.today), - label: const Text('TODAY'), - ), - ); - } -} - -class CalendarSearchDelegate extends SearchDelegate { - late final ScrollController _controller; - final CalendarCubit _cubit; - - CalendarSearchDelegate(this._cubit) { - _controller = ScrollController()..addListener(_scrollListener); - } - - @override - ThemeData appBarTheme(BuildContext context) { - final theme = super.appBarTheme(context); - return theme.copyWith( - textTheme: theme.textTheme.copyWith( - titleLarge: GoogleFonts.openSans( - textStyle: Theme.of(context).textTheme.titleLarge, - ), - ), - ); - } - - void _scrollListener() { - // Only request loading more if that's not already happening. - if (_controller.position.pixels >= - _controller.position.maxScrollExtent - 300) { - if (!_cubit.state.isLoadingMoreDown) { - _cubit.more(); - } - } - } - - @override - List buildActions(BuildContext context) { - if (query.isNotEmpty) { - return [ - IconButton( - padding: const EdgeInsets.all(16), - tooltip: 'Clear search bar', - icon: const Icon(Icons.clear), - onPressed: () { - query = ''; - }, - ) - ]; - } else { - return []; - } - } - - @override - Widget buildLeading(BuildContext context) { - return BackButton( - onPressed: () => close(context, null), - ); - } - - @override - Widget buildResults(BuildContext context) { - return BlocBuilder( - bloc: _cubit..search(query), - builder: (context, calendarState) { - if (calendarState.hasException) { - return ErrorScrollView(calendarState.message!); - } else if (calendarState.isLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - return CalendarScrollView( - key: const PageStorageKey('calendar-search'), - controller: _controller, - calendarState: calendarState, - loadMoreUp: _cubit.moreUp, - now: calendarState.now, - ); - } - }, - ); - } - - @override - Widget buildSuggestions(BuildContext context) { - if (_controller.hasClients) { - _controller.jumpTo(0); - } - return buildResults(context); - } -} - -/// A ScrollView that shows a calendar with [Event]s. -/// -/// The events are grouped by month, and date. -/// -/// This does not take care of communicating with a Bloc. The [controller] -/// should do that. The [calendarState] also must not have an exception. -class CalendarScrollView extends StatelessWidget { - static final monthFormatter = DateFormat('MMMM'); - static final monthYearFormatter = DateFormat('MMMM yyyy'); - - final GlobalKey? todayKey; - final GlobalKey? thisMonthKey; - - final Key centerkey = UniqueKey(); - final ScrollController controller; - final CalendarState calendarState; - final Function() loadMoreUp; - final List _monthGroupedEventsUp; - final List _monthGroupedEventsDown; - final bool _enableLoadMore; - - final DateTime now; - - CalendarScrollView({ - super.key, - required this.controller, - required this.calendarState, - required this.loadMoreUp, - this.todayKey, - this.thisMonthKey, - required this.now, - }) : _monthGroupedEventsUp = groupByMonth(calendarState.resultsUp) - .sortedBy((element) => element.month) - .reversed - .toList(), - _enableLoadMore = !calendarState.isDoneUp && - calendarState.resultsUp.isNotEmpty && - calendarState.resultsDown.isNotEmpty, - _monthGroupedEventsDown = calendarState.resultsDown.isEmpty - ? List.empty() - : ensureMonthsContainsToday( - groupByMonth(calendarState.resultsDown) - .sortedBy((element) => element.month), - now); - - @override - Widget build(BuildContext context) { - final bool moveMonth = _monthGroupedEventsUp.isNotEmpty && - _monthGroupedEventsDown.isEmpty && - calendarState.isDoneDown; - // If there are no future events we should still display some events - final upEvents = moveMonth - ? _monthGroupedEventsUp.skip(1).toList() - : _monthGroupedEventsUp; - final downEvents = - moveMonth ? [_monthGroupedEventsUp.first] : _monthGroupedEventsDown; - - final ThemeData theme = Theme.of(context); - - ScrollPhysics scrollPhysics = const AlwaysScrollableScrollPhysics(); - return Column( - children: [ - Expanded( - child: CustomScrollView( - controller: controller, - physics: _enableLoadMore - ? OnTopCallbackScrollPhysics( - parent: BouncingScrollPhysics( - decelerationRate: ScrollDecelerationRate.fast, - parent: scrollPhysics, - ), - onhittop: loadMoreUp, - ) - : scrollPhysics, - center: centerkey, - anchor: 0.0, - slivers: [ - if (_enableLoadMore) - SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.only(top: 10), - child: calendarState.isLoadingMoreUp - ? Icon( - Icons.more_horiz, - size: 50, - color: theme.colorScheme.secondary, - ) - : Text( - 'SCROLL TO LOAD MORE', - style: theme.textTheme.bodyLarge!.copyWith( - color: theme.colorScheme.secondary, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 12), - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (_, index) => CalendarMonth( - events: upEvents[index], - todayKey: todayKey, - thisMonthKey: thisMonthKey, - now: now, - ), - childCount: upEvents.length, - ), - ), - ), - SliverPadding( - padding: downEvents.isEmpty - ? EdgeInsets.zero - : const EdgeInsets.fromLTRB(12, 0, 12, 12), - key: centerkey, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (_, index) => CalendarMonth( - events: downEvents[index], - todayKey: todayKey, - thisMonthKey: thisMonthKey, - now: now, - ), - childCount: downEvents.length, - ), - ), - ), - if (calendarState.isLoadingMoreDown) - const SliverPadding( - padding: EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Center(child: CircularProgressIndicator()), - ), - ), - ], - ), - ), - ], - ); - } -} - -class OnTopCallbackScrollPhysics extends ScrollPhysics { - final Function() onhittop; - - const OnTopCallbackScrollPhysics({super.parent, required this.onhittop}); - - @override - OnTopCallbackScrollPhysics applyTo(ScrollPhysics? ancestor) { - return OnTopCallbackScrollPhysics( - parent: buildParent(ancestor), onhittop: onhittop); - } - - @override - Simulation? createBallisticSimulation( - ScrollMetrics position, double velocity) { - if (position.pixels < position.minScrollExtent) { - onhittop(); - } - - return super.createBallisticSimulation(position, velocity); - } -} diff --git a/lib/ui/screens/liked_photos_screen.dart b/lib/ui/screens/liked_photos_screen.dart deleted file mode 100644 index ee22ba26f..000000000 --- a/lib/ui/screens/liked_photos_screen.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:reaxit/api/api_repository.dart'; -import 'package:reaxit/blocs/liked_photos_cubit.dart'; -import 'package:reaxit/ui/widgets.dart'; -import 'package:reaxit/ui/widgets/gallery.dart'; -import 'package:reaxit/ui/widgets/photo_tile.dart'; - -class LikedPhotosScreen extends StatefulWidget { - const LikedPhotosScreen(); - - @override - State createState() => _LikedPhotosScreenState(); -} - -class _LikedPhotosScreenState extends State { - late ScrollController _controller; - late final LikedPhotosCubit _cubit; - - @override - void initState() { - _controller = ScrollController()..addListener(_scrollListener); - _cubit = LikedPhotosCubit(RepositoryProvider.of(context)) - ..load(); - super.initState(); - } - - void _scrollListener() { - if (_controller.position.pixels >= - _controller.position.maxScrollExtent - 300) { - _cubit.more(); - } - } - - @override - void dispose() { - _controller.dispose(); - _cubit.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return BlocProvider.value( - value: _cubit, - child: Scaffold( - appBar: ThaliaAppBar( - title: const Text('LIKED PHOTOS'), - ), - body: RefreshIndicator( - onRefresh: () async { - await _cubit.load(); - }, - child: BlocBuilder( - builder: (context, state) { - if (state.hasException) { - return ErrorScrollView(state.message!); - } else { - return _PhotoGridScrollView( - controller: _controller, - listState: state, - ); - } - }, - ), - ), - ), - ); - } -} - -class _PhotoGridScrollView extends StatelessWidget { - final ScrollController controller; - final LikedPhotosState listState; - - const _PhotoGridScrollView({ - required this.controller, - required this.listState, - }); - - void _openGallery(BuildContext context, int index) { - final cubit = BlocProvider.of(context); - showDialog( - context: context, - useSafeArea: false, - barrierColor: Colors.black.withOpacity(0.92), - builder: (context) { - return BlocProvider.value( - value: cubit, - child: BlocBuilder( - buildWhen: (previous, current) => - !current.isLoading && !current.isLoadingMore, - builder: (context, state) { - return Gallery( - photos: state.results, - initialPage: index, - photoAmount: state.count!, - ); - }, - ), - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scrollbar( - controller: controller, - child: CustomScrollView( - controller: controller, - physics: const RangeMaintainingScrollPhysics( - parent: AlwaysScrollableScrollPhysics(), - ), - slivers: [ - SliverPadding( - padding: const EdgeInsets.all(8), - sliver: SliverGrid( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - ), - delegate: SliverChildBuilderDelegate( - (context, index) => PhotoTile( - photo: listState.results[index], - openGallery: () => _openGallery(context, index), - ), - childCount: listState.results.length, - ), - ), - ), - if (listState.isLoadingMore) - const SliverPadding( - padding: EdgeInsets.all(8), - sliver: SliverList( - delegate: SliverChildListDelegate.fixed( - [Center(child: CircularProgressIndicator())], - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets.dart b/lib/ui/widgets.dart index 329f42407..6c4d8e505 100644 --- a/lib/ui/widgets.dart +++ b/lib/ui/widgets.dart @@ -4,12 +4,10 @@ export 'widgets/cached_image.dart'; export 'widgets/error_center.dart'; export 'widgets/error_scroll_view.dart'; export 'widgets/event_detail_card.dart'; -export 'widgets/events.dart'; export 'widgets/member_tile.dart'; export 'widgets/menu_drawer.dart'; export 'widgets/push_notification_dialog.dart'; export 'widgets/push_notification_overlay.dart'; -export 'widgets/sales_order_dialog.dart'; export 'widgets/tpay_button.dart'; export 'widgets/sortbutton.dart'; export 'widgets/filter_popup.dart'; diff --git a/lib/ui/widgets/events.dart b/lib/ui/widgets/events.dart deleted file mode 100644 index 8368b156d..000000000 --- a/lib/ui/widgets/events.dart +++ /dev/null @@ -1,347 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:intl/intl.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/models.dart'; -import 'package:reaxit/ui/theme.dart'; -import 'package:sticky_headers/sticky_headers.dart'; -import 'package:url_launcher/url_launcher.dart'; - -/// CalendarViewDay holds events attached to a day -class CalendarViewDay { - final DateTime day; - final List events; - - CalendarViewDay({required this.day, required List events}) - : events = events.sortedBy((element) => element.start); -} - -List ensureMonthsContainsToday( - List events, DateTime now) { - DateTime today = DateTime( - now.year, - now.month, - now.day, - ); - DateTime thisMonth = DateTime( - now.year, - now.month, - ); - CalendarViewDay emptyDay = CalendarViewDay(day: today, events: []); - for (var i = 0; i < events.length; i++) { - if (events[i].month.isAfter(thisMonth)) { - // The current month does not exist yet - // Make a new month with only today - events.insert(i, CalendarViewMonth(month: thisMonth, events: [])); - events[i].days.add(emptyDay); - return events; - } - if (events[i].month == thisMonth) { - events[i] = ensureMonthContainsDay(events[i], now.day); - return events; - } - } - // We did not find the current month, and there was no month after the current month - // Add a new month to the end - events.add(CalendarViewMonth(month: thisMonth, events: [])); - events.last.days.add(emptyDay); - - return events; -} - -CalendarViewMonth ensureMonthContainsDay(CalendarViewMonth month, int day) { - DateTime thisMonth = month.month; - DateTime today = DateTime( - thisMonth.year, - thisMonth.month, - day, - ); - - CalendarViewDay emptyDay = CalendarViewDay(day: today, events: []); - // Try to find the right day, or a day after - for (var j = 0; j < month.days.length; j++) { - if (month.days[j].day == today) { - // There already exists a day for today in the calendar - // Nothing to be done - return month; - } - if (month.days[j].day.isAfter(today)) { - // We did not find today, but there was is a day after today - // Insert a new day - month.days.insert(j, emptyDay); - return month; - } - } - // We did not find today, and there was no day after today - // Add a new to to the end - month.days.add(emptyDay); - return month; -} - -List groupByMonth( - List eventList, -) => - groupBy( - eventList, - (event) => DateTime( - event.start.year, - event.start.month, - ), - ) - .entries - .map( - (entry) => CalendarViewMonth(month: entry.key, events: entry.value)) - .toList(); - -/// CalendarViewMonth holds events attached to a month -class CalendarViewMonth { - final DateTime month; - final List days; - - CalendarViewMonth({required this.month, required List events}) - : days = groupBy( - events.sortedBy((element) => element.start), - (event) => DateTime( - event.start.year, - event.start.month, - event.start.day, - ), - ) - .entries - .map( - (entry) => CalendarViewDay(day: entry.key, events: entry.value)) - .sortedBy((element) => element.day); - - List byDay() => days; -} - -class CalendarMonth extends StatelessWidget { - final CalendarViewMonth events; - final Key? todayKey; - final Key? thisMonthKey; - final DateTime now; - - static final monthFormatter = DateFormat('MMMM'); - static final monthYearFormatter = DateFormat('MMMM yyyy'); - - const CalendarMonth( - {required this.events, - this.todayKey, - this.thisMonthKey, - required this.now}); - - @override - Widget build(BuildContext context) { - DateTime today = DateTime( - now.year, - now.month, - now.day, - ); - DateTime thisMonth = DateTime( - now.year, - now.month, - ); - return StickyHeader( - header: Column( - key: events.month == thisMonth ? thisMonthKey : null, - children: [ - SizedBox( - width: double.infinity, - child: Material( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Text( - events.month.year == now.year - ? monthFormatter - .format(events.month.toLocal()) - .toUpperCase() - : monthYearFormatter - .format(events.month.toLocal()) - .toUpperCase(), - style: Theme.of(context).textTheme.titleMedium, - ), - ), - ), - ), - const SizedBox(height: 8), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (final day in events.byDay()) - EventsDayCard( - day: day.day, - events: day.events, - now: now, - key: day.day == today ? todayKey : null), - ], - ), - ); - } -} - -class EventsDayCard extends StatelessWidget { - final DateTime day; - final DateTime now; - final List eventWidgets; - - static final dayFormatter = DateFormat(DateFormat.ABBR_WEEKDAY); - - EventsDayCard( - {required DateTime day, - required List events, - required this.now, - Key? key}) - : eventWidgets = events.map((event) => EventCard(event)).toList(), - day = day.toLocal(), - super(key: key ?? ValueKey(day)); - - @override - Widget build(BuildContext context) { - DateTime today = DateTime( - now.year, - now.month, - now.day, - ); - - final textTheme = Theme.of(context).textTheme; - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 60, - child: Padding( - padding: const EdgeInsets.only(right: 12, top: 4), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - dayFormatter.format(day).toUpperCase(), - style: textTheme.bodySmall!.apply( - color: day == today - ? magenta - : textTheme.bodySmall!.color!.withOpacity(0.5)), - ), - Text( - day.day.toString(), - style: textTheme.displaySmall!.apply( - color: day == today - ? magenta - : textTheme.displaySmall!.color!.withOpacity(0.5)), - strutStyle: const StrutStyle( - forceStrutHeight: true, - leading: 2.2, - ), - ), - ], - ), - ), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: eventWidgets.isNotEmpty - ? eventWidgets - : [ - Center( - child: Text( - 'There are no events this day', - style: TextStyle( - color: - textTheme.displaySmall!.color!.withOpacity(0.5), - fontWeight: FontWeight.bold, - fontSize: 20, - ), - strutStyle: const StrutStyle( - forceStrutHeight: true, - leading: 4, - ), - ), - ) - ], - ), - ), - ], - ); - } -} - -class EventCard extends StatelessWidget { - final CalendarEvent event; - - EventCard(this.event) : super(key: ObjectKey(event)); - - void openEvent(BuildContext context) { - // TODO: because adminevent is also a BaseEvent we should implement it as well, and make it a switch expr - - if (event.parentEvent is PartnerEvent) { - launchUrl( - (event.parentEvent as PartnerEvent).url, - mode: LaunchMode.externalApplication, - ); - } else { - context.pushNamed( - 'event', - pathParameters: {'eventPk': event.pk.toString()}, - extra: event.parentEvent, - ); - } - } - - @override - Widget build(BuildContext context) { - Color color = switch (event.parentEvent) { - Event(isRegistered: var isRegistered) when isRegistered => - Theme.of(context).colorScheme.primary, - PartnerEvent _ => Colors.black, - _ => Colors.grey[800]!, - }; - - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Material( - borderRadius: const BorderRadius.all(Radius.circular(2)), - type: MaterialType.card, - color: color, - child: InkWell( - onTap: () => openEvent(context), - // Prevent painting ink outside of the card. - borderRadius: const BorderRadius.all(Radius.circular(2)), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - event.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - Text( - event.label, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Colors.white.withOpacity(0.8), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/ui/widgets/sales_order_dialog.dart b/lib/ui/widgets/sales_order_dialog.dart deleted file mode 100644 index cb2a2372a..000000000 --- a/lib/ui/widgets/sales_order_dialog.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:reaxit/api/api_repository.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/ui/widgets.dart'; - -class SalesOrderDialog extends StatefulWidget { - final String pk; - - SalesOrderDialog({required this.pk}) : super(key: ValueKey(pk)); - - @override - State createState() => _SalesOrderDialogState(); -} - -class _SalesOrderDialogState extends State { - late final SalesOrderCubit _salesOrderCubit; - - @override - void initState() { - _salesOrderCubit = SalesOrderCubit( - RepositoryProvider.of(context), - )..load(widget.pk); - super.initState(); - } - - @override - void dispose() { - _salesOrderCubit.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final textTheme = Theme.of(context).textTheme; - return BlocBuilder( - bloc: _salesOrderCubit, - builder: (context, orderState) { - final Widget content = switch (orderState) { - ErrorState(message: var messsage) => Text( - messsage, - style: textTheme.bodyMedium, - ), - LoadingState _ => const Center(child: CircularProgressIndicator()), - ResultState(result: var order) when order.numItems == 0 => Text( - 'The order is empty.', - style: textTheme.bodyMedium, - ), - ResultState(result: var order) => Text( - order.orderDescription, - style: textTheme.bodyMedium, - ), - }; - late final Widget payButton = switch (orderState) { - ErrorState _ => const SizedBox.shrink(), - LoadingState _ => const SizedBox.shrink(), - ResultState(result: var order) - when order.totalAmount == '0.00' || !order.tpayAllowed => - const SizedBox.shrink(), - ResultState(result: var order) => TPayButton( - onPay: _paySalesOrder, - confirmationMessage: 'Are you sure you want ' - 'to pay €${order.totalAmount} for your ' - 'order of ${order.orderDescription}?', - failureMessage: 'Could not pay your order.', - successMessage: 'Paid your order with Thalia Pay.', - amount: order.totalAmount, - ), - }; - - return AlertDialog( - title: const Text('Your order'), - content: AnimatedSize( - duration: const Duration(milliseconds: 300), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [content], - ), - ), - actions: [ - TextButton.icon( - onPressed: () => Navigator.of( - context, - rootNavigator: true, - ).pop(), - icon: const Icon(Icons.clear), - label: const Text('CLOSE'), - ), - AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 200), - child: payButton, - ), - ], - ); - }, - ); - } - - Future _paySalesOrder() async { - await _salesOrderCubit.paySalesOrder(widget.pk); - if (!mounted) return; - Navigator.of(context, rootNavigator: true).pop(); - } -} diff --git a/test/unit/calendar_test.dart b/test/unit/calendar_test.dart deleted file mode 100644 index a2515c151..000000000 --- a/test/unit/calendar_test.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/ui/widgets.dart'; - -import '../fakes.dart'; - -void main() { - group('CalendarEvent.splitEventIntoCalendarEvents', () { - test('returns 1 CalendarEvent for a short event', () { - final event = FakeEvent( - pk: 1, - title: 'Lorem', - caption: 'Ipsum', - start: DateTime.parse('2022-03-04 13:37'), - end: DateTime.parse('2022-03-04 14:37'), - location: 'Dolor', - ); - - final calendarEvents = CalendarEvent.splitEventIntoCalendarEvents(event); - - expect(calendarEvents.length, 1); - expect(calendarEvents.first.parentEvent, event); - expect(calendarEvents.first.start, event.start); - expect(calendarEvents.first.end, event.end); - expect(calendarEvents.first.title, event.title); - expect(calendarEvents.first.label, '13:37 - 14:37 | Dolor'); - }); - - test('returns 1 CalendarEvent for an event ending at 00:00', () { - final event = FakeEvent( - pk: 1, - title: 'Lorem', - caption: 'Ipsum', - start: DateTime.parse('2022-03-04 13:37'), - end: DateTime.parse('2022-03-05 00:00'), - location: 'Dolor', - ); - - final calendarEvents = CalendarEvent.splitEventIntoCalendarEvents(event); - - expect(calendarEvents.length, 1); - expect(calendarEvents.first.parentEvent, event); - expect(calendarEvents.first.start, event.start); - expect(calendarEvents.first.end, event.end); - expect(calendarEvents.first.title, event.title); - expect(calendarEvents.first.label, '13:37 - 00:00 | Dolor'); - }); - - test('returns 2 CalendarEvents for a night long event', () { - final event = FakeEvent( - pk: 1, - title: 'Lorem', - caption: 'Ipsum', - start: DateTime.parse('2022-03-04 21:00'), - end: DateTime.parse('2022-03-05 04:00'), - location: 'Dolor', - ); - - final calendarEvents = CalendarEvent.splitEventIntoCalendarEvents(event); - - expect(calendarEvents.length, 2); - - expect(calendarEvents.first.parentEvent, event); - expect(calendarEvents.first.start, event.start); - expect(calendarEvents.first.end, DateTime.parse('2022-03-05 00:00')); - expect(calendarEvents.first.title, 'Lorem day 1/2'); - expect(calendarEvents.first.label, 'From 21:00 | Dolor'); - - expect(calendarEvents.last.parentEvent, event); - expect(calendarEvents.last.start, DateTime.parse('2022-03-05 00:00')); - expect(calendarEvents.last.end, event.end); - expect(calendarEvents.last.title, 'Lorem day 2/2'); - expect(calendarEvents.last.label, 'Until 04:00 | Dolor'); - }); - - test('returns 7 CalendarEvents for a week long event', () { - final event = FakeEvent( - pk: 1, - title: 'Lorem', - caption: 'Ipsum', - start: DateTime.parse('2022-03-04 15:00'), - end: DateTime.parse('2022-03-10 12:00'), - location: 'Dolor', - ); - - final calendarEvents = CalendarEvent.splitEventIntoCalendarEvents(event); - - expect(calendarEvents.length, 7); - - expect(calendarEvents.first.parentEvent, event); - expect(calendarEvents.first.start, event.start); - expect(calendarEvents.first.end, DateTime.parse('2022-03-05 00:00')); - expect(calendarEvents.first.title, 'Lorem day 1/7'); - expect(calendarEvents.first.label, 'From 15:00 | Dolor'); - - expect(calendarEvents[3].parentEvent, event); - expect(calendarEvents[3].start, DateTime.parse('2022-03-07 00:00')); - expect(calendarEvents[3].end, DateTime.parse('2022-03-08 00:00')); - expect(calendarEvents[3].title, 'Lorem day 4/7'); - expect(calendarEvents[3].label, 'All day | Dolor'); - - expect(calendarEvents.last.parentEvent, event); - expect(calendarEvents.last.start, DateTime.parse('2022-03-10 00:00')); - expect(calendarEvents.last.end, event.end); - expect(calendarEvents.last.title, 'Lorem day 7/7'); - expect(calendarEvents.last.label, 'Until 12:00 | Dolor'); - }); - }); - group('CalendarEvent.addsToday', () { - test('EmptyCalendar', () { - final now = DateTime.now(); - final event = FakeEvent( - pk: 1, - title: 'Lorem', - caption: 'Ipsum', - start: DateTime.parse('2022-03-04 13:37'), - end: DateTime.parse('2022-03-04 14:37'), - location: 'Dolor', - ); - - final calendarEvents = CalendarEvent.splitEventIntoCalendarEvents(event); - final monthGroupedEventsDown = ensureMonthsContainsToday( - groupByMonth(calendarEvents).sortedBy((element) => element.month), - now); - - expect(monthGroupedEventsDown.length, 2); - expect(monthGroupedEventsDown[1].days.length, 1); - expect(monthGroupedEventsDown[1].days[0].events.length, 0); - }); - test('AddToEnd', () { - final now = DateTime.parse('2022-04-05 13:37'); - final event = FakeEvent( - pk: 1, - title: 'Lorem', - caption: 'Ipsum', - start: DateTime.parse('2022-03-04 13:37'), - end: DateTime.parse('2022-03-04 14:37'), - location: 'Dolor', - ); - - final calendarEvents = CalendarEvent.splitEventIntoCalendarEvents(event); - final monthGroupedEventsDown = ensureMonthsContainsToday( - groupByMonth(calendarEvents).sortedBy((element) => element.month), - now); - - expect(monthGroupedEventsDown.length, 2); - expect(monthGroupedEventsDown[1].days.length, 1); - expect(monthGroupedEventsDown[1].days[0].events.length, 0); - }); - }); -} diff --git a/test/widget/calendar_test.dart b/test/widget/calendar_test.dart deleted file mode 100644 index 5023a1c03..000000000 --- a/test/widget/calendar_test.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:reaxit/blocs.dart'; -import 'package:reaxit/config.dart'; -import 'package:reaxit/ui/screens.dart'; - -import '../fakes.dart'; - -void main() { - group('CalendarScrollView', () { - testWidgets('renders events correctly', (WidgetTester tester) async { - final event1 = FakeEvent( - pk: 1, - title: 'Lorem 1', - caption: 'Ipsum 1', - start: DateTime.parse('2022-03-04 13:37'), - end: DateTime.parse('2022-03-04 14:37'), - location: 'Dolor 1', - ); - - final event2 = FakeEvent( - pk: 2, - title: 'Lorem 2', - caption: 'Ipsum 2', - start: DateTime.parse('2022-04-29 15:00'), - end: DateTime.parse('2022-05-01 12:00'), - location: 'Dolor 2', - ); - DateTime now = DateTime.now(); - final state = CalendarState( - now, - DoubleListState.success( - resultsDown: [ - ...CalendarEvent.splitEventIntoCalendarEvents(event1), - ...CalendarEvent.splitEventIntoCalendarEvents(event2), - ], - isDoneDown: true, - isDoneUp: true, - )); - - await tester.pumpWidget( - MaterialApp( - home: InheritedConfig( - config: Config.defaultConfig, - child: Scaffold( - body: CalendarScrollView( - controller: ScrollController(), - calendarState: state, - loadMoreUp: (() {}), - now: now, - ), - ), - ), - ), - ); - - expect(find.text('Lorem 1'), findsOneWidget); - expect(find.textContaining('APRIL 2022'), findsOneWidget); - expect(find.textContaining('MAY 2022'), findsOneWidget); - expect(find.textContaining('Lorem 2'), findsNWidgets(3)); - }); - testWidgets('adds today', (WidgetTester tester) async { - final now = DateTime.now(); - final event1 = FakeEvent( - pk: 1, - title: 'Lorem 1', - caption: 'Ipsum 1', - start: now.add(const Duration(days: 3)), - end: now.add(const Duration(days: 4)), - location: 'Dolor 1', - ); - final state = CalendarState( - now, - DoubleListState.success( - resultsDown: [ - ...CalendarEvent.splitEventIntoCalendarEvents(event1), - ], - isDoneDown: true, - isDoneUp: true, - )); - - await tester.pumpWidget( - MaterialApp( - home: InheritedConfig( - config: Config.defaultConfig, - child: Scaffold( - body: CalendarScrollView( - controller: ScrollController(), - calendarState: state, - loadMoreUp: (() {}), - now: now, - ), - ), - ), - ), - ); - expect(find.text('There are no events this day'), findsOneWidget); - }); - }); -}