5 Commits

49 changed files with 915 additions and 159 deletions

View File

@@ -35,7 +35,7 @@ GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson(
GetHolidaysResponseObject( GetHolidaysResponseObject(
start: json['start'] as String, start: json['start'] as String,
end: json['end'] as String, end: json['end'] as String,
year: json['year'] as int, year: (json['year'] as num).toInt(),
stateCode: json['stateCode'] as String, stateCode: json['stateCode'] as String,
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String, slug: json['slug'] as String,

View File

@@ -9,7 +9,7 @@ part of 'fileSharingApiParams.dart';
FileSharingApiParams _$FileSharingApiParamsFromJson( FileSharingApiParams _$FileSharingApiParamsFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
FileSharingApiParams( FileSharingApiParams(
shareType: json['shareType'] as int, shareType: (json['shareType'] as num).toInt(),
shareWith: json['shareWith'] as String, shareWith: json['shareWith'] as String,
path: json['path'] as String, path: json['path'] as String,
referenceId: json['referenceId'] as String?, referenceId: json['referenceId'] as String?,

View File

@@ -10,10 +10,10 @@ GetChatParams _$GetChatParamsFromJson(Map<String, dynamic> json) =>
GetChatParams( GetChatParams(
lookIntoFuture: lookIntoFuture:
$enumDecode(_$GetChatParamsSwitchEnumMap, json['lookIntoFuture']), $enumDecode(_$GetChatParamsSwitchEnumMap, json['lookIntoFuture']),
limit: json['limit'] as int?, limit: (json['limit'] as num?)?.toInt(),
lastKnownMessageId: json['lastKnownMessageId'] as int?, lastKnownMessageId: (json['lastKnownMessageId'] as num?)?.toInt(),
lastCommonReadId: json['lastCommonReadId'] as int?, lastCommonReadId: (json['lastCommonReadId'] as num?)?.toInt(),
timeout: json['timeout'] as int?, timeout: (json['timeout'] as num?)?.toInt(),
setReadMarker: $enumDecodeNullable( setReadMarker: $enumDecodeNullable(
_$GetChatParamsSwitchEnumMap, json['setReadMarker']), _$GetChatParamsSwitchEnumMap, json['setReadMarker']),
includeLastKnown: $enumDecodeNullable( includeLastKnown: $enumDecodeNullable(

View File

@@ -32,13 +32,13 @@ Map<String, dynamic> _$GetChatResponseToJson(GetChatResponse instance) {
GetChatResponseObject _$GetChatResponseObjectFromJson( GetChatResponseObject _$GetChatResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetChatResponseObject( GetChatResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['token'] as String, json['token'] as String,
$enumDecode( $enumDecode(
_$GetRoomResponseObjectMessageActorTypeEnumMap, json['actorType']), _$GetRoomResponseObjectMessageActorTypeEnumMap, json['actorType']),
json['actorId'] as String, json['actorId'] as String,
json['actorDisplayName'] as String, json['actorDisplayName'] as String,
json['timestamp'] as int, (json['timestamp'] as num).toInt(),
json['systemMessage'] as String, json['systemMessage'] as String,
$enumDecode( $enumDecode(
_$GetRoomResponseObjectMessageTypeEnumMap, json['messageType']), _$GetRoomResponseObjectMessageTypeEnumMap, json['messageType']),
@@ -47,7 +47,7 @@ GetChatResponseObject _$GetChatResponseObjectFromJson(
json['message'] as String, json['message'] as String,
_fromJson(json['messageParameters']), _fromJson(json['messageParameters']),
(json['reactions'] as Map<String, dynamic>?)?.map( (json['reactions'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as int), (k, e) => MapEntry(k, (e as num).toInt()),
), ),
(json['reactionsSelf'] as List<dynamic>?) (json['reactionsSelf'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)

View File

@@ -8,7 +8,7 @@ part of 'createRoomParams.dart';
CreateRoomParams _$CreateRoomParamsFromJson(Map<String, dynamic> json) => CreateRoomParams _$CreateRoomParamsFromJson(Map<String, dynamic> json) =>
CreateRoomParams( CreateRoomParams(
roomType: json['roomType'] as int, roomType: (json['roomType'] as num).toInt(),
invite: json['invite'] as String, invite: json['invite'] as String,
source: json['source'] as String?, source: json['source'] as String?,
roomName: json['roomName'] as String?, roomName: json['roomName'] as String?,

View File

@@ -35,17 +35,17 @@ Map<String, dynamic> _$GetParticipantsResponseToJson(
GetParticipantsResponseObject _$GetParticipantsResponseObjectFromJson( GetParticipantsResponseObject _$GetParticipantsResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetParticipantsResponseObject( GetParticipantsResponseObject(
json['attendeeId'] as int, (json['attendeeId'] as num).toInt(),
json['actorType'] as String, json['actorType'] as String,
json['actorId'] as String, json['actorId'] as String,
json['displayName'] as String, json['displayName'] as String,
$enumDecode(_$GetParticipantsResponseObjectParticipantTypeEnumMap, $enumDecode(_$GetParticipantsResponseObjectParticipantTypeEnumMap,
json['participantType']), json['participantType']),
json['lastPing'] as int, (json['lastPing'] as num).toInt(),
$enumDecode(_$GetParticipantsResponseObjectParticipantsInCallFlagsEnumMap, $enumDecode(_$GetParticipantsResponseObjectParticipantsInCallFlagsEnumMap,
json['inCall']), json['inCall']),
json['permissions'] as int, (json['permissions'] as num).toInt(),
json['attendeePermissions'] as int, (json['attendeePermissions'] as num).toInt(),
json['sessionId'] as String?, json['sessionId'] as String?,
(json['sessionIds'] as List<dynamic>).map((e) => e as String).toList(), (json['sessionIds'] as List<dynamic>).map((e) => e as String).toList(),
json['status'] as String?, json['status'] as String?,

View File

@@ -44,7 +44,7 @@ GetReactionsResponseObject _$GetReactionsResponseObjectFromJson(
_$GetReactionsResponseObjectActorTypeEnumMap, json['actorType']), _$GetReactionsResponseObjectActorTypeEnumMap, json['actorType']),
json['actorId'] as String, json['actorId'] as String,
json['actorDisplayName'] as String, json['actorDisplayName'] as String,
json['timestamp'] as int, (json['timestamp'] as num).toInt(),
); );
Map<String, dynamic> _$GetReactionsResponseObjectToJson( Map<String, dynamic> _$GetReactionsResponseObjectToJson(

View File

@@ -11,7 +11,7 @@ GetRoomParams _$GetRoomParamsFromJson(Map<String, dynamic> json) =>
noStatusUpdate: $enumDecodeNullable( noStatusUpdate: $enumDecodeNullable(
_$GetRoomParamsStatusUpdateEnumMap, json['noStatusUpdate']), _$GetRoomParamsStatusUpdateEnumMap, json['noStatusUpdate']),
includeStatus: json['includeStatus'] as bool?, includeStatus: json['includeStatus'] as bool?,
modifiedSince: json['modifiedSince'] as int?, modifiedSince: (json['modifiedSince'] as num?)?.toInt(),
); );
Map<String, dynamic> _$GetRoomParamsToJson(GetRoomParams instance) => Map<String, dynamic> _$GetRoomParamsToJson(GetRoomParams instance) =>

View File

@@ -32,33 +32,33 @@ Map<String, dynamic> _$GetRoomResponseToJson(GetRoomResponse instance) {
GetRoomResponseObject _$GetRoomResponseObjectFromJson( GetRoomResponseObject _$GetRoomResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetRoomResponseObject( GetRoomResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['token'] as String, json['token'] as String,
$enumDecode(_$GetRoomResponseObjectConversationTypeEnumMap, json['type']), $enumDecode(_$GetRoomResponseObjectConversationTypeEnumMap, json['type']),
json['name'] as String, json['name'] as String,
json['displayName'] as String, json['displayName'] as String,
json['description'] as String, json['description'] as String,
json['participantType'] as int, (json['participantType'] as num).toInt(),
json['participantFlags'] as int, (json['participantFlags'] as num).toInt(),
json['readOnly'] as int, (json['readOnly'] as num).toInt(),
json['listable'] as int, (json['listable'] as num).toInt(),
json['lastPing'] as int, (json['lastPing'] as num).toInt(),
json['sessionId'] as String, json['sessionId'] as String,
json['hasPassword'] as bool, json['hasPassword'] as bool,
json['hasCall'] as bool, json['hasCall'] as bool,
json['callFlag'] as int, (json['callFlag'] as num).toInt(),
json['canStartCall'] as bool, json['canStartCall'] as bool,
json['canDeleteConversation'] as bool, json['canDeleteConversation'] as bool,
json['canLeaveConversation'] as bool, json['canLeaveConversation'] as bool,
json['lastActivity'] as int, (json['lastActivity'] as num).toInt(),
json['isFavorite'] as bool, json['isFavorite'] as bool,
$enumDecode(_$GetRoomResponseObjectParticipantNotificationLevelEnumMap, $enumDecode(_$GetRoomResponseObjectParticipantNotificationLevelEnumMap,
json['notificationLevel']), json['notificationLevel']),
json['unreadMessages'] as int, (json['unreadMessages'] as num).toInt(),
json['unreadMention'] as bool, json['unreadMention'] as bool,
json['unreadMentionDirect'] as bool, json['unreadMentionDirect'] as bool,
json['lastReadMessage'] as int, (json['lastReadMessage'] as num).toInt(),
json['lastCommonReadMessage'] as int, (json['lastCommonReadMessage'] as num).toInt(),
GetChatResponseObject.fromJson( GetChatResponseObject.fromJson(
json['lastMessage'] as Map<String, dynamic>), json['lastMessage'] as Map<String, dynamic>),
json['status'] as String?, json['status'] as String?,

View File

@@ -8,7 +8,7 @@ part of 'setReadMarkerParams.dart';
SetReadMarkerParams _$SetReadMarkerParamsFromJson(Map<String, dynamic> json) => SetReadMarkerParams _$SetReadMarkerParamsFromJson(Map<String, dynamic> json) =>
SetReadMarkerParams( SetReadMarkerParams(
lastReadMessage: json['lastReadMessage'] as int?, lastReadMessage: (json['lastReadMessage'] as num?)?.toInt(),
); );
Map<String, dynamic> _$SetReadMarkerParamsToJson( Map<String, dynamic> _$SetReadMarkerParamsToJson(

View File

@@ -12,7 +12,7 @@ CacheableFile _$CacheableFileFromJson(Map<String, dynamic> json) =>
isDirectory: json['isDirectory'] as bool, isDirectory: json['isDirectory'] as bool,
name: json['name'] as String, name: json['name'] as String,
mimeType: json['mimeType'] as String?, mimeType: json['mimeType'] as String?,
size: json['size'] as int?, size: (json['size'] as num?)?.toInt(),
eTag: json['eTag'] as String?, eTag: json['eTag'] as String?,
createdAt: json['createdAt'] == null createdAt: json['createdAt'] == null
? null ? null

View File

@@ -11,7 +11,7 @@ AddFeedbackParams _$AddFeedbackParamsFromJson(Map<String, dynamic> json) =>
user: json['user'] as String, user: json['user'] as String,
feedback: json['feedback'] as String, feedback: json['feedback'] as String,
screenshot: json['screenshot'] as String?, screenshot: json['screenshot'] as String?,
appVersion: json['appVersion'] as int, appVersion: (json['appVersion'] as num).toInt(),
); );
Map<String, dynamic> _$AddFeedbackParamsToJson(AddFeedbackParams instance) => Map<String, dynamic> _$AddFeedbackParamsToJson(AddFeedbackParams instance) =>

View File

@@ -12,7 +12,7 @@ UpdateUserIndexParams _$UpdateUserIndexParamsFromJson(
user: json['user'] as String, user: json['user'] as String,
username: json['username'] as String, username: json['username'] as String,
device: json['device'] as String, device: json['device'] as String,
appVersion: json['appVersion'] as int, appVersion: (json['appVersion'] as num).toInt(),
deviceInfo: json['deviceInfo'] as String, deviceInfo: json['deviceInfo'] as String,
); );

View File

@@ -10,9 +10,9 @@ AuthenticateResponse _$AuthenticateResponseFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
AuthenticateResponse( AuthenticateResponse(
json['sessionId'] as String, json['sessionId'] as String,
json['personType'] as int, (json['personType'] as num).toInt(),
json['personId'] as int, (json['personId'] as num).toInt(),
json['klasseId'] as int, (json['klasseId'] as num).toInt(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map( )..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );

View File

@@ -33,11 +33,11 @@ Map<String, dynamic> _$GetHolidaysResponseToJson(GetHolidaysResponse instance) {
GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson( GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetHolidaysResponseObject( GetHolidaysResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longName'] as String, json['longName'] as String,
json['startDate'] as int, (json['startDate'] as num).toInt(),
json['endDate'] as int, (json['endDate'] as num).toInt(),
); );
Map<String, dynamic> _$GetHolidaysResponseObjectToJson( Map<String, dynamic> _$GetHolidaysResponseObjectToJson(

View File

@@ -33,7 +33,7 @@ Map<String, dynamic> _$GetRoomsResponseToJson(GetRoomsResponse instance) {
GetRoomsResponseObject _$GetRoomsResponseObjectFromJson( GetRoomsResponseObject _$GetRoomsResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetRoomsResponseObject( GetRoomsResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longName'] as String, json['longName'] as String,
json['active'] as bool, json['active'] as bool,

View File

@@ -33,7 +33,7 @@ Map<String, dynamic> _$GetSubjectsResponseToJson(GetSubjectsResponse instance) {
GetSubjectsResponseObject _$GetSubjectsResponseObjectFromJson( GetSubjectsResponseObject _$GetSubjectsResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetSubjectsResponseObject( GetSubjectsResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longName'] as String, json['longName'] as String,
json['alternateName'] as String, json['alternateName'] as String,

View File

@@ -22,8 +22,8 @@ GetTimetableParamsOptions _$GetTimetableParamsOptionsFromJson(
GetTimetableParamsOptions( GetTimetableParamsOptions(
element: GetTimetableParamsOptionsElement.fromJson( element: GetTimetableParamsOptionsElement.fromJson(
json['element'] as Map<String, dynamic>), json['element'] as Map<String, dynamic>),
startDate: json['startDate'] as int?, startDate: (json['startDate'] as num?)?.toInt(),
endDate: json['endDate'] as int?, endDate: (json['endDate'] as num?)?.toInt(),
onlyBaseTimetable: json['onlyBaseTimetable'] as bool?, onlyBaseTimetable: json['onlyBaseTimetable'] as bool?,
showBooking: json['showBooking'] as bool?, showBooking: json['showBooking'] as bool?,
showInfo: json['showInfo'] as bool?, showInfo: json['showInfo'] as bool?,
@@ -99,8 +99,8 @@ const _$GetTimetableParamsOptionsFieldsEnumMap = {
GetTimetableParamsOptionsElement _$GetTimetableParamsOptionsElementFromJson( GetTimetableParamsOptionsElement _$GetTimetableParamsOptionsElementFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableParamsOptionsElement( GetTimetableParamsOptionsElement(
id: json['id'] as int, id: (json['id'] as num).toInt(),
type: json['type'] as int, type: (json['type'] as num).toInt(),
keyType: $enumDecodeNullable( keyType: $enumDecodeNullable(
_$GetTimetableParamsOptionsElementKeyTypeEnumMap, json['keyType']), _$GetTimetableParamsOptionsElementKeyTypeEnumMap, json['keyType']),
); );

View File

@@ -35,16 +35,16 @@ Map<String, dynamic> _$GetTimetableResponseToJson(
GetTimetableResponseObject _$GetTimetableResponseObjectFromJson( GetTimetableResponseObject _$GetTimetableResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObject( GetTimetableResponseObject(
id: json['id'] as int, id: (json['id'] as num).toInt(),
date: json['date'] as int, date: (json['date'] as num).toInt(),
startTime: json['startTime'] as int, startTime: (json['startTime'] as num).toInt(),
endTime: json['endTime'] as int, endTime: (json['endTime'] as num).toInt(),
lstype: json['lstype'] as String?, lstype: json['lstype'] as String?,
code: json['code'] as String?, code: json['code'] as String?,
info: json['info'] as String?, info: json['info'] as String?,
substText: json['substText'] as String?, substText: json['substText'] as String?,
lstext: json['lstext'] as String?, lstext: json['lstext'] as String?,
lsnumber: json['lsnumber'] as int?, lsnumber: (json['lsnumber'] as num?)?.toInt(),
statflags: json['statflags'] as String?, statflags: json['statflags'] as String?,
activityType: json['activityType'] as String?, activityType: json['activityType'] as String?,
sg: json['sg'] as String?, sg: json['sg'] as String?,
@@ -110,7 +110,7 @@ GetTimetableResponseObjectFieldsObject
_$GetTimetableResponseObjectFieldsObjectFromJson( _$GetTimetableResponseObjectFieldsObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectFieldsObject( GetTimetableResponseObjectFieldsObject(
id: json['id'] as int?, id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?, name: json['name'] as String?,
longname: json['longname'] as String?, longname: json['longname'] as String?,
externalkey: json['externalkey'] as String?, externalkey: json['externalkey'] as String?,
@@ -128,7 +128,7 @@ Map<String, dynamic> _$GetTimetableResponseObjectFieldsObjectToJson(
GetTimetableResponseObjectClass _$GetTimetableResponseObjectClassFromJson( GetTimetableResponseObjectClass _$GetTimetableResponseObjectClassFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectClass( GetTimetableResponseObjectClass(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
json['externalkey'] as String?, json['externalkey'] as String?,
@@ -146,10 +146,10 @@ Map<String, dynamic> _$GetTimetableResponseObjectClassToJson(
GetTimetableResponseObjectTeacher _$GetTimetableResponseObjectTeacherFromJson( GetTimetableResponseObjectTeacher _$GetTimetableResponseObjectTeacherFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectTeacher( GetTimetableResponseObjectTeacher(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
json['orgid'] as int?, (json['orgid'] as num?)?.toInt(),
json['orgname'] as String?, json['orgname'] as String?,
json['externalkey'] as String?, json['externalkey'] as String?,
); );
@@ -168,7 +168,7 @@ Map<String, dynamic> _$GetTimetableResponseObjectTeacherToJson(
GetTimetableResponseObjectSubject _$GetTimetableResponseObjectSubjectFromJson( GetTimetableResponseObjectSubject _$GetTimetableResponseObjectSubjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectSubject( GetTimetableResponseObjectSubject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
); );
@@ -184,7 +184,7 @@ Map<String, dynamic> _$GetTimetableResponseObjectSubjectToJson(
GetTimetableResponseObjectRoom _$GetTimetableResponseObjectRoomFromJson( GetTimetableResponseObjectRoom _$GetTimetableResponseObjectRoomFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectRoom( GetTimetableResponseObjectRoom(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
); );

View File

@@ -94,55 +94,56 @@ class _AppState extends State<App> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) => PersistentTabView( Widget build(BuildContext context) => PersistentTabView(
controller: Main.bottomNavigator, controller: Main.bottomNavigator,
navBarOverlap: const NavBarOverlap.none(), navBarOverlap: const NavBarOverlap.none(),
backgroundColor: Theme.of(context).colorScheme.primary, backgroundColor: Theme.of(context).colorScheme.primary,
handleAndroidBackButtonPress: false, handleAndroidBackButtonPress: true,
screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)), screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)),
tabs: [ tabs: [
AppModule.getModule(Modules.timetable).toBottomTab(context), AppModule.getModule(Modules.timetable).toBottomTab(context),
AppModule.getModule(Modules.talk).toBottomTab( AppModule.getModule(Modules.talk).toBottomTab(
context, context,
itemBuilder: (icon) => Consumer<ChatListProps>( itemBuilder: (icon) => Consumer<ChatListProps>(
builder: (context, value, child) { builder: (context, value, child) {
if(value.primaryLoading()) return Icon(icon); if(value.primaryLoading()) return Icon(icon);
var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b); var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
return badges.Badge( return badges.Badge(
showBadge: messages > 0, showBadge: messages > 0,
position: badges.BadgePosition.topEnd(top: -3, end: -3), position: badges.BadgePosition.topEnd(top: -3, end: -3),
stackFit: StackFit.loose, stackFit: StackFit.loose,
badgeStyle: badges.BadgeStyle( badgeStyle: badges.BadgeStyle(
padding: const EdgeInsets.all(3), padding: const EdgeInsets.all(3),
badgeColor: Theme.of(context).primaryColor, badgeColor: Theme.of(context).primaryColor,
elevation: 1, 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: Icon(icon), child: Icon(icon),
); );
}, },
),
),
AppModule.getModule(Modules.files).toBottomTab(context),
PersistentTabConfig(
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
item: ItemConfig(
activeForegroundColor: Theme.of(context).primaryColor,
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
icon: const Icon(Icons.apps),
title: 'Mehr'
),
),
],
navBarBuilder: (config) => Style6BottomNavBar(
navBarConfig: config,
navBarDecoration: NavBarDecoration(
border: const Border(top: BorderSide(width: 1, color: Colors.grey)),
color: Theme.of(context).colorScheme.surface,
), ),
), ),
); AppModule.getModule(Modules.blocFiles).toBottomTab(context),
AppModule.getModule(Modules.files).toBottomTab(context),
PersistentTabConfig(
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
item: ItemConfig(
activeForegroundColor: Theme.of(context).primaryColor,
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
icon: const Icon(Icons.apps),
title: 'Mehr'
),
),
],
navBarBuilder: (config) => Style6BottomNavBar(
navBarConfig: config,
navBarDecoration: NavBarDecoration(
border: const Border(top: BorderSide(width: 1, color: Colors.grey)),
color: Theme.of(context).colorScheme.surface,
),
),
);
@override @override
void dispose() { void dispose() {

View File

@@ -42,7 +42,7 @@ Future<void> main() async {
var initialisationTasks = [ var initialisationTasks = [
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform) Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform)
.then((value) async => log("Firebase token: ${await FirebaseMessaging.instance.getToken() ?? "Error: no Firebase token!"}")) .then((value) async => log("Firebase token: ${await FirebaseMessaging.instance.getToken() ?? "Error: no Firebase token!"}"))
.onError((error, stackTrace) => log('Error initializing Firebase: $error')), .onError((error, stackTrace) => log('Error initializing Firebase: $error', stackTrace: stackTrace)),
PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem').then(addCertificateAsTrusted), PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem').then(addCertificateAsTrusted),
PlatformAssetBundle().load('assets/ca/lets-encrypt-r10.pem').then(addCertificateAsTrusted), PlatformAssetBundle().load('assets/ca/lets-encrypt-r10.pem').then(addCertificateAsTrusted),

View File

@@ -1,8 +1,8 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../../infrastructure/dataLoader/data_loader.dart'; import '../../infrastructure/dataLoader/http_data_loader.dart';
abstract class HolidayDataLoader<TResult> extends DataLoader<TResult> { abstract class HolidayDataLoader<TResult> extends HttpDataLoader<TResult> {
HolidayDataLoader() : super(Dio(BaseOptions( HolidayDataLoader() : super(Dio(BaseOptions(
baseUrl: 'https://ferien-api.de/api/v1/', baseUrl: 'https://ferien-api.de/api/v1/',
))); )));

View File

@@ -1,8 +1,8 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../../infrastructure/dataLoader/data_loader.dart'; import '../../infrastructure/dataLoader/http_data_loader.dart';
abstract class MhslDataLoader<TResult> extends DataLoader<TResult> { abstract class MhslDataLoader<TResult> extends HttpDataLoader<TResult> {
MhslDataLoader() : super(Dio(BaseOptions( MhslDataLoader() : super(Dio(BaseOptions(
baseUrl: 'https://mhsl.eu/marianum/marianummobile/' baseUrl: 'https://mhsl.eu/marianum/marianummobile/'
))); )));

View File

@@ -2,26 +2,21 @@ import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
abstract class DataLoader<TResult> { abstract class HttpDataLoader<TResult> {
final Dio dio; final Dio dio;
DataLoader(this.dio) { HttpDataLoader(this.dio) {
dio.options.connectTimeout = const Duration(seconds: 10).inMilliseconds; dio.options.connectTimeout = const Duration(seconds: 10).inMilliseconds;
dio.options.sendTimeout = const Duration(seconds: 30).inMilliseconds; dio.options.sendTimeout = const Duration(seconds: 30).inMilliseconds;
dio.options.receiveTimeout = const Duration(seconds: 30).inMilliseconds; dio.options.receiveTimeout = const Duration(seconds: 30).inMilliseconds;
} }
Future<TResult> run() async { Future<TResult> run() async {
var fetcher = fetch(); var response = await fetch();
await Future.wait([
fetcher,
Future.delayed(const Duration(milliseconds: 500)) // TODO tune or remove
]);
var response = await fetcher;
try { try {
return assemble(DataLoaderResult( return assemble(DataLoaderResult(
json: jsonDecode(response.data!), json: await compute(jsonDecode, response.data!),
headers: response.headers.map.map((key, value) => MapEntry(key, value.join(';'))), headers: response.headers.map.map((key, value) => MapEntry(key, value.join(';'))),
)); ));
} catch(trace, e) { } catch(trace, e) {

View File

@@ -166,27 +166,22 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
class _$LoadableStateImpl<TState> extends _LoadableState<TState> { class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
const _$LoadableStateImpl( const _$LoadableStateImpl(
{this.isLoading = true, {required this.isLoading,
this.data = null, required this.data,
this.lastFetch = null, required this.lastFetch,
this.reFetch = null, required this.reFetch,
this.error = null}) required this.error})
: super._(); : super._();
@override @override
@JsonKey()
final bool isLoading; final bool isLoading;
@override @override
@JsonKey()
final TState? data; final TState? data;
@override @override
@JsonKey()
final int? lastFetch; final int? lastFetch;
@override @override
@JsonKey()
final void Function()? reFetch; final void Function()? reFetch;
@override @override
@JsonKey()
final LoadingError? error; final LoadingError? error;
@override @override
@@ -222,11 +217,11 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
abstract class _LoadableState<TState> extends LoadableState<TState> { abstract class _LoadableState<TState> extends LoadableState<TState> {
const factory _LoadableState( const factory _LoadableState(
{final bool isLoading, {required final bool isLoading,
final TState? data, required final TState? data,
final int? lastFetch, required final int? lastFetch,
final void Function()? reFetch, required final void Function()? reFetch,
final LoadingError? error}) = _$LoadableStateImpl<TState>; required final LoadingError? error}) = _$LoadableStateImpl<TState>;
const _LoadableState._() : super._(); const _LoadableState._() : super._();
@override @override

View File

@@ -17,13 +17,21 @@ class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<T
final Widget Function(TState state, bool loading) child; final Widget Function(TState state, bool loading) child;
final void Function(TState state)? onLoad; final void Function(TState state)? onLoad;
final bool wrapWithScrollView; final bool wrapWithScrollView;
const LoadableStateConsumer({required this.child, this.onLoad, this.wrapWithScrollView = false, super.key}); final TController? controllerByValue;
const LoadableStateConsumer({required this.child, this.onLoad, this.wrapWithScrollView = false, this.controllerByValue = null, super.key});
static Duration animationDuration = const Duration(milliseconds: 200); static Duration animationDuration = const Duration(milliseconds: 200);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var loadableState = context.watch<TController>().state; var loadableState = controllerByValue != null
? controllerByValue!.state
: context.watch<TController>().state;
if(!loadableState.isLoading && onLoad != null) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => onLoad!(loadableState.data));
}
var childWidget = ConditionalWrapper( var childWidget = ConditionalWrapper(
condition: loadableState.reFetch != null, condition: loadableState.reFetch != null,
wrapper: (child) => RefreshIndicator( wrapper: (child) => RefreshIndicator(

View File

@@ -30,16 +30,17 @@ abstract class LoadableHydratedBloc<
isLoading: state.isLoading, isLoading: state.isLoading,
data: event.state(innerState ?? fromNothing()), data: event.state(innerState ?? fromNothing()),
lastFetch: state.lastFetch, lastFetch: state.lastFetch,
reFetch: retry, reFetch: fetch,
error: state.error, error: state.error,
)); ));
if(event.fetch) fetch();
}); });
on<DataGathered<TState>>((event, emit) => emit(LoadableState( on<DataGathered<TState>>((event, emit) => emit(LoadableState(
isLoading: false, isLoading: false,
data: event.state(innerState ?? fromNothing()), data: event.state(innerState ?? fromNothing()),
lastFetch: DateTime.now().millisecondsSinceEpoch, lastFetch: DateTime.now().millisecondsSinceEpoch,
reFetch: retry, reFetch: fetch,
error: null, error: null,
))); )));
@@ -55,7 +56,7 @@ abstract class LoadableHydratedBloc<
isLoading: false, isLoading: false,
data: innerState, data: innerState,
lastFetch: state.lastFetch, lastFetch: state.lastFetch,
reFetch: retry, reFetch: fetch,
error: event.error error: event.error
))); )));
@@ -66,19 +67,14 @@ abstract class LoadableHydratedBloc<
TState? get innerState => state.data; TState? get innerState => state.data;
TRepository get repo => _repository; TRepository get repo => _repository;
void retry() {
log('Fetch retry triggered for ${TState.toString()}');
add(RefetchStarted<TState>());
fetch();
}
void fetch() { void fetch() {
log('Fetching data for ${TState.toString()}'); log('Fetching data for ${TState.toString()}');
add(RefetchStarted<TState>());
gatherData().catchError( gatherData().catchError(
(e) { (e) {
log('Error while fetching ${TState.toString()}: ${e.toString()}'); log('Error while fetching ${TState.toString()}: ${e.toString()}');
add(Error(LoadingError( add(Error(LoadingError(
message: e.message ?? e.toString(), message: e.toString(),
allowRetry: true, allowRetry: true,
))); )));
}, },
@@ -92,7 +88,7 @@ abstract class LoadableHydratedBloc<
var rawData = LoadableSaveContext.unwrap(json); var rawData = LoadableSaveContext.unwrap(json);
return LoadableState( return LoadableState(
isLoading: true, isLoading: true,
data: fromStorage(rawData.data), data: fromStorage(rawData.data), // TODO fromStorage in isolate
lastFetch: rawData.meta.timestamp, lastFetch: rawData.meta.timestamp,
reFetch: null, reFetch: null,
error: null, error: null,
@@ -103,10 +99,9 @@ abstract class LoadableHydratedBloc<
Map<String, dynamic>? toJson(LoadableState<TState> state) { Map<String, dynamic>? toJson(LoadableState<TState> state) {
Map<String, dynamic>? data; Map<String, dynamic>? data;
try { try {
data = toStorage(state.data); data = state.data == null ? null : toStorage(state.data); // TODO toStorage in isolate
} catch(e) { } catch(e) {
log('Failed to save state ${TState.toString()}: ${e.toString()}'); log('Failed to save state ${TState.toString()}: ${e.toString()}');
data = null;
} }
return LoadableSaveContext.wrap( return LoadableSaveContext.wrap(

View File

@@ -3,7 +3,8 @@ import '../../loadableState/loading_error.dart';
class LoadableHydratedBlocEvent<TState> {} class LoadableHydratedBlocEvent<TState> {}
class Emit<TState> extends LoadableHydratedBlocEvent<TState> { class Emit<TState> extends LoadableHydratedBlocEvent<TState> {
final TState Function(TState state) state; final TState Function(TState state) state;
Emit(this.state); final bool fetch;
Emit(this.state, {this.fetch = false});
} }
class DataGathered<TState> extends LoadableHydratedBlocEvent<TState> { class DataGathered<TState> extends LoadableHydratedBlocEvent<TState> {
final TState Function(TState state) state; final TState Function(TState state) state;

View File

@@ -9,7 +9,7 @@ part of 'loadable_save_context.dart';
_$LoadableSaveContextImpl _$$LoadableSaveContextImplFromJson( _$LoadableSaveContextImpl _$$LoadableSaveContextImplFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$LoadableSaveContextImpl( _$LoadableSaveContextImpl(
timestamp: json['timestamp'] as int, timestamp: (json['timestamp'] as num).toInt(),
); );
Map<String, dynamic> _$$LoadableSaveContextImplToJson( Map<String, dynamic> _$$LoadableSaveContextImplToJson(

View File

@@ -0,0 +1,48 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'bloc_module.dart';
class SwappingBloc<TBloc> {
final TBloc initialBloc;
final StreamController<TBloc> updater = StreamController<TBloc>();
SwappingBloc(this.initialBloc);
void swap(TBloc bloc) {
updater.add(bloc);
}
}
class SwappingBlocModule<TBloc extends StateStreamableSource<TState>, TState> extends StatefulWidget {
final SwappingBloc<TBloc> bloc;
final Widget Function(BuildContext context, TBloc bloc, TState state) child;
const SwappingBlocModule({super.key, required this.bloc, required this.child});
@override
State<SwappingBlocModule<TBloc, TState>> createState() => _SwappingBlocModuleState<TBloc, TState>();
}
class _SwappingBlocModuleState<TBloc extends StateStreamableSource<TState>, TState> extends State<SwappingBlocModule<TBloc, TState>> {
late TBloc bloc;
@override
void initState() {
super.initState();
bloc = widget.bloc.initialBloc;
widget.bloc.updater.stream.listen((event) {
setState(() {
bloc = event;
});
});
}
@override
Widget build(BuildContext context) => BlocModule<TBloc, TState>(
autoRebuild: true,
create: (context) => bloc,
child: (context, bloc, state) => widget.child(context, bloc, state),
);
}

View File

@@ -8,6 +8,7 @@ import '../../../view/pages/more/roomplan/roomplan.dart';
import '../../../view/pages/talk/chatList.dart'; import '../../../view/pages/talk/chatList.dart';
import '../../../view/pages/timetable/timetable.dart'; import '../../../view/pages/timetable/timetable.dart';
import '../../../widget/centeredLeading.dart'; import '../../../widget/centeredLeading.dart';
import 'files/view/files_view.dart';
import 'gradeAverages/view/grade_averages_view.dart'; import 'gradeAverages/view/grade_averages_view.dart';
import 'holidays/view/holidays_view.dart'; import 'holidays/view/holidays_view.dart';
import 'marianumMessage/view/marianum_message_list_view.dart'; import 'marianumMessage/view/marianum_message_list_view.dart';
@@ -23,10 +24,11 @@ class AppModule {
Modules.timetable: AppModule('Vertretung', Icons.calendar_month, Timetable.new), Modules.timetable: AppModule('Vertretung', Icons.calendar_month, Timetable.new),
Modules.talk: AppModule('Talk', Icons.chat, ChatList.new), Modules.talk: AppModule('Talk', Icons.chat, ChatList.new),
Modules.files: AppModule('Files', Icons.folder, Files.new), Modules.files: AppModule('Files', Icons.folder, Files.new),
Modules.blocFiles: AppModule('BlocFiles', Icons.folder, FilesView.new),
Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new), Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new),
Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new), Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new),
Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new), Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new),
Modules.holidays: AppModule('Schulferien', Icons.time_to_leave, HolidaysView.new), Modules.holidays: AppModule('Schulferien', Icons.flight, HolidaysView.new),
}; };
static AppModule getModule(Modules module) => modules()[module]!; static AppModule getModule(Modules module) => modules()[module]!;
@@ -53,6 +55,7 @@ enum Modules {
timetable, timetable,
talk, talk,
files, files,
blocFiles,
marianumMessage, marianumMessage,
roomPlan, roomPlan,
gradeAveragesCalculator, gradeAveragesCalculator,

View File

@@ -0,0 +1,71 @@
import 'dart:developer';
import 'package:sorted/sorted.dart';
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart';
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import '../repository/files_repository.dart';
import 'files_event.dart';
import 'files_state.dart';
class FilesBloc extends LoadableHydratedBloc<FilesEvent, FilesState, FilesRepository> {
static const String basePath = '/';
FilesBloc() {
add(Emit((state) => state.copyWith(currentFolder: basePath)));
on<EnterFolder>((event, emit) {
add(Emit((state) {
log('setFolder');
return state.copyWith(currentFolder: event.absolutePath);
}, fetch: true));
});
}
List<File>? getVisibleFiles() => innerState?.files[innerState?.currentFolder];
String getCurrentFolder() => innerState?.currentFolder ?? basePath;
String getCurrentFolderName() {
var folder = innerState?.currentFolder.split('/').reversed.elementAt(1);
return folder!.isEmpty ? 'Dateien' : folder;
}
bool canGoBack() => innerState?.currentFolder != basePath;
String goBackLocation() {
var pathSegments = innerState?.currentFolder.split(basePath) ?? [];
if (pathSegments.isNotEmpty) {
pathSegments.removeLast();
pathSegments.removeLast();
}
return pathSegments.join(basePath) + basePath;
}
List currentSortConfiguration() => [
SortedComparable<File, DateTime>((file) => file.updatedAt ?? DateTime.now()),
];
@override
FilesState fromNothing() => const FilesState(currentFolder: basePath, files: {});
@override
FilesState fromStorage(Map<String, dynamic> json) => FilesState.fromJson(json);
@override
Future<void> gatherData() async {
var fetchFolder = getCurrentFolder();
log(fetchFolder);
var files = await repo.getFileList(fetchFolder);
var newFileMap = Map.of(innerState?.files ?? <String, List<File>>{});
newFileMap[fetchFolder] = files;
if(fetchFolder != getCurrentFolder()) {
log('Fetch aborted due to folder change (expected "$fetchFolder" got "${getCurrentFolder()}")');
return;
}
add(DataGathered((state) => state.copyWith(files: newFileMap)));
}
@override
FilesRepository repository() => FilesRepository();
@override
Map<String, dynamic>? toStorage(FilesState state) => state.toJson();
}

View File

@@ -0,0 +1,10 @@
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import 'files_state.dart';
sealed class FilesEvent extends LoadableHydratedBlocEvent<FilesState> {}
class EnterFolder extends FilesEvent {
String absolutePath;
EnterFolder(this.absolutePath);
}

View File

@@ -0,0 +1,29 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'files_state.freezed.dart';
part 'files_state.g.dart';
@freezed
class FilesState with _$FilesState {
const factory FilesState({
required String currentFolder,
required Map<String, List<File>> files,
}) = _FilesState;
factory FilesState.fromJson(Map<String, Object?> json) => _$FilesStateFromJson(json);
}
@freezed
class File with _$File {
const factory File({
required String path,
required bool isFolder,
required String name,
required DateTime? createdAt,
required DateTime? updatedAt,
required int? size,
required String? mimeType,
}) = _File;
factory File.fromJson(Map<String, Object?> json) => _$FileFromJson(json);
}

View File

@@ -0,0 +1,439 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'files_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
FilesState _$FilesStateFromJson(Map<String, dynamic> json) {
return _FilesState.fromJson(json);
}
/// @nodoc
mixin _$FilesState {
String get currentFolder => throw _privateConstructorUsedError;
Map<String, List<File>> get files => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FilesStateCopyWith<FilesState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FilesStateCopyWith<$Res> {
factory $FilesStateCopyWith(
FilesState value, $Res Function(FilesState) then) =
_$FilesStateCopyWithImpl<$Res, FilesState>;
@useResult
$Res call({String currentFolder, Map<String, List<File>> files});
}
/// @nodoc
class _$FilesStateCopyWithImpl<$Res, $Val extends FilesState>
implements $FilesStateCopyWith<$Res> {
_$FilesStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? currentFolder = null,
Object? files = null,
}) {
return _then(_value.copyWith(
currentFolder: null == currentFolder
? _value.currentFolder
: currentFolder // ignore: cast_nullable_to_non_nullable
as String,
files: null == files
? _value.files
: files // ignore: cast_nullable_to_non_nullable
as Map<String, List<File>>,
) as $Val);
}
}
/// @nodoc
abstract class _$$FilesStateImplCopyWith<$Res>
implements $FilesStateCopyWith<$Res> {
factory _$$FilesStateImplCopyWith(
_$FilesStateImpl value, $Res Function(_$FilesStateImpl) then) =
__$$FilesStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String currentFolder, Map<String, List<File>> files});
}
/// @nodoc
class __$$FilesStateImplCopyWithImpl<$Res>
extends _$FilesStateCopyWithImpl<$Res, _$FilesStateImpl>
implements _$$FilesStateImplCopyWith<$Res> {
__$$FilesStateImplCopyWithImpl(
_$FilesStateImpl _value, $Res Function(_$FilesStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? currentFolder = null,
Object? files = null,
}) {
return _then(_$FilesStateImpl(
currentFolder: null == currentFolder
? _value.currentFolder
: currentFolder // ignore: cast_nullable_to_non_nullable
as String,
files: null == files
? _value._files
: files // ignore: cast_nullable_to_non_nullable
as Map<String, List<File>>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$FilesStateImpl implements _FilesState {
const _$FilesStateImpl(
{required this.currentFolder,
required final Map<String, List<File>> files})
: _files = files;
factory _$FilesStateImpl.fromJson(Map<String, dynamic> json) =>
_$$FilesStateImplFromJson(json);
@override
final String currentFolder;
final Map<String, List<File>> _files;
@override
Map<String, List<File>> get files {
if (_files is EqualUnmodifiableMapView) return _files;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_files);
}
@override
String toString() {
return 'FilesState(currentFolder: $currentFolder, files: $files)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FilesStateImpl &&
(identical(other.currentFolder, currentFolder) ||
other.currentFolder == currentFolder) &&
const DeepCollectionEquality().equals(other._files, _files));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, currentFolder, const DeepCollectionEquality().hash(_files));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FilesStateImplCopyWith<_$FilesStateImpl> get copyWith =>
__$$FilesStateImplCopyWithImpl<_$FilesStateImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$FilesStateImplToJson(
this,
);
}
}
abstract class _FilesState implements FilesState {
const factory _FilesState(
{required final String currentFolder,
required final Map<String, List<File>> files}) = _$FilesStateImpl;
factory _FilesState.fromJson(Map<String, dynamic> json) =
_$FilesStateImpl.fromJson;
@override
String get currentFolder;
@override
Map<String, List<File>> get files;
@override
@JsonKey(ignore: true)
_$$FilesStateImplCopyWith<_$FilesStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
File _$FileFromJson(Map<String, dynamic> json) {
return _File.fromJson(json);
}
/// @nodoc
mixin _$File {
String get path => throw _privateConstructorUsedError;
bool get isFolder => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
DateTime? get createdAt => throw _privateConstructorUsedError;
DateTime? get updatedAt => throw _privateConstructorUsedError;
int? get size => throw _privateConstructorUsedError;
String? get mimeType => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FileCopyWith<File> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FileCopyWith<$Res> {
factory $FileCopyWith(File value, $Res Function(File) then) =
_$FileCopyWithImpl<$Res, File>;
@useResult
$Res call(
{String path,
bool isFolder,
String name,
DateTime? createdAt,
DateTime? updatedAt,
int? size,
String? mimeType});
}
/// @nodoc
class _$FileCopyWithImpl<$Res, $Val extends File>
implements $FileCopyWith<$Res> {
_$FileCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? path = null,
Object? isFolder = null,
Object? name = null,
Object? createdAt = freezed,
Object? updatedAt = freezed,
Object? size = freezed,
Object? mimeType = freezed,
}) {
return _then(_value.copyWith(
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String,
isFolder: null == isFolder
? _value.isFolder
: isFolder // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
size: freezed == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as int?,
mimeType: freezed == mimeType
? _value.mimeType
: mimeType // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$FileImplCopyWith<$Res> implements $FileCopyWith<$Res> {
factory _$$FileImplCopyWith(
_$FileImpl value, $Res Function(_$FileImpl) then) =
__$$FileImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String path,
bool isFolder,
String name,
DateTime? createdAt,
DateTime? updatedAt,
int? size,
String? mimeType});
}
/// @nodoc
class __$$FileImplCopyWithImpl<$Res>
extends _$FileCopyWithImpl<$Res, _$FileImpl>
implements _$$FileImplCopyWith<$Res> {
__$$FileImplCopyWithImpl(_$FileImpl _value, $Res Function(_$FileImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? path = null,
Object? isFolder = null,
Object? name = null,
Object? createdAt = freezed,
Object? updatedAt = freezed,
Object? size = freezed,
Object? mimeType = freezed,
}) {
return _then(_$FileImpl(
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String,
isFolder: null == isFolder
? _value.isFolder
: isFolder // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
size: freezed == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as int?,
mimeType: freezed == mimeType
? _value.mimeType
: mimeType // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$FileImpl implements _File {
const _$FileImpl(
{required this.path,
required this.isFolder,
required this.name,
required this.createdAt,
required this.updatedAt,
required this.size,
required this.mimeType});
factory _$FileImpl.fromJson(Map<String, dynamic> json) =>
_$$FileImplFromJson(json);
@override
final String path;
@override
final bool isFolder;
@override
final String name;
@override
final DateTime? createdAt;
@override
final DateTime? updatedAt;
@override
final int? size;
@override
final String? mimeType;
@override
String toString() {
return 'File(path: $path, isFolder: $isFolder, name: $name, createdAt: $createdAt, updatedAt: $updatedAt, size: $size, mimeType: $mimeType)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FileImpl &&
(identical(other.path, path) || other.path == path) &&
(identical(other.isFolder, isFolder) ||
other.isFolder == isFolder) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.size, size) || other.size == size) &&
(identical(other.mimeType, mimeType) ||
other.mimeType == mimeType));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, path, isFolder, name, createdAt, updatedAt, size, mimeType);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FileImplCopyWith<_$FileImpl> get copyWith =>
__$$FileImplCopyWithImpl<_$FileImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$FileImplToJson(
this,
);
}
}
abstract class _File implements File {
const factory _File(
{required final String path,
required final bool isFolder,
required final String name,
required final DateTime? createdAt,
required final DateTime? updatedAt,
required final int? size,
required final String? mimeType}) = _$FileImpl;
factory _File.fromJson(Map<String, dynamic> json) = _$FileImpl.fromJson;
@override
String get path;
@override
bool get isFolder;
@override
String get name;
@override
DateTime? get createdAt;
@override
DateTime? get updatedAt;
@override
int? get size;
@override
String? get mimeType;
@override
@JsonKey(ignore: true)
_$$FileImplCopyWith<_$FileImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'files_state.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$FilesStateImpl _$$FilesStateImplFromJson(Map<String, dynamic> json) =>
_$FilesStateImpl(
currentFolder: json['currentFolder'] as String,
files: (json['files'] as Map<String, dynamic>).map(
(k, e) => MapEntry(
k,
(e as List<dynamic>)
.map((e) => File.fromJson(e as Map<String, dynamic>))
.toList()),
),
);
Map<String, dynamic> _$$FilesStateImplToJson(_$FilesStateImpl instance) =>
<String, dynamic>{
'currentFolder': instance.currentFolder,
'files': instance.files,
};
_$FileImpl _$$FileImplFromJson(Map<String, dynamic> json) => _$FileImpl(
path: json['path'] as String,
isFolder: json['isFolder'] as bool,
name: json['name'] as String,
createdAt: json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String),
updatedAt: json['updatedAt'] == null
? null
: DateTime.parse(json['updatedAt'] as String),
size: (json['size'] as num?)?.toInt(),
mimeType: json['mimeType'] as String?,
);
Map<String, dynamic> _$$FileImplToJson(_$FileImpl instance) =>
<String, dynamic>{
'path': instance.path,
'isFolder': instance.isFolder,
'name': instance.name,
'createdAt': instance.createdAt?.toIso8601String(),
'updatedAt': instance.updatedAt?.toIso8601String(),
'size': instance.size,
'mimeType': instance.mimeType,
};

View File

@@ -0,0 +1,31 @@
import 'package:nextcloud/nextcloud.dart';
import '../../../../../api/marianumcloud/webdav/webdavApi.dart';
import '../../../infrastructure/repository/repository.dart';
import '../bloc/files_state.dart';
class FilesRepository extends Repository<FilesState> {
Future<List<File>> getFileList(String path) async {
var webdav = await WebdavApi.webdav;
var response = await webdav.propfind(PathUri.parse(path)); // TODO move to custom data loader
var davFiles = response.toWebDavFiles();
davFiles.removeWhere((file) {
var filePath = file.path.path;
return filePath.isEmpty || filePath == path;
});
var files = davFiles.map((davFile) => File(
path: davFile.path.path,
isFolder: davFile.isDirectory,
name: davFile.name,
createdAt: davFile.createdDate,
updatedAt: davFile.lastModified,
size: davFile.size,
mimeType: davFile.mimeType
));
return files.toList();
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import '../../../infrastructure/loadableState/loadable_state.dart';
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
import '../bloc/files_bloc.dart';
import '../bloc/files_state.dart';
import 'folder_view.dart';
class FilesView extends StatelessWidget {
const FilesView({super.key});
@override
Widget build(BuildContext context) => BlocModule<FilesBloc, LoadableState<FilesState>>(
create: (context) => FilesBloc(),
autoRebuild: true,
child: (context, bloc, state) => FolderView(bloc),
);
}

View File

@@ -0,0 +1,57 @@
import 'dart:developer';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import '../../../../../widget/centeredLeading.dart';
import '../../../../../widget/list_view_util.dart';
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
import '../bloc/files_bloc.dart';
import '../bloc/files_event.dart';
import '../bloc/files_state.dart';
class FolderView extends StatelessWidget {
final FilesBloc bloc;
const FolderView(this.bloc, {super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
leading: !bloc.canGoBack() ? null : IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
bloc.add(EnterFolder(bloc.goBackLocation()));
Navigator.of(context).pop();
},
),
title: Text(bloc.getCurrentFolderName()),
actions: [
IconButton(onPressed: () {
log(bloc.innerState?.toJson().toString() ?? 'leer');
}, icon: const Icon(Icons.bug_report)),
IconButton(onPressed: () {
bloc.add(EnterFolder('/'));
}, icon: const Icon(Icons.home)),
],
),
body: LoadableStateConsumer<FilesBloc, FilesState>(
controllerByValue: bloc,
child: (state, loading) => ListViewUtil.fromList<File>(bloc.getVisibleFiles(), (file) => ListTile(
leading: CenteredLeading(Icon(file.isFolder ? Icons.folder : Icons.description_outlined)),
title: Text(file.name),
subtitle: file.isFolder
? Text('geändert ${Jiffy.parseFromDateTime(file.updatedAt ?? DateTime.now()).fromNow()}')
: Text('${filesize(file.size)}, ${Jiffy.parseFromDateTime(file.updatedAt ?? DateTime.now()).fromNow()}'),
trailing: Icon(file.isFolder ? Icons.arrow_right : null),
onTap: () {
log(file.path);
if(!file.isFolder) return;
Navigator.of(context).push(MaterialPageRoute(builder: (context) => FolderView(bloc)));
bloc.add(EnterFolder(file.path));
},
))
)
);
}

View File

@@ -11,7 +11,9 @@ _$GradeAveragesStateImpl _$$GradeAveragesStateImplFromJson(
_$GradeAveragesStateImpl( _$GradeAveragesStateImpl(
gradingSystem: $enumDecode( gradingSystem: $enumDecode(
_$GradeAveragesGradingSystemEnumMap, json['gradingSystem']), _$GradeAveragesGradingSystemEnumMap, json['gradingSystem']),
grades: (json['grades'] as List<dynamic>).map((e) => e as int).toList(), grades: (json['grades'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
); );
Map<String, dynamic> _$$GradeAveragesStateImplToJson( Map<String, dynamic> _$$GradeAveragesStateImplToJson(

View File

@@ -16,6 +16,7 @@ class HolidaysBloc extends LoadableHydratedBloc<HolidaysEvent, HolidaysState, Ho
} }
bool showPastHolidays() => innerState?.showPastHolidays ?? false; bool showPastHolidays() => innerState?.showPastHolidays ?? false;
bool showDisclaimerOnEntry() => innerState?.showDisclaimer ?? false;
List<Holiday>? getHolidays() => innerState?.holidays List<Holiday>? getHolidays() => innerState?.holidays
.where((element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now())) .where((element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now()))
.toList() ?? []; .toList() ?? [];

View File

@@ -26,7 +26,7 @@ _$HolidayImpl _$$HolidayImplFromJson(Map<String, dynamic> json) =>
_$HolidayImpl( _$HolidayImpl(
start: json['start'] as String, start: json['start'] as String,
end: json['end'] as String, end: json['end'] as String,
year: json['year'] as int, year: (json['year'] as num).toInt(),
stateCode: json['stateCode'] as String, stateCode: json['stateCode'] as String,
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String, slug: json['slug'] as String,

View File

@@ -1,7 +1,7 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../../../basis/dataloader/holiday_data_loader.dart'; import '../../../basis/dataloader/holiday_data_loader.dart';
import '../../../infrastructure/dataLoader/data_loader.dart'; import '../../../infrastructure/dataLoader/http_data_loader.dart';
import '../bloc/holidays_state.dart'; import '../bloc/holidays_state.dart';
class HolidaysGetHolidays extends HolidayDataLoader<List<Holiday>> { class HolidaysGetHolidays extends HolidayDataLoader<List<Holiday>> {

View File

@@ -6,6 +6,7 @@ import '../../../../../widget/list_view_util.dart';
import '../../../../../widget/centeredLeading.dart'; import '../../../../../widget/centeredLeading.dart';
import '../../../../../widget/debug/debugTile.dart'; import '../../../../../widget/debug/debugTile.dart';
import '../../../../../widget/string_extensions.dart'; import '../../../../../widget/string_extensions.dart';
import '../../../infrastructure/loadableState/loadable_state.dart';
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart'; import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
import '../../../infrastructure/utilityWidgets/bloc_module.dart'; import '../../../infrastructure/utilityWidgets/bloc_module.dart';
import '../bloc/holidays_bloc.dart'; import '../bloc/holidays_bloc.dart';
@@ -16,7 +17,7 @@ class HolidaysView extends StatelessWidget {
const HolidaysView({super.key}); const HolidaysView({super.key});
@override @override
Widget build(BuildContext context) => BlocModule( Widget build(BuildContext context) => BlocModule<HolidaysBloc, LoadableState<HolidaysState>>(
create: (context) => HolidaysBloc(), create: (context) => HolidaysBloc(),
autoRebuild: true, autoRebuild: true,
child: (context, bloc, state) { child: (context, bloc, state) {
@@ -28,10 +29,7 @@ class HolidaysView extends StatelessWidget {
'Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n' 'Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n'
'Die Daten stammen von https://ferien-api.de/'), 'Die Daten stammen von https://ferien-api.de/'),
actions: [ actions: [
TextButton(child: const Text('Okay'), onPressed: () { TextButton(child: const Text('Okay'), onPressed: () => Navigator.of(context).pop()),
bloc.add(DisclaimerDismissed());
Navigator.of(context).pop();
}),
], ],
)); ));
} }
@@ -63,6 +61,10 @@ class HolidaysView extends StatelessWidget {
], ],
), ),
body: LoadableStateConsumer<HolidaysBloc, HolidaysState>( body: LoadableStateConsumer<HolidaysBloc, HolidaysState>(
onLoad: (state) {
if(state.showDisclaimer) showDisclaimer();
bloc.add(DisclaimerDismissed());
},
child: (state, loading) => ListViewUtil.fromList<Holiday>(bloc.getHolidays(), (holiday) { child: (state, loading) => ListViewUtil.fromList<Holiday>(bloc.getHolidays(), (holiday) {
var holidayType = holiday.name.split(' ').first.capitalize(); var holidayType = holiday.name.split(' ').first.capitalize();
String formatDate(String date) => Jiffy.parse(date).format(pattern: 'dd.MM.yyyy'); String formatDate(String date) => Jiffy.parse(date).format(pattern: 'dd.MM.yyyy');

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../../../infrastructure/dataLoader/data_loader.dart'; import '../../../infrastructure/dataLoader/http_data_loader.dart';
import '../../../basis/dataloader/mhsl_data_loader.dart'; import '../../../basis/dataloader/mhsl_data_loader.dart';
import '../bloc/marianum_message_state.dart'; import '../bloc/marianum_message_state.dart';

View File

@@ -8,6 +8,6 @@ class LightAppTheme {
colorScheme: ColorScheme.fromSeed(seedColor: marianumRed), colorScheme: ColorScheme.fromSeed(seedColor: marianumRed),
floatingActionButtonTheme: const FloatingActionButtonThemeData( floatingActionButtonTheme: const FloatingActionButtonThemeData(
foregroundColor: Colors.white foregroundColor: Colors.white
) ),
); );
} }

View File

@@ -1,3 +1,3 @@
extension StringExtensions on String { extension StringExtensions on String {
String capitalize() => '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; String capitalize() => '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
} }

View File

@@ -104,6 +104,7 @@ dependencies:
connectivity_plus: ^6.0.3 connectivity_plus: ^6.0.3
hydrated_bloc: ^9.1.5 hydrated_bloc: ^9.1.5
dio: ^4.0.6 dio: ^4.0.6
sorted: ^2.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: