diff --git a/lib/core/provider/messages_provider.dart b/lib/core/provider/messages_provider.dart new file mode 100644 index 0000000..7d6cf29 --- /dev/null +++ b/lib/core/provider/messages_provider.dart @@ -0,0 +1,20 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:your_schedule/core/provider/connectivity_provider.dart'; +import 'package:your_schedule/core/untis.dart'; + +part 'messages_provider.g.dart'; + +@Riverpod(keepAlive: true) +class InboxMessages extends _$InboxMessages { + @override + Messages build(UntisSession session) { + assert(session is ActiveUntisSession, "Session must be active"); + if (ref.watch(canMakeRequestProvider)) { + var messages = ref.watch(requestMessagesProvider(session)); + if (messages.hasValue) { + return messages.requireValue; + } + } + return ref.watch(cachedMessagesProvider(session)); + } +} diff --git a/lib/core/provider/specified_message_provider.dart b/lib/core/provider/specified_message_provider.dart new file mode 100644 index 0000000..2f98d92 --- /dev/null +++ b/lib/core/provider/specified_message_provider.dart @@ -0,0 +1,20 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:your_schedule/core/provider/connectivity_provider.dart'; +import 'package:your_schedule/core/untis.dart'; + +part 'specified_message_provider.g.dart'; + +@riverpod +class MessageDetail extends _$MessageDetail { + @override + SpecifiedMessage? build(UntisSession session, int messageId) { + assert(session is ActiveUntisSession, "Session must be active"); + if (ref.watch(canMakeRequestProvider)) { + var message = ref.watch(requestSpecifiedMessageProvider(session, messageId)); + if (message.hasValue) { + return message.requireValue; + } + } + return ref.watch(cachedSpecifiedMessageProvider(session, messageId)); + } +} diff --git a/lib/core/untis.dart b/lib/core/untis.dart index 9f2da23..53e0a68 100644 --- a/lib/core/untis.dart +++ b/lib/core/untis.dart @@ -1,5 +1,7 @@ export 'untis/cached/cached_exams.dart'; export 'untis/cached/cached_timetable.dart'; +export 'untis/cached/cached_messages.dart'; +export 'untis/cached/cached_specified_message.dart'; export 'untis/models/app_shared_secret/app_shared_secret_params.dart'; export 'untis/models/exams/exam.dart'; export 'untis/models/exams/invigilators.dart'; @@ -17,9 +19,15 @@ export 'untis/models/user_data/subject.dart'; export 'untis/models/user_data/teacher.dart'; export 'untis/models/user_data/time_grid_entry.dart'; export 'untis/models/user_data/user_data.dart'; +export 'untis/models/messages/messages.dart'; +export 'untis/models/messages/specified_message.dart'; +export 'untis/models/auth_token/auth_token.dart'; export 'untis/requests/request_app_shared_secret.dart'; export 'untis/requests/request_exams.dart'; export 'untis/requests/request_school_list.dart'; export 'untis/requests/request_timetable.dart'; export 'untis/requests/request_user_data.dart'; +export 'untis/requests/request_auth_token.dart'; +export 'untis/requests/request_messages.dart'; +export 'untis/requests/request_specified_message.dart'; export 'untis/untis_session.dart'; diff --git a/lib/core/untis/cached/cached_messages.dart b/lib/core/untis/cached/cached_messages.dart new file mode 100644 index 0000000..2ae0aea --- /dev/null +++ b/lib/core/untis/cached/cached_messages.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:your_schedule/core/untis.dart'; +import 'package:your_schedule/util/shared_preferences.dart'; + +part 'cached_messages.g.dart'; + +@Riverpod(keepAlive: true) +class CachedMessages extends _$CachedMessages { + @override + Messages build(UntisSession activeSession) { + assert(activeSession is ActiveUntisSession, "Session must be active"); + ActiveUntisSession session = activeSession as ActiveUntisSession; + if (!sharedPreferences.containsKey("${session.userData.id}.messages")) { + return const Messages(incomingMessages: [], readConfirmationMessages: []); + } + return Messages.fromJson( + jsonDecode(sharedPreferences.getString("${session.userData.id}.messages")!), + ); + } + + Future setCachedMessages(Messages messages) async { + await sharedPreferences.setString( + "${(activeSession as ActiveUntisSession).userData.id}.messages", + jsonEncode(messages.toJson()), + ); + state = messages; + } +} diff --git a/lib/core/untis/cached/cached_specified_message.dart b/lib/core/untis/cached/cached_specified_message.dart new file mode 100644 index 0000000..c370b47 --- /dev/null +++ b/lib/core/untis/cached/cached_specified_message.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:your_schedule/core/untis.dart'; +import 'package:your_schedule/util/shared_preferences.dart'; + +part 'cached_specified_message.g.dart'; + +@Riverpod(keepAlive: true) +class CachedSpecifiedMessage extends _$CachedSpecifiedMessage { + @override + SpecifiedMessage? build(UntisSession activeSession, int messageId) { + assert(activeSession is ActiveUntisSession, "Session must be active"); + ActiveUntisSession session = activeSession as ActiveUntisSession; + if (!sharedPreferences.containsKey("${session.userData.id}.message.$messageId")) { + return null; + } + return SpecifiedMessage.fromJson( + jsonDecode(sharedPreferences.getString("${session.userData.id}.message.$messageId")!), + ); + } + + Future setCachedSpecifiedMessage(SpecifiedMessage message) async { + await sharedPreferences.setString( + "${(activeSession as ActiveUntisSession).userData.id}.message.$messageId", + jsonEncode(message.toJson()), + ); + state = message; + } +} diff --git a/lib/core/untis/models/auth_token/auth_token.dart b/lib/core/untis/models/auth_token/auth_token.dart new file mode 100644 index 0000000..95020bf --- /dev/null +++ b/lib/core/untis/models/auth_token/auth_token.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'auth_token.freezed.dart'; +part 'auth_token.g.dart'; + +@freezed +abstract class AuthToken with _$AuthToken { + const factory AuthToken({ + required String jwt, + required bool isPasswordChangeRequired, + required bool isEmailUpdateRequired, + }) = _AuthToken; + + factory AuthToken.fromJson(Map json) => + _$AuthTokenFromJson(json); +} diff --git a/lib/core/untis/models/auth_token/auth_token.txt b/lib/core/untis/models/auth_token/auth_token.txt new file mode 100644 index 0000000..f1beb08 --- /dev/null +++ b/lib/core/untis/models/auth_token/auth_token.txt @@ -0,0 +1,17 @@ +URL: https://herakles.webuntis.com/WebUntis/api/mobile/v2/cjd-k%C3%B6nigswinter/authentication +Method: Post +content-type: application/json; charset=utf-8 + +Request Body: +{ + "oneTimePassword": "123456", + "password": "XXXXXXXXXX", + "username": "xxxxxxx" +} + +Response Body: +{ + "jwt": "very long auth token ..............................", + "isPasswordChangeRequired": false, + "isEmailUpdateRequired": false +} \ No newline at end of file diff --git a/lib/core/untis/models/messages/messages.dart b/lib/core/untis/models/messages/messages.dart new file mode 100644 index 0000000..05820aa --- /dev/null +++ b/lib/core/untis/models/messages/messages.dart @@ -0,0 +1,48 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'messages.freezed.dart'; +part 'messages.g.dart'; + +@freezed +abstract class MessageSender with _$MessageSender { + const factory MessageSender({ + required String displayName, + required int userId, + String? className, + String? imageUrl, + }) = _MessageSender; + + factory MessageSender.fromJson(Map json) => + _$MessageSenderFromJson(json); +} + +@freezed +abstract class IncomingMessage with _$IncomingMessage { + const factory IncomingMessage({ + required int id, + required String subject, + required String contentPreview, + required MessageSender sender, + required DateTime sentDateTime, + required bool allowMessageDeletion, + required bool hasAttachments, + required bool isMessageRead, + required bool isReply, + required bool isReplyAllowed, + }) = _IncomingMessage; + + factory IncomingMessage.fromJson(Map json) => + _$IncomingMessageFromJson(json); +} + +@freezed +abstract class Messages with _$Messages { + const factory Messages({ + required List incomingMessages, + required List> readConfirmationMessages, + }) = _Messages; + + factory Messages.fromJson(Map json) => + _$MessagesFromJson(json); +} diff --git a/lib/core/untis/models/messages/messages.txt b/lib/core/untis/models/messages/messages.txt new file mode 100644 index 0000000..70f6f56 --- /dev/null +++ b/lib/core/untis/models/messages/messages.txt @@ -0,0 +1,103 @@ +URL: https://herakles.webuntis.com/WebUntis/api/rest/view/v1/messages?school=cjd-k%C3%B6nigswinter +Method: GET +content-type: application/json; charset=UTF-8 + +Request Header: +Accept-Encoding: gzip +Authorization: Bearer ...long auth token from getAuthToken +Cache-Control: public, no-cache +Cache-Time: 60 +Connection: Keep-Alive +Host: herakles.webuntis.com + +Response Body: +{ + "incomingMessages": [ + { + "id": 6680, + "subject": "Erinnerung", + "contentPreview": "Der Inhalt der Nachricht", + "sender": { + "className": null, + "displayName": "Ser", + "imageUrl": null, + "userId": 7027 + }, + "sentDateTime": "2026-02-12T18:30:00", + "allowMessageDeletion": false, + "hasAttachments": false, + "isMessageRead": true, + "isReply": false, + "isReplyAllowed": false + }, + { + "id": 6053, + "subject": "Unterricht nächste Woche", + "contentPreview": "Ein sehr langer text der der Errinerung an den Untericht in der nächsten Woche errinern soll", + "sender": { + "className": null, + "displayName": "Bek", + "imageUrl": null, + "userId": 7228 + }, + "sentDateTime": "2026-01-30T17:02:00", + "allowMessageDeletion": false, + "hasAttachments": false, + "isMessageRead": true, + "isReply": false, + "isReplyAllowed": false + }, + { + "id": 3273, + "subject": "Zettel", + "contentPreview": "Hallo, dies ist eine Errinnerung an den Zettel", + "sender": { + "className": null, + "displayName": "Ser", + "imageUrl": null, + "userId": 7027 + }, + "sentDateTime": "2025-11-12T18:32:00", + "allowMessageDeletion": false, + "hasAttachments": false, + "isMessageRead": true, + "isReply": false, + "isReplyAllowed": false + }, + { + "id": 2203, + "subject": "Material", + "contentPreview": "Das Material wird morgen benötigt", + "sender": { + "className": null, + "displayName": "Ser", + "imageUrl": null, + "userId": 7027 + }, + "sentDateTime": "2025-10-14T14:42:00", + "allowMessageDeletion": false, + "hasAttachments": false, + "isMessageRead": true, + "isReply": false, + "isReplyAllowed": false + }, + { + "id": 497, + "subject": "Informatik", + "contentPreview": "Morgen wird der Test durchgeführt", + "sender": { + "className": null, + "displayName": "Sab", + "imageUrl": null, + "userId": 12 + }, + "sentDateTime": "2025-06-24T23:37:00", + "allowMessageDeletion": false, + "hasAttachments": false, + "isMessageRead": true, + "isReply": false, + "isReplyAllowed": false + } + ], + "readConfirmationMessages": [] +} \ No newline at end of file diff --git a/lib/core/untis/models/messages/specified_message.dart b/lib/core/untis/models/messages/specified_message.dart new file mode 100644 index 0000000..931132d --- /dev/null +++ b/lib/core/untis/models/messages/specified_message.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:your_schedule/core/untis.dart'; + +part 'specified_message.freezed.dart'; +part 'specified_message.g.dart'; + +@freezed +abstract class SpecifiedMessage with _$SpecifiedMessage { + const factory SpecifiedMessage({ + required int id, + required String subject, + required String content, + required MessageSender sender, + required DateTime sentDateTime, + required bool allowMessageDeletion, + required bool isReply, + required bool isReplyAllowed, + required bool isReportMessage, + required bool isReplyForbidden, + required List> attachments, + required List> storageAttachments, + required List> replyHistory, + Map? blobAttachment, + Map? requestConfirmation, + }) = _SpecifiedMessage; + + factory SpecifiedMessage.fromJson(Map json) => + _$SpecifiedMessageFromJson(json); +} diff --git a/lib/core/untis/models/messages/specified_message.txt b/lib/core/untis/models/messages/specified_message.txt new file mode 100644 index 0000000..2908433 --- /dev/null +++ b/lib/core/untis/models/messages/specified_message.txt @@ -0,0 +1,35 @@ +URL: https://herakles.webuntis.com/WebUntis/api/rest/view/v1/messages/0000?school=cjd-k%C3%B6nigswinter // 0000 ist ein platzhalter für die id der zugehörigen Nachricht +Method: GET +content-type: application/json; charset=UTF-8 + +Request Header: +Accept-Encoding: gzip +Authorization: Bearer ...long auth token from getAuthToken +Cache-Control: public, no-cache +Cache-Time: 60 +Connection: Keep-Alive +Host: herakles.webuntis.com + +Response Body: +{ + "id": 0000, + "subject": "Ein Zettel", + "content": "Hallo, dies ist eine Errinnerung an den Zettel", + "sender": { + "className": null, + "displayName": "Ser", + "imageUrl": null, + "userId": 7027 + }, + "sentDateTime": "2026-01-13T17:55:00", + "allowMessageDeletion": false, + "attachments": [], + "blobAttachment": null, + "storageAttachments": [], + "isReply": false, + "isReplyAllowed": false, + "isReportMessage": false, + "isReplyForbidden": false, + "replyHistory": [], + "requestConfirmation": null +} \ No newline at end of file diff --git a/lib/core/untis/requests/request_auth_token.dart b/lib/core/untis/requests/request_auth_token.dart new file mode 100644 index 0000000..dcca686 --- /dev/null +++ b/lib/core/untis/requests/request_auth_token.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:your_schedule/core/rpc_request/rpc.dart'; +import 'package:your_schedule/core/untis.dart'; +import 'package:your_schedule/util/logger.dart'; + + +Future requestAuthToken( + UntisSession session, + String appSharedSecret, { + String token = "", + }) async { + final schoolEncoded = Uri.encodeComponent(session.school.loginName); + final uri = Uri.https( + session.school.server, + '/WebUntis/api/mobile/v2/$schoolEncoded/authentication', + ); + + http.Response response; + try { + response = await http.post( + uri, + headers: {'Content-Type': 'application/json; charset=utf-8'}, + body: jsonEncode({ + 'username': session.username, + 'password': session.password, + 'oneTimePassword': token, + }), + ); + } catch (e, s) { + getLogger().e("Error while requesting auth token", error: e, stackTrace: s); + rethrow; + } + + switch (response.statusCode) { + case 200: + getLogger().i("Successful auth token request"); + return AuthToken.fromJson( + jsonDecode(utf8.decode(response.bodyBytes)) as Map, + ); + default: + getLogger().e('HTTP Error: ${response.statusCode} ${response.reasonPhrase}'); + throw HttpException( + response.statusCode, + response.reasonPhrase.toString(), + uri: uri, + ); + } +} diff --git a/lib/core/untis/requests/request_messages.dart b/lib/core/untis/requests/request_messages.dart new file mode 100644 index 0000000..3941aad --- /dev/null +++ b/lib/core/untis/requests/request_messages.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:your_schedule/core/rpc_request/rpc.dart'; +import 'package:your_schedule/core/untis.dart'; +import 'package:your_schedule/util/logger.dart'; + +part 'request_messages.g.dart'; + +@Riverpod(keepAlive: true) +class RequestMessages extends _$RequestMessages { + @override + Future build(UntisSession activeSession) async { + assert(activeSession is ActiveUntisSession, "Session must be active"); + ActiveUntisSession session = activeSession as ActiveUntisSession; + + listenSelf((previous, data) { + if (previous == data) { + return; + } + data.when( + data: (data) { + ref.read(cachedMessagesProvider(session).notifier).setCachedMessages(data); + }, + error: (error, stackTrace) { + logRequestError("Error while requesting messages", error, stackTrace); + }, + loading: () {}, + ); + }); + + final uri = Uri.https( + session.school.server, + '/WebUntis/api/rest/view/v1/messages', + {'school': session.school.loginName}, + ); + + http.Response response; + try { + response = await http.get( + uri, + headers: { + 'Authorization': 'Bearer ${session.authToken!.jwt}', + 'Content-Type': 'application/json; charset=UTF-8', + 'Accept-Encoding': 'gzip', + 'Cache-Control': 'public, no-cache', + }, + ); + } catch (e, s) { + getLogger().e("Error while requesting messages", error: e, stackTrace: s); + rethrow; + } + + switch (response.statusCode) { + case 200: + getLogger().i("Successful messages request"); + return Messages.fromJson( + jsonDecode(utf8.decode(response.bodyBytes)) as Map, + ); + default: + getLogger().e('HTTP Error: ${response.statusCode} ${response.reasonPhrase}'); + throw HttpException( + response.statusCode, + response.reasonPhrase.toString(), + uri: uri, + ); + } + } +} diff --git a/lib/core/untis/requests/request_specified_message.dart b/lib/core/untis/requests/request_specified_message.dart new file mode 100644 index 0000000..4b9284a --- /dev/null +++ b/lib/core/untis/requests/request_specified_message.dart @@ -0,0 +1,73 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:your_schedule/core/rpc_request/rpc.dart'; +import 'package:your_schedule/core/untis.dart'; +import 'package:your_schedule/util/logger.dart'; + +part 'request_specified_message.g.dart'; + +@riverpod +class RequestSpecifiedMessage extends _$RequestSpecifiedMessage { + @override + Future build(UntisSession activeSession, int messageId) async { + assert(activeSession is ActiveUntisSession, "Session must be active"); + ActiveUntisSession session = activeSession as ActiveUntisSession; + + listenSelf((previous, data) { + if (previous == data) { + return; + } + data.when( + data: (data) { + ref + .read(cachedSpecifiedMessageProvider(session, messageId).notifier) + .setCachedSpecifiedMessage(data); + ref.invalidate(requestMessagesProvider(session)); + }, + error: (error, stackTrace) { + logRequestError("Error while requesting message $messageId", error, stackTrace); + }, + loading: () {}, + ); + }); + + final uri = Uri.https( + session.school.server, + '/WebUntis/api/rest/view/v1/messages/$messageId', + {'school': session.school.loginName}, + ); + + http.Response response; + try { + response = await http.get( + uri, + headers: { + 'Authorization': 'Bearer ${session.authToken!.jwt}', + 'Content-Type': 'application/json; charset=UTF-8', + 'Accept-Encoding': 'gzip', + 'Cache-Control': 'public, no-cache', + }, + ); + } catch (e, s) { + getLogger().e("Error while requesting specified message", error: e, stackTrace: s); + rethrow; + } + + switch (response.statusCode) { + case 200: + getLogger().i("Successful specified message request"); + return SpecifiedMessage.fromJson( + jsonDecode(utf8.decode(response.bodyBytes)) as Map, + ); + default: + getLogger().e('HTTP Error: ${response.statusCode} ${response.reasonPhrase}'); + throw HttpException( + response.statusCode, + response.reasonPhrase.toString(), + uri: uri, + ); + } + } +} diff --git a/lib/core/untis/untis_session.dart b/lib/core/untis/untis_session.dart index d0129e6..9001264 100644 --- a/lib/core/untis/untis_session.dart +++ b/lib/core/untis/untis_session.dart @@ -18,7 +18,8 @@ sealed class UntisSession with _$UntisSession { String appSharedSecret, @JsonKey(defaultValue: false) bool passwordIsAppSharedSecret, UserData userData, - ) = ActiveUntisSession; + @JsonKey(defaultValue: null) AuthToken? authToken, + ) = ActiveUntisSession; const factory UntisSession.inactive({ required School school, @@ -74,6 +75,7 @@ Future activateSession(WidgetRef ref, UntisSession session, appSharedSecret, passwordIsAppSharedSecret, userData, + await requestAuthToken(session, appSharedSecret, token: token), ) as ActiveUntisSession; ref.read(untisSessionsProvider.notifier).updateSession(session, activeSession); return activeSession; diff --git a/lib/ui/screens/messages_screen/message_detail_screen.dart b/lib/ui/screens/messages_screen/message_detail_screen.dart new file mode 100644 index 0000000..b310f21 --- /dev/null +++ b/lib/ui/screens/messages_screen/message_detail_screen.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:your_schedule/core/provider/specified_message_provider.dart'; +import 'package:your_schedule/core/provider/untis_session_provider.dart'; +import 'package:your_schedule/core/untis.dart'; + +class MessageDetailScreen extends ConsumerWidget { + final int messageId; + + const MessageDetailScreen({required this.messageId, super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final session = + ref.watch(selectedUntisSessionProvider) as ActiveUntisSession; + final message = ref.watch(messageDetailProvider(session, messageId)); + + return Scaffold( + appBar: AppBar(title: const Text('Nachricht')), + body: message == null + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + message.subject, + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 4), + Text( + message.sender.displayName, + style: Theme.of(context).textTheme.bodySmall, + ), + const Divider(height: 24), + Text(message.content), + ], + ), + ), + ); + } +} diff --git a/lib/ui/screens/messages_screen/messages_screen.dart b/lib/ui/screens/messages_screen/messages_screen.dart new file mode 100644 index 0000000..34bee7f --- /dev/null +++ b/lib/ui/screens/messages_screen/messages_screen.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:your_schedule/core/provider/messages_provider.dart'; +import 'package:your_schedule/core/provider/untis_session_provider.dart'; +import 'package:your_schedule/core/untis.dart'; +import 'package:your_schedule/ui/screens/messages_screen/message_detail_screen.dart'; + +class MessagesScreen extends ConsumerWidget { + const MessagesScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final session = + ref.watch(selectedUntisSessionProvider) as ActiveUntisSession; + final messages = ref.watch(inboxMessagesProvider(session)); + + return Scaffold( + appBar: AppBar(title: const Text('Nachrichten')), + body: RefreshIndicator( + onRefresh: () async { + ref.invalidate(requestMessagesProvider(session)); + await ref.read(requestMessagesProvider(session).future); + }, + child: ListView.builder( + itemCount: messages.incomingMessages.length, + itemBuilder: (context, index) { + final msg = messages.incomingMessages[index]; + return ListTile( + leading: Icon( + msg.isMessageRead ? Icons.mail_outline : Icons.mail, + ), + title: Text(msg.subject), + subtitle: Text( + msg.contentPreview, + maxLines: 1, // Feature 1 + overflow: TextOverflow.ellipsis, + ), + trailing: Text(msg.sender.displayName), + onTap: () { // Feature 2 + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + MessageDetailScreen(messageId: msg.id), + ), + ); + }, + ); + }, + ), + ), + ); + } +} diff --git a/lib/ui/shared/my_drawer.dart b/lib/ui/shared/my_drawer.dart index 889cfbe..e598444 100644 --- a/lib/ui/shared/my_drawer.dart +++ b/lib/ui/shared/my_drawer.dart @@ -5,6 +5,7 @@ import 'package:your_schedule/core/untis.dart'; import 'package:your_schedule/ui/screens/filter_screen/filter_screen.dart'; import 'package:your_schedule/ui/screens/login_screen/welcome_screen.dart'; import 'package:your_schedule/ui/screens/settings_screen/settings_screen.dart'; +import 'package:your_schedule/ui/screens/messages_screen/messages_screen.dart'; class MyDrawer extends ConsumerWidget { const MyDrawer({ @@ -47,6 +48,18 @@ class MyDrawer extends ConsumerWidget { ); }, ), + ListTile( + title: const Text("Nachrichten"), + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const MessagesScreen(), + ), + ); + }, + ), Expanded(child: Container()), ListTile( title: const Text("Einstellungen"),