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: () {

View File

@ -6,12 +6,16 @@
#include "generated_plugin_registrant.h"
#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin");
emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
emoji_picker_flutter
file_selector_linux
flutter_secure_storage_linux
objectbox_flutter_libs

View File

@ -5,22 +5,26 @@
import FlutterMacOS
import Foundation
import emoji_picker_flutter
import file_selector_macos
import flutter_secure_storage_macos
import objectbox_flutter_libs
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
import sqflite_darwin
import url_launcher_macos
import video_player_avfoundation
import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))

View File

@ -185,6 +185,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.11"
emoji_picker_flutter:
dependency: "direct main"
description:
name: emoji_picker_flutter
sha256: "9a44c102079891ea5877f78c70f2e3c6e9df7b7fe0a01757d31f1046eeaa016d"
url: "https://pub.dev"
source: hosted
version: "4.3.0"
equatable:
dependency: transitive
description:
@ -777,6 +785,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
@ -894,6 +958,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
url_launcher:
dependency: "direct main"
description:

View File

@ -29,6 +29,7 @@ dependencies:
url_launcher: ^6.3.1
chewie: ^1.11.3
image_picker: ^1.1.2
emoji_picker_flutter: ^4.3.0
dependency_overrides:
ndk:

View File

@ -6,12 +6,15 @@
#include "generated_plugin_registrant.h"
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
EmojiPickerFlutterPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
emoji_picker_flutter
file_selector_windows
flutter_secure_storage_windows
objectbox_flutter_libs