···
/// - Loading, empty, and error states
/// - Automatic comment loading on screen init
class PostDetailScreen extends StatefulWidget {
40
-
const PostDetailScreen({required this.post, this.isOptimistic = false, super.key});
40
+
const PostDetailScreen({
42
+
this.isOptimistic = false,
/// Post to display (passed via route extras)
···
class _PostDetailScreenState extends State<PostDetailScreen> {
54
-
final ScrollController _scrollController = ScrollController();
58
+
// ScrollController created lazily with cached scroll position for instant restoration
59
+
late ScrollController _scrollController;
final GlobalKey _commentsHeaderKey = GlobalKey();
// Cached provider from CommentsProviderCache
···
70
-
_scrollController.addListener(_onScroll);
75
+
// ScrollController and provider initialization moved to didChangeDependencies
76
+
// where we have access to context for synchronous provider acquisition
72
-
// Initialize provider after frame is built
73
-
WidgetsBinding.instance.addPostFrameCallback((_) {
75
-
_initializeProvider();
76
-
_setupAuthListener();
80
+
void didChangeDependencies() {
81
+
super.didChangeDependencies();
82
+
// Initialize provider synchronously on first call (has context access)
83
+
// This ensures cached data is available for the first build, avoiding
84
+
// the flash from loading state → content → scroll position jump
85
+
if (!_isInitialized) {
86
+
_initializeProviderSync();
/// Listen for auth state changes to handle sign-out
···
// If user signed out while viewing this screen, navigate back
// The CommentsProviderCache has already disposed our provider
95
-
if (!authProvider.isAuthenticated && _isInitialized && !_providerInvalidated) {
104
+
if (!authProvider.isAuthenticated &&
106
+
!_providerInvalidated) {
_providerInvalidated = true;
···
116
-
/// Initialize provider from cache and restore state
117
-
void _initializeProvider() {
127
+
/// Initialize provider synchronously from cache
129
+
/// Called from didChangeDependencies to ensure cached data is available
130
+
/// for the first build. Creates ScrollController with initialScrollOffset
131
+
/// set to cached position for instant scroll restoration without flicker.
132
+
void _initializeProviderSync() {
// Get or create provider from cache
final cache = context.read<CommentsProviderCache>();
···
postCid: widget.post.post.cid,
141
+
// Create scroll controller with cached position for instant restoration
142
+
// This avoids the flash: loading → content at top → jump to cached position
143
+
final cachedScrollPosition = _commentsProvider.scrollPosition;
144
+
_scrollController = ScrollController(
145
+
initialScrollOffset: cachedScrollPosition,
147
+
_scrollController.addListener(_onScroll);
149
+
if (kDebugMode && cachedScrollPosition > 0) {
151
+
'📍 Created ScrollController with initial offset: $cachedScrollPosition',
// Listen for changes to trigger rebuilds
_commentsProvider.addListener(_onProviderChanged);
158
+
// Setup auth listener
159
+
_setupAuthListener();
161
+
// Mark as initialized before triggering any loads
162
+
// This ensures the first build shows content (not loading) when cached
163
+
_isInitialized = true;
// Skip loading for optimistic posts (just created, not yet indexed)
if (widget.isOptimistic) {
···
// Don't load comments - there won't be any yet
} else if (_commentsProvider.comments.isNotEmpty) {
136
-
// Already have data - restore scroll position immediately
172
+
// Already have cached data - it will render immediately
'📦 Using cached comments (${_commentsProvider.comments.length})',
142
-
_restoreScrollPosition();
144
-
// Background refresh if data is stale
179
+
// Background refresh if data is stale (won't cause flicker)
if (_commentsProvider.isStale) {
debugPrint('🔄 Data stale, refreshing in background');
···
// No cached data - load fresh
_commentsProvider.loadComments(refresh: true);
157
-
_isInitialized = true;
···
199
-
/// Restore scroll position from provider
200
-
void _restoreScrollPosition() {
201
-
final savedPosition = _commentsProvider.scrollPosition;
202
-
if (savedPosition <= 0) {
206
-
WidgetsBinding.instance.addPostFrameCallback((_) {
207
-
if (!mounted || !_scrollController.hasClients) {
211
-
final maxExtent = _scrollController.position.maxScrollExtent;
212
-
final targetPosition = savedPosition.clamp(0.0, maxExtent);
214
-
if (targetPosition > 0) {
215
-
_scrollController.jumpTo(targetPosition);
217
-
debugPrint('📍 Restored scroll to $targetPosition (max: $maxExtent)');
/// Handle sort changes from dropdown