From c7435d5772b6830b5256493b0f7b55bf079256f2 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 16 May 2025 12:46:53 +0100 Subject: [PATCH] feat: following section on stream grid closes #26 --- lib/pages/profile.dart | 3 +- lib/widgets/button_follow.dart | 95 +++++++++++++++++++++------------- lib/widgets/stream_grid.dart | 39 ++++++++++---- 3 files changed, 89 insertions(+), 48 deletions(-) diff --git a/lib/pages/profile.dart b/lib/pages/profile.dart index bed10a9..58a5c08 100644 --- a/lib/pages/profile.dart +++ b/lib/pages/profile.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:ndk/ndk.dart'; @@ -9,6 +8,7 @@ import 'package:zap_stream_flutter/theme.dart'; import 'package:zap_stream_flutter/utils.dart'; import 'package:zap_stream_flutter/widgets/avatar.dart'; import 'package:zap_stream_flutter/widgets/button.dart'; +import 'package:zap_stream_flutter/widgets/button_follow.dart'; import 'package:zap_stream_flutter/widgets/header.dart'; import 'package:zap_stream_flutter/widgets/nostr_text.dart'; import 'package:zap_stream_flutter/widgets/profile.dart'; @@ -89,6 +89,7 @@ class ProfilePage extends StatelessWidget { ), ], ), + if (!isMe) FollowButton(pubkey: hexPubkey), Text( "Past Streams", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), diff --git a/lib/widgets/button_follow.dart b/lib/widgets/button_follow.dart index 12090b0..6df510d 100644 --- a/lib/widgets/button_follow.dart +++ b/lib/widgets/button_follow.dart @@ -8,8 +8,9 @@ class FollowButton extends StatelessWidget { final void Function()? onTap; final void Function()? onFollow; final void Function()? onUnfollow; + final ValueNotifier _loading = ValueNotifier(false); - const FollowButton({ + FollowButton({ super.key, required this.pubkey, this.onTap, @@ -24,43 +25,63 @@ class FollowButton extends StatelessWidget { 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), + return ValueListenableBuilder( + valueListenable: _loading, + builder: (context, loading, _) { + 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: 8, + children: [ + loading + ? SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator(), + ) + : 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!(); - } - } + disabled: loading, + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 12), + decoration: BoxDecoration( + borderRadius: DEFAULT_BR, + color: LAYER_2, + ), + onTap: () async { + _loading.value = true; + try { + 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!(); + } + } + } finally { + _loading.value = false; + } + }, + ); }, ); }, diff --git a/lib/widgets/stream_grid.dart b/lib/widgets/stream_grid.dart index bbd9540..e0e1bf7 100644 --- a/lib/widgets/stream_grid.dart +++ b/lib/widgets/stream_grid.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/widgets.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/utils.dart'; import 'package:zap_stream_flutter/widgets/stream_tile.dart'; @@ -35,16 +36,34 @@ class StreamGrid extends StatelessWidget { final live = streams.where((s) => s.info.status == StreamStatus.live); final ended = streams.where((s) => s.info.status == StreamStatus.ended); final planned = streams.where((s) => s.info.status == StreamStatus.planned); - return Column( - spacing: 16, - children: [ - if (showLive && live.isNotEmpty) - _streamGroup(context, "Live", live.toList()), - if (showPlanned && planned.isNotEmpty) - _streamGroup(context, "Planned", planned.toList()), - if (showEnded && ended.isNotEmpty) - _streamGroup(context, "Ended", ended.toList()), - ], + + final followList = + ndk.accounts.isLoggedIn + ? ndk.follows.getContactList(ndk.accounts.getPublicKey()!) + : Future.value(null); + return FutureBuilder( + future: followList, + builder: (context, state) { + final follows = state.data?.contacts ?? []; + final followsLive = live.where((e) => follows.contains(e.info.host)); + final liveNotFollowing = live.where( + (e) => !follows.contains(e.info.host), + ); + + return Column( + spacing: 16, + children: [ + if (followsLive.isNotEmpty) + _streamGroup(context, "Following", followsLive.toList()), + if (showLive && liveNotFollowing.isNotEmpty) + _streamGroup(context, "Live", liveNotFollowing.toList()), + if (showPlanned && planned.isNotEmpty) + _streamGroup(context, "Planned", planned.toList()), + if (showEnded && ended.isNotEmpty) + _streamGroup(context, "Ended", ended.toList()), + ], + ); + }, ); }