Skip to content

Commit e3bc454

Browse files
committed
Add feed engagement lenses, prompts, and ghost reputation
1 parent df992c6 commit e3bc454

9 files changed

Lines changed: 656 additions & 27 deletions

File tree

apps/mobile/lib/features/feed/data/feed_repository.dart

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ class FeedRepository {
8383
final posts = rows.map(ShadePost.fromMap).toList(growable: false);
8484
final hasMore = posts.length > limit;
8585
final visiblePosts = hasMore ? posts.sublist(0, limit) : posts;
86-
final hydratedPosts = await _attachPollMetadata(visiblePosts);
86+
final postsWithPolls = await _attachPollMetadata(visiblePosts);
87+
final hydratedPosts = await _attachReplyCounts(postsWithPolls);
8788

8889
return FeedBatch(
8990
items: hydratedPosts,
@@ -212,6 +213,58 @@ class FeedRepository {
212213
)
213214
.toList(growable: false);
214215
}
216+
217+
Future<List<ShadePost>> _attachReplyCounts(List<ShadePost> posts) async {
218+
if (posts.isEmpty) {
219+
return posts;
220+
}
221+
222+
final postIds = posts
223+
.map((ShadePost post) => post.id)
224+
.where((String id) => id.isNotEmpty)
225+
.toList(growable: false);
226+
if (postIds.isEmpty) {
227+
return posts;
228+
}
229+
230+
List<Map<String, dynamic>> replyRows;
231+
try {
232+
final result = await _client
233+
.from('replies')
234+
.select('id, post_id')
235+
.inFilter('post_id', postIds)
236+
.limit(5000);
237+
replyRows = (result as List<dynamic>)
238+
.map(
239+
(dynamic item) => Map<String, dynamic>.from(item as Map),
240+
)
241+
.toList(growable: false);
242+
} catch (_) {
243+
return posts;
244+
}
245+
246+
if (replyRows.isEmpty) {
247+
return posts;
248+
}
249+
250+
final countsByPostId = <String, int>{};
251+
for (final row in replyRows) {
252+
final postId = row['post_id'] as String?;
253+
if (postId == null || postId.isEmpty) {
254+
continue;
255+
}
256+
countsByPostId.update(postId, (int value) => value + 1,
257+
ifAbsent: () => 1);
258+
}
259+
260+
return posts
261+
.map(
262+
(ShadePost post) => post.copyWith(
263+
replyCount: countsByPostId[post.id] ?? 0,
264+
),
265+
)
266+
.toList(growable: false);
267+
}
215268
}
216269

217270
List<String> _asStringList(dynamic value) {

apps/mobile/lib/features/feed/presentation/community_feed_screen.dart

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ class _FeedPostCard extends StatelessWidget {
595595
InkWell(
596596
onTap: onOpenReplies,
597597
borderRadius: BorderRadius.circular(20),
598-
child: const Padding(
598+
child: Padding(
599599
padding: EdgeInsets.symmetric(
600600
horizontal: 4,
601601
vertical: 2,
@@ -609,14 +609,29 @@ class _FeedPostCard extends StatelessWidget {
609609
),
610610
SizedBox(width: 6),
611611
Text(
612-
'Reply',
612+
'${post.replyCount} replies',
613613
style: TextStyle(color: Colors.white70),
614614
),
615615
],
616616
),
617617
),
618618
),
619619
const SizedBox(width: 14),
620+
Icon(
621+
Icons.local_fire_department_rounded,
622+
size: 16,
623+
color: _heatAccent(post),
624+
),
625+
const SizedBox(width: 4),
626+
Text(
627+
_heatLabel(post),
628+
style: TextStyle(
629+
color: _heatAccent(post),
630+
fontSize: 12,
631+
fontWeight: FontWeight.w700,
632+
),
633+
),
634+
const SizedBox(width: 14),
620635
const Icon(
621636
Icons.visibility_outlined,
622637
size: 16,
@@ -821,6 +836,35 @@ String _compactTime(String value) {
821836
return '${diff.inDays}d';
822837
}
823838

839+
String _heatLabel(ShadePost post) {
840+
final score =
841+
(post.likeCount * 4) + (post.replyCount * 6) + (post.viewCount ~/ 12);
842+
if (score >= 120) {
843+
return 'Exploding';
844+
}
845+
if (score >= 75) {
846+
return 'Trending';
847+
}
848+
if (score >= 40) {
849+
return 'Rising';
850+
}
851+
return 'Fresh';
852+
}
853+
854+
Color _heatAccent(ShadePost post) {
855+
final label = _heatLabel(post);
856+
switch (label) {
857+
case 'Exploding':
858+
return const Color(0xFFFACC15);
859+
case 'Trending':
860+
return const Color(0xFFFF2D55);
861+
case 'Rising':
862+
return const Color(0xFF38BDF8);
863+
default:
864+
return const Color(0xFF2DD4BF);
865+
}
866+
}
867+
824868
enum _PostAction {
825869
report,
826870
blockAuthor,

0 commit comments

Comments
 (0)