From fea36b9a6dd8d97c6398c8bb9c6434a1a7e23d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Mon, 20 Feb 2023 10:56:51 +0100 Subject: [PATCH] Added Nextcloud base --- .idea/libraries/Dart_Packages.xml | 60 ++---- .idea/libraries/Flutter_Plugins.xml | 1 - assets/background/chat.png | Bin 0 -> 31776 bytes flutter_01.log | 59 ++++++ lib/api/{webuntis => }/apiParams.dart | 0 lib/api/apiRequest.dart | 31 --- lib/api/{webuntis => }/apiResponse.dart | 0 lib/api/marianumcloud/talk/chat/getChat.dart | 26 +++ .../marianumcloud/talk/chat/getChatCache.dart | 30 +++ .../talk/chat/getChatParams.dart | 33 ++++ .../talk/chat/getChatParams.g.dart | 48 +++++ .../talk/chat/getChatResponse.dart | 53 +++++ .../talk/chat/getChatResponse.g.dart | 69 +++++++ lib/api/marianumcloud/talk/room/getRoom.dart | 32 +++ .../marianumcloud/talk/room/getRoomCache.dart | 30 +++ .../talk/room/getRoomParams.dart | 26 +++ .../talk/room/getRoomParams.g.dart | 28 +++ .../talk/room/getRoomResponse.dart | 151 ++++++++++++++ .../talk/room/getRoomResponse.g.dart | 158 +++++++++++++++ lib/api/marianumcloud/talk/talkApi.dart | 51 +++++ lib/api/marianumcloud/talk/talkError.dart | 12 ++ lib/api/marianumcloud/webdav/webdavApi.dart | 9 + lib/api/requestCache.dart | 6 + lib/api/talk/requestLoginTest.dart | 6 - .../authenticate/authenticateParams.dart | 2 +- .../authenticate/authenticateResponse.dart | 2 +- .../queries/getHolidays/getHolidays.dart | 17 +- .../queries/getHolidays/getHolidaysCache.dart | 4 +- .../getHolidays/getHolidaysResponse.dart | 2 +- .../webuntis/queries/getRooms/getRooms.dart | 2 +- .../queries/getRooms/getRoomsCache.dart | 4 +- .../queries/getRooms/getRoomsResponse.dart | 3 +- .../queries/getSubjects/getSubjects.dart | 2 +- .../queries/getSubjects/getSubjectsCache.dart | 4 +- .../getSubjects/getSubjectsResponse.dart | 2 +- .../queries/getTimetable/getTimetable.dart | 2 +- .../getTimetable/getTimetableCache.dart | 24 ++- .../getTimetable/getTimetableParams.dart | 14 +- .../getTimetable/getTimetableParams.g.dart | 48 +++-- .../getTimetable/getTimetableResponse.dart | 2 +- lib/api/webuntis/webuntisApi.dart | 17 +- lib/api/webuntis/webuntisError.dart | 1 + lib/app.dart | 50 ++--- lib/data/chatList/chatListProps.dart | 29 +++ lib/data/chatList/chatProps.dart | 35 ++++ lib/data/dataHolder.dart | 9 +- lib/data/timetable/persistantTimetable.dart | 12 -- lib/data/timetable/timetable.dart | 60 ------ lib/data/timetable/timetableProps.dart | 101 ++++++++++ lib/main.dart | 40 ++-- lib/screen/pages/talk/chatList.dart | 101 ++++++++++ lib/screen/pages/talk/chatOverview.dart | 164 ---------------- lib/screen/pages/talk/chatView.dart | 184 +++++++++++++----- lib/screen/pages/timetable/dayListView.dart | 139 +++++++++++++ .../pages/timetable/storedTimetable.dart | 104 ---------- lib/screen/pages/timetable/testTimetable.dart | 9 +- lib/screen/pages/timetable/timetable.dart | 142 ++++++-------- lib/screen/pages/timetable/timetableOld.dart | 106 ++++++++++ lib/screen/settings/debug/debugOverview.dart | 90 +++++++++ lib/screen/settings/debug/jsonViewer.dart | 21 ++ lib/screen/settings/settings.dart | 92 +++++---- macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.yaml | 2 +- 63 files changed, 1863 insertions(+), 700 deletions(-) create mode 100644 assets/background/chat.png create mode 100644 flutter_01.log rename lib/api/{webuntis => }/apiParams.dart (100%) rename lib/api/{webuntis => }/apiResponse.dart (100%) create mode 100644 lib/api/marianumcloud/talk/chat/getChat.dart create mode 100644 lib/api/marianumcloud/talk/chat/getChatCache.dart create mode 100644 lib/api/marianumcloud/talk/chat/getChatParams.dart create mode 100644 lib/api/marianumcloud/talk/chat/getChatParams.g.dart create mode 100644 lib/api/marianumcloud/talk/chat/getChatResponse.dart create mode 100644 lib/api/marianumcloud/talk/chat/getChatResponse.g.dart create mode 100644 lib/api/marianumcloud/talk/room/getRoom.dart create mode 100644 lib/api/marianumcloud/talk/room/getRoomCache.dart create mode 100644 lib/api/marianumcloud/talk/room/getRoomParams.dart create mode 100644 lib/api/marianumcloud/talk/room/getRoomParams.g.dart create mode 100644 lib/api/marianumcloud/talk/room/getRoomResponse.dart create mode 100644 lib/api/marianumcloud/talk/room/getRoomResponse.g.dart create mode 100644 lib/api/marianumcloud/talk/talkApi.dart create mode 100644 lib/api/marianumcloud/talk/talkError.dart create mode 100644 lib/api/marianumcloud/webdav/webdavApi.dart delete mode 100644 lib/api/talk/requestLoginTest.dart create mode 100644 lib/data/chatList/chatListProps.dart create mode 100644 lib/data/chatList/chatProps.dart delete mode 100644 lib/data/timetable/persistantTimetable.dart delete mode 100644 lib/data/timetable/timetable.dart create mode 100644 lib/data/timetable/timetableProps.dart create mode 100644 lib/screen/pages/talk/chatList.dart delete mode 100644 lib/screen/pages/talk/chatOverview.dart create mode 100644 lib/screen/pages/timetable/dayListView.dart delete mode 100644 lib/screen/pages/timetable/storedTimetable.dart create mode 100644 lib/screen/pages/timetable/timetableOld.dart create mode 100644 lib/screen/settings/debug/debugOverview.dart create mode 100644 lib/screen/settings/debug/jsonViewer.dart diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 307aa47..df7af86 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -5,14 +5,14 @@ - - @@ -89,7 +89,7 @@ - @@ -194,7 +194,7 @@ - @@ -222,7 +222,7 @@ - @@ -383,7 +383,7 @@ - @@ -653,20 +653,6 @@ - - - - - - - - - - - - @@ -695,13 +681,6 @@ - - - - - - @@ -740,7 +719,7 @@ - @@ -824,7 +803,7 @@ - @@ -845,7 +824,7 @@ - @@ -859,8 +838,8 @@ - - + + @@ -871,7 +850,7 @@ - + @@ -886,11 +865,11 @@ - + - + @@ -910,7 +889,7 @@ - + @@ -948,19 +927,16 @@ - - - - + @@ -972,10 +948,10 @@ - + - + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 0ed35db..75b4719 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -11,7 +11,6 @@ - diff --git a/assets/background/chat.png b/assets/background/chat.png new file mode 100644 index 0000000000000000000000000000000000000000..d41c9fce34105d67969feb11c9e0b5e636f20a46 GIT binary patch literal 31776 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 TzRZqR00000NkvXXu0mjfYX6P- literal 0 HcmV?d00001 diff --git a/flutter_01.log b/flutter_01.log new file mode 100644 index 0000000..7b6fdc5 --- /dev/null +++ b/flutter_01.log @@ -0,0 +1,59 @@ +Flutter crash report. +Please report a bug at https://github.com/flutter/flutter/issues. + +## command + +flutter --no-color run --machine --device-id=5200de3d4dd02295 --profile --dart-define=flutter.inspector.structuredErrors=true lib/main.dart + +## exception + +String: failed to connect to http://127.0.0.1:45275/X0V7tlGyJXA=/ + +``` +``` + +## flutter doctor + +``` +[!] Flutter (Channel stable, 3.7.3, on Linux Mint 20.3 5.15.0-60-generic, locale de_DE.UTF-8) + • Flutter version 3.7.3 on channel stable at /opt/flutter + ! The flutter binary is not on your path. Consider adding /opt/flutter/bin to your path. + ! The dart binary is not on your path. Consider adding /opt/flutter/bin to your path. + • Upstream repository https://github.com/flutter/flutter.git + • Framework revision 9944297138 (vor 8 Tagen), 2023-02-08 15:46:04 -0800 + • Engine revision 248290d6d5 + • Dart version 2.19.2 + • DevTools version 2.20.1 + • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. + +[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) + • Android SDK at /home/elias/Android/Sdk + • Platform android-33, build-tools 33.0.0 + • Java binary at: /home/elias/.local/share/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9123335/jre/bin/java + • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) + • All Android licenses accepted. + +[✗] Chrome - develop for the web (Cannot find Chrome executable at google-chrome) + ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable. + +[✓] Linux toolchain - develop for Linux desktop + • clang version 10.0.0-4ubuntu1 + • cmake version 3.16.3 + • ninja version 1.10.0 + • pkg-config version 0.29.1 + +[✓] Android Studio (version 2021.3) + • Android Studio at /home/elias/.local/share/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9123335 + • Flutter plugin version 71.0.3 + • Dart plugin version 213.7433 + • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) + +[✓] Connected device (2 available) + • SM G800F (mobile) • 5200de3d4dd02295 • android-arm • Android 6.0.1 (API 23) + • Linux (desktop) • linux • linux-x64 • Linux Mint 20.3 5.15.0-60-generic + +[✓] HTTP Host Availability + • All required HTTP hosts are available + +! Doctor found issues in 2 categories. +``` diff --git a/lib/api/webuntis/apiParams.dart b/lib/api/apiParams.dart similarity index 100% rename from lib/api/webuntis/apiParams.dart rename to lib/api/apiParams.dart diff --git a/lib/api/apiRequest.dart b/lib/api/apiRequest.dart index c17ad0b..dec2cdb 100644 --- a/lib/api/apiRequest.dart +++ b/lib/api/apiRequest.dart @@ -4,36 +4,5 @@ import 'package:http/http.dart' as http; import 'package:marianum_mobile/api/apiError.dart'; class ApiRequest { - Uri endpoint; - ApiRequest(this.endpoint); - - Future get(Map? headers) async { - return await http.get(endpoint, headers: headers); - } - - Future post(String data, Map? headers) async { - log("Fetching: ${data}"); - try { - http.Response response = await http - .post(endpoint, body: data, headers: headers) - .timeout( - const Duration(seconds: 10), - onTimeout: () { - log("timeout!"); - throw ApiError("Network timeout"); - } - ); - - if(response.statusCode != 200) { - log("Got ${response.statusCode}"); - throw ApiError("Service response invalid, got status ${response.statusCode}"); - } - return response; - - } on Exception catch(e) { - throw ApiError("Http: ${e.toString()}"); - } - - } } \ No newline at end of file diff --git a/lib/api/webuntis/apiResponse.dart b/lib/api/apiResponse.dart similarity index 100% rename from lib/api/webuntis/apiResponse.dart rename to lib/api/apiResponse.dart diff --git a/lib/api/marianumcloud/talk/chat/getChat.dart b/lib/api/marianumcloud/talk/chat/getChat.dart new file mode 100644 index 0000000..5c23004 --- /dev/null +++ b/lib/api/marianumcloud/talk/chat/getChat.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:http/src/response.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/chat/getChatResponse.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/talkApi.dart'; + +import 'getChatParams.dart'; + +class GetChat extends TalkApi { + String chatToken; + + GetChatParams params; + GetChat(this.chatToken, this.params) : super("v1/chat/$chatToken", null, getParameters: params.toJson()); + + @override + assemble(String raw) { + return GetChatResponse.fromJson(jsonDecode(raw)['ocs']); // TODO move "ocs" to superclass + } + + @override + Future request(Uri uri, Object? body, Map? headers) { + return http.get(uri, headers: headers); + } + +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/chat/getChatCache.dart b/lib/api/marianumcloud/talk/chat/getChatCache.dart new file mode 100644 index 0000000..a645faf --- /dev/null +++ b/lib/api/marianumcloud/talk/chat/getChatCache.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +import 'package:marianum_mobile/api/marianumcloud/talk/chat/getChat.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/chat/getChatParams.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/chat/getChatResponse.dart'; +import 'package:marianum_mobile/api/requestCache.dart'; + +class GetChatCache extends RequestCache { + String chatToken; + + GetChatCache({required onUpdate, required this.chatToken}) : super(RequestCache.cacheNothing, onUpdate) { + start("MarianumMobile", "nc-chat-$chatToken"); + } + + @override + Future onLoad() { + return GetChat( + chatToken, + GetChatParams( + lookIntoFuture: GetChatParamsSwitch.off + ) + ).run(); + } + + @override + GetChatResponse onLocalData(String json) { + return GetChatResponse.fromJson(jsonDecode(json)); + } + +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/chat/getChatParams.dart b/lib/api/marianumcloud/talk/chat/getChatParams.dart new file mode 100644 index 0000000..5644e18 --- /dev/null +++ b/lib/api/marianumcloud/talk/chat/getChatParams.dart @@ -0,0 +1,33 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/apiParams.dart'; + +part 'getChatParams.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class GetChatParams extends ApiParams { + GetChatParamsSwitch lookIntoFuture; + int? limit; + int? lastKnownMessageId; + int? lastCommonReadId; + int? timeout; + GetChatParamsSwitch? setReadMarker; + GetChatParamsSwitch? includeLastKnown; + + GetChatParams({ + required this.lookIntoFuture, + this.limit, + this.lastKnownMessageId, + this.lastCommonReadId, + this.timeout, + this.setReadMarker, + this.includeLastKnown + }); + + factory GetChatParams.fromJson(Map json) => _$GetChatParamsFromJson(json); + Map toJson() => _$GetChatParamsToJson(this); +} + +enum GetChatParamsSwitch { + @JsonValue(1) on, + @JsonValue(0) off, +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/chat/getChatParams.g.dart b/lib/api/marianumcloud/talk/chat/getChatParams.g.dart new file mode 100644 index 0000000..743e219 --- /dev/null +++ b/lib/api/marianumcloud/talk/chat/getChatParams.g.dart @@ -0,0 +1,48 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getChatParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetChatParams _$GetChatParamsFromJson(Map json) => + GetChatParams( + lookIntoFuture: + $enumDecode(_$GetChatParamsSwitchEnumMap, json['lookIntoFuture']), + limit: json['limit'] as int?, + lastKnownMessageId: json['lastKnownMessageId'] as int?, + lastCommonReadId: json['lastCommonReadId'] as int?, + timeout: json['timeout'] as int?, + setReadMarker: $enumDecodeNullable( + _$GetChatParamsSwitchEnumMap, json['setReadMarker']), + includeLastKnown: $enumDecodeNullable( + _$GetChatParamsSwitchEnumMap, json['includeLastKnown']), + ); + +Map _$GetChatParamsToJson(GetChatParams instance) { + final val = { + 'lookIntoFuture': _$GetChatParamsSwitchEnumMap[instance.lookIntoFuture]!, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('limit', instance.limit); + writeNotNull('lastKnownMessageId', instance.lastKnownMessageId); + writeNotNull('lastCommonReadId', instance.lastCommonReadId); + writeNotNull('timeout', instance.timeout); + writeNotNull( + 'setReadMarker', _$GetChatParamsSwitchEnumMap[instance.setReadMarker]); + writeNotNull('includeLastKnown', + _$GetChatParamsSwitchEnumMap[instance.includeLastKnown]); + return val; +} + +const _$GetChatParamsSwitchEnumMap = { + GetChatParamsSwitch.on: 1, + GetChatParamsSwitch.off: 0, +}; diff --git a/lib/api/marianumcloud/talk/chat/getChatResponse.dart b/lib/api/marianumcloud/talk/chat/getChatResponse.dart new file mode 100644 index 0000000..6d9c3f4 --- /dev/null +++ b/lib/api/marianumcloud/talk/chat/getChatResponse.dart @@ -0,0 +1,53 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomResponse.dart'; + +part 'getChatResponse.g.dart'; + +@JsonSerializable(explicitToJson: true) +class GetChatResponse extends ApiResponse { + Set data; + + GetChatResponse(this.data); + + factory GetChatResponse.fromJson(Map json) => _$GetChatResponseFromJson(json); + Map toJson() => _$GetChatResponseToJson(this); + + List sortByTimestamp() { + List sorted = data.toList(); + sorted.sort((a, b) => a.timestamp.compareTo(b.timestamp)); + return sorted; + } +} + +@JsonSerializable(explicitToJson: true) +class GetChatResponseObject { + int id; + String token; + GetRoomResponseObjectMessageActorType actorType; + String actorId; + String actorDisplayName; + int timestamp; + String systemMessage; + GetRoomResponseObjectMessageType messageType; + bool isReplyable; + String referenceId; + String message; + + GetChatResponseObject( + this.id, + this.token, + this.actorType, + this.actorId, + this.actorDisplayName, + this.timestamp, + this.systemMessage, + this.messageType, + this.isReplyable, + this.referenceId, + this.message); + + factory GetChatResponseObject.fromJson(Map json) => _$GetChatResponseObjectFromJson(json); + Map toJson() => _$GetChatResponseObjectToJson(this); +} + diff --git a/lib/api/marianumcloud/talk/chat/getChatResponse.g.dart b/lib/api/marianumcloud/talk/chat/getChatResponse.g.dart new file mode 100644 index 0000000..7e70ddc --- /dev/null +++ b/lib/api/marianumcloud/talk/chat/getChatResponse.g.dart @@ -0,0 +1,69 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getChatResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetChatResponse _$GetChatResponseFromJson(Map json) => + GetChatResponse( + (json['data'] as List) + .map((e) => GetChatResponseObject.fromJson(e as Map)) + .toSet(), + ); + +Map _$GetChatResponseToJson(GetChatResponse instance) => + { + 'data': instance.data.map((e) => e.toJson()).toList(), + }; + +GetChatResponseObject _$GetChatResponseObjectFromJson( + Map json) => + GetChatResponseObject( + json['id'] as int, + json['token'] as String, + $enumDecode( + _$GetRoomResponseObjectMessageActorTypeEnumMap, json['actorType']), + json['actorId'] as String, + json['actorDisplayName'] as String, + json['timestamp'] as int, + json['systemMessage'] as String, + $enumDecode( + _$GetRoomResponseObjectMessageTypeEnumMap, json['messageType']), + json['isReplyable'] as bool, + json['referenceId'] as String, + json['message'] as String, + ); + +Map _$GetChatResponseObjectToJson( + GetChatResponseObject instance) => + { + 'id': instance.id, + 'token': instance.token, + 'actorType': + _$GetRoomResponseObjectMessageActorTypeEnumMap[instance.actorType]!, + 'actorId': instance.actorId, + 'actorDisplayName': instance.actorDisplayName, + 'timestamp': instance.timestamp, + 'systemMessage': instance.systemMessage, + 'messageType': + _$GetRoomResponseObjectMessageTypeEnumMap[instance.messageType]!, + 'isReplyable': instance.isReplyable, + 'referenceId': instance.referenceId, + 'message': instance.message, + }; + +const _$GetRoomResponseObjectMessageActorTypeEnumMap = { + GetRoomResponseObjectMessageActorType.user: 'users', + GetRoomResponseObjectMessageActorType.guest: 'guests', + GetRoomResponseObjectMessageActorType.bot: 'bots', + GetRoomResponseObjectMessageActorType.bridge: 'bridged', +}; + +const _$GetRoomResponseObjectMessageTypeEnumMap = { + GetRoomResponseObjectMessageType.comment: 'comment', + GetRoomResponseObjectMessageType.deletedComment: 'comment_deleted', + GetRoomResponseObjectMessageType.system: 'system', + GetRoomResponseObjectMessageType.command: 'command', +}; diff --git a/lib/api/marianumcloud/talk/room/getRoom.dart b/lib/api/marianumcloud/talk/room/getRoom.dart new file mode 100644 index 0000000..f677c0d --- /dev/null +++ b/lib/api/marianumcloud/talk/room/getRoom.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:http/http.dart' as http; +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomResponse.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/talkApi.dart'; + +import 'getRoomParams.dart'; + + +class GetRoom extends TalkApi { + GetRoomParams params; + GetRoom(this.params) : super("v4/room", null, getParameters: params.toJson()); + + + + @override + GetRoomResponse assemble(String raw) { + log("ASSEMBLING"); + log(raw); + return GetRoomResponse.fromJson(jsonDecode(raw)['ocs']); + } + + @override + Future request(Uri uri, Object? body, Map? headers) { + log("REQUSTING..."); + log(uri.toString()); + log(headers.toString()); + return http.get(uri, headers: headers); + } + +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/room/getRoomCache.dart b/lib/api/marianumcloud/talk/room/getRoomCache.dart new file mode 100644 index 0000000..0bcc387 --- /dev/null +++ b/lib/api/marianumcloud/talk/room/getRoomCache.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomParams.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomResponse.dart'; +import 'package:marianum_mobile/api/requestCache.dart'; + +import 'getRoom.dart'; + +class GetRoomCache extends RequestCache { + GetRoomCache({onUpdate}) : super(RequestCache.cacheMinute, onUpdate) { + start("MarianumMobile", "nc-rooms"); + } + + @override + GetRoomResponse onLocalData(String json) { + log("LOCAL DATA FOUND"); + return GetRoomResponse.fromJson(jsonDecode(json)); + } + + @override + Future onLoad() { + log("FETCHING DATA"); + return GetRoom( + GetRoomParams( + includeStatus: true, + ) + ).run(); + } +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/room/getRoomParams.dart b/lib/api/marianumcloud/talk/room/getRoomParams.dart new file mode 100644 index 0000000..cdf8e89 --- /dev/null +++ b/lib/api/marianumcloud/talk/room/getRoomParams.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; + +import 'package:json_annotation/json_annotation.dart'; + +import '../../../apiParams.dart'; + +part 'getRoomParams.g.dart'; + +@JsonSerializable(explicitToJson: true) +class GetRoomParams extends ApiParams { + GetRoomParamsStatusUpdate? noStatusUpdate; + @JsonKey(toJson: _format) bool? includeStatus; + int? modifiedSince; + + GetRoomParams({this.noStatusUpdate, this.includeStatus, this.modifiedSince}); + + factory GetRoomParams.fromJson(Map json) => _$GetRoomParamsFromJson(json); + Map toJson() => _$GetRoomParamsToJson(this); + + static String _format(bool? v) => v.toString(); +} + +enum GetRoomParamsStatusUpdate { + @JsonValue(0) defaults, + @JsonValue(1) keepAlive, +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/room/getRoomParams.g.dart b/lib/api/marianumcloud/talk/room/getRoomParams.g.dart new file mode 100644 index 0000000..616c591 --- /dev/null +++ b/lib/api/marianumcloud/talk/room/getRoomParams.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getRoomParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetRoomParams _$GetRoomParamsFromJson(Map json) => + GetRoomParams( + noStatusUpdate: $enumDecodeNullable( + _$GetRoomParamsStatusUpdateEnumMap, json['noStatusUpdate']), + includeStatus: json['includeStatus'] as bool?, + modifiedSince: json['modifiedSince'] as int?, + ); + +Map _$GetRoomParamsToJson(GetRoomParams instance) => + { + 'noStatusUpdate': + _$GetRoomParamsStatusUpdateEnumMap[instance.noStatusUpdate], + 'includeStatus': GetRoomParams._format(instance.includeStatus), + 'modifiedSince': instance.modifiedSince, + }; + +const _$GetRoomParamsStatusUpdateEnumMap = { + GetRoomParamsStatusUpdate.defaults: 0, + GetRoomParamsStatusUpdate.keepAlive: 1, +}; diff --git a/lib/api/marianumcloud/talk/room/getRoomResponse.dart b/lib/api/marianumcloud/talk/room/getRoomResponse.dart new file mode 100644 index 0000000..ee95fb9 --- /dev/null +++ b/lib/api/marianumcloud/talk/room/getRoomResponse.dart @@ -0,0 +1,151 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; + +part 'getRoomResponse.g.dart'; + +@JsonSerializable(explicitToJson: true) +class GetRoomResponse extends ApiResponse { + Set data; + + GetRoomResponse(this.data); + + factory GetRoomResponse.fromJson(Map json) => _$GetRoomResponseFromJson(json); + Map toJson() => _$GetRoomResponseToJson(this); + + List sortByLastActivity() { + List sorted = data.toList(); + sorted.sort((a, b) => -a.lastActivity.compareTo(b.lastActivity)); + return sorted; + } + +} + +@JsonSerializable(explicitToJson: true) +class GetRoomResponseObject { + int id; + String token; + GetRoomResponseObjectConversationType type; + String name; + String displayName; + String description; + int participantType; + int participantFlags; + int readOnly; + int listable; + int lastPing; + String sessionId; + bool hasPassword; + bool hasCall; + int callFlag; + bool canStartCall; + bool canDeleteConversation; + bool canLeaveConversation; + int lastActivity; + bool isFavorite; + GetRoomResponseObjectParticipantNotificationLevel notificationLevel; + int unreadMessages; + bool unreadMention; + bool unreadMentionDirect; + int lastReadMessage; + int lastCommonReadMessage; + GetRoomResponseObjectMessage lastMessage; + String? status; + String? statusIcon; + String? statusMessage; + + GetRoomResponseObject( + this.id, + this.token, + this.type, + this.name, + this.displayName, + this.description, + this.participantType, + this.participantFlags, + this.readOnly, + this.listable, + this.lastPing, + this.sessionId, + this.hasPassword, + this.hasCall, + this.callFlag, + this.canStartCall, + this.canDeleteConversation, + this.canLeaveConversation, + this.lastActivity, + this.isFavorite, + this.notificationLevel, + this.unreadMessages, + this.unreadMention, + this.unreadMentionDirect, + this.lastReadMessage, + this.lastCommonReadMessage, + this.lastMessage, + this.status, + this.statusIcon, + this.statusMessage); + + factory GetRoomResponseObject.fromJson(Map json) => _$GetRoomResponseObjectFromJson(json); + Map toJson() => _$GetRoomResponseObjectToJson(this); +} + +enum GetRoomResponseObjectConversationType { + @JsonValue(1) oneToOne, + @JsonValue(2) group, + @JsonValue(3) public, + @JsonValue(4) changelog, + @JsonValue(5) deleted, +} + +enum GetRoomResponseObjectParticipantNotificationLevel { + @JsonValue(0) defaultLevel, + @JsonValue(1) alwaysNotify, + @JsonValue(2) notifyOnMention, + @JsonValue(3) neverNotify, +} + +@JsonSerializable(explicitToJson: true) +class GetRoomResponseObjectMessage { + int id; + String token; + GetRoomResponseObjectMessageActorType actorType; + String actorId; + String actorDisplayName; + int timestamp; + String message; + String systemMessage; + GetRoomResponseObjectMessageType messageType; + bool isReplyable; + String referenceId; + + + GetRoomResponseObjectMessage( + this.id, + this.token, + this.actorType, + this.actorId, + this.actorDisplayName, + this.timestamp, + this.message, + this.systemMessage, + this.messageType, + this.isReplyable, + this.referenceId); + + factory GetRoomResponseObjectMessage.fromJson(Map json) => _$GetRoomResponseObjectMessageFromJson(json); + Map toJson() => _$GetRoomResponseObjectMessageToJson(this); +} + +enum GetRoomResponseObjectMessageActorType { + @JsonValue("users") user, + @JsonValue("guests") guest, + @JsonValue("bots") bot, + @JsonValue("bridged") bridge, +} + +enum GetRoomResponseObjectMessageType { + @JsonValue("comment") comment, + @JsonValue("comment_deleted") deletedComment, + @JsonValue("system") system, + @JsonValue("command") command, +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/room/getRoomResponse.g.dart b/lib/api/marianumcloud/talk/room/getRoomResponse.g.dart new file mode 100644 index 0000000..5400980 --- /dev/null +++ b/lib/api/marianumcloud/talk/room/getRoomResponse.g.dart @@ -0,0 +1,158 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getRoomResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetRoomResponse _$GetRoomResponseFromJson(Map json) => + GetRoomResponse( + (json['data'] as List) + .map((e) => GetRoomResponseObject.fromJson(e as Map)) + .toSet(), + ); + +Map _$GetRoomResponseToJson(GetRoomResponse instance) => + { + 'data': instance.data.map((e) => e.toJson()).toList(), + }; + +GetRoomResponseObject _$GetRoomResponseObjectFromJson( + Map json) => + GetRoomResponseObject( + json['id'] as int, + json['token'] as String, + $enumDecode(_$GetRoomResponseObjectConversationTypeEnumMap, json['type']), + json['name'] as String, + json['displayName'] as String, + json['description'] as String, + json['participantType'] as int, + json['participantFlags'] as int, + json['readOnly'] as int, + json['listable'] as int, + json['lastPing'] as int, + json['sessionId'] as String, + json['hasPassword'] as bool, + json['hasCall'] as bool, + json['callFlag'] as int, + json['canStartCall'] as bool, + json['canDeleteConversation'] as bool, + json['canLeaveConversation'] as bool, + json['lastActivity'] as int, + json['isFavorite'] as bool, + $enumDecode(_$GetRoomResponseObjectParticipantNotificationLevelEnumMap, + json['notificationLevel']), + json['unreadMessages'] as int, + json['unreadMention'] as bool, + json['unreadMentionDirect'] as bool, + json['lastReadMessage'] as int, + json['lastCommonReadMessage'] as int, + GetRoomResponseObjectMessage.fromJson( + json['lastMessage'] as Map), + json['status'] as String?, + json['statusIcon'] as String?, + json['statusMessage'] as String?, + ); + +Map _$GetRoomResponseObjectToJson( + GetRoomResponseObject instance) => + { + 'id': instance.id, + 'token': instance.token, + 'type': _$GetRoomResponseObjectConversationTypeEnumMap[instance.type]!, + 'name': instance.name, + 'displayName': instance.displayName, + 'description': instance.description, + 'participantType': instance.participantType, + 'participantFlags': instance.participantFlags, + 'readOnly': instance.readOnly, + 'listable': instance.listable, + 'lastPing': instance.lastPing, + 'sessionId': instance.sessionId, + 'hasPassword': instance.hasPassword, + 'hasCall': instance.hasCall, + 'callFlag': instance.callFlag, + 'canStartCall': instance.canStartCall, + 'canDeleteConversation': instance.canDeleteConversation, + 'canLeaveConversation': instance.canLeaveConversation, + 'lastActivity': instance.lastActivity, + 'isFavorite': instance.isFavorite, + 'notificationLevel': + _$GetRoomResponseObjectParticipantNotificationLevelEnumMap[ + instance.notificationLevel]!, + 'unreadMessages': instance.unreadMessages, + 'unreadMention': instance.unreadMention, + 'unreadMentionDirect': instance.unreadMentionDirect, + 'lastReadMessage': instance.lastReadMessage, + 'lastCommonReadMessage': instance.lastCommonReadMessage, + 'lastMessage': instance.lastMessage.toJson(), + 'status': instance.status, + 'statusIcon': instance.statusIcon, + 'statusMessage': instance.statusMessage, + }; + +const _$GetRoomResponseObjectConversationTypeEnumMap = { + GetRoomResponseObjectConversationType.oneToOne: 1, + GetRoomResponseObjectConversationType.group: 2, + GetRoomResponseObjectConversationType.public: 3, + GetRoomResponseObjectConversationType.changelog: 4, + GetRoomResponseObjectConversationType.deleted: 5, +}; + +const _$GetRoomResponseObjectParticipantNotificationLevelEnumMap = { + GetRoomResponseObjectParticipantNotificationLevel.defaultLevel: 0, + GetRoomResponseObjectParticipantNotificationLevel.alwaysNotify: 1, + GetRoomResponseObjectParticipantNotificationLevel.notifyOnMention: 2, + GetRoomResponseObjectParticipantNotificationLevel.neverNotify: 3, +}; + +GetRoomResponseObjectMessage _$GetRoomResponseObjectMessageFromJson( + Map json) => + GetRoomResponseObjectMessage( + json['id'] as int, + json['token'] as String, + $enumDecode( + _$GetRoomResponseObjectMessageActorTypeEnumMap, json['actorType']), + json['actorId'] as String, + json['actorDisplayName'] as String, + json['timestamp'] as int, + json['message'] as String, + json['systemMessage'] as String, + $enumDecode( + _$GetRoomResponseObjectMessageTypeEnumMap, json['messageType']), + json['isReplyable'] as bool, + json['referenceId'] as String, + ); + +Map _$GetRoomResponseObjectMessageToJson( + GetRoomResponseObjectMessage instance) => + { + 'id': instance.id, + 'token': instance.token, + 'actorType': + _$GetRoomResponseObjectMessageActorTypeEnumMap[instance.actorType]!, + 'actorId': instance.actorId, + 'actorDisplayName': instance.actorDisplayName, + 'timestamp': instance.timestamp, + 'message': instance.message, + 'systemMessage': instance.systemMessage, + 'messageType': + _$GetRoomResponseObjectMessageTypeEnumMap[instance.messageType]!, + 'isReplyable': instance.isReplyable, + 'referenceId': instance.referenceId, + }; + +const _$GetRoomResponseObjectMessageActorTypeEnumMap = { + GetRoomResponseObjectMessageActorType.user: 'users', + GetRoomResponseObjectMessageActorType.guest: 'guests', + GetRoomResponseObjectMessageActorType.bot: 'bots', + GetRoomResponseObjectMessageActorType.bridge: 'bridged', +}; + +const _$GetRoomResponseObjectMessageTypeEnumMap = { + GetRoomResponseObjectMessageType.comment: 'comment', + GetRoomResponseObjectMessageType.deletedComment: 'comment_deleted', + GetRoomResponseObjectMessageType.system: 'system', + GetRoomResponseObjectMessageType.command: 'command', +}; diff --git a/lib/api/marianumcloud/talk/talkApi.dart b/lib/api/marianumcloud/talk/talkApi.dart new file mode 100644 index 0000000..4c8afce --- /dev/null +++ b/lib/api/marianumcloud/talk/talkApi.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:http/http.dart' as http; +import 'package:marianum_mobile/api/apiRequest.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../apiParams.dart'; + +enum TalkApiMethod { + get, + post, + put, + delete, +} + +abstract class TalkApi extends ApiRequest { + String path; + ApiParams? body; + Map? headers = {}; + Map? getParameters; + + http.Response? response; + + TalkApi(this.path, this.body, {this.headers, this.getParameters}); + + Future request(Uri uri, Object? body, Map? headers); + T assemble(String raw); + + Future run() async { + getParameters?.forEach((key, value) { + getParameters?.update(key, (value) => value.toString()); + }); + + SharedPreferences preferences = await SharedPreferences.getInstance(); + + Uri endpoint = Uri.https("${preferences.getString("username")!}:${preferences.getString("password")!}@cloud.marianum-fulda.de", "/ocs/v2.php/apps/spreed/api/$path", getParameters); + + headers ??= {}; + headers?.putIfAbsent("Accept", () => "application/json"); + headers?.putIfAbsent("OCS-APIRequest", () => "true"); + + http.Response data = await request(endpoint, body, headers); + dynamic jsonData = jsonDecode(data.body); + + + return assemble(data.body); + } + +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/talkError.dart b/lib/api/marianumcloud/talk/talkError.dart new file mode 100644 index 0000000..f4ac002 --- /dev/null +++ b/lib/api/marianumcloud/talk/talkError.dart @@ -0,0 +1,12 @@ +class TalkError { + String status; + int code; + String message; + + TalkError(this.status, this.code, this.message); + + @override + String toString() { + return "Talk - $status - ($code): $message"; + } +} \ No newline at end of file diff --git a/lib/api/marianumcloud/webdav/webdavApi.dart b/lib/api/marianumcloud/webdav/webdavApi.dart new file mode 100644 index 0000000..3ab0855 --- /dev/null +++ b/lib/api/marianumcloud/webdav/webdavApi.dart @@ -0,0 +1,9 @@ +import 'package:marianum_mobile/api/apiRequest.dart'; + +class WebdavApi extends ApiRequest { + String basePath; + + WebdavApi(this.basePath); + + +} \ No newline at end of file diff --git a/lib/api/requestCache.dart b/lib/api/requestCache.dart index 5e716bb..9b9182e 100644 --- a/lib/api/requestCache.dart +++ b/lib/api/requestCache.dart @@ -3,6 +3,11 @@ import 'dart:convert'; import 'package:localstore/localstore.dart'; abstract class RequestCache { + static const int cacheNothing = 0; + static const int cacheMinute = 60; + static const int cacheHour = 60 * 60; + static const int cacheDay = 60 * 60 * 24; + int maxCacheTime; Function(T) onUpdate; @@ -29,4 +34,5 @@ abstract class RequestCache { T onLocalData(String json); Future onLoad(); + } \ No newline at end of file diff --git a/lib/api/talk/requestLoginTest.dart b/lib/api/talk/requestLoginTest.dart deleted file mode 100644 index 724e63c..0000000 --- a/lib/api/talk/requestLoginTest.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:marianum_mobile/api/apiRequest.dart'; - -class RequestLoginTest extends ApiRequest { - RequestLoginTest(super.endpoint); - -} \ No newline at end of file diff --git a/lib/api/webuntis/queries/authenticate/authenticateParams.dart b/lib/api/webuntis/queries/authenticate/authenticateParams.dart index 30ce8e7..cab8111 100644 --- a/lib/api/webuntis/queries/authenticate/authenticateParams.dart +++ b/lib/api/webuntis/queries/authenticate/authenticateParams.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:marianum_mobile/api/webuntis/apiParams.dart'; +import 'package:marianum_mobile/api/apiParams.dart'; part 'authenticateParams.g.dart'; diff --git a/lib/api/webuntis/queries/authenticate/authenticateResponse.dart b/lib/api/webuntis/queries/authenticate/authenticateResponse.dart index 2ff4e0f..d0b7d56 100644 --- a/lib/api/webuntis/queries/authenticate/authenticateResponse.dart +++ b/lib/api/webuntis/queries/authenticate/authenticateResponse.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; part 'authenticateResponse.g.dart'; diff --git a/lib/api/webuntis/queries/getHolidays/getHolidays.dart b/lib/api/webuntis/queries/getHolidays/getHolidays.dart index 37dabc6..d900ec0 100644 --- a/lib/api/webuntis/queries/getHolidays/getHolidays.dart +++ b/lib/api/webuntis/queries/getHolidays/getHolidays.dart @@ -1,6 +1,8 @@ import 'dart:convert'; +import 'dart:developer'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:intl/intl.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; import 'package:marianum_mobile/api/webuntis/webuntisApi.dart'; import 'getHolidaysResponse.dart'; @@ -14,4 +16,17 @@ class GetHolidays extends WebuntisApi { return finalize(GetHolidaysResponse.fromJson(jsonDecode(rawAnswer))); } + static GetHolidaysResponseObject? find(GetHolidaysResponse holidaysResponse, {DateTime? time}) { + time ??= DateTime.now(); + time = DateTime(time.year, time.month, time.day, 0, 0, 0, 0, 0); + + for (var element in holidaysResponse.result) { + DateTime start = DateTime.parse(element.startDate.toString()); + DateTime end = DateTime.parse(element.endDate.toString()); + + if(!start.isAfter(time) && !end.isBefore(time)) return element; + } + return null; + } + } \ No newline at end of file diff --git a/lib/api/webuntis/queries/getHolidays/getHolidaysCache.dart b/lib/api/webuntis/queries/getHolidays/getHolidaysCache.dart index 88c20a1..2accf6b 100644 --- a/lib/api/webuntis/queries/getHolidays/getHolidaysCache.dart +++ b/lib/api/webuntis/queries/getHolidays/getHolidaysCache.dart @@ -5,8 +5,8 @@ import 'package:marianum_mobile/api/webuntis/queries/getHolidays/getHolidays.dar import 'package:marianum_mobile/api/webuntis/queries/getHolidays/getHolidaysResponse.dart'; class GetHolidaysCache extends RequestCache { - GetHolidaysCache({onUpdate}) : super(60 * 60, onUpdate) { - start("holidays", "data"); + GetHolidaysCache({onUpdate}) : super(RequestCache.cacheDay, onUpdate) { + start("MarianumMobile", "wu-holidays"); } @override diff --git a/lib/api/webuntis/queries/getHolidays/getHolidaysResponse.dart b/lib/api/webuntis/queries/getHolidays/getHolidaysResponse.dart index a991c8f..46790ec 100644 --- a/lib/api/webuntis/queries/getHolidays/getHolidaysResponse.dart +++ b/lib/api/webuntis/queries/getHolidays/getHolidaysResponse.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; part 'getHolidaysResponse.g.dart'; diff --git a/lib/api/webuntis/queries/getRooms/getRooms.dart b/lib/api/webuntis/queries/getRooms/getRooms.dart index 58b303c..03a7a87 100644 --- a/lib/api/webuntis/queries/getRooms/getRooms.dart +++ b/lib/api/webuntis/queries/getRooms/getRooms.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; import 'package:marianum_mobile/api/webuntis/webuntisApi.dart'; import 'getRoomsResponse.dart'; diff --git a/lib/api/webuntis/queries/getRooms/getRoomsCache.dart b/lib/api/webuntis/queries/getRooms/getRoomsCache.dart index 1104637..6c0687e 100644 --- a/lib/api/webuntis/queries/getRooms/getRoomsCache.dart +++ b/lib/api/webuntis/queries/getRooms/getRoomsCache.dart @@ -6,8 +6,8 @@ import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRoomsResponse.d import 'getRooms.dart'; class GetRoomsCache extends RequestCache { - GetRoomsCache({onUpdate}) : super(60 * 60, onUpdate) { - start("rooms", "data"); + GetRoomsCache({onUpdate}) : super(RequestCache.cacheHour, onUpdate) { + start("MarianumMobile", "wu-rooms"); } @override diff --git a/lib/api/webuntis/queries/getRooms/getRoomsResponse.dart b/lib/api/webuntis/queries/getRooms/getRoomsResponse.dart index 4f1cfe3..9c96cd0 100644 --- a/lib/api/webuntis/queries/getRooms/getRoomsResponse.dart +++ b/lib/api/webuntis/queries/getRooms/getRoomsResponse.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; part 'getRoomsResponse.g.dart'; @@ -21,7 +21,6 @@ class GetRoomsResponseObject { bool active; String building; - GetRoomsResponseObject(this.id, this.name, this.longName, this.active, this.building); factory GetRoomsResponseObject.fromJson(Map json) => _$GetRoomsResponseObjectFromJson(json); diff --git a/lib/api/webuntis/queries/getSubjects/getSubjects.dart b/lib/api/webuntis/queries/getSubjects/getSubjects.dart index 78a0a0f..127d28c 100644 --- a/lib/api/webuntis/queries/getSubjects/getSubjects.dart +++ b/lib/api/webuntis/queries/getSubjects/getSubjects.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:marianum_mobile/api/webuntis/webuntisApi.dart'; -import '../../apiResponse.dart'; +import '../../../apiResponse.dart'; import 'getSubjectsResponse.dart'; class GetSubjects extends WebuntisApi { diff --git a/lib/api/webuntis/queries/getSubjects/getSubjectsCache.dart b/lib/api/webuntis/queries/getSubjects/getSubjectsCache.dart index 8c4a6ba..3c2d9d6 100644 --- a/lib/api/webuntis/queries/getSubjects/getSubjectsCache.dart +++ b/lib/api/webuntis/queries/getSubjects/getSubjectsCache.dart @@ -6,8 +6,8 @@ import 'package:marianum_mobile/api/webuntis/queries/getSubjects/getSubjectsResp import 'getSubjects.dart'; class GetSubjectsCache extends RequestCache { - GetSubjectsCache({onUpdate}) : super(60 * 60, onUpdate) { - start("subjects", "data"); + GetSubjectsCache({onUpdate}) : super(RequestCache.cacheHour, onUpdate) { + start("MarianumMobile", "wu-subjects"); } @override diff --git a/lib/api/webuntis/queries/getSubjects/getSubjectsResponse.dart b/lib/api/webuntis/queries/getSubjects/getSubjectsResponse.dart index 897e2ae..4fd6aad 100644 --- a/lib/api/webuntis/queries/getSubjects/getSubjectsResponse.dart +++ b/lib/api/webuntis/queries/getSubjects/getSubjectsResponse.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; part 'getSubjectsResponse.g.dart'; diff --git a/lib/api/webuntis/queries/getTimetable/getTimetable.dart b/lib/api/webuntis/queries/getTimetable/getTimetable.dart index 6d75057..78d1de4 100644 --- a/lib/api/webuntis/queries/getTimetable/getTimetable.dart +++ b/lib/api/webuntis/queries/getTimetable/getTimetable.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:developer'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; import 'package:marianum_mobile/api/webuntis/webuntisApi.dart'; import 'getTimetableParams.dart'; diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableCache.dart b/lib/api/webuntis/queries/getTimetable/getTimetableCache.dart index 48290ca..9302fed 100644 --- a/lib/api/webuntis/queries/getTimetable/getTimetableCache.dart +++ b/lib/api/webuntis/queries/getTimetable/getTimetableCache.dart @@ -12,8 +12,8 @@ import 'getTimetableResponse.dart'; class GetTimetableCache extends RequestCache { int day; - GetTimetableCache({required onUpdate, required this.day}) : super(30, onUpdate) { - start("timetable", "$day"); + GetTimetableCache({required onUpdate, required this.day}) : super(RequestCache.cacheMinute, onUpdate) { + start("MarianumMobile", "wu-timetable-$day"); } @override @@ -25,14 +25,18 @@ class GetTimetableCache extends RequestCache { Future onLoad() async { return GetTimetable( GetTimetableParams( - options: GetTimetableParamsOptions( - element: GetTimetableParamsOptionsElement( - id: (await Authenticate.getSession()).personId, - type: 5, - keyType: GetTimetableParamsOptionsElementKeyType.id, - ), - startDate: day, - endDate: day, + options: GetTimetableParamsOptions( + element: GetTimetableParamsOptionsElement( + id: (await Authenticate.getSession()).personId, + type: 5, + keyType: GetTimetableParamsOptionsElementKeyType.id, + ), + startDate: day, + endDate: day, + teacherFields: GetTimetableParamsOptionsFields.all, + subjectFields: GetTimetableParamsOptionsFields.all, + roomFields: GetTimetableParamsOptionsFields.all, + klasseFields: GetTimetableParamsOptionsFields.all, ) ) ).run(); diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableParams.dart b/lib/api/webuntis/queries/getTimetable/getTimetableParams.dart index 3a5ea76..efb3726 100644 --- a/lib/api/webuntis/queries/getTimetable/getTimetableParams.dart +++ b/lib/api/webuntis/queries/getTimetable/getTimetableParams.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:marianum_mobile/api/webuntis/apiParams.dart'; +import 'package:marianum_mobile/api/apiParams.dart'; part 'getTimetableParams.g.dart'; @@ -36,13 +36,13 @@ class GetTimetableParamsOptions { @JsonKey(includeIfNull: false) bool? showStudentgroup; @JsonKey(includeIfNull: false) - GetTimetableParamsOptionsFields? klasseFields; + List? klasseFields; @JsonKey(includeIfNull: false) - GetTimetableParamsOptionsFields? roomFields; + List? roomFields; @JsonKey(includeIfNull: false) - GetTimetableParamsOptionsFields? subjectFields; + List? subjectFields; @JsonKey(includeIfNull: false) - GetTimetableParamsOptionsFields? teacherFields; + List? teacherFields; GetTimetableParamsOptions({ required this.element, @@ -69,7 +69,9 @@ enum GetTimetableParamsOptionsFields { @JsonValue("id") id, @JsonValue("name") name, @JsonValue("longname") longname, - @JsonValue("externalkey") externalkey, + @JsonValue("externalkey") externalkey; + + static List all = [id, name, longname, externalkey]; } @JsonSerializable() diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableParams.g.dart b/lib/api/webuntis/queries/getTimetable/getTimetableParams.g.dart index 803512c..c5b6d92 100644 --- a/lib/api/webuntis/queries/getTimetable/getTimetableParams.g.dart +++ b/lib/api/webuntis/queries/getTimetable/getTimetableParams.g.dart @@ -31,14 +31,18 @@ GetTimetableParamsOptions _$GetTimetableParamsOptionsFromJson( showLsText: json['showLsText'] as bool?, showLsNumber: json['showLsNumber'] as bool?, showStudentgroup: json['showStudentgroup'] as bool?, - klasseFields: $enumDecodeNullable( - _$GetTimetableParamsOptionsFieldsEnumMap, json['klasseFields']), - roomFields: $enumDecodeNullable( - _$GetTimetableParamsOptionsFieldsEnumMap, json['roomFields']), - subjectFields: $enumDecodeNullable( - _$GetTimetableParamsOptionsFieldsEnumMap, json['subjectFields']), - teacherFields: $enumDecodeNullable( - _$GetTimetableParamsOptionsFieldsEnumMap, json['teacherFields']), + klasseFields: (json['klasseFields'] as List?) + ?.map((e) => $enumDecode(_$GetTimetableParamsOptionsFieldsEnumMap, e)) + .toList(), + roomFields: (json['roomFields'] as List?) + ?.map((e) => $enumDecode(_$GetTimetableParamsOptionsFieldsEnumMap, e)) + .toList(), + subjectFields: (json['subjectFields'] as List?) + ?.map((e) => $enumDecode(_$GetTimetableParamsOptionsFieldsEnumMap, e)) + .toList(), + teacherFields: (json['teacherFields'] as List?) + ?.map((e) => $enumDecode(_$GetTimetableParamsOptionsFieldsEnumMap, e)) + .toList(), ); Map _$GetTimetableParamsOptionsToJson( @@ -62,14 +66,26 @@ Map _$GetTimetableParamsOptionsToJson( writeNotNull('showLsText', instance.showLsText); writeNotNull('showLsNumber', instance.showLsNumber); writeNotNull('showStudentgroup', instance.showStudentgroup); - writeNotNull('klasseFields', - _$GetTimetableParamsOptionsFieldsEnumMap[instance.klasseFields]); - writeNotNull('roomFields', - _$GetTimetableParamsOptionsFieldsEnumMap[instance.roomFields]); - writeNotNull('subjectFields', - _$GetTimetableParamsOptionsFieldsEnumMap[instance.subjectFields]); - writeNotNull('teacherFields', - _$GetTimetableParamsOptionsFieldsEnumMap[instance.teacherFields]); + writeNotNull( + 'klasseFields', + instance.klasseFields + ?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!) + .toList()); + writeNotNull( + 'roomFields', + instance.roomFields + ?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!) + .toList()); + writeNotNull( + 'subjectFields', + instance.subjectFields + ?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!) + .toList()); + writeNotNull( + 'teacherFields', + instance.teacherFields + ?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!) + .toList()); return val; } diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableResponse.dart b/lib/api/webuntis/queries/getTimetable/getTimetableResponse.dart index 547af3c..b32c1b9 100644 --- a/lib/api/webuntis/queries/getTimetable/getTimetableResponse.dart +++ b/lib/api/webuntis/queries/getTimetable/getTimetableResponse.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; part 'getTimetableResponse.g.dart'; diff --git a/lib/api/webuntis/webuntisApi.dart b/lib/api/webuntis/webuntisApi.dart index 1df6327..c5e435c 100644 --- a/lib/api/webuntis/webuntisApi.dart +++ b/lib/api/webuntis/webuntisApi.dart @@ -3,19 +3,20 @@ import 'dart:developer'; import 'package:marianum_mobile/api/apiRequest.dart'; import 'package:http/http.dart' as http; import 'package:marianum_mobile/api/webuntis/webuntisError.dart'; -import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; -import 'apiParams.dart'; +import '../apiParams.dart'; import 'queries/authenticate/authenticate.dart'; abstract class WebuntisApi extends ApiRequest { + Uri endpoint = Uri.parse("https://peleus.webuntis.com/WebUntis/jsonrpc.do?school=marianum-fulda"); String method; ApiParams? genericParam; http.Response? response; bool authenticatedResponse; - WebuntisApi(this.method, this.genericParam, {this.authenticatedResponse = true}) : super(Uri.parse("https://peleus.webuntis.com/WebUntis/jsonrpc.do?school=marianum-fulda")); + WebuntisApi(this.method, this.genericParam, {this.authenticatedResponse = true}); Future query(WebuntisApi untis) async { @@ -50,4 +51,14 @@ abstract class WebuntisApi extends ApiRequest { String _body() { return genericParam == null ? "{}" : jsonEncode(genericParam); } + + Future post(String data, Map? headers) async { + log("POST: $endpoint\n$data"); + return await http + .post(endpoint, body: data, headers: headers) + .timeout( + const Duration(seconds: 10), + onTimeout: () => throw WebuntisError("Timeout", 1) + ); + } } \ No newline at end of file diff --git a/lib/api/webuntis/webuntisError.dart b/lib/api/webuntis/webuntisError.dart index edd1843..8963e19 100644 --- a/lib/api/webuntis/webuntisError.dart +++ b/lib/api/webuntis/webuntisError.dart @@ -4,6 +4,7 @@ class WebuntisError { WebuntisError(this.message, this.code); + @override String toString() { return "WebUntis ($code): $message"; } diff --git a/lib/app.dart b/lib/app.dart index b4133b2..14b0362 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:marianum_mobile/screen/pages/timetable/storedTimetable.dart'; +import 'package:marianum_mobile/screen/pages/timetable/timetable.dart'; import 'package:marianum_mobile/screen/pages/timetable/testTimetable.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -8,8 +8,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'dataOld/incommingPackets/talkNotificationsPacket.dart'; import 'screen/pages/files/files.dart'; import 'screen/pages/more/overhang.dart'; -import 'screen/pages/talk/chatOverview.dart'; -import 'screen/pages/timetable/timetable.dart'; +import 'screen/pages/talk/chatList.dart'; +import 'screen/pages/timetable/timetableOld.dart'; import 'screen/settings/settings.dart'; class App extends StatefulWidget { @@ -26,6 +26,7 @@ class _AppState extends State { Widget build(BuildContext context) { final PageController pageController = PageController(); return Scaffold( + resizeToAvoidBottomInset: false, appBar: AppBar( title: const Text("Marianum Fulda"), actions: [ @@ -38,27 +39,32 @@ class _AppState extends State { ) ], ), - body: Stack( + body: Column( children: [ - PageView( - controller: pageController, - children: const [ - StoredTimetable(), - Talk(), - Files(), - Overhang(), - ], - onPageChanged: (page) { - setState(() { - currentPage = page; - }); - }, + Visibility( + visible: false, + child: LinearProgressIndicator( + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation(Theme.of(context).primaryColor), + minHeight: 5, + ), ), - // LinearProgressIndicator( - // backgroundColor: Colors.transparent, - // valueColor: AlwaysStoppedAnimation(Theme.of(context).primaryColor), - // minHeight: 5, - // ), + Flexible( + child: PageView( + controller: pageController, + children: [ + Timetable(), + ChatList(), + Files(), + Overhang(), + ], + onPageChanged: (page) { + setState(() { + currentPage = page; + }); + }, + ), + ) ], ), diff --git a/lib/data/chatList/chatListProps.dart b/lib/data/chatList/chatListProps.dart new file mode 100644 index 0000000..e0dca12 --- /dev/null +++ b/lib/data/chatList/chatListProps.dart @@ -0,0 +1,29 @@ +import 'dart:developer'; + +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomCache.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomResponse.dart'; +import 'package:marianum_mobile/data/dataHolder.dart'; + +import '../../api/apiResponse.dart'; + +class ChatListProps extends DataHolder { + GetRoomResponse? _getRoomResponse; + GetRoomResponse get getRoomsResponse => _getRoomResponse!; + + @override + List properties() { + return [_getRoomResponse]; + } + + @override + void run() { + log("RUN CACHE"); + GetRoomCache( + onUpdate: (GetRoomResponse data) => { + _getRoomResponse = data, + notifyListeners(), + } + ); + } + +} \ No newline at end of file diff --git a/lib/data/chatList/chatProps.dart b/lib/data/chatList/chatProps.dart new file mode 100644 index 0000000..8a8f8c9 --- /dev/null +++ b/lib/data/chatList/chatProps.dart @@ -0,0 +1,35 @@ +import 'package:marianum_mobile/api/apiResponse.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/chat/getChatCache.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/chat/getChatResponse.dart'; +import 'package:marianum_mobile/data/dataHolder.dart'; + +class ChatProps extends DataHolder { + String _queryToken = ""; + + GetChatResponse? _getChatResponse; + GetChatResponse get getChatResponse => _getChatResponse!; + + @override + List properties() { + return [_getChatResponse]; + } + + @override + void run() { + notifyListeners(); + GetChatCache( + chatToken: _queryToken, + onUpdate: (GetChatResponse data) => { + _getChatResponse = data, + notifyListeners(), + } + ); + } + + void setQueryToken(String token) { + _queryToken = token; + _getChatResponse = null; + run(); + } + +} \ No newline at end of file diff --git a/lib/data/dataHolder.dart b/lib/data/dataHolder.dart index 08e140e..2c431bd 100644 --- a/lib/data/dataHolder.dart +++ b/lib/data/dataHolder.dart @@ -1,11 +1,18 @@ import 'package:flutter/cupertino.dart'; import 'package:localstore/localstore.dart'; +import '../api/apiResponse.dart'; + abstract class DataHolder extends ChangeNotifier { CollectionRef storage(String path) { return Localstore.instance.collection(path); } - Future run(); + void run(); + List properties(); + + bool primaryLoading() { + return properties().where((element) => element != null).isEmpty; + } } \ No newline at end of file diff --git a/lib/data/timetable/persistantTimetable.dart b/lib/data/timetable/persistantTimetable.dart deleted file mode 100644 index 27af2c7..0000000 --- a/lib/data/timetable/persistantTimetable.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/widgets.dart'; -import 'package:path/path.dart'; -import 'package:sqflite/sqflite.dart'; - -class PersistantTimetable { - final int id; - final String json; - - PersistantTimetable(this.id, this.json); -} \ No newline at end of file diff --git a/lib/data/timetable/timetable.dart b/lib/data/timetable/timetable.dart deleted file mode 100644 index 082a631..0000000 --- a/lib/data/timetable/timetable.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'dart:collection'; -import 'dart:convert'; -import 'dart:developer'; - -import 'package:intl/intl.dart'; -import 'package:marianum_mobile/api/webuntis/queries/authenticate/authenticate.dart'; -import 'package:marianum_mobile/api/webuntis/queries/authenticate/authenticateResponse.dart'; -import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRoomsCache.dart'; -import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRoomsResponse.dart'; -import 'package:marianum_mobile/api/webuntis/queries/getSubjects/getSubjectsCache.dart'; -import 'package:marianum_mobile/api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; -import 'package:marianum_mobile/data/dataHolder.dart'; - -import '../../api/webuntis/queries/getTimetable/getTimetable.dart'; -import '../../api/webuntis/queries/getTimetable/getTimetableCache.dart'; -import '../../api/webuntis/queries/getTimetable/getTimetableParams.dart'; -import '../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; - -class Timetable extends DataHolder { - int day = int.parse(DateFormat("yyyyMMdd").format(DateTime.now())); - - Timetable() : super(); - - GetTimetableResponse? _getTimetableResponse; - GetTimetableResponse? get getTimetableResponse => _getTimetableResponse; - - GetRoomsResponse? _getRoomsResponse; - GetRoomsResponse? get getRoomsResponse => _getRoomsResponse; - - GetSubjectsResponse? _getSubjectsResponse; - GetSubjectsResponse? get getSubjectsResponse => _getSubjectsResponse; - - @override - Future run() async { - GetTimetableCache( - day: day, - onUpdate: (data) => - { - _getTimetableResponse = data, - notifyListeners(), - } - ); - - GetRoomsCache( - onUpdate: (data) => - { - _getRoomsResponse = data, - notifyListeners(), - } - ); - - GetSubjectsCache( - onUpdate: (data) => - { - _getSubjectsResponse = data, - notifyListeners(), - } - ); - } -} \ No newline at end of file diff --git a/lib/data/timetable/timetableProps.dart b/lib/data/timetable/timetableProps.dart new file mode 100644 index 0000000..d18bff7 --- /dev/null +++ b/lib/data/timetable/timetableProps.dart @@ -0,0 +1,101 @@ +import 'dart:collection'; +import 'dart:convert'; +import 'dart:developer'; + +import 'package:intl/intl.dart'; +import 'package:marianum_mobile/api/apiResponse.dart'; +import 'package:marianum_mobile/api/webuntis/queries/authenticate/authenticate.dart'; +import 'package:marianum_mobile/api/webuntis/queries/authenticate/authenticateResponse.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getHolidays/getHolidaysCache.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getHolidays/getHolidaysResponse.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRoomsCache.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRoomsResponse.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getSubjects/getSubjectsCache.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; +import 'package:marianum_mobile/data/dataHolder.dart'; + +import '../../api/webuntis/queries/getTimetable/getTimetable.dart'; +import '../../api/webuntis/queries/getTimetable/getTimetableCache.dart'; +import '../../api/webuntis/queries/getTimetable/getTimetableParams.dart'; +import '../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; + +class TimetableProps extends DataHolder { + late DateTime queryDate; + + GetTimetableResponse? _getTimetableResponse; + GetTimetableResponse get getTimetableResponse => _getTimetableResponse!; + + GetRoomsResponse? _getRoomsResponse; + GetRoomsResponse get getRoomsResponse => _getRoomsResponse!; + + GetSubjectsResponse? _getSubjectsResponse; + GetSubjectsResponse get getSubjectsResponse => _getSubjectsResponse!; + + GetHolidaysResponse? _getHolidaysResponse; + GetHolidaysResponse get getHolidaysResponse => _getHolidaysResponse!; + + + TimetableProps() { + nearest(); + } + + @override + List properties() { + return [_getTimetableResponse, _getRoomsResponse, _getSubjectsResponse, _getHolidaysResponse]; + } + + @override + void run() { + GetTimetableCache( + day: int.parse(DateFormat("yyyyMMdd").format(queryDate)), + onUpdate: (GetTimetableResponse data) => { + _getTimetableResponse = data, + notifyListeners(), + } + ); + + GetRoomsCache( + onUpdate: (GetRoomsResponse data) => { + _getRoomsResponse = data, + notifyListeners(), + } + ); + + GetSubjectsCache( + onUpdate: (GetSubjectsResponse data) => { + _getSubjectsResponse = data, + notifyListeners(), + } + ); + + GetHolidaysCache( + onUpdate: (GetHolidaysResponse data) => { + _getHolidaysResponse = data, + notifyListeners(), + } + ); + } + + void nearest() { + queryDate = DateTime.now(); + while(isWeekend(queryDate)) { + next(); + } + run(); + } + + void next({previous = false}) { + do { + if(previous) { + queryDate = queryDate.subtract(const Duration(days: 1)); + } else { + queryDate = queryDate.add(const Duration(days: 1)); + } + } while(isWeekend(queryDate)); + run(); + } + + bool isWeekend(DateTime queryDate) { + return queryDate.weekday == DateTime.saturday || queryDate.weekday == DateTime.sunday; + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 7497568..106a734 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:marianum_mobile/data/timetable/timetable.dart'; +import 'package:marianum_mobile/data/timetable/timetableProps.dart'; import 'package:marianum_mobile/screen/login/login.dart'; import 'package:marianum_mobile/widget/loadingSpinner.dart'; import 'package:provider/provider.dart'; @@ -10,6 +10,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'app.dart'; +import 'data/chatList/chatListProps.dart'; +import 'data/chatList/chatProps.dart'; import 'dataOld/accountModel.dart'; import 'dataOld/incommingPackets/authenticatePacket.dart'; import 'dataOld/incommingPackets/errorPacket.dart'; @@ -39,7 +41,9 @@ Future main() async { // ChangeNotifierProvider(create: (context) => FileListPacket()), // ChangeNotifierProvider(create: (context) => TalkChatPacket()), // ChangeNotifierProvider(create: (context) => TimetablePacket()), - ChangeNotifierProvider(create: (context) => Timetable()), + ChangeNotifierProvider(create: (context) => TimetableProps()), + ChangeNotifierProvider(create: (context) => ChatListProps()), + ChangeNotifierProvider(create: (context) => ChatProps()), ], child: const Main(), ) @@ -54,7 +58,7 @@ class Main extends StatefulWidget { } class _MainState extends State
{ - static const Color red = Color.fromARGB(255, 153, 51, 51); + static const Color marianumRed = Color.fromARGB(255, 153, 51, 51); final Future _storage = SharedPreferences.getInstance(); @@ -79,12 +83,16 @@ class _MainState extends State
{ title: 'Marianum Fulda', theme: ThemeData( brightness: Brightness.light, - primaryColor: red, + primaryColor: marianumRed, + hintColor: marianumRed, + inputDecorationTheme: const InputDecorationTheme( + border: UnderlineInputBorder(borderSide: BorderSide(color: marianumRed)), + ), appBarTheme: const AppBarTheme( - backgroundColor: red, + backgroundColor: marianumRed, ), progressIndicatorTheme: const ProgressIndicatorThemeData( - color: red, + color: marianumRed, ), ), @@ -93,30 +101,16 @@ class _MainState extends State
{ builder: (BuildContext context, AsyncSnapshot snapshot) { if(snapshot.hasData) { - - return Consumer2( - builder: (context, accountModel, errorPacket, child) { - if(errorPacket.errorDismissed) { - return accountModel.isLoggedIn ? const App() : const Login(); - } else { - return AlertDialog(title: const Text("Serverseitige Fehlermeldung"), content: Text(errorPacket.errorText), actions: [ - TextButton(onPressed: () { - Provider.of(context, listen: false).errorDismissed = true; - }, child: const Text("Weiter")) - ]); - } + return Consumer( + builder: (context, accountModel, child) { + return accountModel.isLoggedIn ? const App() : const Login(); }, ); - } else { - return const LoadingSpinner(); - } - }, ) - ); } } diff --git a/lib/screen/pages/talk/chatList.dart b/lib/screen/pages/talk/chatList.dart new file mode 100644 index 0000000..06446c8 --- /dev/null +++ b/lib/screen/pages/talk/chatList.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:jiffy/jiffy.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomResponse.dart'; +import 'package:marianum_mobile/data/chatList/chatListProps.dart'; +import 'package:marianum_mobile/widget/loadingPacket.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'chatView.dart'; + +class ChatList extends StatefulWidget { + const ChatList({Key? key}) : super(key: key); + + @override + State createState() => _ChatListState(); +} + +class _ChatListState extends State { + late String username; + + @override + void initState() { + super.initState(); + + SharedPreferences.getInstance().then((value) => { + username = value.getString("username")! + }); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Provider.of(context, listen: false).run(); + }); + } + + @override + Widget build(BuildContext context) { + + return Consumer( + builder: (context, data, child) { + + if(data.primaryLoading()) { + return const Center(child: CircularProgressIndicator()); + } + + List chats = List.empty(growable: true); + + for (var chatRoom in data.getRoomsResponse.sortByLastActivity()) { + + CircleAvatar _circleAvatar = CircleAvatar( + foregroundImage: chatRoom.type == GetRoomResponseObjectConversationType.oneToOne ? Image.network("https://cloud.marianum-fulda.de/avatar/${chatRoom.name}/128").image : null, + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, + child: chatRoom.type == GetRoomResponseObjectConversationType.group ? const Icon(Icons.group) : const Icon(Icons.person), + ); + + chats.add(ListTile( + title: Text(chatRoom.displayName), + subtitle: Text("${Jiffy.unixFromSecondsSinceEpoch(chatRoom.lastMessage.timestamp).fromNow()}: ${chatRoom.lastMessage.message.replaceAll("\n", " ")}", overflow: TextOverflow.ellipsis), + trailing: Visibility( + visible: chatRoom.unreadMessages > 0, + child: Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(30), + ), + constraints: const BoxConstraints( + minWidth: 20, + minHeight: 20, + ), + child: Text( + "${chatRoom.unreadMessages}", + style: const TextStyle( + color: Colors.white, + fontSize: 15, + ), + textAlign: TextAlign.center, + ), + ), + ), + leading: _circleAvatar, + onTap: () async { + Navigator.of(context).push(MaterialPageRoute(builder: (context) { + return ChatView( + user: chatRoom, + selfId: username, + avatar: _circleAvatar, + ); + })); + }, + )); + } + + return ListView(children: chats); + }, + ); + } +} \ No newline at end of file diff --git a/lib/screen/pages/talk/chatOverview.dart b/lib/screen/pages/talk/chatOverview.dart deleted file mode 100644 index be89182..0000000 --- a/lib/screen/pages/talk/chatOverview.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'package:marianum_mobile/widget/loadingPacket.dart'; -import 'package:provider/provider.dart'; - -import '../../../dataOld/incommingPackets/talkContactsPacket.dart'; -import '../../../widget/loadingSpinner.dart'; -import 'chatView.dart'; - -class Talk extends StatefulWidget { - const Talk({Key? key}) : super(key: key); - - @override - State createState() => _TalkState(); -} - -class _TalkState extends State { - // List chats = List.empty(growable: true); - // - // Future> getChats() async { - // var url = Uri.https("***REMOVED***:***REMOVED***@mhsl.eu", "marianum/app/middleware/chat.php"); - // var response = await http.get( - // url, - // headers: ( - // { - // "Accept": "application/json", - // "OCS-APIRequest": "true", - // } - // ), - // ); - // - // return compute(parseChats, response.body); - // } - - - @override - void initState() { - Provider.of(context, listen: false).invoke(); - //TalkContactsAskPacket().send(); - super.initState(); - // Future.delayed(Duration.zero).then((context) => updateChats()); - // Provider.of(context, listen: false).channel.sink.add("chat"); - - } - - void updateChats() { - // var chats = getChats(); - // - // showDialog( - // context: context, - // barrierDismissible: false, - // builder: (BuildContext context) { - // return const LoadingSpinner(); - // } - // ); - // - // chats.then((value) => - // setState(() { - // Navigator.pop(context); - // this.chats.clear(); - // this.chats = value; - // }) - // ); - } - - @override - Widget build(BuildContext context) { - // List chats = List.empty(growable: true); - // - // for (var element in this.chats) { - // chats.add( - // ListTile( - // leading: element.type == 1 ? CircleAvatar( - // backgroundColor: Colors.grey, - // foregroundImage: Image.network(element.avatar).image, - // ) : const Icon(Icons.group), - // title: Text(element.name), - // subtitle: Text( - // "${element.lastMessageAuthor}: ${element.lastMessage.replaceAll("\n", "")}", - // overflow: TextOverflow.ellipsis, - // ), - // onTap: () { - // Navigator.push(context, MaterialPageRoute(builder: (builder) => const ChatView())); - // }, - // trailing: element.unreadMessages > 0 ? const Icon(Icons.mark_chat_unread) : Text(element.lastActivity), - // ) - // ); - // } - // - // return ListView( - // children: chats, - // ); - - return Consumer( - builder: (context, data, child) { - List chats = List.empty(growable: true); - - for (var element in data.contacts) { - chats.add(ListTile( - title: Text(element.name), - subtitle: Text("${element.lastTime}: ${element.lastMessage}".replaceAll("\n", " "), overflow: TextOverflow.ellipsis), - trailing: element.unreadMessages ? const Icon(Icons.new_releases_outlined) : null, - leading: CircleAvatar( - foregroundImage: element.isGroup ? null : Image.network(element.profilePicture).image, - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - child: element.isGroup ? const Icon(Icons.group) : const Icon(Icons.person), - ), - onTap: () { - Navigator.of(context).push(MaterialPageRoute(builder: (context) { - return ChatView( - userToken: element.userToken, - ); - })); - }, - )); - } - - return LoadingPacket(packet: data, child: ListView(children: chats)); - }, - ); - } -} - -// -// List parseChats(String json) { -// final parsed = jsonDecode(json).cast>(); -// return parsed.map((a) => ChatData.fromJson(a)).toList(); -// } -// -// class ChatData { -// final String name; -// final String lastMessage; -// final String lastMessageAuthor; -// final String avatar; -// final int type; -// final String lastActivity; -// final int unreadMessages; -// -// const ChatData({ -// required this.name, -// required this.lastMessage, -// required this.lastMessageAuthor, -// required this.avatar, -// required this.type, -// required this.lastActivity, -// required this.unreadMessages, -// }); -// -// factory ChatData.fromJson(Map json) { -// return ChatData( -// name: json['name'] as String, -// lastMessage: json['last_message'] as String, -// lastMessageAuthor: json['last_message_author'] as String, -// avatar: json['avatar'] as String, -// type: json['type'] as int, -// lastActivity: json['lastActivity'] as String, -// unreadMessages: json['unreadMessages'] as int, -// ); -// } -// } \ No newline at end of file diff --git a/lib/screen/pages/talk/chatView.dart b/lib/screen/pages/talk/chatView.dart index 0da55cf..af82c33 100644 --- a/lib/screen/pages/talk/chatView.dart +++ b/lib/screen/pages/talk/chatView.dart @@ -1,14 +1,18 @@ +import 'dart:developer'; import 'package:bubble/bubble.dart'; import 'package:flutter/material.dart'; -import 'package:marianum_mobile/widget/loadingPacket.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/room/getRoomResponse.dart'; +import 'package:marianum_mobile/data/chatList/chatProps.dart'; import 'package:provider/provider.dart'; -import '../../../dataOld/incommingPackets/talkChatPacket.dart'; - class ChatView extends StatefulWidget { - final String userToken; - const ChatView({Key? key, required this.userToken}) : super(key: key); + final GetRoomResponseObject user; + final String selfId; + final CircleAvatar avatar; + + const ChatView({Key? key, required this.user, required this.selfId, required this.avatar}) : super(key: key); @override State createState() => _ChatViewState(); @@ -16,7 +20,7 @@ class ChatView extends StatefulWidget { class _ChatViewState extends State { static const styleSystem = BubbleStyle( - color: Color.fromRGBO(212, 234, 244, 1.0), + color: Color(0xffd4eaf4), borderWidth: 1, elevation: 2, margin: BubbleEdges.only(top: 15), @@ -24,79 +28,151 @@ class _ChatViewState extends State { ); static const styleOther = BubbleStyle( - nip: BubbleNip.leftBottom, + nip: BubbleNip.leftTop, color: Colors.white, borderWidth: 1, - elevation: 2, - margin: BubbleEdges.only(top: 15, left: 10), + elevation: 1, + margin: BubbleEdges.only(top: 15, left: 10, right: 50), alignment: Alignment.topLeft, ); static const styleSelf = BubbleStyle( nip: BubbleNip.rightBottom, - color: Color.fromRGBO(225, 255, 199, 1.0), + color: Color(0xffd9fdd3), borderWidth: 1, - elevation: 2, - margin: BubbleEdges.only(top: 15, right: 10), + elevation: 1, + margin: BubbleEdges.only(top: 15, right: 10, left: 50), alignment: Alignment.topRight, ); + final ScrollController _listController = ScrollController(); @override void initState() { super.initState(); - Provider.of(context, listen: false).invoke( - data: { - "token": widget.userToken - }, - indicateLoading: true, - allowNotifyListeners: false, - ); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Provider.of(context, listen: false).setQueryToken(widget.user.token); + }); } @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.grey, - appBar: AppBar( - title: const Text("Chat mit jemandem"), - ), - body: Consumer( - builder: (context, data, child) { - List messages = List.empty(growable: true); + return Consumer( + builder: (context, data, child) { + List messages = List.empty(growable: true); + + if(!data.primaryLoading()) { + String lastActor = ""; + bool showMetadata = true; + + data.getChatResponse.sortByTimestamp().forEach((element) { + + showMetadata = element.messageType == GetRoomResponseObjectMessageType.comment; + + BubbleStyle currentStyle; + if(element.messageType == GetRoomResponseObjectMessageType.comment) { + if(element.actorId == widget.selfId) { + currentStyle = styleSelf; + } else { + currentStyle = styleOther; + } + } else { + currentStyle = styleSystem; + } + - data.messages.forEach((element) { messages.add(Bubble( - style: styleSelf, - child: Text(element.content), + margin: BubbleEdges.only(bottom: element == data.getChatResponse.sortByTimestamp().last ? 20 : 0), + + style: currentStyle, + child: Stack( + children: [ + Visibility( + visible: showMetadata, + child: Positioned( + top: 0, + left: 0, + child: Text("${element.actorDisplayName}", style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor)), + ), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: showMetadata ? 18 : 0), + child: Text(element.message), + ), + Visibility( + visible: showMetadata, + child: Positioned( + bottom: 0, + right: 0, + child: Text( + "${Jiffy.unixFromSecondsSinceEpoch(element.timestamp).yMMMMd} - ${Jiffy.unixFromSecondsSinceEpoch(element.timestamp).format("HH:mm")}", + style: TextStyle(color: Theme.of(context).disabledColor), + ), + ), + ), + ], + ), )); + + lastActor = element.actorId; }); + } - return LoadingPacket(packet: data, child: ListView( - children: [], - )); - }, - ), - - - - // ListView( - // children: [ - // Bubble( - // style: styleSystem, - // child: const Text("Chat gestartet"), - // ), - // Bubble( - // style: styleOther, - // child: const Text("Hi, das ist ein Testtext"), - // ), - // Bubble( - // style: styleSelf, - // child: Text(widget.userToken), - // ) - // ], - // ), + return Scaffold( + backgroundColor: const Color(0xffefeae2), + appBar: AppBar( + title: Row( + children: [ + widget.avatar, + const SizedBox(width: 10), + Text(widget.user.displayName, overflow: TextOverflow.ellipsis, maxLines: 1), + ], + ), + ), + body: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/background/chat.png"), + scale: 1.5, + opacity: 0.5, + repeat: ImageRepeat.repeat, + colorFilter: ColorFilter.linearToSrgbGamma() + ) + ), + child: data.primaryLoading() ? const Center(child: CircularProgressIndicator()) : Column( + children: [ + Expanded( + child: ListView( + reverse: true, + controller: _listController, + children: messages.reversed.toList(), + ), + ), + Container( + color: Theme.of(context).dividerColor, + padding: const EdgeInsets.all(10), + child: Row( + children: [ + const Expanded( + child: TextField( + maxLines: null, + decoration: InputDecoration( + hintText: "Nachricht", + border: OutlineInputBorder(), + labelText: "", + ), + ), + ), + IconButton(onPressed: () {}, icon: const Icon(Icons.send)) + ], + ), + ) + ], + ) + ), + ); + }, ); } } diff --git a/lib/screen/pages/timetable/dayListView.dart b/lib/screen/pages/timetable/dayListView.dart new file mode 100644 index 0000000..bafd9c4 --- /dev/null +++ b/lib/screen/pages/timetable/dayListView.dart @@ -0,0 +1,139 @@ +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getHolidays/getHolidaysResponse.dart'; +import 'package:timetable_view/timetable_view.dart'; + +import '../../../api/webuntis/queries/getHolidays/getHolidays.dart'; +import '../../../api/webuntis/queries/getRooms/getRoomsResponse.dart'; +import '../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; +import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; +import '../../../data/timetable/timetableProps.dart'; + +class DayListView extends StatefulWidget { + final TimetableProps value; + const DayListView(this.value, {Key? key}) : super(key: key); + + @override + State createState() => _DayListViewState(); +} + +class _DayListViewState extends State { + @override + Widget build(BuildContext context) { + return TimetableView( + laneEventsList: _buildLaneEvents(widget.value), + onEventTap: (TableEvent event) {}, + timetableStyle: CustomTableStyle(context), + onEmptySlotTap: (int laneIndex, TableEventTime start, TableEventTime end) => {}, + ); + } + + List _buildLaneEvents(TimetableProps data) { + List laneEvents = List.empty(growable: true); + Jiffy.locale("de"); // todo move outwards + + GetTimetableResponse timetable = data.getTimetableResponse; + GetRoomsResponse rooms = data.getRoomsResponse; + GetSubjectsResponse subjects = data.getSubjectsResponse; + GetHolidaysResponse holidays = data.getHolidaysResponse; + + GetHolidaysResponseObject? holidayInfo = GetHolidays.find(holidays, time: data.queryDate); + if(holidayInfo != null) { + laneEvents.add( + LaneEvents( + lane: Lane( + laneIndex: data.queryDate.millisecondsSinceEpoch, + name: "${Jiffy(data.queryDate.toString()).format("dd.MM.yy")}\n${Jiffy(data.queryDate.toString()).format("EEEE")}", + textStyle: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + fontSize: 14 + ) + ), + events: List.of([ + TableEvent( + title: holidayInfo.name, + eventId: holidayInfo.id, + laneIndex: data.queryDate.millisecondsSinceEpoch, + startTime: parseTime(0800), + endTime: parseTime(1500), + padding: const EdgeInsets.all(5), + backgroundColor: Theme.of(context).disabledColor, + location: "\n${holidayInfo.longName}", + ) + ]), + ) + ); + } + + List dayList = timetable.result.map((e) => e.date).toSet().toList(); + dayList.sort((a, b) => a-b); + dayList.forEach((day) { + //Every Day + laneEvents.add( + LaneEvents( + lane: Lane( + laneIndex: day, + name: "${Jiffy(day.toString()).format("dd.MM.yy")}\n${Jiffy(day.toString()).format("EEEE")}", + textStyle: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + fontSize: 14 + ) + ), + events: List.generate( + timetable.result.where((element) => element.date == day).length, + (index) { + GetTimetableResponseObject tableEvent = timetable.result.where((element) => element.date == day).elementAt(index); + + GetSubjectsResponseObject subject = subjects.result.firstWhere((subject) => subject.id == tableEvent.su[0]['id']); + + return TableEvent( + title: "${subject.alternateName} (${subject.longName})", + eventId: tableEvent.id, + laneIndex: day, + startTime: parseTime(tableEvent.startTime), + endTime: parseTime(tableEvent.endTime), + padding: const EdgeInsets.all(5), + backgroundColor: Theme.of(context).primaryColor, + location: "\n${rooms.result.firstWhere((room) => room.id == tableEvent.ro[0]['id']).name} - ${tableEvent.te[0]['longname']} (${tableEvent.te[0]['name']})", + ); + } + ) + ) + ); + }); + + return laneEvents; + } + + TableEventTime parseTime(int input) { + String time = input.toString().length < 4 ? "0$input" : input.toString(); + return TableEventTime(hour: int.parse(time.substring(0, 2)), minute: int.parse(time.substring(2, 4))); + } +} + +class CustomTableStyle extends TimetableStyle { + dynamic context; + CustomTableStyle(this.context); + + @override + int get startHour => 07; + @override + int get endHour => 17; + @override + Color get cornerColor => Theme.of(context).primaryColor; + @override + Color get timeItemTextColor => Theme.of(context).primaryColor; + @override + double get timeItemHeight => 70; + @override + double get timeItemWidth => 50; + @override + double get laneWidth => MediaQuery.of(context).size.width - timeItemWidth; + +} \ No newline at end of file diff --git a/lib/screen/pages/timetable/storedTimetable.dart b/lib/screen/pages/timetable/storedTimetable.dart deleted file mode 100644 index 0a63e79..0000000 --- a/lib/screen/pages/timetable/storedTimetable.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'dart:developer'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:jiffy/jiffy.dart'; -import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRoomsResponse.dart'; -import 'package:marianum_mobile/api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; -import 'package:marianum_mobile/data/timetable/timetable.dart'; -import 'package:marianum_mobile/screen/pages/timetable/testTimetable.dart'; -import 'package:marianum_mobile/widget/loadingSpinner.dart'; -import 'package:provider/provider.dart'; -import 'package:timetable_view/timetable_view.dart'; - -import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; - -class StoredTimetable extends StatefulWidget { - const StoredTimetable({Key? key}) : super(key: key); - - @override - State createState() => _StoredTimetableState(); -} - -class _StoredTimetableState extends State { - @override - void initState() { - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - Provider.of(context, listen: false).run(); - }); - } - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (context, value, child) { - if(value.getTimetableResponse == null) { - return const LoadingSpinner(); - } - - return TimetableView( - laneEventsList: _buildLaneEvents(value), - onEventTap: (TableEvent event) {}, - timetableStyle: CustomTableStyle(context), - onEmptySlotTap: (int laneIndex, TableEventTime start, TableEventTime end) => {}, - ); - }, - ); - } - - - List _buildLaneEvents(Timetable data) { - List laneEvents = List.empty(growable: true); - Jiffy.locale("de"); // todo move outwards - - GetTimetableResponse timetable = data.getTimetableResponse!; - GetRoomsResponse rooms = data.getRoomsResponse!; - GetSubjectsResponse subjects = data.getSubjectsResponse!; - - List dayList = timetable.result.map((e) => e.date).toSet().toList(); - dayList.sort((a, b) => a-b); - dayList.forEach((day) { - //Every Day - - laneEvents.add( - LaneEvents( - lane: Lane( - laneIndex: day, - name: "${Jiffy(day.toString()).format("dd.MM.yy")}\n${Jiffy(day.toString()).format("EEEE")}", - textStyle: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold, - fontSize: 14 - ) - ), - events: List.generate( - timetable.result.where((element) => element.date == day).length, - (index) { - GetTimetableResponseObject tableEvent = timetable.result.where((element) => element.date == day).elementAt(index); - return TableEvent( - title: subjects.result.firstWhere((subject) => subject.id == tableEvent.su[0]['id']).longName, - eventId: tableEvent.id, - laneIndex: day, - startTime: parseTime(tableEvent.startTime), - endTime: parseTime(tableEvent.endTime), - padding: const EdgeInsets.all(5), - backgroundColor: Theme.of(context).primaryColor, - location: "\n${rooms.result.firstWhere((room) => room.id == tableEvent.ro[0]['id']).name}", - ); - } - ) - ) - ); - - }); - - return laneEvents; - } - - TableEventTime parseTime(int input) { - String time = input.toString().length < 4 ? "0$input" : input.toString(); - return TableEventTime(hour: int.parse(time.substring(0, 2)), minute: int.parse(time.substring(2, 4))); - } -} diff --git a/lib/screen/pages/timetable/testTimetable.dart b/lib/screen/pages/timetable/testTimetable.dart index 3023a8c..13aff8f 100644 --- a/lib/screen/pages/timetable/testTimetable.dart +++ b/lib/screen/pages/timetable/testTimetable.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRooms.dart'; +import 'package:marianum_mobile/data/timetable/timetableProps.dart'; import 'package:marianum_mobile/widget/loadingSpinner.dart'; import 'package:marianum_mobile/widget/offlineError.dart'; import 'package:timetable_view/timetable_view.dart'; @@ -131,14 +132,14 @@ class CustomTableStyle extends TimetableStyle { @override int get endHour => 17; @override - double get laneWidth => 300; - @override Color get cornerColor => Theme.of(context).primaryColor; @override Color get timeItemTextColor => Theme.of(context).primaryColor; @override - double get timeItemHeight => 80; + double get timeItemHeight => 70; @override - double get timeItemWidth => 70; + double get timeItemWidth => 50; + @override + double get laneWidth => MediaQuery.of(context).size.width - timeItemWidth; } \ No newline at end of file diff --git a/lib/screen/pages/timetable/timetable.dart b/lib/screen/pages/timetable/timetable.dart index 9beb944..36f184e 100644 --- a/lib/screen/pages/timetable/timetable.dart +++ b/lib/screen/pages/timetable/timetable.dart @@ -1,13 +1,18 @@ - import 'dart:developer'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:marianum_mobile/widget/loadingPacket.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRoomsResponse.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; +import 'package:marianum_mobile/data/timetable/timetableProps.dart'; +import 'package:marianum_mobile/screen/pages/timetable/dayListView.dart'; +import 'package:marianum_mobile/screen/pages/timetable/testTimetable.dart'; +import 'package:marianum_mobile/widget/loadingSpinner.dart'; import 'package:provider/provider.dart'; import 'package:timetable_view/timetable_view.dart'; -import '../../../dataOld/incommingPackets/timetablePacket.dart'; +import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; class Timetable extends StatefulWidget { const Timetable({Key? key}) : super(key: key); @@ -17,90 +22,69 @@ class Timetable extends StatefulWidget { } class _TimetableState extends State { - @override void initState() { - Provider.of(context, listen: false).invoke(); super.initState(); - } + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Provider.of(context, listen: false).run(); + }); + } + @override Widget build(BuildContext context) { - return Consumer( - builder: (context, data, child) { + return Consumer( + builder: (context, value, child) { + if(value.primaryLoading()) { + return const Center(child: CircularProgressIndicator()); + } - return LoadingPacket(packet: data, child: TimetableView( - laneEventsList: _buildLaneEvents(context, data), - onEventTap: (TableEvent event) {}, - timetableStyle: CustomTableStyle(context), - onEmptySlotTap: (int laneIndex, TableEventTime start, TableEventTime end) => {}, - )); + TimetableProps timetable = Provider.of(context, listen: false); + return Column( + children: [ + Flexible( + child: DayListView(value), + ), + + Container( + padding: const EdgeInsets.only(top: 5, bottom: 5), + decoration: BoxDecoration( + border: Border( + top: BorderSide(width: 2, color: Theme.of(context).disabledColor) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + + children: [ + IconButton( + onPressed: () => timetable.next(previous: true), + icon: const Icon(Icons.navigate_before_sharp), + color: Theme.of(context).primaryColor, + iconSize: 30, + ), + Row( + children: [ + IconButton( + onPressed: () => timetable.nearest(), + icon: const Icon(Icons.home), + color: Theme.of(context).primaryColor, + iconSize: 30, + ), + ], + ), + IconButton( + onPressed: () => timetable.next(), + icon: const Icon(Icons.navigate_next_sharp), + color: Theme.of(context).primaryColor, + iconSize: 30, + ) + ], + ), + ) + ], + ); }, ); } - - List _buildLaneEvents(context, TimetablePacket data) { - List laneEvents = List.empty(growable: true); - data.timeTable.days.forEach((day) { - List tableEvents = List.empty(growable: true); - - day.entries.forEach((element) { - tableEvents.add( - TableEvent( - backgroundColor: Theme.of(context).primaryColor, - padding: const EdgeInsets.all(5), - title: element.subject, - location: "\n${element.room}", - eventId: tableEvents.length, - laneIndex: tableEvents.length, - startTime: TableEventTime(hour: element.start.hour, minute: element.start.minute), - endTime: TableEventTime(hour: element.end.hour, minute: element.end.minute) - ) - ); - }); - - laneEvents.add( - LaneEvents( - lane: Lane(laneIndex: laneEvents.length, name: day.name, textStyle: TextStyle(color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold)), - events: tableEvents - ) - ); - }); - - - return laneEvents; - } - - void onEventTapCallBack(TableEvent event) { - print( - "Event Clicked!! LaneIndex ${event.laneIndex} Title: ${event.title} StartHour: ${event.startTime.hour} EndHour: ${event.endTime.hour}"); - } - - void onTimeSlotTappedCallBack( - int laneIndex, TableEventTime start, TableEventTime end) { - print( - "Empty Slot Clicked !! LaneIndex: $laneIndex StartHour: ${start.hour} EndHour: ${end.hour}"); - } } - -class CustomTableStyle extends TimetableStyle { - dynamic context; - CustomTableStyle(context) { - this.context = context; - } - - - @override - int get startHour => 07; - @override - int get endHour => 17; - @override - double get laneWidth => 200; - @override - Color get cornerColor => Theme.of(this.context).primaryColor; - @override - Color get timeItemTextColor => Theme.of(this.context).primaryColor; - @override - // TODO: implement timeItemHeight - double get timeItemHeight => 60; -} \ No newline at end of file diff --git a/lib/screen/pages/timetable/timetableOld.dart b/lib/screen/pages/timetable/timetableOld.dart new file mode 100644 index 0000000..ecdc56f --- /dev/null +++ b/lib/screen/pages/timetable/timetableOld.dart @@ -0,0 +1,106 @@ + +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:marianum_mobile/widget/loadingPacket.dart'; +import 'package:provider/provider.dart'; +import 'package:timetable_view/timetable_view.dart'; + +import '../../../dataOld/incommingPackets/timetablePacket.dart'; + +class TimetableOld extends StatefulWidget { + const TimetableOld({Key? key}) : super(key: key); + + @override + State createState() => _TimetableOldState(); +} + +class _TimetableOldState extends State { + + @override + void initState() { + Provider.of(context, listen: false).invoke(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, data, child) { + + return LoadingPacket(packet: data, child: TimetableView( + laneEventsList: _buildLaneEvents(context, data), + onEventTap: (TableEvent event) {}, + timetableStyle: CustomTableStyle(context), + onEmptySlotTap: (int laneIndex, TableEventTime start, TableEventTime end) => {}, + )); + }, + ); + } + + List _buildLaneEvents(context, TimetablePacket data) { + List laneEvents = List.empty(growable: true); + data.timeTable.days.forEach((day) { + List tableEvents = List.empty(growable: true); + + day.entries.forEach((element) { + tableEvents.add( + TableEvent( + backgroundColor: Theme.of(context).primaryColor, + padding: const EdgeInsets.all(5), + title: element.subject, + location: "\n${element.room}", + eventId: tableEvents.length, + laneIndex: tableEvents.length, + startTime: TableEventTime(hour: element.start.hour, minute: element.start.minute), + endTime: TableEventTime(hour: element.end.hour, minute: element.end.minute) + ) + ); + }); + + laneEvents.add( + LaneEvents( + lane: Lane(laneIndex: laneEvents.length, name: day.name, textStyle: TextStyle(color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold)), + events: tableEvents + ) + ); + }); + + + return laneEvents; + } + + void onEventTapCallBack(TableEvent event) { + print( + "Event Clicked!! LaneIndex ${event.laneIndex} Title: ${event.title} StartHour: ${event.startTime.hour} EndHour: ${event.endTime.hour}"); + } + + void onTimeSlotTappedCallBack( + int laneIndex, TableEventTime start, TableEventTime end) { + print( + "Empty Slot Clicked !! LaneIndex: $laneIndex StartHour: ${start.hour} EndHour: ${end.hour}"); + } +} + +class CustomTableStyle extends TimetableStyle { + dynamic context; + CustomTableStyle(context) { + this.context = context; + } + + + @override + int get startHour => 07; + @override + int get endHour => 17; + @override + double get laneWidth => 200; + @override + Color get cornerColor => Theme.of(this.context).primaryColor; + @override + Color get timeItemTextColor => Theme.of(this.context).primaryColor; + @override + // TODO: implement timeItemHeight + double get timeItemHeight => 60; +} \ No newline at end of file diff --git a/lib/screen/settings/debug/debugOverview.dart b/lib/screen/settings/debug/debugOverview.dart new file mode 100644 index 0000000..23ecb94 --- /dev/null +++ b/lib/screen/settings/debug/debugOverview.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:localstore/localstore.dart'; +import 'package:marianum_mobile/screen/settings/debug/jsonViewer.dart'; + +class DebugOverview extends StatefulWidget { + const DebugOverview({Key? key}) : super(key: key); + + @override + State createState() => _DebugOverviewState(); +} + +class _DebugOverviewState extends State { + + final Localstore storage = Localstore.instance; + Future?> files = Localstore.instance.collection("MarianumMobile").get(); + dynamic data; + + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Lokaler cache"), + ), + body: Column( + children: [ + Expanded( + flex: 1, + child: ListView( + children: [ + ListTile( + leading: const Icon(Icons.delete_forever), + title: const Text("Cache löschen"), + onTap: () { + storage.collection("MarianumMobile").delete().then((value) => { + Navigator.pop(context) + }); + }, + ) + ], + ), + ), + const Divider(), + FutureBuilder( + future: files, + builder: (context, snapshot) { + if(snapshot.hasData) { + List files = snapshot.data?.keys.map((e) => e.toString()).toList() ?? List.empty(); + + Map getValue(int index) { + return snapshot.data?[files[index]]; + } + + return Expanded( + flex: 5, + child: ListView.builder( + itemCount: files.length, + itemBuilder: (context, index) { + String filename = files[index].split("/").last; + //String data = getValue(index).toString().replaceAll("{", "{\n ").replaceAll("[", "[\n ").replaceAll(",", ",\n "); + String data = getValue(index).toString(); + + return ListTile( + leading: const Icon(Icons.text_snippet_outlined), + title: Text("(${data.length} z) [$filename] ${Jiffy.unixFromMillisecondsSinceEpoch(getValue(index)['lastupdate']).fromNow()}"), + textColor: Colors.black, + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return JsonViewer(title: filename, data: data); + },)); + }, + ); + }, + ), + ); + } else { + return snapshot.data == null ? const Text("No data") : const Center(child: CircularProgressIndicator()); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/screen/settings/debug/jsonViewer.dart b/lib/screen/settings/debug/jsonViewer.dart new file mode 100644 index 0000000..7bd1211 --- /dev/null +++ b/lib/screen/settings/debug/jsonViewer.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class JsonViewer extends StatelessWidget { + String title; + String data; + + JsonViewer({Key? key, required this.title, required this.data}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + body: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Text(data.replaceAllMapped(RegExp(r'[{,}]'), (match) => "${match.group(0)}\n ")), + ), + ); + } +} diff --git a/lib/screen/settings/settings.dart b/lib/screen/settings/settings.dart index f269c99..a9595d5 100644 --- a/lib/screen/settings/settings.dart +++ b/lib/screen/settings/settings.dart @@ -6,6 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../../dataOld/accountModel.dart'; import '../../dataOld/incommingPackets/serverInfoPacket.dart'; import '../../widget/ListItem.dart'; +import 'debug/debugOverview.dart'; class Settings extends StatefulWidget { const Settings({Key? key}) : super(key: key); @@ -19,7 +20,6 @@ class _SettingsState extends State { @override void initState() { super.initState(); - Provider.of(context, listen: false).invoke(); } @override @@ -31,56 +31,64 @@ class _SettingsState extends State { body: ListView( children: [ - const ListItemNavigator(icon: Icons.info, text: "Über diese App", target: AboutDialog( - applicationIcon: Icon(Icons.send_time_extension_outlined), - applicationLegalese: "Released under MIT-License", - applicationName: "Marianum Fulda", - applicationVersion: "ALPHA 0.1", - )), - ListTile( leading: const Icon(Icons.logout), - title: const Text("Account abmelden"), + title: const Text("Konto abmelden"), onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (builder) => AlertDialog( - title: const Text("Abmelden?"), - content: const Text("Möchtest du dich wirklich abmelden?"), - actions: [ - TextButton( - child: const Text("Abmelden"), - onPressed: () { - SharedPreferences.getInstance().then((value) => { - value.clear(), - }).then((value) => { - Provider.of(context, listen: false).logout(), - Navigator.popUntil(context, (route) => !Navigator.canPop(context)), - }); - } - ), + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text("Abmelden?"), + content: const Text("Möchtest du dich wirklich abmelden?"), + actions: [ + TextButton( + child: const Text("Abmelden"), + onPressed: () { + SharedPreferences.getInstance().then((value) => { + value.clear(), + }).then((value) => { + Provider.of(context, listen: false).logout(), + Navigator.popUntil(context, (route) => !Navigator.canPop(context)), + }); + } + ), - TextButton( - child: const Text("Abbrechen"), - onPressed: () { - Navigator.pop(context); - }, - ), - ], - ))); + TextButton( + child: const Text("Abbrechen"), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ); + }, + ); }, ), - Consumer( - builder: (context, serverInfo, child) { - return ListTile( - leading: const Icon(Icons.home_work_outlined), - title: Text("Server: ${serverInfo.serverName}"), - subtitle: Text( - "Betreiber: ${serverInfo.serverOwner}\n" - "Serverversion: ${serverInfo.serverVersion}\n" - "Rechtliche hinweise: ${serverInfo.legal}\n" - ), + ListTile( + leading: const Icon(Icons.info), + title: const Text("Informationen und Lizenzen"), + onTap: () { + showAboutDialog( + context: context, + applicationIcon: const Icon(Icons.send_time_extension_outlined), + applicationName: "MarianumMobile", + applicationVersion: "Development Build", + applicationLegalese: "Marianum Fulda 2023 Elias Müller", ); }, + ), + + ListTile( + leading: const Icon(Icons.bug_report_outlined), + title: const Text("Speicheransicht"), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return const DebugOverview(); + })); + }, ) ], diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 63ad0d1..b19945c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,10 @@ import Foundation import path_provider_foundation import shared_preferences_foundation -import sqflite import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.yaml b/pubspec.yaml index 8da3b6d..af72320 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,6 @@ dependencies: jiffy: ^5.0.0 timetable_view: ^0.3.0 json_annotation: ^4.8.0 - sqflite: ^2.2.4+1 localstore: ^1.2.3 intl: ^0.17.0 @@ -82,6 +81,7 @@ flutter: assets: - assets/ca/ + - assets/background/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware