feat: follow button

closes #22
This commit is contained in:
2025-05-14 14:04:44 +01:00
parent 465c6f222e
commit e0e9175536
5 changed files with 111 additions and 17 deletions

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:zap_stream_flutter/main.dart';
import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/widgets/button.dart';
class FollowButton extends StatelessWidget {
final String pubkey;
final void Function()? onTap;
final void Function()? onFollow;
final void Function()? onUnfollow;
const FollowButton({
super.key,
required this.pubkey,
this.onTap,
this.onFollow,
this.onUnfollow,
});
@override
Widget build(BuildContext context) {
final signer = ndk.accounts.getLoggedAccount()?.signer;
if (signer == null || signer.getPublicKey() == pubkey) {
return SizedBox.shrink();
}
return FutureBuilder(
future: ndk.follows.getContactList(signer.getPublicKey()),
builder: (context, state) {
final follows = state.data?.contacts ?? [];
final isFollowing = follows.contains(pubkey);
return BasicButton(
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Icon(isFollowing ? Icons.person_remove : Icons.person_add, size: 16),
Text(
isFollowing ? "Unfollow" : "Follow",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 12),
decoration: BoxDecoration(
borderRadius: DEFAULT_BR,
color: LAYER_2,
),
onTap: () async {
if (onTap != null) {
onTap!();
}
if (isFollowing) {
await ndk.follows.broadcastRemoveContact(pubkey);
if (onUnfollow != null) {
onUnfollow!();
}
} else {
await ndk.follows.broadcastAddContact(pubkey);
if (onFollow != null) {
onFollow!();
}
}
},
);
},
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ndk/ndk.dart'; import 'package:ndk/ndk.dart';
import 'package:zap_stream_flutter/theme.dart'; import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/widgets/button_follow.dart';
import 'package:zap_stream_flutter/widgets/mute_button.dart'; import 'package:zap_stream_flutter/widgets/mute_button.dart';
import 'package:zap_stream_flutter/widgets/nostr_text.dart'; import 'package:zap_stream_flutter/widgets/nostr_text.dart';
import 'package:zap_stream_flutter/widgets/profile.dart'; import 'package:zap_stream_flutter/widgets/profile.dart';
@ -75,6 +76,12 @@ class _ChatModalWidget extends State<ChatModalWidget> {
], ],
), ),
if (_showEmojiPicker) ReactionWidget(event: widget.event), if (_showEmojiPicker) ReactionWidget(event: widget.event),
FollowButton(
pubkey: widget.event.pubKey,
onTap: () {
Navigator.pop(context);
},
),
MuteButton( MuteButton(
pubkey: widget.event.pubKey, pubkey: widget.event.pubKey,
onTap: () { onTap: () {

View File

@ -39,7 +39,10 @@ class MuteButton extends StatelessWidget {
), ),
), ),
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 12), padding: EdgeInsets.symmetric(vertical: 4, horizontal: 12),
decoration: BoxDecoration(color: WARNING, borderRadius: DEFAULT_BR), decoration: BoxDecoration(
color: isMuted ? LAYER_2 : WARNING,
borderRadius: DEFAULT_BR,
),
onTap: () async { onTap: () async {
if (onTap != null) { if (onTap != null) {
onTap!(); onTap!();

View File

@ -60,26 +60,34 @@ class StreamCardsWidget extends StatelessWidget {
decoration: BoxDecoration(color: LAYER_2, borderRadius: DEFAULT_BR), decoration: BoxDecoration(color: LAYER_2, borderRadius: DEFAULT_BR),
child: Column( child: Column(
spacing: 8, spacing: 8,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (title?.isNotEmpty ?? false) if (title?.isNotEmpty ?? false)
Text( Center(
title!, child: Text(
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), title!,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
), ),
if (image?.isNotEmpty ?? false) if (image?.isNotEmpty ?? false)
link != null Center(
? GestureDetector( child:
onTap: () { link != null
launchUrl(Uri.parse(link)); ? GestureDetector(
}, onTap: () {
child: CachedNetworkImage( launchUrl(Uri.parse(link));
imageUrl: proxyImg(context, image!), },
errorWidget: child: CachedNetworkImage(
(_, _, _) => imageUrl: proxyImg(context, image!),
SvgPicture.asset("assets/svg/logo.svg", height: 40), errorWidget:
), (_, _, _) => SvgPicture.asset(
) "assets/svg/logo.svg",
: CachedNetworkImage(imageUrl: proxyImg(context, image!)), height: 40,
),
),
)
: CachedNetworkImage(imageUrl: proxyImg(context, image!)),
),
MarkdownBody( MarkdownBody(
data: card.content, data: card.content,
onTapLink: (text, href, title) { onTapLink: (text, href, title) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:zap_stream_flutter/theme.dart'; import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/utils.dart'; import 'package:zap_stream_flutter/utils.dart';
import 'package:zap_stream_flutter/widgets/button_follow.dart';
import 'package:zap_stream_flutter/widgets/profile.dart'; import 'package:zap_stream_flutter/widgets/profile.dart';
import 'package:zap_stream_flutter/widgets/stream_cards.dart'; import 'package:zap_stream_flutter/widgets/stream_cards.dart';
@ -23,6 +24,12 @@ class StreamInfoWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ProfileWidget.pubkey(stream.info.host), ProfileWidget.pubkey(stream.info.host),
FollowButton(
pubkey: stream.info.host,
onTap: () {
Navigator.pop(context);
},
),
Text( Text(
stream.info.title ?? "", stream.info.title ?? "",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),