From 14fa3f2c80d6e3d7e35f68a3eedd08e679ac65fe Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Sun, 22 Mar 2026 02:30:35 -0500 Subject: [PATCH] feat: post interactions sheet --- .../feed/data/post_action_repository.dart | 12 + .../feed/presentation/post_thread_screen.dart | 60 +++- .../widgets/post_interactions_sheet.dart | 267 ++++++++++++++++++ .../profile/presentation/profile_screen.dart | 2 + .../data/post_action_repository_test.dart | 50 ++++ .../post_interactions_sheet_test.dart | 161 +++++++++++ .../create_edit_starter_pack_screen_test.dart | 23 -- .../starter_pack_detail_screen_test.dart | 3 +- 8 files changed, 541 insertions(+), 37 deletions(-) create mode 100644 lib/features/feed/presentation/widgets/post_interactions_sheet.dart create mode 100644 test/features/feed/presentation/post_interactions_sheet_test.dart diff --git a/lib/features/feed/data/post_action_repository.dart b/lib/features/feed/data/post_action_repository.dart index 108e0ac..4fdd654 100644 --- a/lib/features/feed/data/post_action_repository.dart +++ b/lib/features/feed/data/post_action_repository.dart @@ -1,6 +1,8 @@ import 'package:atproto/com_atproto_repo_strongref.dart'; import 'package:atproto_core/atproto_core.dart'; import 'package:bluesky/app_bsky_bookmark_getbookmarks.dart'; +import 'package:bluesky/app_bsky_feed_getlikes.dart'; +import 'package:bluesky/app_bsky_feed_getrepostedby.dart'; import 'package:bluesky/bluesky.dart'; class PostActionRepository { @@ -54,6 +56,16 @@ class PostActionRepository { return response.data; } + Future getLikes({required AtUri uri, String? cursor}) async { + final response = await _bluesky.feed.getLikes(uri: uri, limit: 25, cursor: cursor); + return response.data; + } + + Future getRepostedBy({required AtUri uri, String? cursor}) async { + final response = await _bluesky.feed.getRepostedBy(uri: uri, limit: 25, cursor: cursor); + return response.data; + } + String _extractRkey(String uri) { final atUri = AtUri.parse(uri); return atUri.rkey; diff --git a/lib/features/feed/presentation/post_thread_screen.dart b/lib/features/feed/presentation/post_thread_screen.dart index a911650..e3bc780 100644 --- a/lib/features/feed/presentation/post_thread_screen.dart +++ b/lib/features/feed/presentation/post_thread_screen.dart @@ -18,6 +18,7 @@ import 'package:lazurite/features/feed/data/post_thread_repository.dart'; import 'package:lazurite/features/feed/presentation/widgets/post_action_bar.dart'; import 'package:lazurite/features/feed/presentation/widgets/post_card.dart'; import 'package:lazurite/features/feed/presentation/widgets/post_card_with_actions.dart'; +import 'package:lazurite/features/feed/presentation/widgets/post_interactions_sheet.dart'; import 'package:lazurite/features/moderation/presentation/moderation_ui_helpers.dart'; import 'package:lazurite/features/moderation/presentation/widgets/moderated_avatar.dart'; import 'package:lazurite/features/profile/cubit/profile_action_cubit.dart'; @@ -339,6 +340,7 @@ class _ExpandedThreadReply extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 8), PostCardWithActions( feedViewPost: FeedViewPost(post: thread.post), accountDid: accountDid, @@ -652,26 +654,34 @@ class _FocusedPostContent extends StatelessWidget { final record = _parsePostRecord(post.record); final timestamp = record?.createdAt ?? post.indexedAt; + final hasStats = (post.replyCount ?? 0) > 0 || (post.repostCount ?? 0) > 0 || (post.likeCount ?? 0) > 0; + return PostCard( feedViewPost: FeedViewPost(post: post), moderationContext: bsky_moderation.ModerationBehaviorContext.contentView, actionBar: Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 4), + const SizedBox(height: 10), Text( _formatTimestamp(timestamp), style: Theme.of( context, ).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), ), - const Divider(), - _buildStats(context, post), + const SizedBox(height: 10), const Divider(height: 1), - const SizedBox(height: 4), + if (hasStats) ...[ + const SizedBox(height: 10), + _buildStats(context, post), + const SizedBox(height: 10), + const Divider(height: 1), + ], + const SizedBox(height: 6), _buildActionBar(context, post), + const SizedBox(height: 6), ], ), ), @@ -685,22 +695,44 @@ class _FocusedPostContent extends StatelessWidget { items.addAll([_buildStat(context, post.replyCount!, 'replies'), const SizedBox(width: 20)]); } if ((post.repostCount ?? 0) > 0) { - items.addAll([_buildStat(context, post.repostCount!, 'reposts'), const SizedBox(width: 20)]); + items.addAll([ + _buildStat( + context, + post.repostCount!, + 'reposts', + onTap: () => _showInteractions(context, post, showLikes: false), + ), + const SizedBox(width: 20), + ]); } if ((post.likeCount ?? 0) > 0) { - items.add(_buildStat(context, post.likeCount!, 'likes')); + items.add( + _buildStat(context, post.likeCount!, 'likes', onTap: () => _showInteractions(context, post, showLikes: true)), + ); } if (items.isEmpty) return const SizedBox.shrink(); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row(children: items), + return Row(children: items); + } + + void _showInteractions(BuildContext context, PostView post, {required bool showLikes}) { + final repository = context.read(); + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (_) => PostInteractionsSheet( + postUri: post.uri, + likeCount: post.likeCount ?? 0, + repostCount: post.repostCount ?? 0, + initialTab: showLikes ? InteractionTab.likes : InteractionTab.reposts, + repository: repository, + ), ); } - Widget _buildStat(BuildContext context, int count, String label) { - return RichText( + Widget _buildStat(BuildContext context, int count, String label, {VoidCallback? onTap}) { + final text = RichText( text: TextSpan( children: [ TextSpan( @@ -716,6 +748,10 @@ class _FocusedPostContent extends StatelessWidget { ], ), ); + + if (onTap == null) return text; + + return GestureDetector(onTap: onTap, child: text); } Widget _buildActionBar(BuildContext context, PostView post) { diff --git a/lib/features/feed/presentation/widgets/post_interactions_sheet.dart b/lib/features/feed/presentation/widgets/post_interactions_sheet.dart new file mode 100644 index 0000000..f1da9d6 --- /dev/null +++ b/lib/features/feed/presentation/widgets/post_interactions_sheet.dart @@ -0,0 +1,267 @@ +import 'package:atproto_core/atproto_core.dart'; +import 'package:bluesky/app_bsky_actor_defs.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:lazurite/features/feed/data/post_action_repository.dart'; + +enum InteractionTab { likes, reposts } + +class PostInteractionsSheet extends StatefulWidget { + const PostInteractionsSheet({ + super.key, + required this.postUri, + required this.likeCount, + required this.repostCount, + required this.repository, + this.initialTab, + }); + + final AtUri postUri; + final int likeCount; + final int repostCount; + final PostActionRepository repository; + final InteractionTab? initialTab; + + @override + State createState() => _PostInteractionsSheetState(); +} + +class _PostInteractionsSheetState extends State { + late InteractionTab _selectedTab; + + final List _likers = []; + bool _loadingLikes = false; + String? _likesCursor; + bool _likesLoaded = false; + + final List _reposters = []; + bool _loadingReposts = false; + String? _repostsCursor; + bool _repostsLoaded = false; + + @override + void initState() { + super.initState(); + final initial = widget.initialTab ?? (widget.likeCount > 0 ? InteractionTab.likes : InteractionTab.reposts); + _selectedTab = initial; + if (initial == InteractionTab.likes) { + _loadLikes(); + } else { + _loadReposts(); + } + } + + Future _loadLikes() async { + if (_loadingLikes) return; + setState(() => _loadingLikes = true); + try { + final output = await widget.repository.getLikes(uri: widget.postUri, cursor: _likesCursor); + if (mounted) { + setState(() { + _likers.addAll(output.likes.map((l) => l.actor)); + _likesCursor = output.cursor; + _likesLoaded = true; + }); + } + } catch (_) { + if (mounted) setState(() => _likesLoaded = true); + } finally { + if (mounted) setState(() => _loadingLikes = false); + } + } + + Future _loadReposts() async { + if (_loadingReposts) return; + setState(() => _loadingReposts = true); + try { + final output = await widget.repository.getRepostedBy(uri: widget.postUri, cursor: _repostsCursor); + if (mounted) { + setState(() { + _reposters.addAll(output.repostedBy); + _repostsCursor = output.cursor; + _repostsLoaded = true; + }); + } + } catch (_) { + if (mounted) setState(() => _repostsLoaded = true); + } finally { + if (mounted) setState(() => _loadingReposts = false); + } + } + + void _selectTab(InteractionTab tab) { + setState(() => _selectedTab = tab); + if (tab == InteractionTab.likes && !_likesLoaded) _loadLikes(); + if (tab == InteractionTab.reposts && !_repostsLoaded) _loadReposts(); + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final hasBothTabs = widget.likeCount > 0 && widget.repostCount > 0; + + return DraggableScrollableSheet( + initialChildSize: 0.6, + maxChildSize: 0.9, + minChildSize: 0.3, + expand: false, + builder: (context, scrollController) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (hasBothTabs) ...[_buildTabBar(context, colorScheme)] else ...[_buildSectionLabel(context, colorScheme)], + Expanded( + child: _selectedTab == InteractionTab.likes + ? _buildProfileList( + profiles: _likers, + loading: _loadingLikes, + loaded: _likesLoaded, + cursor: _likesCursor, + onLoadMore: _loadLikes, + scrollController: scrollController, + colorScheme: colorScheme, + ) + : _buildProfileList( + profiles: _reposters, + loading: _loadingReposts, + loaded: _repostsLoaded, + cursor: _repostsCursor, + onLoadMore: _loadReposts, + scrollController: scrollController, + colorScheme: colorScheme, + ), + ), + ], + ); + }, + ); + } + + Widget _buildSectionLabel(BuildContext context, ColorScheme colorScheme) { + final label = _selectedTab == InteractionTab.likes ? 'LIKED BY' : 'REPOSTED BY'; + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Text( + label, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + letterSpacing: 2.2, + color: colorScheme.onSurfaceVariant, + ), + ), + ); + } + + Widget _buildTabBar(BuildContext context, ColorScheme colorScheme) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Row( + children: [ + _buildTabChip( + colorScheme: colorScheme, + tab: InteractionTab.likes, + icon: Icons.favorite_outline, + label: '${widget.likeCount} Likes', + ), + const SizedBox(width: 10), + _buildTabChip( + colorScheme: colorScheme, + tab: InteractionTab.reposts, + icon: Icons.repeat, + label: '${widget.repostCount} Reposts', + ), + ], + ), + ); + } + + Widget _buildTabChip({ + required ColorScheme colorScheme, + required InteractionTab tab, + required IconData icon, + required String label, + }) { + final isSelected = _selectedTab == tab; + return GestureDetector( + onTap: () => _selectTab(tab), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? colorScheme.primary.withValues(alpha: 0.15) : Colors.transparent, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: isSelected ? colorScheme.primary : colorScheme.outlineVariant), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 14, color: isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant), + const SizedBox(width: 6), + Text( + label, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + ); + } + + Widget _buildProfileList({ + required List profiles, + required bool loading, + required bool loaded, + required String? cursor, + required VoidCallback onLoadMore, + required ScrollController scrollController, + required ColorScheme colorScheme, + }) { + if (!loaded && loading) { + return const Center(child: CircularProgressIndicator()); + } + + if (loaded && profiles.isEmpty) { + return Center( + child: Text('No interactions yet', style: TextStyle(color: colorScheme.onSurfaceVariant)), + ); + } + + return ListView.builder( + controller: scrollController, + itemCount: profiles.length + (cursor != null ? 1 : 0), + itemBuilder: (context, index) { + if (index == profiles.length) { + if (!loading) onLoadMore(); + return const Padding( + padding: EdgeInsets.all(16), + child: Center(child: CircularProgressIndicator()), + ); + } + + final profile = profiles[index]; + final initials = ((profile.displayName?.isNotEmpty == true ? profile.displayName! : profile.handle)) + .substring(0, 1) + .toUpperCase(); + + return ListTile( + leading: CircleAvatar( + backgroundImage: profile.avatar != null ? NetworkImage(profile.avatar!) : null, + backgroundColor: colorScheme.surfaceContainerHighest, + child: profile.avatar == null ? Text(initials) : null, + ), + title: Text(profile.displayName ?? profile.handle, maxLines: 1, overflow: TextOverflow.ellipsis), + subtitle: Text('@${profile.handle}', style: TextStyle(color: colorScheme.onSurfaceVariant)), + onTap: () { + Navigator.pop(context); + GoRouter.maybeOf(context)?.push('/profile/view?actor=${Uri.encodeQueryComponent(profile.did)}'); + }, + ); + }, + ); + } +} diff --git a/lib/features/profile/presentation/profile_screen.dart b/lib/features/profile/presentation/profile_screen.dart index 0d47c36..acf0dd6 100644 --- a/lib/features/profile/presentation/profile_screen.dart +++ b/lib/features/profile/presentation/profile_screen.dart @@ -175,6 +175,8 @@ class _ProfileScreenState extends State with SingleTickerProvider _loadProfileAndFeed(filter: _feedTabs[index].filter); } }, + isScrollable: true, + tabAlignment: TabAlignment.start, labelStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 2.2), unselectedLabelStyle: const TextStyle( fontSize: 11, diff --git a/test/features/feed/data/post_action_repository_test.dart b/test/features/feed/data/post_action_repository_test.dart index 19c32ea..4ff5609 100644 --- a/test/features/feed/data/post_action_repository_test.dart +++ b/test/features/feed/data/post_action_repository_test.dart @@ -1,5 +1,7 @@ import 'package:atproto_core/atproto_core.dart'; import 'package:bluesky/app_bsky_bookmark_getbookmarks.dart'; +import 'package:bluesky/app_bsky_feed_getlikes.dart'; +import 'package:bluesky/app_bsky_feed_getrepostedby.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lazurite/features/feed/data/post_action_repository.dart'; @@ -53,6 +55,16 @@ class MockPostActionRepository implements PostActionRepository { return const BookmarkGetBookmarksOutput(bookmarks: []); } + @override + Future getLikes({required dynamic uri, String? cursor}) async { + return FeedGetLikesOutput(uri: AtUri.parse(uri.toString()), likes: []); + } + + @override + Future getRepostedBy({required dynamic uri, String? cursor}) async { + return FeedGetRepostedByOutput(uri: AtUri.parse(uri.toString()), repostedBy: []); + } + bool isLiked(String postUri) => _likes.containsKey(postUri); bool isReposted(String postUri) => _reposts.containsKey(postUri); bool isDeleted(String postUri) => _deletedPosts.contains(postUri); @@ -209,6 +221,44 @@ void main() { expect(output.bookmarks, isEmpty); }); }); + + group('getLikes', () { + test('should return empty likes list', () async { + final uri = _createTestUri('abc123'); + + final output = await repository.getLikes(uri: uri); + + expect(output.likes, isEmpty); + expect(output.cursor, isNull); + }); + + test('should accept cursor param', () async { + final uri = _createTestUri('abc123'); + + final output = await repository.getLikes(uri: uri, cursor: 'next-page'); + + expect(output.likes, isEmpty); + }); + }); + + group('getRepostedBy', () { + test('should return empty reposters list', () async { + final uri = _createTestUri('abc123'); + + final output = await repository.getRepostedBy(uri: uri); + + expect(output.repostedBy, isEmpty); + expect(output.cursor, isNull); + }); + + test('should accept cursor param', () async { + final uri = _createTestUri('abc123'); + + final output = await repository.getRepostedBy(uri: uri, cursor: 'next-page'); + + expect(output.repostedBy, isEmpty); + }); + }); }); } diff --git a/test/features/feed/presentation/post_interactions_sheet_test.dart b/test/features/feed/presentation/post_interactions_sheet_test.dart new file mode 100644 index 0000000..04c6bc0 --- /dev/null +++ b/test/features/feed/presentation/post_interactions_sheet_test.dart @@ -0,0 +1,161 @@ +import 'package:atproto_core/atproto_core.dart'; +import 'package:bluesky/app_bsky_actor_defs.dart'; +import 'package:bluesky/app_bsky_bookmark_getbookmarks.dart'; +import 'package:bluesky/app_bsky_feed_getlikes.dart'; +import 'package:bluesky/app_bsky_feed_getrepostedby.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lazurite/core/theme/app_theme.dart'; +import 'package:lazurite/features/feed/data/post_action_repository.dart'; +import 'package:lazurite/features/feed/presentation/widgets/post_interactions_sheet.dart'; + +class _FakeRepository implements PostActionRepository { + _FakeRepository({this.reposters = const []}); + final List reposters; + + @override + Future getLikes({required AtUri uri, String? cursor}) async { + return FeedGetLikesOutput(uri: uri, likes: []); + } + + @override + Future getRepostedBy({required AtUri uri, String? cursor}) async { + return FeedGetRepostedByOutput(uri: uri, repostedBy: reposters); + } + + @override + Future likePost({required AtUri uri, required String cid}) async => ''; + + @override + Future unlikePost({required String likeUri}) async {} + + @override + Future repostPost({required AtUri uri, required String cid}) async => ''; + + @override + Future unrepostPost({required String repostUri}) async {} + + @override + Future deletePost({required String postUri}) async {} + + @override + Future createBookmark({required AtUri uri, required String cid}) async {} + + @override + Future deleteBookmark({required AtUri uri}) async {} + + @override + Future getBookmarks({int? limit, String? cursor}) async { + return const BookmarkGetBookmarksOutput(bookmarks: []); + } +} + +class _FakeLikesRepository extends _FakeRepository { + _FakeLikesRepository(this._likers); + final List _likers; + + @override + Future getLikes({required AtUri uri, String? cursor}) async { + return FeedGetLikesOutput( + uri: uri, + likes: _likers.map((p) => Like(indexedAt: DateTime.utc(2026), createdAt: DateTime.utc(2026), actor: p)).toList(), + ); + } +} + +final _testUri = AtUri.parse('at://did:plc:test/app.bsky.feed.post/abc'); + +ProfileView _makeProfile({String handle = 'alice.bsky.social', String? displayName}) { + return ProfileView(did: 'did:plc:$handle', handle: handle, displayName: displayName); +} + +Widget _buildSheet({required PostActionRepository repository, int likeCount = 0, int repostCount = 0}) { + final theme = AppTheme.getTheme(AppThemePalette.oxocarbon, AppThemeVariant.dark); + return MaterialApp( + theme: theme, + home: Scaffold( + body: PostInteractionsSheet( + postUri: _testUri, + likeCount: likeCount, + repostCount: repostCount, + repository: repository, + ), + ), + ); +} + +void main() { + group('PostInteractionsSheet', () { + testWidgets('shows loading indicator while fetching likes', (tester) async { + final repo = _FakeRepository(); + await tester.pumpWidget(_buildSheet(repository: repo, likeCount: 5)); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + }); + + testWidgets('shows "LIKED BY" label when only likes available', (tester) async { + final repo = _FakeLikesRepository([_makeProfile()]); + await tester.pumpWidget(_buildSheet(repository: repo, likeCount: 1)); + await tester.pump(); + await tester.pump(); + + expect(find.text('LIKED BY'), findsOneWidget); + }); + + testWidgets('shows "REPOSTED BY" label when only reposts available', (tester) async { + final repo = _FakeRepository(reposters: [_makeProfile()]); + await tester.pumpWidget(_buildSheet(repository: repo, repostCount: 1)); + await tester.pump(); + await tester.pump(); + + expect(find.text('REPOSTED BY'), findsOneWidget); + }); + + testWidgets('shows tab chips when both likes and reposts are present', (tester) async { + final repo = _FakeLikesRepository([_makeProfile()]); + await tester.pumpWidget(_buildSheet(repository: repo, likeCount: 3, repostCount: 2)); + await tester.pump(); + await tester.pump(); + + expect(find.text('3 Likes'), findsOneWidget); + expect(find.text('2 Reposts'), findsOneWidget); + }); + + testWidgets('shows empty message when no likers returned', (tester) async { + final repo = _FakeLikesRepository([]); + await tester.pumpWidget(_buildSheet(repository: repo, likeCount: 1)); + await tester.pump(); + await tester.pump(); + + expect(find.text('No interactions yet'), findsOneWidget); + }); + + testWidgets('shows likers list after loading', (tester) async { + final repo = _FakeLikesRepository([ + _makeProfile(handle: 'alice.bsky.social', displayName: 'Alice'), + _makeProfile(handle: 'bob.bsky.social', displayName: 'Bob'), + ]); + await tester.pumpWidget(_buildSheet(repository: repo, likeCount: 2)); + await tester.pump(); + await tester.pump(); + + expect(find.text('Alice'), findsOneWidget); + expect(find.text('Bob'), findsOneWidget); + }); + + testWidgets('shows reposters list when repost tab selected', (tester) async { + final repo = _FakeRepository( + reposters: [_makeProfile(handle: 'carol.bsky.social', displayName: 'Carol')], + ); + await tester.pumpWidget(_buildSheet(repository: repo, likeCount: 3, repostCount: 1)); + await tester.pump(); + await tester.pump(); + + await tester.tap(find.text('1 Reposts')); + await tester.pump(); + await tester.pump(); + + expect(find.text('Carol'), findsOneWidget); + }); + }); +} diff --git a/test/features/starter_packs/presentation/create_edit_starter_pack_screen_test.dart b/test/features/starter_packs/presentation/create_edit_starter_pack_screen_test.dart index 4244210..471845d 100644 --- a/test/features/starter_packs/presentation/create_edit_starter_pack_screen_test.dart +++ b/test/features/starter_packs/presentation/create_edit_starter_pack_screen_test.dart @@ -1,7 +1,6 @@ import 'package:atproto_core/atproto_core.dart' show AtUri; import 'package:bluesky/app_bsky_actor_defs.dart'; import 'package:bluesky/app_bsky_feed_defs.dart'; -import 'package:bluesky/app_bsky_graph_defs.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -23,7 +22,6 @@ void main() { const userDid = 'did:plc:user'; final packUri = AtUri.parse('at://did:plc:user/app.bsky.graph.starterpack/pack-1'); - final refListUri = AtUri.parse('at://did:plc:user/app.bsky.graph.list/list-1'); setUpAll(() { registerFallbackValue(AtUri.parse('at://did:plc:fallback/app.bsky.graph.starterpack/fallback')); @@ -34,27 +32,6 @@ void main() { mockListRepo = MockListRepository(); }); - StarterPackView buildPackView() { - return StarterPackView( - uri: packUri, - cid: 'cid-pack', - record: const { - r'$type': 'app.bsky.graph.starterpack', - 'name': 'My Pack', - 'list': 'at://did:plc:user/app.bsky.graph.list/list-1', - 'createdAt': '2026-03-22T00:00:00.000Z', - }, - creator: const ProfileViewBasic(did: userDid, handle: 'user.bsky.social'), - list: ListViewBasic( - uri: refListUri, - cid: 'cid-list', - name: 'Starter Pack Members', - purpose: const ListPurpose.knownValue(data: KnownListPurpose.appBskyGraphDefsReferencelist), - ), - indexedAt: DateTime.utc(2026, 3, 22), - ); - } - Widget buildSubject() { return MultiRepositoryProvider( providers: [ diff --git a/test/features/starter_packs/presentation/starter_pack_detail_screen_test.dart b/test/features/starter_packs/presentation/starter_pack_detail_screen_test.dart index e6b6473..fb8db8c 100644 --- a/test/features/starter_packs/presentation/starter_pack_detail_screen_test.dart +++ b/test/features/starter_packs/presentation/starter_pack_detail_screen_test.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; -import 'package:lazurite/features/starter_packs/bloc/starter_pack_bloc.dart'; import 'package:lazurite/features/starter_packs/data/starter_pack_repository.dart'; import 'package:lazurite/features/starter_packs/presentation/starter_pack_detail_screen.dart'; import 'package:mocktail/mocktail.dart'; @@ -75,7 +74,7 @@ void main() { routes: [ GoRoute( path: '/', - builder: (_, __) => MultiRepositoryProvider( + builder: (_, _) => MultiRepositoryProvider( providers: [ RepositoryProvider.value(value: mockRepository), if (currentUserDid != null) RepositoryProvider.value(value: currentUserDid), -- 2.51.2