diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json
index c907edd..2093070 100644
--- a/assets/translations/en-US.json
+++ b/assets/translations/en-US.json
@@ -91,6 +91,11 @@
     "one": "{} comment",
     "other": "{} comments"
   },
+  "postCommentsDetailed": {
+    "zero": "No comments",
+    "one": "{} comment",
+    "other": "{} comments"
+  },
   "settingsAppearance": "Appearance",
   "settingsBackgroundImage": "Background Image",
   "settingsBackgroundImageDescription": "Set the background image that will be applied globally.",
diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json
index 73741b4..9499893 100644
--- a/assets/translations/zh-CN.json
+++ b/assets/translations/zh-CN.json
@@ -91,6 +91,11 @@
     "one": "{} 条评论",
     "other": "{} 条评论"
   },
+  "postCommentsDetailed": {
+    "zero": "没有评论",
+    "one": "{} 条评论",
+    "other": "{} 条评论"
+  },
   "settingsAppearance": "外观",
   "settingsBackgroundImage": "背景图片",
   "settingsBackgroundImageDescription": "设置应用全局生效的的背景图片。",
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 1f5c230..374309d 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -41,41 +41,18 @@ PODS:
     - DKImagePickerController/PhotoGallery
     - Flutter
   - Flutter (1.0.0)
-  - flutter_image_compress_common (1.0.0):
-    - Flutter
-    - Mantle
-    - SDWebImage
-    - SDWebImageWebPCoder
   - flutter_native_splash (0.0.1):
     - Flutter
   - flutter_secure_storage (3.3.1):
     - Flutter
   - image_picker_ios (0.0.1):
     - Flutter
-  - libwebp (1.3.2):
-    - libwebp/demux (= 1.3.2)
-    - libwebp/mux (= 1.3.2)
-    - libwebp/sharpyuv (= 1.3.2)
-    - libwebp/webp (= 1.3.2)
-  - libwebp/demux (1.3.2):
-    - libwebp/webp
-  - libwebp/mux (1.3.2):
-    - libwebp/demux
-  - libwebp/sharpyuv (1.3.2)
-  - libwebp/webp (1.3.2):
-    - libwebp/sharpyuv
-  - Mantle (2.2.0):
-    - Mantle/extobjc (= 2.2.0)
-  - Mantle/extobjc (2.2.0)
   - path_provider_foundation (0.0.1):
     - Flutter
     - FlutterMacOS
   - SDWebImage (5.19.7):
     - SDWebImage/Core (= 5.19.7)
   - SDWebImage/Core (5.19.7)
-  - SDWebImageWebPCoder (0.14.6):
-    - libwebp (~> 1.0)
-    - SDWebImage/Core (~> 5.17)
   - shared_preferences_foundation (0.0.1):
     - Flutter
     - FlutterMacOS
@@ -92,7 +69,6 @@ DEPENDENCIES:
   - cupertino_http (from `.symlinks/plugins/cupertino_http/ios`)
   - file_picker (from `.symlinks/plugins/file_picker/ios`)
   - Flutter (from `Flutter`)
-  - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
   - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
   - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
@@ -105,10 +81,7 @@ SPEC REPOS:
   trunk:
     - DKImagePickerController
     - DKPhotoGallery
-    - libwebp
-    - Mantle
     - SDWebImage
-    - SDWebImageWebPCoder
     - SwiftyGif
 
 EXTERNAL SOURCES:
@@ -122,8 +95,6 @@ EXTERNAL SOURCES:
     :path: ".symlinks/plugins/file_picker/ios"
   Flutter:
     :path: Flutter
-  flutter_image_compress_common:
-    :path: ".symlinks/plugins/flutter_image_compress_common/ios"
   flutter_native_splash:
     :path: ".symlinks/plugins/flutter_native_splash/ios"
   flutter_secure_storage:
@@ -147,15 +118,11 @@ SPEC CHECKSUMS:
   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
   file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
-  flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
   flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
   flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
   image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
-  libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
-  Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
   path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
   SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
-  SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart
index 38a7f14..a452fad 100644
--- a/lib/screens/explore.dart
+++ b/lib/screens/explore.dart
@@ -168,6 +168,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
             SliverInfiniteList(
               itemCount: _posts.length,
               isLoading: _isBusy,
+              centerLoading: true,
               hasReachedMax: _postCount != null && _posts.length >= _postCount!,
               onFetchData: _fetchPosts,
               itemBuilder: (context, idx) {
@@ -182,8 +183,8 @@ class _ExploreScreenState extends State<ExploreScreen> {
                   },
                 );
               },
-              separatorBuilder: (context, index) => const Divider(),
-            )
+              separatorBuilder: (context, index) => const Divider(height: 1),
+            ),
           ],
         ),
       ),
diff --git a/lib/screens/post/post_detail.dart b/lib/screens/post/post_detail.dart
index 32c450a..f78097a 100644
--- a/lib/screens/post/post_detail.dart
+++ b/lib/screens/post/post_detail.dart
@@ -2,6 +2,7 @@ import 'dart:math' as math;
 
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
 import 'package:go_router/go_router.dart';
 import 'package:provider/provider.dart';
 import 'package:styled_widget/styled_widget.dart';
@@ -11,6 +12,7 @@ import 'package:surface/types/post.dart';
 import 'package:surface/widgets/dialog.dart';
 import 'package:surface/widgets/loading_indicator.dart';
 import 'package:surface/widgets/navigation/app_scaffold.dart';
+import 'package:surface/widgets/post/post_comment_list.dart';
 import 'package:surface/widgets/post/post_item.dart';
 
 class PostDetailScreen extends StatefulWidget {
@@ -89,13 +91,26 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
           ],
         ).padding(top: math.max(MediaQuery.of(context).padding.top, 8)),
       ),
-      body: SingleChildScrollView(
-        child: Column(
-          children: [
-            LoadingIndicator(isActive: _isBusy),
-            if (_data != null) PostItem(data: _data!),
-          ],
-        ),
+      body: CustomScrollView(
+        slivers: [
+          SliverToBoxAdapter(
+            child: LoadingIndicator(isActive: _isBusy),
+          ),
+          if (_data != null)
+            SliverToBoxAdapter(
+              child: PostItem(data: _data!),
+            ),
+          const SliverToBoxAdapter(child: Divider(height: 1)),
+          if (_data != null)
+            SliverToBoxAdapter(
+              child: Text('postCommentsDetailed')
+                  .plural(_data!.metric.replyCount)
+                  .textStyle(Theme.of(context).textTheme.titleLarge!)
+                  .padding(horizontal: 16, top: 12, bottom: 4),
+            ),
+          if (_data != null) PostCommentSliverList(parentPostId: _data!.id),
+          SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
+        ],
       ),
     );
   }
diff --git a/lib/widgets/attachment/attachment_detail.dart b/lib/widgets/attachment/attachment_detail.dart
index 0e268e4..d483fda 100644
--- a/lib/widgets/attachment/attachment_detail.dart
+++ b/lib/widgets/attachment/attachment_detail.dart
@@ -27,6 +27,7 @@ class AttachmentDetailPopup extends StatelessWidget {
       child: Hero(
         tag: 'attachment-${data.rid}-${heroTag ?? uuid.v4()}',
         child: PhotoView(
+          key: Key('attachment-detail-${data.rid}-$heroTag'),
           imageProvider: UniversalImage.provider(
             sn.getAttachmentUrl(data.rid),
           ),
diff --git a/lib/widgets/attachment/attachment_item.dart b/lib/widgets/attachment/attachment_item.dart
index ead8491..e73df98 100644
--- a/lib/widgets/attachment/attachment_item.dart
+++ b/lib/widgets/attachment/attachment_item.dart
@@ -26,6 +26,7 @@ class AttachmentItem extends StatelessWidget {
           child: LayoutBuilder(builder: (context, constraints) {
             return UniversalImage(
               sn.getAttachmentUrl(data.rid),
+              key: Key('attachment-${data.rid}-$heroTag'),
               fit: BoxFit.cover,
               cacheHeight: constraints.maxHeight,
               cacheWidth: constraints.maxWidth,
diff --git a/lib/widgets/post/post_comment_list.dart b/lib/widgets/post/post_comment_list.dart
new file mode 100644
index 0000000..405851b
--- /dev/null
+++ b/lib/widgets/post/post_comment_list.dart
@@ -0,0 +1,134 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:go_router/go_router.dart';
+import 'package:material_symbols_icons/symbols.dart';
+import 'package:provider/provider.dart';
+import 'package:styled_widget/styled_widget.dart';
+import 'package:surface/providers/sn_attachment.dart';
+import 'package:surface/providers/sn_network.dart';
+import 'package:surface/types/post.dart';
+import 'package:surface/widgets/post/post_item.dart';
+import 'package:very_good_infinite_list/very_good_infinite_list.dart';
+
+class PostCommentSliverList extends StatefulWidget {
+  final int parentPostId;
+  const PostCommentSliverList({super.key, required this.parentPostId});
+
+  @override
+  State<PostCommentSliverList> createState() => _PostCommentSliverListState();
+}
+
+class _PostCommentSliverListState extends State<PostCommentSliverList> {
+  bool _isBusy = true;
+
+  final List<SnPost> _posts = List.empty(growable: true);
+  int? _postCount;
+
+  Future<void> _fetchPosts() async {
+    if (_postCount != null && _posts.length >= _postCount!) return;
+
+    setState(() => _isBusy = true);
+
+    final sn = context.read<SnNetworkProvider>();
+    final resp = await sn.client.get(
+      '/cgi/co/posts/${widget.parentPostId}/replies',
+      queryParameters: {
+        'take': 10,
+        'offset': _posts.length,
+      },
+    );
+    final List<SnPost> out =
+        List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []);
+
+    Set<String> rids = {};
+    for (var i = 0; i < out.length; i++) {
+      rids.addAll(out[i].body['attachments']?.cast<String>() ?? []);
+    }
+
+    if (!mounted) return;
+    final attach = context.read<SnAttachmentProvider>();
+    final attachments = await attach.getMultiple(rids.toList());
+    for (var i = 0; i < out.length; i++) {
+      out[i] = out[i].copyWith(
+        preload: SnPostPreload(
+          attachments: attachments
+              .where(
+                (ele) => out[i].body['attachments']?.contains(ele.rid) ?? false,
+              )
+              .toList(),
+        ),
+      );
+    }
+
+    _postCount = resp.data['count'];
+    _posts.addAll(out);
+
+    if (mounted) setState(() => _isBusy = false);
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _fetchPosts();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return SliverInfiniteList(
+      itemCount: _posts.length,
+      isLoading: _isBusy,
+      hasReachedMax: _postCount != null && _posts.length >= _postCount!,
+      onFetchData: _fetchPosts,
+      itemBuilder: (context, idx) {
+        return GestureDetector(
+          child: PostItem(data: _posts[idx]),
+          onTap: () {
+            GoRouter.of(context).pushNamed(
+              'postDetail',
+              pathParameters: {'slug': _posts[idx].id.toString()},
+              extra: _posts[idx],
+            );
+          },
+        );
+      },
+      separatorBuilder: (context, index) => const Divider(height: 1),
+    );
+  }
+}
+
+class PostCommentListPopup extends StatelessWidget {
+  final int postId;
+  final int commentCount;
+  const PostCommentListPopup({
+    super.key,
+    required this.postId,
+    this.commentCount = 0,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Row(
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: [
+            const Icon(Symbols.comment, size: 24),
+            const Gap(16),
+            Text('postCommentsDetailed')
+                .plural(commentCount)
+                .textStyle(Theme.of(context).textTheme.titleLarge!),
+          ],
+        ).padding(horizontal: 20, top: 16, bottom: 12),
+        Expanded(
+          child: CustomScrollView(
+            slivers: [
+              PostCommentSliverList(parentPostId: postId),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart
index de5acd2..3451f06 100644
--- a/lib/widgets/post/post_item.dart
+++ b/lib/widgets/post/post_item.dart
@@ -11,10 +11,16 @@ import 'package:surface/widgets/account/account_image.dart';
 import 'package:surface/widgets/attachment/attachment_list.dart';
 import 'package:surface/widgets/markdown_content.dart';
 import 'package:gap/gap.dart';
+import 'package:surface/widgets/post/post_comment_list.dart';
 
 class PostItem extends StatelessWidget {
   final SnPost data;
-  const PostItem({super.key, required this.data});
+  final bool showComments;
+  const PostItem({
+    super.key,
+    required this.data,
+    this.showComments = true,
+  });
 
   @override
   Widget build(BuildContext context) {
@@ -24,9 +30,12 @@ class PostItem extends StatelessWidget {
         _PostContentHeader(data: data).padding(horizontal: 12, vertical: 8),
         _PostContentBody(data: data.body).padding(horizontal: 16, bottom: 6),
         if (data.preload?.attachments?.isNotEmpty ?? true)
-          AttachmentList(data: data.preload!.attachments!, bordered: true),
-        _PostBottomAction(data: data)
-            .padding(left: 20, right: 26, top: 8, bottom: 2),
+          AttachmentList(
+            data: data.preload!.attachments!,
+            bordered: true,
+          ),
+        _PostBottomAction(data: data, showComments: showComments)
+            .padding(left: 12, right: 18),
       ],
     );
   }
@@ -34,7 +43,8 @@ class PostItem extends StatelessWidget {
 
 class _PostBottomAction extends StatelessWidget {
   final SnPost data;
-  const _PostBottomAction({required this.data});
+  final bool showComments;
+  const _PostBottomAction({required this.data, required this.showComments});
 
   @override
   Widget build(BuildContext context) {
@@ -53,25 +63,38 @@ class _PostBottomAction extends StatelessWidget {
                   const Gap(8),
                   Text('postReact').tr(),
                 ],
-              ),
+              ).padding(horizontal: 8, vertical: 8),
               onTap: () {},
             ),
-            const Gap(16),
-            InkWell(
-              child: Row(
-                children: [
-                  Icon(Symbols.comment, size: 20, color: iconColor),
-                  const Gap(8),
-                  Text('postComments').plural(data.metric.replyCount),
-                ],
+            if (showComments)
+              InkWell(
+                child: Row(
+                  children: [
+                    Icon(Symbols.comment, size: 20, color: iconColor),
+                    const Gap(8),
+                    Text('postComments').plural(data.metric.replyCount),
+                  ],
+                ).padding(horizontal: 8, vertical: 8),
+                onTap: () {
+                  showModalBottomSheet(
+                    context: context,
+                    useRootNavigator: true,
+                    builder: (context) => PostCommentListPopup(
+                      postId: data.id,
+                      commentCount: data.metric.replyCount,
+                    ),
+                  );
+                },
               ),
-              onTap: () {},
-            ),
           ].expand((ele) => [ele, const Gap(8)]).toList()
             ..removeLast(),
         ),
         InkWell(
-          child: Icon(Symbols.share, size: 20, color: iconColor),
+          child: Icon(
+            Symbols.share,
+            size: 20,
+            color: iconColor,
+          ).padding(horizontal: 8, vertical: 8),
           onTap: () {},
         ),
       ],
diff --git a/lib/widgets/post/post_list.dart b/lib/widgets/post/post_list.dart
deleted file mode 100644
index e69de29..0000000
diff --git a/pubspec.lock b/pubspec.lock
index 188d160..9bab929 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -5,23 +5,23 @@ packages:
     dependency: transitive
     description:
       name: _fe_analyzer_shared
-      sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
+      sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
       url: "https://pub.dev"
     source: hosted
-    version: "72.0.0"
+    version: "76.0.0"
   _macros:
     dependency: transitive
     description: dart
     source: sdk
-    version: "0.3.2"
+    version: "0.3.3"
   analyzer:
     dependency: transitive
     description:
       name: analyzer
-      sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
+      sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
       url: "https://pub.dev"
     source: hosted
-    version: "6.7.0"
+    version: "6.11.0"
   animations:
     dependency: "direct main"
     description:
@@ -202,10 +202,10 @@ packages:
     dependency: "direct main"
     description:
       name: collection
-      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+      sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
       url: "https://pub.dev"
     source: hosted
-    version: "1.18.0"
+    version: "1.19.0"
   connectivity_plus:
     dependency: transitive
     description:
@@ -790,18 +790,18 @@ packages:
     dependency: transitive
     description:
       name: leak_tracker
-      sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
+      sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
       url: "https://pub.dev"
     source: hosted
-    version: "10.0.5"
+    version: "10.0.8"
   leak_tracker_flutter_testing:
     dependency: transitive
     description:
       name: leak_tracker_flutter_testing
-      sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
+      sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.5"
+    version: "3.0.9"
   leak_tracker_testing:
     dependency: transitive
     description:
@@ -838,10 +838,10 @@ packages:
     dependency: transitive
     description:
       name: macros
-      sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
+      sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
       url: "https://pub.dev"
     source: hosted
-    version: "0.1.2-main.4"
+    version: "0.1.3-main.0"
   markdown:
     dependency: "direct main"
     description:
@@ -1150,7 +1150,7 @@ packages:
     dependency: transitive
     description: flutter
     source: sdk
-    version: "0.0.99"
+    version: "0.0.0"
   source_gen:
     dependency: transitive
     description:
@@ -1227,10 +1227,10 @@ packages:
     dependency: transitive
     description:
       name: stack_trace
-      sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+      sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
       url: "https://pub.dev"
     source: hosted
-    version: "1.11.1"
+    version: "1.12.0"
   stream_channel:
     dependency: transitive
     description:
@@ -1251,10 +1251,10 @@ packages:
     dependency: transitive
     description:
       name: string_scanner
-      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+      sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
       url: "https://pub.dev"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.0"
   styled_widget:
     dependency: "direct main"
     description:
@@ -1291,10 +1291,10 @@ packages:
     dependency: transitive
     description:
       name: test_api
-      sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
+      sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.2"
+    version: "0.7.3"
   timing:
     dependency: transitive
     description:
@@ -1411,10 +1411,10 @@ packages:
     dependency: transitive
     description:
       name: vm_service
-      sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
+      sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
       url: "https://pub.dev"
     source: hosted
-    version: "14.2.5"
+    version: "14.3.0"
   watcher:
     dependency: transitive
     description: