dart format

This commit is contained in:
2026-05-08 20:12:40 +02:00
parent 9e139b5704
commit 3b8da1d3d6
295 changed files with 6404 additions and 4161 deletions
@@ -4,8 +4,11 @@ import 'account_event.dart';
import 'account_state.dart';
class AccountBloc extends Bloc<AccountEvent, AccountState> {
AccountBloc({AccountStatus initialStatus = AccountStatus.undefined}) : super(AccountState(status: initialStatus)) {
on<AccountStatusChanged>((event, emit) => emit(state.copyWith(status: event.status)));
AccountBloc({AccountStatus initialStatus = AccountStatus.undefined})
: super(AccountState(status: initialStatus)) {
on<AccountStatusChanged>(
(event, emit) => emit(state.copyWith(status: event.status)),
);
}
void setStatus(AccountStatus status) => add(AccountStatusChanged(status));
@@ -4,5 +4,6 @@ class AccountState {
final AccountStatus status;
const AccountState({this.status = AccountStatus.undefined});
AccountState copyWith({AccountStatus? status}) => AccountState(status: status ?? this.status);
AccountState copyWith({AccountStatus? status}) =>
AccountState(status: status ?? this.status);
}
+66 -17
View File
@@ -27,9 +27,18 @@ class AppModule {
BreakerArea breakerArea;
Widget Function() create;
AppModule(this.module, {required this.name, required this.icon, this.breakerArea = BreakerArea.global, required this.create});
AppModule(
this.module, {
required this.name,
required this.icon,
this.breakerArea = BreakerArea.global,
required this.create,
});
static Map<Modules, AppModule> modules(BuildContext context, {bool showFiltered = false}) {
static Map<Modules, AppModule> modules(
BuildContext context, {
bool showFiltered = false,
}) {
final settings = context.read<SettingsCubit>();
var available = {
Modules.timetable: AppModule(
@@ -45,8 +54,12 @@ class AppModule {
icon: () => BlocBuilder<ChatListBloc, LoadableState<ChatListState>>(
builder: (context, state) {
final rooms = state.data?.rooms;
if (rooms == null || rooms.data.isEmpty) return const Icon(Icons.chat);
final messages = rooms.data.map((e) => e.unreadMessages).reduce((a, b) => a + b);
if (rooms == null || rooms.data.isEmpty) {
return const Icon(Icons.chat);
}
final messages = rooms.data
.map((e) => e.unreadMessages)
.reduce((a, b) => a + b);
return badges.Badge(
showBadge: messages > 0,
position: badges.BadgePosition.topEnd(top: -3, end: -3),
@@ -56,7 +69,14 @@ class AppModule {
badgeColor: Theme.of(context).primaryColor,
elevation: 1,
),
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
badgeContent: Text(
'$messages',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
child: const Icon(Icons.chat),
);
},
@@ -108,9 +128,19 @@ class AppModule {
),
};
if (!showFiltered) available.removeWhere((key, value) => settings.val().modulesSettings.hiddenModules.contains(key));
if (!showFiltered) {
available.removeWhere(
(key, value) =>
settings.val().modulesSettings.hiddenModules.contains(key),
);
}
return { for (var element in settings.val().modulesSettings.moduleOrder.where((element) => available.containsKey(element))) element : available[element]! };
return {
for (var element in settings.val().modulesSettings.moduleOrder.where(
(element) => available.containsKey(element),
))
element: available[element]!,
};
}
static const int minBottomBarSlots = 3;
@@ -150,26 +180,45 @@ class AppModule {
return all.skip(slots).toList();
}
Widget toListTile(BuildContext context, {Key? key, bool isReorder = false, Function()? onVisibleChange, bool isVisible = true}) => ListTile(
Widget toListTile(
BuildContext context, {
Key? key,
bool isReorder = false,
Function()? onVisibleChange,
bool isVisible = true,
}) => ListTile(
key: key,
leading: CenteredLeading(icon()),
title: Text(name),
onTap: isReorder ? null : () => AppRoutes.openModule(context, this),
trailing: isReorder
? Row(mainAxisSize: MainAxisSize.min, children: [
IconButton(onPressed: onVisibleChange, icon: Icon(isVisible ? Icons.visibility_outlined : Icons.visibility_off_outlined)),
Icon(Icons.drag_handle_outlined)
])
? Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: onVisibleChange,
icon: Icon(
isVisible
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
),
Icon(Icons.drag_handle_outlined),
],
)
: const Icon(Icons.arrow_right),
);
PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? iconBuilder}) => PersistentTabConfig(
PersistentTabConfig toBottomTab(
BuildContext context, {
Widget Function(IconData icon)? iconBuilder,
}) => PersistentTabConfig(
screen: Breaker(breaker: breakerArea, child: create()),
item: ItemConfig(
activeForegroundColor: Theme.of(context).primaryColor,
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
icon: icon(),
title: name
activeForegroundColor: Theme.of(context).primaryColor,
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
icon: icon(),
title: name,
),
);
}
@@ -8,7 +8,9 @@ import '../repository/breaker_repository.dart';
import 'breaker_event.dart';
import 'breaker_state.dart';
class BreakerBloc extends LoadableHydratedBloc<BreakerEvent, BreakerState, BreakerRepository> {
class BreakerBloc
extends
LoadableHydratedBloc<BreakerEvent, BreakerState, BreakerRepository> {
PackageInfo? _packageInfo;
@override
@@ -18,7 +20,8 @@ class BreakerBloc extends LoadableHydratedBloc<BreakerEvent, BreakerState, Break
BreakerState fromNothing() => const BreakerState();
@override
BreakerState fromStorage(Map<String, dynamic> json) => BreakerState.fromJson(json);
BreakerState fromStorage(Map<String, dynamic> json) =>
BreakerState.fromJson(json);
@override
Map<String, dynamic>? toStorage(BreakerState state) => state.toJson();
@@ -7,9 +7,8 @@ part 'breaker_state.g.dart';
@freezed
abstract class BreakerState with _$BreakerState {
const factory BreakerState({
GetBreakersResponse? response,
}) = _BreakerState;
const factory BreakerState({GetBreakersResponse? response}) = _BreakerState;
factory BreakerState.fromJson(Map<String, Object?> json) => _$BreakerStateFromJson(json);
factory BreakerState.fromJson(Map<String, Object?> json) =>
_$BreakerStateFromJson(json);
}
@@ -6,9 +6,11 @@ import '../../../../../api/mhsl/breaker/get_breakers/get_breakers_response.dart'
class BreakerDataProvider {
Future<GetBreakersResponse> getBreakers() {
final completer = Completer<GetBreakersResponse>();
GetBreakersCache(onUpdate: (data) {
if (!completer.isCompleted) completer.complete(data);
});
GetBreakersCache(
onUpdate: (data) {
if (!completer.isCompleted) completer.complete(data);
},
);
return completer.future;
}
}
@@ -5,7 +5,8 @@ import '../data_provider/breaker_data_provider.dart';
class BreakerRepository extends Repository<BreakerState> {
final BreakerDataProvider _provider;
BreakerRepository([BreakerDataProvider? provider]) : _provider = provider ?? BreakerDataProvider();
BreakerRepository([BreakerDataProvider? provider])
: _provider = provider ?? BreakerDataProvider();
BreakerDataProvider get data => _provider;
}
+11 -6
View File
@@ -6,7 +6,8 @@ import '../repository/chat_repository.dart';
import 'chat_event.dart';
import 'chat_state.dart';
class ChatBloc extends LoadableHydratedBloc<ChatEvent, ChatState, ChatRepository> {
class ChatBloc
extends LoadableHydratedBloc<ChatEvent, ChatState, ChatRepository> {
DateTime _lastTokenSet = DateTime.fromMillisecondsSinceEpoch(0);
@override
@@ -86,11 +87,15 @@ class ChatBloc extends LoadableHydratedBloc<ChatEvent, ChatState, ChatRepository
if (!stillCurrent()) return;
if (capturedError != null) {
add(Error(LoadingError(
message: errorToUserMessage(capturedError),
technicalDetails: errorToTechnicalDetails(capturedError),
allowRetry: errorAllowsRetry(capturedError),
)));
add(
Error(
LoadingError(
message: errorToUserMessage(capturedError),
technicalDetails: errorToTechnicalDetails(capturedError),
allowRetry: errorAllowsRetry(capturedError),
),
),
);
}
}
}
@@ -13,5 +13,6 @@ abstract class ChatState with _$ChatState {
int? referenceMessageId,
}) = _ChatState;
factory ChatState.fromJson(Map<String, Object?> json) => _$ChatStateFromJson(json);
factory ChatState.fromJson(Map<String, Object?> json) =>
_$ChatStateFromJson(json);
}
@@ -5,7 +5,8 @@ import '../data_provider/chat_data_provider.dart';
class ChatRepository extends Repository<ChatState> {
final ChatDataProvider _provider;
ChatRepository([ChatDataProvider? provider]) : _provider = provider ?? ChatDataProvider();
ChatRepository([ChatDataProvider? provider])
: _provider = provider ?? ChatDataProvider();
ChatDataProvider get data => _provider;
}
@@ -11,7 +11,9 @@ import '../repository/chat_list_repository.dart';
import 'chat_list_event.dart';
import 'chat_list_state.dart';
class ChatListBloc extends LoadableHydratedBloc<ChatListEvent, ChatListState, ChatListRepository> {
class ChatListBloc
extends
LoadableHydratedBloc<ChatListEvent, ChatListState, ChatListRepository> {
bool _forceRenew = false;
@override
@@ -27,7 +29,8 @@ class ChatListBloc extends LoadableHydratedBloc<ChatListEvent, ChatListState, Ch
ChatListState fromNothing() => const ChatListState();
@override
ChatListState fromStorage(Map<String, dynamic> json) => ChatListState.fromJson(json);
ChatListState fromStorage(Map<String, dynamic> json) =>
ChatListState.fromJson(json);
@override
Map<String, dynamic>? toStorage(ChatListState state) => state.toJson();
@@ -62,11 +65,15 @@ class ChatListBloc extends LoadableHydratedBloc<ChatListEvent, ChatListState, Ch
capturedError = e;
}
if (capturedError != null) {
add(Error(LoadingError(
message: errorToUserMessage(capturedError),
technicalDetails: errorToTechnicalDetails(capturedError),
allowRetry: errorAllowsRetry(capturedError),
)));
add(
Error(
LoadingError(
message: errorToUserMessage(capturedError),
technicalDetails: errorToTechnicalDetails(capturedError),
allowRetry: errorAllowsRetry(capturedError),
),
),
);
}
}
@@ -77,7 +84,10 @@ class ChatListBloc extends LoadableHydratedBloc<ChatListEvent, ChatListState, Ch
void _updateAppBadge(GetRoomResponse rooms) {
try {
final unread = rooms.data.fold<int>(0, (a, room) => a + room.unreadMessages);
final unread = rooms.data.fold<int>(
0,
(a, room) => a + room.unreadMessages,
);
FlutterAppBadge.count(unread);
} on Object catch (e) {
log('Failed to update app badge: $e');
@@ -7,9 +7,8 @@ part 'chat_list_state.g.dart';
@freezed
abstract class ChatListState with _$ChatListState {
const factory ChatListState({
GetRoomResponse? rooms,
}) = _ChatListState;
const factory ChatListState({GetRoomResponse? rooms}) = _ChatListState;
factory ChatListState.fromJson(Map<String, Object?> json) => _$ChatListStateFromJson(json);
factory ChatListState.fromJson(Map<String, Object?> json) =>
_$ChatListStateFromJson(json);
}
@@ -8,16 +8,12 @@ class ChatListDataProvider {
Future<GetRoomResponse> getRooms({
void Function(Object)? onError,
bool renew = false,
}) =>
resolveFromCache<GetRoomResponse>(
(onUpdate, onError) => GetRoomCache(
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getRooms',
);
}) => resolveFromCache<GetRoomResponse>(
(onUpdate, onError) =>
GetRoomCache(renew: renew, onUpdate: onUpdate, onError: onError),
onError: onError,
operationName: 'getRooms',
);
Future<void> createDirectRoom(String invite) =>
CreateRoom(CreateRoomParams(roomType: 1, invite: invite)).run();
@@ -5,7 +5,8 @@ import '../data_provider/chat_list_data_provider.dart';
class ChatListRepository extends Repository<ChatListState> {
final ChatListDataProvider _provider;
ChatListRepository([ChatListDataProvider? provider]) : _provider = provider ?? ChatListDataProvider();
ChatListRepository([ChatListDataProvider? provider])
: _provider = provider ?? ChatListDataProvider();
ChatListDataProvider get data => _provider;
}
@@ -7,7 +7,8 @@ import '../repository/files_repository.dart';
import 'files_event.dart';
import 'files_state.dart';
class FilesBloc extends LoadableHydratedBloc<FilesEvent, FilesState, FilesRepository> {
class FilesBloc
extends LoadableHydratedBloc<FilesEvent, FilesState, FilesRepository> {
final List<String> initialPath;
FilesBloc({this.initialPath = const []});
@@ -19,7 +20,8 @@ class FilesBloc extends LoadableHydratedBloc<FilesEvent, FilesState, FilesReposi
FilesState fromNothing() => FilesState(currentPath: initialPath);
@override
FilesState fromStorage(Map<String, dynamic> json) => FilesState.fromJson(json);
FilesState fromStorage(Map<String, dynamic> json) =>
FilesState.fromJson(json);
@override
Map<String, dynamic>? toStorage(FilesState state) => null;
@@ -60,7 +62,9 @@ class FilesBloc extends LoadableHydratedBloc<FilesEvent, FilesState, FilesReposi
// Cached payload arrives before the network call settles. Surface it
// immediately via Emit so the listing is visible while isLoading
// stays true and the top loading bar keeps spinning.
cached.files.removeWhere((file) => file.name.isEmpty || file.name == path.lastOrNull);
cached.files.removeWhere(
(file) => file.name.isEmpty || file.name == path.lastOrNull,
);
add(Emit((s) => s.copyWith(listing: cached)));
},
onError: (e) => capturedError = e,
@@ -70,15 +74,21 @@ class FilesBloc extends LoadableHydratedBloc<FilesEvent, FilesState, FilesReposi
}
if (listing != null) {
listing.files.removeWhere((file) => file.name.isEmpty || file.name == path.lastOrNull);
listing.files.removeWhere(
(file) => file.name.isEmpty || file.name == path.lastOrNull,
);
add(DataGathered((s) => s.copyWith(listing: listing)));
}
if (capturedError != null) {
add(Error(LoadingError(
message: errorToUserMessage(capturedError),
technicalDetails: errorToTechnicalDetails(capturedError),
allowRetry: errorAllowsRetry(capturedError),
)));
add(
Error(
LoadingError(
message: errorToUserMessage(capturedError),
technicalDetails: errorToTechnicalDetails(capturedError),
allowRetry: errorAllowsRetry(capturedError),
),
),
);
}
}
}
@@ -12,5 +12,6 @@ abstract class FilesState with _$FilesState {
ListFilesResponse? listing,
}) = _FilesState;
factory FilesState.fromJson(Map<String, Object?> json) => _$FilesStateFromJson(json);
factory FilesState.fromJson(Map<String, Object?> json) =>
_$FilesStateFromJson(json);
}
@@ -15,17 +15,16 @@ class FilesDataProvider {
String path, {
void Function(ListFilesResponse)? onCacheData,
void Function(Object)? onError,
}) =>
resolveFromCache<ListFilesResponse>(
(onUpdate, onError) => ListFilesCache(
path: path,
onUpdate: onUpdate,
onCacheData: onCacheData,
onError: onError,
),
onError: onError,
operationName: 'listFiles',
);
}) => resolveFromCache<ListFilesResponse>(
(onUpdate, onError) => ListFilesCache(
path: path,
onUpdate: onUpdate,
onCacheData: onCacheData,
onError: onError,
),
onError: onError,
operationName: 'listFiles',
);
Future<void> createFolder(String fullPath) async {
final webdav = await WebdavApi.webdav;
@@ -5,7 +5,8 @@ import '../data_provider/files_data_provider.dart';
class FilesRepository extends Repository<FilesState> {
final FilesDataProvider _provider;
FilesRepository([FilesDataProvider? provider]) : _provider = provider ?? FilesDataProvider();
FilesRepository([FilesDataProvider? provider])
: _provider = provider ?? FilesDataProvider();
FilesDataProvider get data => _provider;
}
@@ -3,17 +3,23 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'grade_averages_event.dart';
import 'grade_averages_state.dart';
class GradeAveragesBloc extends HydratedBloc<GradeAveragesEvent, GradeAveragesState> {
GradeAveragesBloc() : super(const GradeAveragesState(gradingSystem: GradeAveragesGradingSystem.middleSchool, grades: [])) {
class GradeAveragesBloc
extends HydratedBloc<GradeAveragesEvent, GradeAveragesState> {
GradeAveragesBloc()
: super(
const GradeAveragesState(
gradingSystem: GradeAveragesGradingSystem.middleSchool,
grades: [],
),
) {
on<GradingSystemChanged>((event, emit) {
add(ResetAll());
emit(
state.copyWith(
gradingSystem: event.isMiddleSchool
? GradeAveragesGradingSystem.middleSchool
: GradeAveragesGradingSystem.highSchool
)
? GradeAveragesGradingSystem.middleSchool
: GradeAveragesGradingSystem.highSchool,
),
);
});
@@ -22,7 +28,12 @@ class GradeAveragesBloc extends HydratedBloc<GradeAveragesEvent, GradeAveragesSt
});
on<ResetGrade>((event, emit) {
emit(state.copyWith(grades: [...state.grades]..removeWhere((grade) => grade == event.grade)));
emit(
state.copyWith(
grades: [...state.grades]
..removeWhere((grade) => grade == event.grade),
),
);
});
on<IncrementGrade>((event, emit) {
@@ -30,20 +41,26 @@ class GradeAveragesBloc extends HydratedBloc<GradeAveragesEvent, GradeAveragesSt
});
on<DecrementGrade>((event, emit) {
emit(state.copyWith(grades: List.from(state.grades)..remove(event.grade)));
emit(
state.copyWith(grades: List.from(state.grades)..remove(event.grade)),
);
});
}
double average() => state.grades.isEmpty ? 0 : state.grades.reduce((a, b) => a + b) / state.grades.length;
bool isMiddleSchool() => state.gradingSystem == GradeAveragesGradingSystem.middleSchool;
double average() => state.grades.isEmpty
? 0
: state.grades.reduce((a, b) => a + b) / state.grades.length;
bool isMiddleSchool() =>
state.gradingSystem == GradeAveragesGradingSystem.middleSchool;
bool canDecrementOrDelete(int grade) => state.grades.contains(grade);
int countOfGrade(int grade) => state.grades.where((g) => g == grade).length;
int gradesInGradingSystem() => state.gradingSystem == GradeAveragesGradingSystem.middleSchool ? 6 : 16;
int gradesInGradingSystem() =>
state.gradingSystem == GradeAveragesGradingSystem.middleSchool ? 6 : 16;
int getGradeFromIndex(int index) => isMiddleSchool() ? index + 1 : 15 - index;
@override
GradeAveragesState? fromJson(Map<String, dynamic> json) => GradeAveragesState.fromJson(json);
GradeAveragesState? fromJson(Map<String, dynamic> json) =>
GradeAveragesState.fromJson(json);
@override
Map<String, dynamic>? toJson(GradeAveragesState state) => state.toJson();
}
@@ -1,19 +1,22 @@
sealed class GradeAveragesEvent {}
final class GradingSystemChanged extends GradeAveragesEvent {
final bool isMiddleSchool;
GradingSystemChanged(this.isMiddleSchool);
}
final class ResetAll extends GradeAveragesEvent {}
final class ResetGrade extends GradeAveragesEvent {
final int grade;
ResetGrade(this.grade);
}
final class IncrementGrade extends GradeAveragesEvent {
final int grade;
IncrementGrade(this.grade);
}
final class DecrementGrade extends GradeAveragesEvent {
final int grade;
DecrementGrade(this.grade);
@@ -10,10 +10,8 @@ abstract class GradeAveragesState with _$GradeAveragesState {
required List<int> grades,
}) = _GradeAveragesState;
factory GradeAveragesState.fromJson(Map<String, dynamic> json) => _$GradeAveragesStateFromJson(json);
factory GradeAveragesState.fromJson(Map<String, dynamic> json) =>
_$GradeAveragesStateFromJson(json);
}
enum GradeAveragesGradingSystem {
highSchool,
middleSchool,
}
enum GradeAveragesGradingSystem { highSchool, middleSchool }
@@ -4,32 +4,51 @@ import '../repository/holidays_repository.dart';
import 'holidays_event.dart';
import 'holidays_state.dart';
class HolidaysBloc extends LoadableHydratedBloc<HolidaysEvent, HolidaysState, HolidaysRepository> {
class HolidaysBloc
extends
LoadableHydratedBloc<HolidaysEvent, HolidaysState, HolidaysRepository> {
HolidaysBloc() {
on<SetPastHolidaysVisible>((event, emit) {
add(Emit((state) => state.copyWith(showPastHolidays: event.shouldBeVisible)));
add(
Emit(
(state) => state.copyWith(showPastHolidays: event.shouldBeVisible),
),
);
});
on<DisclaimerDismissed>((event, emit) => add(
Emit((state) => state.copyWith(showDisclaimer: false))
));
on<DisclaimerDismissed>(
(event, emit) =>
add(Emit((state) => state.copyWith(showDisclaimer: false))),
);
}
bool showPastHolidays() => innerState?.showPastHolidays ?? false;
bool showDisclaimerOnEntry() => innerState?.showDisclaimer ?? false;
List<Holiday>? getHolidays() => innerState?.holidays
.where((element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now()))
.toList() ?? [];
List<Holiday>? getHolidays() =>
innerState?.holidays
.where(
(element) =>
showPastHolidays() ||
DateTime.parse(element.end).isAfter(DateTime.now()),
)
.toList() ??
[];
@override
HolidaysState fromNothing() => const HolidaysState(showPastHolidays: false, holidays: [], showDisclaimer: true);
HolidaysState fromNothing() => const HolidaysState(
showPastHolidays: false,
holidays: [],
showDisclaimer: true,
);
@override
HolidaysState fromStorage(Map<String, dynamic> json) => HolidaysState.fromJson(json);
HolidaysState fromStorage(Map<String, dynamic> json) =>
HolidaysState.fromJson(json);
@override
Future<void> gatherData() async {
var holidays = await repo.getHolidays();
add(DataGathered((state) => state.copyWith(holidays: holidays)));
}
@override
HolidaysRepository repository() => HolidaysRepository();
@override
@@ -2,8 +2,10 @@ import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_
import 'holidays_state.dart';
sealed class HolidaysEvent extends LoadableHydratedBlocEvent<HolidaysState> {}
class SetPastHolidaysVisible extends HolidaysEvent {
final bool shouldBeVisible;
SetPastHolidaysVisible(this.shouldBeVisible);
}
class DisclaimerDismissed extends HolidaysEvent {}
@@ -12,7 +12,8 @@ abstract class HolidaysState with _$HolidaysState {
required List<Holiday> holidays,
}) = _HolidaysState;
factory HolidaysState.fromJson(Map<String, Object?> json) => _$HolidaysStateFromJson(json);
factory HolidaysState.fromJson(Map<String, Object?> json) =>
_$HolidaysStateFromJson(json);
}
@freezed
@@ -26,5 +27,6 @@ abstract class Holiday with _$Holiday {
required String slug,
}) = _Holiday;
factory Holiday.fromJson(Map<String, Object?> json) => _$HolidayFromJson(json);
factory Holiday.fromJson(Map<String, Object?> json) =>
_$HolidayFromJson(json);
}
@@ -6,7 +6,8 @@ import '../bloc/holidays_state.dart';
class HolidaysGetHolidays extends HolidayDataLoader<List<Holiday>> {
@override
List<Holiday> assemble(DataLoaderResult data) => data.asListOfMaps().map(Holiday.fromJson).toList();
List<Holiday> assemble(DataLoaderResult data) =>
data.asListOfMaps().map(Holiday.fromJson).toList();
@override
Future<Response<String>> fetch() => dio.get('/holidays/HE');
@@ -4,28 +4,41 @@ import '../repository/marianum_dates_repository.dart';
import 'marianum_dates_event.dart';
import 'marianum_dates_state.dart';
class MarianumDatesBloc extends LoadableHydratedBloc<MarianumDatesEvent, MarianumDatesState, MarianumDatesRepository> {
class MarianumDatesBloc
extends
LoadableHydratedBloc<
MarianumDatesEvent,
MarianumDatesState,
MarianumDatesRepository
> {
MarianumDatesBloc() {
on<SetPastEventsVisible>((event, emit) {
add(Emit((state) => state.copyWith(showPastEvents: event.shouldBeVisible)));
add(
Emit((state) => state.copyWith(showPastEvents: event.shouldBeVisible)),
);
});
}
bool showPastEvents() => innerState?.showPastEvents ?? false;
List<MarianumDate>? getEvents() => innerState?.events
.where((e) => showPastEvents() || e.end.isAfter(DateTime.now()))
.toList() ?? [];
List<MarianumDate>? getEvents() =>
innerState?.events
.where((e) => showPastEvents() || e.end.isAfter(DateTime.now()))
.toList() ??
[];
@override
MarianumDatesState fromNothing() => const MarianumDatesState(showPastEvents: false, events: []);
MarianumDatesState fromNothing() =>
const MarianumDatesState(showPastEvents: false, events: []);
@override
MarianumDatesState fromStorage(Map<String, dynamic> json) => MarianumDatesState.fromJson(json);
MarianumDatesState fromStorage(Map<String, dynamic> json) =>
MarianumDatesState.fromJson(json);
@override
Future<void> gatherData() async {
final events = await repo.getEvents();
add(DataGathered((state) => state.copyWith(events: events)));
}
@override
MarianumDatesRepository repository() => MarianumDatesRepository();
@override
@@ -1,7 +1,8 @@
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
import 'marianum_dates_state.dart';
sealed class MarianumDatesEvent extends LoadableHydratedBlocEvent<MarianumDatesState> {}
sealed class MarianumDatesEvent
extends LoadableHydratedBlocEvent<MarianumDatesState> {}
class SetPastEventsVisible extends MarianumDatesEvent {
final bool shouldBeVisible;
@@ -11,7 +11,8 @@ abstract class MarianumDatesState with _$MarianumDatesState {
required List<MarianumDate> events,
}) = _MarianumDatesState;
factory MarianumDatesState.fromJson(Map<String, Object?> json) => _$MarianumDatesStateFromJson(json);
factory MarianumDatesState.fromJson(Map<String, Object?> json) =>
_$MarianumDatesStateFromJson(json);
}
@freezed
@@ -25,5 +26,6 @@ abstract class MarianumDate with _$MarianumDate {
required bool isAllDay,
}) = _MarianumDate;
factory MarianumDate.fromJson(Map<String, Object?> json) => _$MarianumDateFromJson(json);
factory MarianumDate.fromJson(Map<String, Object?> json) =>
_$MarianumDateFromJson(json);
}
@@ -4,12 +4,15 @@ import 'package:enough_icalendar/enough_icalendar.dart';
import '../bloc/marianum_dates_state.dart';
class MarianumDatesGetEvents {
static const String url = 'https://public-cal.marianumlan.de/cal_public/ad4c5da8-7466-9c72-89cb-8b8d9a5cf26c';
static const String url =
'https://public-cal.marianumlan.de/cal_public/ad4c5da8-7466-9c72-89cb-8b8d9a5cf26c';
final Dio _dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
));
final Dio _dio = Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
),
);
Future<List<MarianumDate>> run() async {
final response = await _dio.get<String>(url);
@@ -20,7 +23,11 @@ class MarianumDatesGetEvents {
final calendar = root is VCalendar ? root : null;
final source = calendar?.children ?? root.children;
final events = source.whereType<VEvent>().map(_toMarianumDate).whereType<MarianumDate>().toList();
final events = source
.whereType<VEvent>()
.map(_toMarianumDate)
.whereType<MarianumDate>()
.toList();
events.sort((a, b) => a.start.compareTo(b.start));
return events;
}
@@ -41,8 +48,11 @@ class MarianumDatesGetEvents {
}
static bool _isAllDay(DateTime start, DateTime end) {
final startMidnight = start.hour == 0 && start.minute == 0 && start.second == 0;
final startMidnight =
start.hour == 0 && start.minute == 0 && start.second == 0;
final endMidnight = end.hour == 0 && end.minute == 0 && end.second == 0;
return startMidnight && endMidnight && end.difference(start).inHours % 24 == 0;
return startMidnight &&
endMidnight &&
end.difference(start).inHours % 24 == 0;
}
}
@@ -4,7 +4,13 @@ import '../repository/marianum_message_repository.dart';
import 'marianum_message_event.dart';
import 'marianum_message_state.dart';
class MarianumMessageBloc extends LoadableHydratedBloc<MarianumMessageEvent, MarianumMessageState, MarianumMessageRepository> {
class MarianumMessageBloc
extends
LoadableHydratedBloc<
MarianumMessageEvent,
MarianumMessageState,
MarianumMessageRepository
> {
@override
Future<void> gatherData() async {
var messages = await repo.getMessages();
@@ -15,10 +21,13 @@ class MarianumMessageBloc extends LoadableHydratedBloc<MarianumMessageEvent, Mar
MarianumMessageRepository repository() => MarianumMessageRepository();
@override
MarianumMessageState fromNothing() => const MarianumMessageState(messageList: MarianumMessageList(base: '', messages: []));
MarianumMessageState fromNothing() => const MarianumMessageState(
messageList: MarianumMessageList(base: '', messages: []),
);
@override
MarianumMessageState fromStorage(Map<String, dynamic> json) => MarianumMessageState.fromJson(json);
MarianumMessageState fromStorage(Map<String, dynamic> json) =>
MarianumMessageState.fromJson(json);
@override
Map<String, dynamic>? toStorage(MarianumMessageState state) => state.toJson();
}
@@ -1,5 +1,7 @@
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
import 'marianum_message_state.dart';
sealed class MarianumMessageEvent extends LoadableHydratedBlocEvent<MarianumMessageState> {}
sealed class MarianumMessageEvent
extends LoadableHydratedBlocEvent<MarianumMessageState> {}
class MessageEvent extends MarianumMessageEvent {}
@@ -3,14 +3,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'marianum_message_state.freezed.dart';
part 'marianum_message_state.g.dart';
@freezed
abstract class MarianumMessageState with _$MarianumMessageState {
const factory MarianumMessageState({
required MarianumMessageList messageList,
}) = _MarianumMessageState;
factory MarianumMessageState.fromJson(Map<String, dynamic> json) => _$MarianumMessageStateFromJson(json);
factory MarianumMessageState.fromJson(Map<String, dynamic> json) =>
_$MarianumMessageStateFromJson(json);
}
@freezed
@@ -20,7 +20,8 @@ abstract class MarianumMessageList with _$MarianumMessageList {
required List<MarianumMessage> messages,
}) = _MarianumMessageList;
factory MarianumMessageList.fromJson(Map<String, dynamic> json) => _$MarianumMessageListFromJson(json);
factory MarianumMessageList.fromJson(Map<String, dynamic> json) =>
_$MarianumMessageListFromJson(json);
}
@freezed
@@ -31,11 +32,8 @@ abstract class MarianumMessage with _$MarianumMessage {
required String url,
}) = _MarianumMessage;
factory MarianumMessage.fromJson(Map<String, dynamic> json) => _$MarianumMessageFromJson(json);
factory MarianumMessage.fromJson(Map<String, dynamic> json) =>
_$MarianumMessageFromJson(json);
}
enum GradeAveragesGradingSystem {
highSchool,
middleSchool,
}
enum GradeAveragesGradingSystem { highSchool, middleSchool }
@@ -8,5 +8,6 @@ class MarianumMessageGetMessages extends MhslDataLoader<MarianumMessageList> {
@override
Future<Response<String>> fetch() async => dio.get('/message/messages.json');
@override
MarianumMessageList assemble(DataLoaderResult data) => MarianumMessageList.fromJson(data.asMap());
MarianumMessageList assemble(DataLoaderResult data) =>
MarianumMessageList.fromJson(data.asMap());
}
@@ -3,5 +3,6 @@ import '../bloc/marianum_message_state.dart';
import '../data_provider/marianum_message_get_messages.dart';
class MarianumMessageRepository extends Repository<MarianumMessageState> {
Future<MarianumMessageList> getMessages() => MarianumMessageGetMessages().run();
Future<MarianumMessageList> getMessages() =>
MarianumMessageGetMessages().run();
}
@@ -27,7 +27,11 @@ class SettingsCubit extends HydratedCubit<Settings> {
_emitFreshInstance();
});
}
Debouncer.debounce(_debounceTag, const Duration(milliseconds: 500), _emitFreshInstance);
Debouncer.debounce(
_debounceTag,
const Duration(milliseconds: 500),
_emitFreshInstance,
);
}
return state;
}
@@ -50,7 +54,11 @@ class SettingsCubit extends HydratedCubit<Settings> {
return _appendNewModules(Settings.fromJson(json));
} catch (_) {
try {
return _appendNewModules(Settings.fromJson(_mergeSettings(json, DefaultSettings.get().toJson())));
return _appendNewModules(
Settings.fromJson(
_mergeSettings(json, DefaultSettings.get().toJson()),
),
);
} catch (_) {
return DefaultSettings.get();
}
@@ -63,7 +71,9 @@ class SettingsCubit extends HydratedCubit<Settings> {
Settings _appendNewModules(Settings s) {
final order = s.modulesSettings.moduleOrder;
final hidden = s.modulesSettings.hiddenModules;
final missing = Modules.values.where((m) => !order.contains(m) && !hidden.contains(m));
final missing = Modules.values.where(
(m) => !order.contains(m) && !hidden.contains(m),
);
if (missing.isEmpty) return s;
s.modulesSettings.moduleOrder = [...order, ...missing];
return s;
@@ -72,12 +82,19 @@ class SettingsCubit extends HydratedCubit<Settings> {
@override
Map<String, dynamic>? toJson(Settings state) => state.toJson();
Map<String, dynamic> _mergeSettings(Map<String, dynamic> oldMap, Map<String, dynamic> newMap) {
Map<String, dynamic> _mergeSettings(
Map<String, dynamic> oldMap,
Map<String, dynamic> newMap,
) {
final merged = Map<String, dynamic>.from(newMap);
oldMap.forEach((key, value) {
if (merged.containsKey(key)) {
if (value is Map<String, dynamic> && merged[key] is Map<String, dynamic>) {
merged[key] = _mergeSettings(value, merged[key] as Map<String, dynamic>);
if (value is Map<String, dynamic> &&
merged[key] is Map<String, dynamic>) {
merged[key] = _mergeSettings(
value,
merged[key] as Map<String, dynamic>,
);
} else {
merged[key] = value;
}
@@ -8,7 +8,13 @@ import '../repository/timetable_repository.dart';
import 'timetable_event.dart';
import 'timetable_state.dart';
class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState, TimetableRepository> {
class TimetableBloc
extends
LoadableHydratedBloc<
TimetableEvent,
TimetableState,
TimetableRepository
> {
static const Duration _weekSpan = Duration(days: 7);
static final DateFormat _weekKeyFormat = DateFormat('yyyyMMdd');
@@ -37,7 +43,8 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
}
@override
TimetableState fromStorage(Map<String, dynamic> json) => TimetableState.fromJson(json);
TimetableState fromStorage(Map<String, dynamic> json) =>
TimetableState.fromJson(json);
@override
Map<String, dynamic>? toStorage(TimetableState state) => state.toJson();
@@ -54,7 +61,12 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
}
await Future.wait([
_loadCurrentWeek(initial.startDate, initial.endDate, onError: recordError, renew: renew),
_loadCurrentWeek(
initial.startDate,
initial.endDate,
onError: recordError,
renew: renew,
),
_loadStaticReferenceData(onError: recordError, renew: renew),
_loadCustomEvents(onError: recordError, renew: renew),
]);
@@ -104,7 +116,12 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
final requestStart = DateTime.now();
_lastWeekRequestStart = requestStart;
try {
final week = await repo.data.getWeek(startDate, endDate, onError: onError, renew: renew);
final week = await repo.data.getWeek(
startDate,
endDate,
onError: onError,
renew: renew,
);
if (_lastWeekRequestStart.isAfter(requestStart)) return;
_writeWeekToCache(startDate, week);
} catch (e) {
@@ -123,19 +140,27 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
repo.data.getSchoolHolidays(onError: onError, renew: renew),
).wait;
add(Emit((s) => s.copyWith(
add(
Emit(
(s) => s.copyWith(
rooms: rooms,
subjects: subjects,
schoolHolidays: schoolHolidays,
dataVersion: s.dataVersion + 1,
)));
),
),
);
} catch (e) {
onError?.call(e);
}
try {
final timegrid = await repo.data.getTimegrid(renew: renew);
add(Emit((s) => s.copyWith(timegrid: timegrid, dataVersion: s.dataVersion + 1)));
add(
Emit(
(s) => s.copyWith(timegrid: timegrid, dataVersion: s.dataVersion + 1),
),
);
} catch (_) {
// Timegrid load failure falls back to a hardcoded schedule in the UI layer.
}
@@ -146,8 +171,16 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
bool renew = false,
}) async {
try {
final events = await repo.data.getCustomEvents(renew: renew, onError: onError);
add(Emit((s) => s.copyWith(customEvents: events, dataVersion: s.dataVersion + 1)));
final events = await repo.data.getCustomEvents(
renew: renew,
onError: onError,
);
add(
Emit(
(s) =>
s.copyWith(customEvents: events, dataVersion: s.dataVersion + 1),
),
);
} catch (e) {
onError?.call(e);
}
@@ -155,7 +188,11 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
Future<void> _refreshCustomEvents() async {
final events = await repo.data.getCustomEvents(renew: true);
add(DataGathered((s) => s.copyWith(customEvents: events, dataVersion: s.dataVersion + 1)));
add(
DataGathered(
(s) => s.copyWith(customEvents: events, dataVersion: s.dataVersion + 1),
),
);
}
void _prefetchAdjacentWeeks(DateTime start, DateTime end) {
@@ -164,16 +201,21 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
}
void _prefetchWeek(DateTime start, DateTime end) {
repo.data.getWeek(start, end).then((week) => _writeWeekToCache(start, week)).catchError((_) {});
repo.data
.getWeek(start, end)
.then((week) => _writeWeekToCache(start, week))
.catchError((_) {});
}
void _writeWeekToCache(DateTime weekStart, GetTimetableResponse week) {
final key = _weekKeyFormat.format(weekStart);
add(Emit((s) {
final updated = Map<String, GetTimetableResponse>.of(s.weekCache);
updated[key] = week;
return s.copyWith(weekCache: updated, dataVersion: s.dataVersion + 1);
}));
add(
Emit((s) {
final updated = Map<String, GetTimetableResponse>.of(s.weekCache);
updated[key] = week;
return s.copyWith(weekCache: updated, dataVersion: s.dataVersion + 1);
}),
);
}
static DateTime _startOfWeek(DateTime reference) {
@@ -182,7 +224,9 @@ class TimetableBloc extends LoadableHydratedBloc<TimetableEvent, TimetableState,
}
static DateTime _endOfWeek(DateTime reference) {
final friday = reference.add(Duration(days: DateTime.daysPerWeek - reference.weekday - 2));
final friday = reference.add(
Duration(days: DateTime.daysPerWeek - reference.weekday - 2),
);
return DateTime(friday.year, friday.month, friday.day);
}
}
@@ -15,7 +15,8 @@ abstract class TimetableState with _$TimetableState {
const TimetableState._();
const factory TimetableState({
@Default(<String, GetTimetableResponse>{}) Map<String, GetTimetableResponse> weekCache,
@Default(<String, GetTimetableResponse>{})
Map<String, GetTimetableResponse> weekCache,
GetRoomsResponse? rooms,
GetSubjectsResponse? subjects,
GetHolidaysResponse? schoolHolidays,
@@ -26,10 +27,15 @@ abstract class TimetableState with _$TimetableState {
@Default(0) int dataVersion,
}) = _TimetableState;
factory TimetableState.fromJson(Map<String, Object?> json) => _$TimetableStateFromJson(json);
factory TimetableState.fromJson(Map<String, Object?> json) =>
_$TimetableStateFromJson(json);
Iterable<GetTimetableResponseObject> getAllKnownLessons() =>
weekCache.values.expand((response) => response.result);
bool get hasReferenceData => rooms != null && subjects != null && schoolHolidays != null && customEvents != null;
bool get hasReferenceData =>
rooms != null &&
subjects != null &&
schoolHolidays != null &&
customEvents != null;
}
@@ -31,90 +31,78 @@ class TimetableDataProvider {
DateTime endDate, {
void Function(Object)? onError,
bool renew = false,
}) =>
resolveFromCache<GetTimetableResponse>(
(onUpdate, onError) => GetTimetableCache(
startdate: int.parse(_dateFormat.format(startDate)),
enddate: int.parse(_dateFormat.format(endDate)),
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getWeek',
);
}) => resolveFromCache<GetTimetableResponse>(
(onUpdate, onError) => GetTimetableCache(
startdate: int.parse(_dateFormat.format(startDate)),
enddate: int.parse(_dateFormat.format(endDate)),
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getWeek',
);
Future<GetRoomsResponse> getRooms({
void Function(Object)? onError,
bool renew = false,
}) =>
resolveFromCache<GetRoomsResponse>(
(onUpdate, onError) => GetRoomsCache(
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getRooms',
);
}) => resolveFromCache<GetRoomsResponse>(
(onUpdate, onError) =>
GetRoomsCache(renew: renew, onUpdate: onUpdate, onError: onError),
onError: onError,
operationName: 'getRooms',
);
Future<GetSubjectsResponse> getSubjects({
void Function(Object)? onError,
bool renew = false,
}) =>
resolveFromCache<GetSubjectsResponse>(
(onUpdate, onError) => GetSubjectsCache(
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getSubjects',
);
}) => resolveFromCache<GetSubjectsResponse>(
(onUpdate, onError) =>
GetSubjectsCache(renew: renew, onUpdate: onUpdate, onError: onError),
onError: onError,
operationName: 'getSubjects',
);
Future<GetHolidaysResponse> getSchoolHolidays({
void Function(Object)? onError,
bool renew = false,
}) =>
resolveFromCache<GetHolidaysResponse>(
(onUpdate, onError) => GetHolidaysCache(
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getSchoolHolidays',
);
}) => resolveFromCache<GetHolidaysResponse>(
(onUpdate, onError) =>
GetHolidaysCache(renew: renew, onUpdate: onUpdate, onError: onError),
onError: onError,
operationName: 'getSchoolHolidays',
);
Future<GetTimegridUnitsResponse> getTimegrid({bool renew = false}) =>
resolveFromCache<GetTimegridUnitsResponse>(
(onUpdate, _) => GetTimegridUnitsCache(
renew: renew,
onUpdate: onUpdate,
),
(onUpdate, _) =>
GetTimegridUnitsCache(renew: renew, onUpdate: onUpdate),
operationName: 'getTimegrid',
);
Future<GetCustomTimetableEventResponse> getCustomEvents({
bool renew = false,
void Function(Object)? onError,
}) =>
resolveFromCache<GetCustomTimetableEventResponse>(
(onUpdate, onError) => GetCustomTimetableEventCache(
GetCustomTimetableEventParams(AccountData().getUserSecret()),
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getCustomEvents',
);
}) => resolveFromCache<GetCustomTimetableEventResponse>(
(onUpdate, onError) => GetCustomTimetableEventCache(
GetCustomTimetableEventParams(AccountData().getUserSecret()),
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getCustomEvents',
);
Future<void> addCustomEvent(CustomTimetableEvent event) =>
AddCustomTimetableEvent(AddCustomTimetableEventParams(AccountData().getUserSecret(), event)).run();
AddCustomTimetableEvent(
AddCustomTimetableEventParams(AccountData().getUserSecret(), event),
).run();
Future<void> updateCustomEvent(String id, CustomTimetableEvent event) =>
UpdateCustomTimetableEvent(UpdateCustomTimetableEventParams(id, event)).run();
UpdateCustomTimetableEvent(
UpdateCustomTimetableEventParams(id, event),
).run();
Future<void> removeCustomEvent(String id) =>
RemoveCustomTimetableEvent(RemoveCustomTimetableEventParams(id)).run();
@@ -5,7 +5,8 @@ import '../data_provider/timetable_data_provider.dart';
class TimetableRepository extends Repository<TimetableState> {
final TimetableDataProvider _provider;
TimetableRepository([TimetableDataProvider? provider]) : _provider = provider ?? TimetableDataProvider();
TimetableRepository([TimetableDataProvider? provider])
: _provider = provider ?? TimetableDataProvider();
TimetableDataProvider get data => _provider;
}