feat: chat reactions

closes #2
This commit is contained in:
2025-05-12 14:12:33 +01:00
parent 4b363762dd
commit 829dd7a0d0
9 changed files with 214 additions and 29 deletions

View File

@ -246,40 +246,97 @@ class _ChatMessageWidget extends StatelessWidget {
final profile = state.data ?? Metadata(pubKey: msg.pubKey);
return GestureDetector(
onLongPress: () {
showModalBottomSheet(
context: context,
constraints: BoxConstraints.expand(),
builder: (ctx) => ProfileModalWidget(profile: profile, event: msg),
);
if (ndk.accounts.canSign) {
showModalBottomSheet(
context: context,
constraints: BoxConstraints.expand(),
builder:
(ctx) => ProfileModalWidget(profile: profile, event: msg),
);
}
},
child: RichText(
text: TextSpan(
children: [
WidgetSpan(
child: AvatarWidget(profile: profile, size: 24),
alignment: PlaceholderAlignment.middle,
),
TextSpan(text: " "),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: ProfileNameWidget(
profile: profile,
style: TextStyle(
color:
msg.pubKey == stream.info.host
? PRIMARY_1
: SECONDARY_1,
),
),
),
TextSpan(text: " "),
TextSpan(text: msg.content, style: TextStyle(color: FONT_COLOR)),
],
),
child: Column(
spacing: 2,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_chatText(profile),
RxFilter<Nip01Event>(
filters: [
Filter(kinds: [9735, 7], eTags: [msg.id]),
],
builder: (ctx, data) => _chatReactions(data),
),
],
),
);
});
}
Widget _chatText(Metadata profile) {
return RichText(
text: TextSpan(
children: [
WidgetSpan(
child: AvatarWidget(profile: profile, size: 24),
alignment: PlaceholderAlignment.middle,
),
TextSpan(text: " "),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: ProfileNameWidget(
profile: profile,
style: TextStyle(
color: msg.pubKey == stream.info.host ? PRIMARY_1 : SECONDARY_1,
),
),
),
TextSpan(text: " "),
TextSpan(text: msg.content, style: TextStyle(color: FONT_COLOR)),
],
),
);
}
Widget _chatReactions(List<Nip01Event>? events) {
if ((events?.length ?? 0) == 0) return SizedBox.shrink();
final zaps = events!
.where((e) => e.kind == 9735)
.map((e) => ZapReceipt.fromEvent(e));
final reactions = events.where((e) => e.kind == 7);
return Row(
spacing: 8,
children: [
if (zaps.isNotEmpty)
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(color: LAYER_2, borderRadius: DEFAULT_BR),
child: Text(
formatSats(zaps.fold(0, (acc, v) => acc + (v.amountSats ?? 0))),
),
),
if (reactions.isNotEmpty)
...reactions
.fold(<String, Set<String>>{}, (acc, v) {
acc[v.content] ??= Set();
acc[v.content]!.add(v.pubKey);
return acc;
})
.entries
.map(
(v) => Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: LAYER_2,
borderRadius: DEFAULT_BR,
),
child: Center(child: Text(v.key)),
),
),
],
);
}
}
class _WriteMessageWidget extends StatefulWidget {

View File

@ -1,5 +1,11 @@
import 'dart:developer' as developer;
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter/foundation.dart' as foundation;
import 'package:flutter/material.dart';
import 'package:ndk/ndk.dart';
import 'package:zap_stream_flutter/main.dart';
import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/widgets/button.dart';
import 'package:zap_stream_flutter/widgets/mute_button.dart';
import 'package:zap_stream_flutter/widgets/profile.dart';
@ -23,6 +29,42 @@ class ProfileModalWidget extends StatelessWidget {
spacing: 10,
children: [
ProfileWidget(profile: profile),
EmojiPicker(
onEmojiSelected: (category, emoji) {
developer.log(emoji.emoji);
ndk.broadcast.broadcastReaction(
eventId: event.id,
reaction: emoji.emoji,
);
Navigator.pop(context);
},
config: Config(
height: 256,
checkPlatformCompatibility: true,
emojiViewConfig: EmojiViewConfig(
emojiSizeMax:
28 *
(foundation.defaultTargetPlatform == TargetPlatform.iOS
? 1.20
: 1.0),
backgroundColor: LAYER_1,
),
viewOrderConfig: const ViewOrderConfig(
top: EmojiPickerItem.categoryBar,
middle: EmojiPickerItem.emojiView,
bottom: EmojiPickerItem.searchBar,
),
bottomActionBarConfig: BottomActionBarConfig(
backgroundColor: LAYER_2,
buttonColor: PRIMARY_1,
),
categoryViewConfig: CategoryViewConfig(backgroundColor: LAYER_2),
searchViewConfig: SearchViewConfig(
backgroundColor: LAYER_2,
buttonIconColor: PRIMARY_1,
),
),
),
BasicButton.text(
"Zap",
onTap: () {