diff --git a/lib/models/user_profile.dart b/lib/models/user_profile.dart index bef2a91..bb1072d 100644 --- a/lib/models/user_profile.dart +++ b/lib/models/user_profile.dart @@ -277,8 +277,11 @@ class ProfileViewerState { }); factory ProfileViewerState.fromJson(Map json) { - final blocked = json['blocked'] as bool? ?? false; - final blockUri = json['blockUri'] as String?; + // The backend serializes the viewer's block as `blocking` — the block + // record URI (see users/user.go ProfileViewerState). Accept the older + // `blocked`/`blockUri` shape too. + final blockUri = (json['blockUri'] ?? json['blocking']) as String?; + final blocked = json['blocked'] as bool? ?? (blockUri != null); return ProfileViewerState._( // If blocked but no blockUri, treat as not blocked (defensive) diff --git a/lib/providers/block_provider.dart b/lib/providers/block_provider.dart index 893fc4b..9541c8d 100644 --- a/lib/providers/block_provider.dart +++ b/lib/providers/block_provider.dart @@ -140,12 +140,22 @@ class BlockProvider with ChangeNotifier { } } - /// Initialize user block state from profile data + /// Initialize user block state from server data (e.g. profile + /// viewer.blocking). + /// + /// Seed-only: never clobbers existing local state — an optimistic + /// toggle already in memory (or in flight) is fresher than a possibly + /// cached server response. void setInitialUserBlockState({ required String userDid, required bool isBlocked, }) { + if (_userBlocks.containsKey(userDid) || + (_pendingUserBlocks[userDid] ?? false)) { + return; + } _userBlocks[userDid] = isBlocked; + notifyListeners(); } /// Initialize community block state from community data diff --git a/lib/screens/home/profile_screen.dart b/lib/screens/home/profile_screen.dart index 47ae8d9..4f5a59d 100644 --- a/lib/screens/home/profile_screen.dart +++ b/lib/screens/home/profile_screen.dart @@ -9,6 +9,7 @@ import '../../utils/responsive_utils.dart'; import '../../models/comment.dart'; import '../../models/user_profile.dart'; import '../../providers/auth_provider.dart'; +import '../../providers/block_provider.dart'; import '../../providers/user_profile_provider.dart'; import '../../widgets/comment_card.dart'; import '../../widgets/loading_error_states.dart'; @@ -85,6 +86,17 @@ class _ProfileScreenState extends State { // Check mounted after async gap (CLAUDE.md requirement) if (!mounted) return; + // Seed block state from the profile's viewer data so block/unblock + // menus reflect the server-side block after an app restart (the + // seed never clobbers fresher in-session optimistic state). + final profile = profileProvider.profile; + if (profile != null && profile.did != authProvider.did) { + context.read().setInitialUserBlockState( + userDid: profile.did, + isBlocked: profile.viewer?.blocked ?? false, + ); + } + // Only load posts if profile loaded successfully (no error) if (profileProvider.profileError == null) { await profileProvider.loadPosts(refresh: true); diff --git a/test/models/user_profile_test.dart b/test/models/user_profile_test.dart index 6af0cba..95b218b 100644 --- a/test/models/user_profile_test.dart +++ b/test/models/user_profile_test.dart @@ -51,4 +51,38 @@ void main() { ); }); }); + + group('ProfileViewerState.fromJson', () { + const uri = 'at://did:plc:viewer/social.coves.actor.block/3abc'; + + test('parses backend "blocking" record-URI shape as blocked', () { + final viewer = ProfileViewerState.fromJson(const {'blocking': uri}); + + expect(viewer.blocked, isTrue); + expect(viewer.blockUri, uri); + }); + + test('parses legacy blocked/blockUri shape', () { + final viewer = ProfileViewerState.fromJson(const { + 'blocked': true, + 'blockUri': uri, + }); + + expect(viewer.blocked, isTrue); + expect(viewer.blockUri, uri); + }); + + test('not blocked when no block keys present', () { + final viewer = ProfileViewerState.fromJson(const {'blockedBy': false}); + + expect(viewer.blocked, isFalse); + expect(viewer.blockUri, isNull); + }); + + test('blocked without any URI is treated as not blocked (defensive)', () { + final viewer = ProfileViewerState.fromJson(const {'blocked': true}); + + expect(viewer.blocked, isFalse); + }); + }); }