|
| 1 | +import 'dart:math' as math; |
| 2 | + |
1 | 3 | import 'package:collection/collection.dart'; |
2 | 4 | import 'package:flutter/material.dart'; |
3 | 5 | import 'package:flutter_state_notifier/flutter_state_notifier.dart'; |
@@ -66,109 +68,172 @@ class _UserProfileState extends State<UserProfile> { |
66 | 68 | Widget build(BuildContext context) { |
67 | 69 | return StateNotifierBuilder( |
68 | 70 | stateNotifier: widget.timelineFeed.notifier, |
69 | | - builder: (context, state, child) { |
70 | | - final feedMembers = state.members; |
71 | | - final followRequests = state.followRequests; |
72 | | - final following = state.following; |
73 | | - final currentUser = client.user; |
74 | | - |
75 | | - final followIncludesCurrentUser = |
76 | | - following.any((it) => it.targetFeed.id == currentUser.id) || |
77 | | - (followSuggestions?.any((it) => it.fid.id == currentUser.id) ?? |
78 | | - false); |
79 | | - |
80 | | - return SingleChildScrollView( |
81 | | - controller: widget.scrollController, |
82 | | - padding: const EdgeInsets.all(20), |
83 | | - child: Column( |
84 | | - crossAxisAlignment: CrossAxisAlignment.start, |
85 | | - spacing: 24, |
86 | | - children: [ |
87 | | - // Profile Header Section |
88 | | - ProfileHeader( |
89 | | - user: currentUser, |
90 | | - membersCount: state.feed?.memberCount ?? 0, |
91 | | - followingCount: state.feed?.followingCount ?? 0, |
92 | | - followersCount: state.feed?.followerCount ?? 0, |
| 71 | + builder: (context, timelineState, child) { |
| 72 | + return StateNotifierBuilder( |
| 73 | + stateNotifier: widget.userFeed.notifier, |
| 74 | + builder: (context, userState, child) { |
| 75 | + return _UserProfileContent( |
| 76 | + client: client, |
| 77 | + timelineState: timelineState, |
| 78 | + userState: userState, |
| 79 | + followSuggestions: followSuggestions, |
| 80 | + scrollController: widget.scrollController, |
| 81 | + onAcceptFollow: (fid) => widget.userFeed.acceptFollow( |
| 82 | + sourceFid: fid, |
93 | 83 | ), |
94 | | - |
95 | | - // Members Section |
96 | | - ProfileSection<FeedMemberData>( |
97 | | - title: 'Members', |
98 | | - items: feedMembers, |
99 | | - emptyMessage: 'No members yet', |
100 | | - itemBuilder: (member) => MemberListItem(member: member), |
| 84 | + onRejectFollow: (fid) => widget.userFeed.rejectFollow( |
| 85 | + sourceFid: fid, |
101 | 86 | ), |
102 | | - |
103 | | - // Follow Requests Section |
104 | | - ProfileSection<FollowData>( |
105 | | - title: 'Follow Requests', |
106 | | - items: followRequests, |
107 | | - emptyMessage: 'No pending requests', |
108 | | - itemBuilder: (followRequest) => FollowRequestListItem( |
109 | | - followRequest: followRequest, |
110 | | - onAcceptPressed: () => widget.timelineFeed.acceptFollow( |
111 | | - sourceFid: followRequest.sourceFeed.fid, |
112 | | - ), |
113 | | - onRejectPressed: () => widget.timelineFeed.rejectFollow( |
114 | | - sourceFid: followRequest.sourceFeed.fid, |
| 87 | + onFollow: (targetFeed) async { |
| 88 | + final result = await widget.timelineFeed.follow( |
| 89 | + targetFid: targetFeed.fid, |
| 90 | + createNotificationActivity: true, |
| 91 | + ); |
| 92 | + |
| 93 | + // Remove the followed user from suggestions |
| 94 | + result.onSuccess( |
| 95 | + (_) => _updateFollowSuggestions([ |
| 96 | + ...?followSuggestions?.where((it) => it != targetFeed), |
| 97 | + ]), |
| 98 | + ); |
| 99 | + }, |
| 100 | + onUnfollow: (targetFeed) async { |
| 101 | + final result = await widget.timelineFeed.unfollow( |
| 102 | + targetFid: targetFeed.fid, |
| 103 | + ); |
| 104 | + |
| 105 | + // Add the unfollowed user back to suggestions |
| 106 | + result.onSuccess( |
| 107 | + (_) => _updateFollowSuggestions( |
| 108 | + [...?followSuggestions, targetFeed], |
115 | 109 | ), |
116 | | - ), |
117 | | - ), |
| 110 | + ); |
| 111 | + }, |
| 112 | + ); |
| 113 | + }, |
| 114 | + ); |
| 115 | + }, |
| 116 | + ); |
| 117 | + } |
| 118 | +} |
118 | 119 |
|
119 | | - // Following Section |
120 | | - ProfileSection<FollowData>( |
121 | | - title: 'Following', |
122 | | - items: following, |
123 | | - emptyMessage: 'Not following anyone yet', |
124 | | - itemBuilder: (follow) => FollowingListItem( |
125 | | - follow: follow, |
126 | | - onUnfollowPressed: () async { |
127 | | - final result = await widget.timelineFeed.unfollow( |
128 | | - targetFid: follow.targetFeed.fid, |
129 | | - ); |
130 | | - |
131 | | - // Add the unfollowed user back to suggestions |
132 | | - result.onSuccess( |
133 | | - (_) => _updateFollowSuggestions( |
134 | | - [...?followSuggestions, follow.targetFeed], |
135 | | - ), |
136 | | - ); |
137 | | - }, |
138 | | - ), |
139 | | - ), |
| 120 | +class _UserProfileContent extends StatelessWidget { |
| 121 | + const _UserProfileContent({ |
| 122 | + required this.client, |
| 123 | + required this.timelineState, |
| 124 | + required this.userState, |
| 125 | + required this.followSuggestions, |
| 126 | + required this.scrollController, |
| 127 | + required this.onAcceptFollow, |
| 128 | + required this.onRejectFollow, |
| 129 | + required this.onFollow, |
| 130 | + required this.onUnfollow, |
| 131 | + }); |
140 | 132 |
|
141 | | - // Follow Suggestions Section |
142 | | - ProfileSection<FeedData>( |
143 | | - title: 'Suggested', |
144 | | - items: [ |
145 | | - if (!followIncludesCurrentUser && |
146 | | - widget.userFeed.state.feed != null) |
147 | | - widget.userFeed.state.feed!, |
148 | | - ...(followSuggestions ?? []), |
149 | | - ], |
150 | | - emptyMessage: 'No suggestions available', |
151 | | - itemBuilder: (suggestion) => SuggestionListItem( |
152 | | - suggestion: suggestion, |
153 | | - onFollowPressed: () async { |
154 | | - final result = await widget.timelineFeed.follow( |
155 | | - targetFid: suggestion.fid, |
156 | | - createNotificationActivity: true, |
157 | | - ); |
158 | | - |
159 | | - // Remove the followed user from suggestions |
160 | | - result.onSuccess( |
161 | | - (_) => _updateFollowSuggestions([ |
162 | | - ...?followSuggestions?.where((it) => it != suggestion), |
163 | | - ]), |
164 | | - ); |
165 | | - }, |
166 | | - ), |
167 | | - ), |
| 133 | + final StreamFeedsClient client; |
| 134 | + final FeedState timelineState; |
| 135 | + final FeedState userState; |
| 136 | + final List<FeedData>? followSuggestions; |
| 137 | + final ScrollController? scrollController; |
| 138 | + |
| 139 | + final ValueSetter<FeedId> onAcceptFollow; |
| 140 | + final ValueSetter<FeedId> onRejectFollow; |
| 141 | + final ValueSetter<FeedData> onFollow; |
| 142 | + final ValueSetter<FeedData> onUnfollow; |
| 143 | + |
| 144 | + @override |
| 145 | + Widget build(BuildContext context) { |
| 146 | + final currentUser = client.user; |
| 147 | + final feedMembers = timelineState.members; |
| 148 | + |
| 149 | + // We always follow ourselves, so we don't need to show it in the following list |
| 150 | + final following = timelineState.following |
| 151 | + .where((it) => it.targetFeed.id != currentUser.id) |
| 152 | + .toList(); |
| 153 | + final followingCount = math.max( |
| 154 | + 0, |
| 155 | + (timelineState.feed?.followingCount ?? 0) - 1, |
| 156 | + ); |
| 157 | + final followersCount = math.max( |
| 158 | + 0, |
| 159 | + (userState.feed?.followerCount ?? 0) - 1, |
| 160 | + ); |
| 161 | + |
| 162 | + final followRequests = userState.followRequests; |
| 163 | + |
| 164 | + final followIncludesCurrentUser = following |
| 165 | + .any((it) => it.targetFeed.id == currentUser.id) || |
| 166 | + (followSuggestions?.any((it) => it.fid.id == currentUser.id) ?? false); |
| 167 | + |
| 168 | + return SingleChildScrollView( |
| 169 | + controller: scrollController, |
| 170 | + padding: const EdgeInsets.all(20), |
| 171 | + child: Column( |
| 172 | + crossAxisAlignment: CrossAxisAlignment.start, |
| 173 | + spacing: 24, |
| 174 | + children: [ |
| 175 | + // Profile Header Section |
| 176 | + ProfileHeader( |
| 177 | + user: currentUser, |
| 178 | + membersCount: timelineState.feed?.memberCount ?? 0, |
| 179 | + followingCount: followingCount, |
| 180 | + followersCount: followersCount, |
| 181 | + ), |
| 182 | + |
| 183 | + // Members Section |
| 184 | + ProfileSection<FeedMemberData>( |
| 185 | + title: 'Members', |
| 186 | + items: feedMembers, |
| 187 | + emptyMessage: 'No members yet', |
| 188 | + itemBuilder: (member) => MemberListItem(member: member), |
| 189 | + ), |
| 190 | + |
| 191 | + // Follow Requests Section |
| 192 | + ProfileSection<FollowData>( |
| 193 | + title: 'Follow Requests', |
| 194 | + items: followRequests, |
| 195 | + emptyMessage: 'No pending requests', |
| 196 | + itemBuilder: (followRequest) => FollowRequestListItem( |
| 197 | + followRequest: followRequest, |
| 198 | + onAcceptPressed: () => |
| 199 | + onAcceptFollow(followRequest.sourceFeed.fid), |
| 200 | + onRejectPressed: () => |
| 201 | + onRejectFollow(followRequest.sourceFeed.fid), |
| 202 | + ), |
| 203 | + ), |
| 204 | + |
| 205 | + // Following Section |
| 206 | + ProfileSection<FollowData>( |
| 207 | + title: 'Following', |
| 208 | + count: followingCount, |
| 209 | + items: following, |
| 210 | + emptyMessage: 'Not following anyone yet', |
| 211 | + itemBuilder: (follow) => FollowingListItem( |
| 212 | + follow: follow, |
| 213 | + onUnfollowPressed: () { |
| 214 | + onUnfollow(follow.targetFeed); |
| 215 | + }, |
| 216 | + ), |
| 217 | + ), |
| 218 | + |
| 219 | + // Follow Suggestions Section |
| 220 | + ProfileSection<FeedData>( |
| 221 | + title: 'Suggested', |
| 222 | + items: [ |
| 223 | + if (!followIncludesCurrentUser && userState.feed != null) |
| 224 | + userState.feed!, |
| 225 | + ...(followSuggestions ?? []), |
168 | 226 | ], |
| 227 | + emptyMessage: 'No suggestions available', |
| 228 | + itemBuilder: (suggestion) => SuggestionListItem( |
| 229 | + suggestion: suggestion, |
| 230 | + onFollowPressed: () { |
| 231 | + onFollow(suggestion); |
| 232 | + }, |
| 233 | + ), |
169 | 234 | ), |
170 | | - ); |
171 | | - }, |
| 235 | + ], |
| 236 | + ), |
172 | 237 | ); |
173 | 238 | } |
174 | 239 | } |
0 commit comments