[READ ONLY MIRROR] Open Source TikTok alternative built on AT Protocol github.com/sprksocial/client
flutter atproto video dart
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix follow button on following list (#76)

authored by

Davi Rodrigues and committed by
GitHub
fcafe87a e0dd8a34

+61 -44
+46 -41
lib/src/features/profile/providers/user_list_provider.dart
··· 1 1 import 'package:atproto_core/atproto_core.dart'; 2 - import 'package:bluesky/bluesky.dart'; 2 + import 'package:bluesky/bluesky.dart' as bsky; 3 3 import 'package:riverpod_annotation/riverpod_annotation.dart'; 4 4 import 'package:sparksocial/src/core/auth/data/repositories/auth_repository.dart'; 5 5 import 'package:sparksocial/src/core/di/service_locator.dart'; ··· 36 36 final GraphRepository _graphRepository = sl<GraphRepository>(); 37 37 final AuthRepository _authRepository = sl<AuthRepository>(); 38 38 39 + bool isCurrentUser(String did) { 40 + final session = _authRepository.session; 41 + if (session == null) return false; 42 + return session.did == did; 43 + } 44 + 39 45 @override 40 46 Future<PaginatedUserList> build({required String did, required UserListType type}) async { 41 47 List<ProfileView> profiles; ··· 65 71 if (didsToFetch.isNotEmpty) { 66 72 final session = _authRepository.session; 67 73 if (session != null) { 68 - final bsky = Bluesky.fromSession(session); 69 - final fetchedProfiles = <ActorProfile>[]; 74 + final bskyClient = bsky.Bluesky.fromSession(session); 75 + final fetchedProfiles = <bsky.ActorProfile>[]; 70 76 71 77 for (var i = 0; i < didsToFetch.length; i += 25) { 72 78 final batch = didsToFetch.sublist(i, i + 25 > didsToFetch.length ? didsToFetch.length : i + 25); 73 - final profilesResponse = await bsky.actor.getProfiles(actors: batch); 79 + final profilesResponse = await bskyClient.actor.getProfiles(actors: batch); 74 80 fetchedProfiles.addAll(profilesResponse.data.profiles); 75 81 } 76 82 final profilesMap = {for (final p in fetchedProfiles) p.did: p}; ··· 91 97 } 92 98 } 93 99 100 + Future<void> followUser(String userDid) async { 101 + try { 102 + final response = await _graphRepository.followUser(userDid); 103 + final updatedProfiles = state.value!.profiles.map((p) { 104 + if (p.did == userDid) { 105 + return p.copyWith( 106 + viewer: p.viewer?.copyWith(following: AtUri.parse(response.uri)) ?? ActorViewer(following: AtUri.parse(response.uri)), 107 + ); 108 + } 109 + return p; 110 + }).toList(); 111 + state = AsyncValue.data(state.value!.copyWith(profiles: updatedProfiles)); 112 + } catch (e) { 113 + // handle error, maybe revert state 114 + } 115 + } 116 + 117 + Future<void> unfollowUser(String userDid) async { 118 + final profile = state.value!.profiles.firstWhere((p) => p.did == userDid); 119 + final followUri = profile.viewer?.following; 120 + if (followUri == null) return; 121 + 122 + try { 123 + await _graphRepository.unfollowUser(followUri); 124 + final updatedProfiles = state.value!.profiles.map((p) { 125 + if (p.did == userDid) { 126 + return p.copyWith(viewer: p.viewer?.copyWith(following: null)); 127 + } 128 + return p; 129 + }).toList(); 130 + state = AsyncValue.data(state.value!.copyWith(profiles: updatedProfiles)); 131 + } catch (e) { 132 + // handle error, maybe revert state 133 + } 134 + } 135 + 94 136 Future<void> fetchMore() async { 95 137 if (state.value == null || state.value!.cursor == null || state.value!.isFetchingMore) return; 96 138 ··· 123 165 } catch (e) { 124 166 // Revert on error 125 167 state = AsyncValue.data(state.value!.copyWith(isFetchingMore: false)); 126 - } 127 - } 128 - 129 - Future<void> toggleFollow(String did) async { 130 - final currentState = state.valueOrNull; 131 - if (currentState == null) return; 132 - 133 - final userIndex = currentState.profiles.indexWhere((user) => user.did == did); 134 - if (userIndex == -1) return; 135 - 136 - final user = currentState.profiles[userIndex]; 137 - final isCurrentlyFollowing = user.viewer?.following != null; 138 - final currentFollowUri = user.viewer?.following; 139 - 140 - // Optimistic UI update 141 - final updatedUser = user.copyWith( 142 - viewer: user.viewer?.copyWith(following: isCurrentlyFollowing ? null : AtUri.parse('at://temp/uri')), 143 - ); 144 - final newList = List<ProfileView>.from(currentState.profiles); 145 - newList[userIndex] = updatedUser; 146 - state = AsyncValue.data(currentState.copyWith(profiles: newList)); 147 - 148 - try { 149 - final newUriString = await _graphRepository.toggleFollow(did, currentFollowUri); 150 - final newUri = newUriString != null ? AtUri.parse(newUriString) : null; 151 - 152 - // Final state update with correct URI 153 - final finalUser = user.copyWith( 154 - viewer: user.viewer?.copyWith(following: newUri), 155 - ); 156 - final finalList = List<ProfileView>.from(state.value!.profiles); 157 - finalList[userIndex] = finalUser; 158 - state = AsyncValue.data(currentState.copyWith(profiles: finalList)); 159 - } catch (e) { 160 - // Revert on error 161 - state = AsyncValue.data(currentState); 162 - // Optionally, show an error message to the user 163 168 } 164 169 } 165 170 }
+2
lib/src/features/profile/ui/pages/user_list_page.dart
··· 56 56 child: userListAsync.when( 57 57 data: (userList) => UserListView( 58 58 users: userList.profiles, 59 + did: widget.did, 60 + type: widget.type, 59 61 scrollController: _scrollController, 60 62 isFetchingMore: userList.isFetchingMore, 61 63 ),
+13 -3
lib/src/features/profile/ui/widgets/user_list_view.dart
··· 11 11 final List<ProfileView> users; 12 12 final ScrollController? scrollController; 13 13 final bool isFetchingMore; 14 + final String did; 15 + final UserListType type; 14 16 15 - const UserListView({required this.users, this.scrollController, this.isFetchingMore = false, super.key}); 17 + const UserListView({ 18 + required this.users, 19 + required this.did, 20 + required this.type, 21 + this.scrollController, 22 + this.isFetchingMore = false, 23 + super.key, 24 + }); 16 25 17 26 @override 18 27 Widget build(BuildContext context, WidgetRef ref) { ··· 43 52 avatarUrl: user.avatar.toString(), 44 53 description: user.description, 45 54 isFollowing: user.viewer?.following != null, 55 + showFollowButton: !ref.read(userListProvider(did: did, type: type).notifier).isCurrentUser(user.did), 46 56 onTap: () => context.router.push(ProfileRoute(did: user.did)), 47 57 onFollowTap: () { 48 - ref.read(userListProvider(did: user.did, type: UserListType.followers).notifier).toggleFollow(user.did); 58 + ref.read(userListProvider(did: did, type: type).notifier).followUser(user.did); 49 59 }, 50 60 onUnfollowTap: () { 51 - ref.read(userListProvider(did: user.did, type: UserListType.following).notifier).toggleFollow(user.did); 61 + ref.read(userListProvider(did: did, type: type).notifier).unfollowUser(user.did); 52 62 }, 53 63 ), 54 64 );