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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:commet/client/timeline_events/timeline_event.dart';
abstract class ReadReceiptComponent<R extends Client, T extends Room>
implements RoomComponent<R, T> {
Stream<String> get onReadReceiptsUpdated;
bool? get usePublicReadReceiptsForRoom;
Future<void> setUsePublicReadReceiptsForRoom(bool? value);

List<String>? getReceipts(TimelineEvent event);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ abstract class TypingIndicatorComponent<R extends Client, T extends Room>
implements RoomComponent<R, T> {
Stream<void> get onTypingUsersUpdated;

bool? get typingIndicatorEnabledForRoom;
Future<void> setTypingIndicatorEnabledForRoom(bool? value);

List<Member> get typingUsers;

Future<void> setTypingStatus(bool status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class UserPresence {
abstract class UserPresenceComponent<T extends Client> implements Component<T> {
Stream<(String, UserPresence)> get onPresenceChanged;

bool get usePublicReadReceipts;
Future<void> setUsePublicReadReceipts(bool value);

bool get typingIndicatorEnabled;
Future<void> setTypingIndicatorEnabled(bool value);

Future<UserPresence> getUserPresence(String userId);

Future<void> setStatus(UserPresenceStatus status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:commet/client/components/read_receipts/read_receipt_component.dart';
import 'package:commet/client/matrix/components/matrix_sync_listener.dart';
import 'package:commet/client/matrix/components/user_presence/matrix_user_presence.dart';
import 'package:commet/client/matrix/matrix_client.dart';
import 'package:commet/client/matrix/matrix_room.dart';
import 'package:commet/client/matrix/matrix_timeline.dart';
Expand All @@ -18,6 +19,9 @@ class MatrixReadReceiptComponent
@override
MatrixRoom room;

static const String publicReadReceiptsKey =
"chat.commet.public_read_receipts";

final StreamController<String> _controller =
StreamController<String>.broadcast();

Expand All @@ -30,6 +34,22 @@ class MatrixReadReceiptComponent

Map<String, String> userToPreviousReceipt = {};

@override
bool? get usePublicReadReceiptsForRoom {
var publicReadReceiptsForRoom = room
.matrixRoom.roomAccountData[publicReadReceiptsKey]?.content["enabled"];
return publicReadReceiptsForRoom is bool ? publicReadReceiptsForRoom : null;
}

@override
Future<void> setUsePublicReadReceiptsForRoom(bool? value) async =>
await client.matrixClient.setAccountDataPerRoom(
client.matrixClient.userID!,
room.matrixRoom.id,
publicReadReceiptsKey,
{"enabled": value},
);

@override
onSync(JoinedRoomUpdate update) {
final ephemeral = update.ephemeral;
Expand Down Expand Up @@ -59,6 +79,10 @@ class MatrixReadReceiptComponent
}
}
}

client.matrixClient.receiptsPublicByDefault = client
.getComponent<MatrixUserPresenceComponent>()!
.usePublicReadReceipts;
}

void handleEvent(String eventId, String userId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:commet/client/components/typing_indicators/typing_indicator_component.dart';
import 'package:commet/client/matrix/components/matrix_sync_listener.dart';
import 'package:commet/client/matrix/components/user_presence/matrix_user_presence.dart';
import 'package:commet/client/matrix/matrix_client.dart';
import 'package:commet/client/matrix/matrix_member.dart';
import 'package:commet/client/matrix/matrix_room.dart';
Expand All @@ -19,8 +20,29 @@ class MatrixTypingIndicatorsComponent

MatrixTypingIndicatorsComponent(this.client, this.room);

static const String publicTypingIndicatorKey =
"chat.commet.private_typing_indicator";

final StreamController<void> _controller = StreamController.broadcast();

@override
bool? get typingIndicatorEnabledForRoom {
var publicTypingIndicatorForRoom = room.matrixRoom
.roomAccountData[publicTypingIndicatorKey]?.content["enabled"];
return publicTypingIndicatorForRoom is bool
? publicTypingIndicatorForRoom
: null;
}

@override
Future<void> setTypingIndicatorEnabledForRoom(bool? value) async =>
await client.matrixClient.setAccountDataPerRoom(
client.matrixClient.userID!,
room.matrixRoom.id,
publicTypingIndicatorKey,
{"enabled": value},
);

@override
onSync(JoinedRoomUpdate update) {
final ephemeral = update.ephemeral;
Expand All @@ -44,7 +66,11 @@ class MatrixTypingIndicatorsComponent
.toList();

@override
Future<void> setTypingStatus(bool status) {
return room.matrixRoom.setTyping(status, timeout: 2000);
Future<void> setTypingStatus(bool status) async {
var typingIndicatorEnabled = client
.getComponent<MatrixUserPresenceComponent>()!
.typingIndicatorEnabled;
if (typingIndicatorEnabledForRoom ?? typingIndicatorEnabled)
return room.matrixRoom.setTyping(status, timeout: 2000);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:async';

import 'package:commet/client/components/user_presence/user_presence_component.dart';
import 'package:commet/client/components/user_presence/user_presence_lifecycle_watcher.dart';
import 'package:commet/client/matrix/components/read_receipts/matrix_read_receipt_component.dart';
import 'package:commet/client/matrix/components/typing_indicators/matrix_typing_indicators_component.dart';
import 'package:commet/client/matrix/matrix_client.dart';
import 'package:commet/utils/in_memory_cache.dart';
import 'package:matrix/matrix.dart';
Expand All @@ -28,6 +30,42 @@ class MatrixUserPresenceComponent
UserPresenceLifecycleWatcher().init();
}

@override
bool get usePublicReadReceipts {
var publicReadReceipts = client
.matrixClient
.accountData[MatrixReadReceiptComponent.publicReadReceiptsKey]
?.content["enabled"];
return publicReadReceipts is bool ? publicReadReceipts : true;
}

@override
Future<void> setUsePublicReadReceipts(bool value) async {
await client.matrixClient.setAccountData(
client.matrixClient.userID!,
MatrixReadReceiptComponent.publicReadReceiptsKey,
{"enabled": value},
);
client.matrixClient.receiptsPublicByDefault = value;
}

@override
bool get typingIndicatorEnabled {
var publicTypingIndicator = client
.matrixClient
.accountData[MatrixTypingIndicatorsComponent.publicTypingIndicatorKey]
?.content["enabled"];
return publicTypingIndicator is bool ? publicTypingIndicator : true;
}

@override
Future<void> setTypingIndicatorEnabled(bool value) async =>
await client.matrixClient.setAccountData(
client.matrixClient.userID!,
MatrixTypingIndicatorsComponent.publicTypingIndicatorKey,
{"enabled": value},
);

@override
Future<UserPresence> getUserPresence(String userId) async {
final presence = await client.matrixClient.fetchCurrentPresence(userId);
Expand Down
16 changes: 15 additions & 1 deletion commet/lib/client/matrix/matrix_room.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import 'package:commet/client/components/room_component.dart';
import 'package:commet/client/components/user_color/user_color_component.dart';
import 'package:commet/client/matrix/components/calendar_room_component/matrix_calendar_room_component.dart';
import 'package:commet/client/matrix/components/emoticon/matrix_room_emoticon_component.dart';
import 'package:commet/client/matrix/components/read_receipts/matrix_read_receipt_component.dart';
import 'package:commet/client/matrix/components/user_presence/matrix_user_presence.dart';
import 'package:commet/client/matrix/matrix_attachment.dart';
import 'package:commet/client/matrix/matrix_client.dart';
import 'package:commet/client/matrix/matrix_member.dart';
Expand Down Expand Up @@ -844,8 +846,20 @@ class MatrixRoom extends Room {
@override
Future<void> markAsRead() async {
var tl = await matrixRoom.getTimeline();
var readReceiptComponent = getComponent<MatrixReadReceiptComponent>();

await tl.setReadMarker();
bool public = true;
var presenceComp = client.getComponent<MatrixUserPresenceComponent>();

if (presenceComp?.usePublicReadReceipts != null) {
public = presenceComp!.usePublicReadReceipts;
}

if (readReceiptComponent?.usePublicReadReceiptsForRoom != null) {
public = readReceiptComponent!.usePublicReadReceiptsForRoom!;
}

await tl.setReadMarker(public: public);
}

@override
Expand Down
5 changes: 3 additions & 2 deletions commet/lib/client/matrix/matrix_timeline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,12 @@ class MatrixTimeline extends Timeline {

@override
void markAsRead(TimelineEvent event) async {
var receipts = room.getComponent<MatrixReadReceiptComponent>();
if (event.status == TimelineEventStatus.synced ||
event.status == TimelineEventStatus.sent) {
await _matrixTimeline?.setReadMarker();
await _matrixTimeline?.setReadMarker(
public: receipts?.usePublicReadReceiptsForRoom);

var receipts = room.getComponent<MatrixReadReceiptComponent>();
receipts?.handleEvent(event.eventId, room.client.self!.identifier);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'package:commet/client/client.dart';
import 'package:commet/client/components/user_presence/user_presence_component.dart';
import 'package:commet/utils/error_utils.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tiamat/tiamat.dart' as tiamat;

class ChatPrivacyPreferences<T extends Client> extends StatefulWidget {
const ChatPrivacyPreferences({required this.client, super.key});
final T client;

@override
State<ChatPrivacyPreferences> createState() => _ChatPrivacyPreferences();
}

class _ChatPrivacyPreferences extends State<ChatPrivacyPreferences> {
bool publicReadReceipts = true;
bool publicTypingIndicator = true;

UserPresenceComponent get userPresenceComponent =>
widget.client.getComponent<UserPresenceComponent>()!;

String get labelChatPrivacyTitle => Intl.message("Chat Privacy",
desc: "Header for the chat privacy section in settings",
name: "labelChatPrivacyTitle");

String get labelPublicReadReceiptsToggle => Intl.message("Read receipts",
desc:
"Label for the toggle for enabling and disabling sending read receipts",
name: "labelPublicReadReceiptsToggle");

String get labelPublicReadReceiptsDescription => Intl.message(
"Let other members of a room know when you have read their messages.",
desc:
"description for the toggle for enabling and disabling sending read receipts",
name: "labelPublicReadReceiptsDescriptionn");

String get labelTypingIndicatorsToggle => Intl.message("Typing indicator",
desc:
"Label for the toggle for enabling and disabling sending typing indicator",
name: "labelTypingIndicatorsToggle");

String get labelPublicTypingIndicatorDescription => Intl.message(
"Let other members of a room know when you are typing a message.",
desc:
"description for the toggle for enabling and disabling sending typing indicator",
name: "labelPublicTypingIndicatorDescription");

@override
void initState() {
setState(() {
publicReadReceipts = userPresenceComponent.usePublicReadReceipts;
publicTypingIndicator = userPresenceComponent.typingIndicatorEnabled;
});
super.initState();
}

@override
Widget build(BuildContext context) {
return tiamat.Panel(
header: labelChatPrivacyTitle,
mode: tiamat.TileType.surfaceContainerLow,
child: Column(children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
tiamat.Text.labelEmphasised(
labelPublicReadReceiptsToggle),
tiamat.Text.labelLow(labelPublicReadReceiptsDescription)
]),
),
tiamat.Switch(
state: publicReadReceipts,
onChanged: (value) async {
setState(() {
publicReadReceipts = value;
});
await ErrorUtils.tryRun(context, () async {
await userPresenceComponent
.setUsePublicReadReceipts(value);
});
setState(() => publicReadReceipts = value);
})
],
),
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
tiamat.Text.labelEmphasised(labelTypingIndicatorsToggle),
tiamat.Text.labelLow(
labelPublicTypingIndicatorDescription)
]),
),
tiamat.Switch(
state: publicTypingIndicator,
onChanged: (value) async {
setState(() {
publicTypingIndicator = value;
});
await ErrorUtils.tryRun(context, () async {
await userPresenceComponent
.setTypingIndicatorEnabled(value);
});
setState(() => publicTypingIndicator = value);
})
],
),
),
]),
);
}
}
Loading