#8 Added marker to indicate read status in talk chats

This commit is contained in:
Elias Müller 2024-03-10 22:03:01 +01:00
parent 948ee19bda
commit 41372e9e86
25 changed files with 279 additions and 99 deletions

View File

@ -3,4 +3,7 @@ import 'package:json_annotation/json_annotation.dart';
abstract class ApiResponse {
@JsonKey(includeFromJson: false, includeToJson: false)
late http.Response rawResponse;
@JsonKey(includeIfNull: false)
late Map<String, String>? headers;
}

View File

@ -12,13 +12,23 @@ GetHolidaysResponse _$GetHolidaysResponseFromJson(Map<String, dynamic> json) =>
.map((e) =>
GetHolidaysResponseObject.fromJson(e as Map<String, dynamic>))
.toList(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetHolidaysResponseToJson(
GetHolidaysResponse instance) =>
<String, dynamic>{
'data': instance.data.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$GetHolidaysResponseToJson(GetHolidaysResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -11,12 +11,23 @@ GetChatResponse _$GetChatResponseFromJson(Map<String, dynamic> json) =>
(json['data'] as List<dynamic>)
.map((e) => GetChatResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetChatResponseToJson(GetChatResponse instance) =>
<String, dynamic>{
'data': instance.data.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$GetChatResponseToJson(GetChatResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetChatResponseObject _$GetChatResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -4,13 +4,14 @@ import 'package:http/http.dart';
import '../talkApi.dart';
import 'createRoomParams.dart';
class CreateRoom extends TalkApi<void> {
class CreateRoom extends TalkApi {
CreateRoomParams params;
CreateRoom(this.params) : super("v4/room", params);
@override
assemble(String raw) {
return null;
}
@override

View File

@ -1,10 +1,11 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:marianum_mobile/api/apiResponse.dart';
part 'getParticipantsResponse.g.dart';
@JsonSerializable(explicitToJson: true)
class GetParticipantsResponse {
class GetParticipantsResponse extends ApiResponse {
Set<GetParticipantsResponseObject> data;
GetParticipantsResponse(this.data);

View File

@ -13,13 +13,24 @@ GetParticipantsResponse _$GetParticipantsResponseFromJson(
.map((e) =>
GetParticipantsResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetParticipantsResponseToJson(
GetParticipantsResponse instance) =>
<String, dynamic>{
'data': instance.data.map((e) => e.toJson()).toList(),
};
GetParticipantsResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetParticipantsResponseObject _$GetParticipantsResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -1,9 +1,10 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:marianum_mobile/api/apiResponse.dart';
part 'getReactionsResponse.g.dart';
@JsonSerializable(explicitToJson: true)
class GetReactionsResponse {
class GetReactionsResponse extends ApiResponse {
Map<String, List<GetReactionsResponseObject>> data;
GetReactionsResponse(this.data);

View File

@ -17,14 +17,25 @@ GetReactionsResponse _$GetReactionsResponseFromJson(
e as Map<String, dynamic>))
.toList()),
),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetReactionsResponseToJson(
GetReactionsResponse instance) =>
<String, dynamic>{
'data': instance.data
.map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())),
};
GetReactionsResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data
.map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList()));
return val;
}
GetReactionsResponseObject _$GetReactionsResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -3,13 +3,14 @@ import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import '../talkApi.dart';
class LeaveRoom extends TalkApi<void> {
class LeaveRoom extends TalkApi {
String chatToken;
LeaveRoom(this.chatToken) : super("v4/room/$chatToken/participants/self", null);
@override
assemble(String raw) {
return null;
}
@override

View File

@ -11,12 +11,23 @@ GetRoomResponse _$GetRoomResponseFromJson(Map<String, dynamic> json) =>
(json['data'] as List<dynamic>)
.map((e) => GetRoomResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetRoomResponseToJson(GetRoomResponse instance) =>
<String, dynamic>{
'data': instance.data.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$GetRoomResponseToJson(GetRoomResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetRoomResponseObject _$GetRoomResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -4,14 +4,16 @@ import 'package:http/http.dart';
import '../talkApi.dart';
class SetFavorite extends TalkApi<void> {
class SetFavorite extends TalkApi {
String chatToken;
bool favoriteState;
SetFavorite(this.chatToken, this.favoriteState) : super("v4/room/$chatToken/favorite", null);
@override
assemble(String raw) {}
assemble(String raw) {
return null;
}
@override
Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) {

View File

@ -5,7 +5,7 @@ import 'package:http/http.dart';
import '../talkApi.dart';
import 'setReadMarkerParams.dart';
class SetReadMarker extends TalkApi<void> {
class SetReadMarker extends TalkApi {
String chatToken;
bool readState;
SetReadMarkerParams? setReadMarkerParams;
@ -16,7 +16,7 @@ class SetReadMarker extends TalkApi<void> {
@override
assemble(String raw) {
return null;
}
@override

View File

@ -1,6 +1,7 @@
import 'dart:developer';
import 'package:http/http.dart' as http;
import 'package:marianum_mobile/api/apiResponse.dart';
import '../../../model/accountData.dart';
import '../../../model/endpointData.dart';
@ -15,7 +16,7 @@ enum TalkApiMethod {
delete,
}
abstract class TalkApi<T> extends ApiRequest {
abstract class TalkApi<T extends ApiResponse?> extends ApiRequest {
String path;
ApiParams? body;
Map<String, String>? headers = {};
@ -54,6 +55,7 @@ abstract class TalkApi<T> extends ApiRequest {
T assembled;
try {
assembled = assemble(data.body);
assembled?.headers = data.headers;
return assembled;
} catch (e) {
// TODO report error

View File

@ -11,9 +11,20 @@ ListFilesResponse _$ListFilesResponseFromJson(Map<String, dynamic> json) =>
(json['files'] as List<dynamic>)
.map((e) => CacheableFile.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$ListFilesResponseToJson(ListFilesResponse instance) =>
<String, dynamic>{
'files': instance.files.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$ListFilesResponseToJson(ListFilesResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['files'] = instance.files.map((e) => e.toJson()).toList();
return val;
}

View File

@ -13,14 +13,24 @@ GetBreakersResponse _$GetBreakersResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(
k, GetBreakersReponseObject.fromJson(e as Map<String, dynamic>)),
),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetBreakersResponseToJson(
GetBreakersResponse instance) =>
<String, dynamic>{
'global': instance.global.toJson(),
'regional': instance.regional.map((k, e) => MapEntry(k, e.toJson())),
};
Map<String, dynamic> _$GetBreakersResponseToJson(GetBreakersResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['global'] = instance.global.toJson();
val['regional'] = instance.regional.map((k, e) => MapEntry(k, e.toJson()));
return val;
}
GetBreakersReponseObject _$GetBreakersReponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -12,10 +12,21 @@ GetCustomTimetableEventResponse _$GetCustomTimetableEventResponseFromJson(
(json['events'] as List<dynamic>)
.map((e) => CustomTimetableEvent.fromJson(e as Map<String, dynamic>))
.toList(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetCustomTimetableEventResponseToJson(
GetCustomTimetableEventResponse instance) =>
<String, dynamic>{
'events': instance.events,
};
GetCustomTimetableEventResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['events'] = instance.events;
return val;
}

View File

@ -13,14 +13,24 @@ GetMessagesResponse _$GetMessagesResponseFromJson(Map<String, dynamic> json) =>
.map((e) =>
GetMessagesResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetMessagesResponseToJson(
GetMessagesResponse instance) =>
<String, dynamic>{
'base': instance.base,
'messages': instance.messages.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$GetMessagesResponseToJson(GetMessagesResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['base'] = instance.base;
val['messages'] = instance.messages.map((e) => e.toJson()).toList();
return val;
}
GetMessagesResponseObject _$GetMessagesResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -1,10 +1,11 @@
import 'dart:convert';
import 'package:localstore/localstore.dart';
import 'package:marianum_mobile/api/apiResponse.dart';
import 'webuntis/webuntisError.dart';
abstract class RequestCache<T> {
abstract class RequestCache<T extends ApiResponse?> {
static const int cacheNothing = 0;
static const int cacheMinute = 60;
static const int cacheHour = 60 * 60;

View File

@ -13,13 +13,24 @@ AuthenticateResponse _$AuthenticateResponseFromJson(
json['personType'] as int,
json['personId'] as int,
json['klasseId'] as int,
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$AuthenticateResponseToJson(
AuthenticateResponse instance) =>
<String, dynamic>{
'sessionId': instance.sessionId,
'personType': instance.personType,
'personId': instance.personId,
'klasseId': instance.klasseId,
};
AuthenticateResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['sessionId'] = instance.sessionId;
val['personType'] = instance.personType;
val['personId'] = instance.personId;
val['klasseId'] = instance.klasseId;
return val;
}

View File

@ -12,13 +12,23 @@ GetHolidaysResponse _$GetHolidaysResponseFromJson(Map<String, dynamic> json) =>
.map((e) =>
GetHolidaysResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetHolidaysResponseToJson(
GetHolidaysResponse instance) =>
<String, dynamic>{
'result': instance.result.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$GetHolidaysResponseToJson(GetHolidaysResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -12,12 +12,23 @@ GetRoomsResponse _$GetRoomsResponseFromJson(Map<String, dynamic> json) =>
.map(
(e) => GetRoomsResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetRoomsResponseToJson(GetRoomsResponse instance) =>
<String, dynamic>{
'result': instance.result.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$GetRoomsResponseToJson(GetRoomsResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetRoomsResponseObject _$GetRoomsResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -12,13 +12,23 @@ GetSubjectsResponse _$GetSubjectsResponseFromJson(Map<String, dynamic> json) =>
.map((e) =>
GetSubjectsResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetSubjectsResponseToJson(
GetSubjectsResponse instance) =>
<String, dynamic>{
'result': instance.result.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> _$GetSubjectsResponseToJson(GetSubjectsResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetSubjectsResponseObject _$GetSubjectsResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -13,13 +13,24 @@ GetTimetableResponse _$GetTimetableResponseFromJson(
.map((e) =>
GetTimetableResponseObject.fromJson(e as Map<String, dynamic>))
.toSet(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetTimetableResponseToJson(
GetTimetableResponse instance) =>
<String, dynamic>{
'result': instance.result.map((e) => e.toJson()).toList(),
};
GetTimetableResponse instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetTimetableResponseObject _$GetTimetableResponseObjectFromJson(
Map<String, dynamic> json) =>

View File

@ -53,6 +53,7 @@ class _ChatViewState extends State<ChatView> {
DateTime elementDate = DateTime.fromMillisecondsSinceEpoch(element.timestamp * 1000);
if(element.systemMessage.contains("reaction")) return;
int commonRead = int.parse(data.getChatResponse.headers?['x-chat-last-common-read'] ?? "0");
if(!elementDate.isSameDay(lastDate)) {
lastDate = elementDate;
@ -70,7 +71,8 @@ class _ChatViewState extends State<ChatView> {
isSender: element.actorId == widget.selfId && element.messageType == GetRoomResponseObjectMessageType.comment,
bubbleData: element,
chatData: widget.room,
refetch: _query
refetch: _query,
isRead: element.id <= commonRead,
)
);
});

View File

@ -27,6 +27,11 @@ class ChatBubble extends StatefulWidget {
final bool isSender;
final GetChatResponseObject bubbleData;
final GetRoomResponseObject chatData;
final bool isRead;
final double spacing = 3;
final double timeIconSize = 11;
final Color timeIconColor = Colors.grey;
final void Function({bool renew}) refetch;
@ -36,6 +41,7 @@ class ChatBubble extends StatefulWidget {
required this.bubbleData,
required this.chatData,
required this.refetch,
this.isRead = false,
super.key});
@override
@ -110,7 +116,7 @@ class _ChatBubbleState extends State<ChatBubble> {
Text timeText = Text(
Jiffy.parseFromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).format(pattern: "HH:mm"),
textAlign: TextAlign.end,
style: const TextStyle(color: Colors.grey, fontSize: 12),
style: TextStyle(color: widget.timeIconColor, fontSize: widget.timeIconSize),
);
return Column(
@ -128,7 +134,7 @@ class _ChatBubbleState extends State<ChatBubble> {
maxWidth: MediaQuery.of(context).size.width * 0.9,
minWidth: showActorDisplayName
? actorText.size.width
: timeText.size.width,
: timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3,
),
child: Stack(
children: [
@ -149,7 +155,18 @@ class _ChatBubbleState extends State<ChatBubble> {
child: Positioned(
bottom: 0,
right: 0,
child: timeText
child: Row(
children: [
timeText,
if(widget.isSender) ...[
SizedBox(width: widget.spacing),
if(widget.isRead)
Icon(Icons.done_all_outlined, size: widget.timeIconSize, color: widget.timeIconColor)
else
Icon(Icons.done_outlined, size: widget.timeIconSize, color: widget.timeIconColor)
]
],
)
),
),
Visibility(