Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/core/provider/messages_provider.dart
Original file line number Diff line number Diff line change
@@ -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));
}
}
20 changes: 20 additions & 0 deletions lib/core/provider/specified_message_provider.dart
Original file line number Diff line number Diff line change
@@ -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));
}
}
8 changes: 8 additions & 0 deletions lib/core/untis.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
30 changes: 30 additions & 0 deletions lib/core/untis/cached/cached_messages.dart
Original file line number Diff line number Diff line change
@@ -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<void> setCachedMessages(Messages messages) async {
await sharedPreferences.setString(
"${(activeSession as ActiveUntisSession).userData.id}.messages",
jsonEncode(messages.toJson()),
);
state = messages;
}
}
30 changes: 30 additions & 0 deletions lib/core/untis/cached/cached_specified_message.dart
Original file line number Diff line number Diff line change
@@ -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<void> setCachedSpecifiedMessage(SpecifiedMessage message) async {
await sharedPreferences.setString(
"${(activeSession as ActiveUntisSession).userData.id}.message.$messageId",
jsonEncode(message.toJson()),
);
state = message;
}
}
17 changes: 17 additions & 0 deletions lib/core/untis/models/auth_token/auth_token.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> json) =>
_$AuthTokenFromJson(json);
}
17 changes: 17 additions & 0 deletions lib/core/untis/models/auth_token/auth_token.txt
Original file line number Diff line number Diff line change
@@ -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
}
48 changes: 48 additions & 0 deletions lib/core/untis/models/messages/messages.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> 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<String, dynamic> json) =>
_$IncomingMessageFromJson(json);
}

@freezed
abstract class Messages with _$Messages {
const factory Messages({
required List<IncomingMessage> incomingMessages,
required List<Map<String, dynamic>> readConfirmationMessages,
}) = _Messages;

factory Messages.fromJson(Map<String, dynamic> json) =>
_$MessagesFromJson(json);
}
103 changes: 103 additions & 0 deletions lib/core/untis/models/messages/messages.txt
Original file line number Diff line number Diff line change
@@ -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": []
}
30 changes: 30 additions & 0 deletions lib/core/untis/models/messages/specified_message.dart
Original file line number Diff line number Diff line change
@@ -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<Map<String, dynamic>> attachments,
required List<Map<String, dynamic>> storageAttachments,
required List<Map<String, dynamic>> replyHistory,
Map<String, dynamic>? blobAttachment,
Map<String, dynamic>? requestConfirmation,
}) = _SpecifiedMessage;

factory SpecifiedMessage.fromJson(Map<String, dynamic> json) =>
_$SpecifiedMessageFromJson(json);
}
35 changes: 35 additions & 0 deletions lib/core/untis/models/messages/specified_message.txt
Original file line number Diff line number Diff line change
@@ -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
}
Loading