mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
mobile: share and test the page typing selector
Channel and thread pages now watch one visibleTypingProvider, and a test checks that the user's own echoed indicator does not notify it while another user's typing does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
@@ -366,12 +366,10 @@ class _ResolvedChannelDetailPage extends HookConsumerWidget {
|
|||||||
// Ordinary channels keep thread activity inside the thread view.
|
// Ordinary channels keep thread activity inside the thread view.
|
||||||
final typingEntries = ref
|
final typingEntries = ref
|
||||||
.watch(
|
.watch(
|
||||||
channelTypingProvider(channel.id).select(
|
visibleTypingProvider(
|
||||||
(all) => VisibleTyping(
|
channel.id,
|
||||||
all,
|
selfPubkey: currentPubkey,
|
||||||
selfPubkey: currentPubkey,
|
include: (e) => personalChat || e.threadHeadId == null,
|
||||||
include: (e) => personalChat || e.threadHeadId == null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.entries;
|
.entries;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:hooks_riverpod/misc.dart' show ProviderListenable;
|
||||||
|
|
||||||
import '../../shared/relay/relay.dart';
|
import '../../shared/relay/relay.dart';
|
||||||
|
|
||||||
@@ -56,6 +57,17 @@ class VisibleTyping {
|
|||||||
int get hashCode => Object.hashAll(entries);
|
int get hashCode => Object.hashAll(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a page watches to show typing in [channelId]: everyone but
|
||||||
|
/// [selfPubkey], filtered by [include]. The user's own echoed indicator
|
||||||
|
/// refreshes its expiry every few seconds, and this skips those updates.
|
||||||
|
ProviderListenable<VisibleTyping> visibleTypingProvider(
|
||||||
|
String channelId, {
|
||||||
|
required String? selfPubkey,
|
||||||
|
required bool Function(TypingEntry entry) include,
|
||||||
|
}) => channelTypingProvider(
|
||||||
|
channelId,
|
||||||
|
).select((all) => VisibleTyping(all, selfPubkey: selfPubkey, include: include));
|
||||||
|
|
||||||
/// Tracks who is currently typing in a specific channel.
|
/// Tracks who is currently typing in a specific channel.
|
||||||
///
|
///
|
||||||
/// Subscribes to typing and message events so a reply ends its typing signal.
|
/// Subscribes to typing and message events so a reply ends its typing signal.
|
||||||
|
|||||||
@@ -786,12 +786,10 @@ class ThreadDetailPage extends HookConsumerWidget {
|
|||||||
// Thread-scoped typing indicators (exclude self).
|
// Thread-scoped typing indicators (exclude self).
|
||||||
final threadTyping = ref
|
final threadTyping = ref
|
||||||
.watch(
|
.watch(
|
||||||
channelTypingProvider(channelId).select(
|
visibleTypingProvider(
|
||||||
(all) => VisibleTyping(
|
channelId,
|
||||||
all,
|
selfPubkey: currentPubkey,
|
||||||
selfPubkey: currentPubkey,
|
include: (e) => e.threadHeadId == threadHead.id,
|
||||||
include: (e) => e.threadHeadId == threadHead.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.entries;
|
.entries;
|
||||||
|
|||||||
@@ -247,4 +247,47 @@ void main() {
|
|||||||
container.dispose();
|
container.dispose();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
test(
|
||||||
|
'pages ignore the user\'s own typing echo but see others',
|
||||||
|
() => fakeAsync((async) {
|
||||||
|
final relay = _Relay();
|
||||||
|
final container = ProviderContainer(
|
||||||
|
overrides: [relaySessionProvider.overrideWith(() => relay)],
|
||||||
|
);
|
||||||
|
final seen = <List<String>>[];
|
||||||
|
container.listen(
|
||||||
|
visibleTypingProvider(
|
||||||
|
'chat',
|
||||||
|
selfPubkey: 'ME',
|
||||||
|
include: (e) => e.threadHeadId == null,
|
||||||
|
),
|
||||||
|
(_, next) => seen.add([for (final e in next.entries) e.pubkey]),
|
||||||
|
);
|
||||||
|
_settle(container, async);
|
||||||
|
void typing(String id, String pubkey) => relay.receive(
|
||||||
|
NostrEvent(
|
||||||
|
id: id,
|
||||||
|
pubkey: pubkey,
|
||||||
|
kind: 20002,
|
||||||
|
createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
content: '',
|
||||||
|
sig: '',
|
||||||
|
tags: [
|
||||||
|
['h', 'chat'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
typing('self-$i', 'me');
|
||||||
|
async.elapse(const Duration(seconds: 3));
|
||||||
|
}
|
||||||
|
expect(container.read(channelTypingProvider('chat')), hasLength(1));
|
||||||
|
expect(seen, isEmpty);
|
||||||
|
typing('other', 'agent');
|
||||||
|
expect(seen, [
|
||||||
|
['agent'],
|
||||||
|
]);
|
||||||
|
container.dispose();
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user