fix(reply): cache FlutterView to prevent crash on screen re-open

View.of(context) was being called in didChangeMetrics() during widget
deactivation, causing "Looking up a deactivated widget's ancestor"
errors. The context becomes invalid before mounted becomes false.

Fixed by caching the FlutterView reference in didChangeDependencies()
for both _ReplyScreenState and _ReplyToolbarState, then using the
cached reference in didChangeMetrics().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+21 -5
lib
screens
+21 -5
lib/screens/compose/reply_screen.dart
··· 1 1 import 'dart:async'; 2 2 import 'dart:math' as math; 3 + import 'dart:ui' show FlutterView; 3 4 4 5 import 'package:flutter/foundation.dart'; 5 6 import 'package:flutter/material.dart'; ··· 67 68 bool _authInvalidated = false; 68 69 double _lastKeyboardHeight = 0; 69 70 Timer? _bannerDismissTimer; 71 + FlutterView? _cachedView; 70 72 71 73 @override 72 74 void initState() { ··· 92 94 }); 93 95 } 94 96 97 + @override 98 + void didChangeDependencies() { 99 + super.didChangeDependencies(); 100 + // Cache the view reference so we can safely use it in didChangeMetrics 101 + // even when the widget is being deactivated 102 + _cachedView = View.of(context); 103 + } 104 + 95 105 void _setupAuthListener() { 96 106 try { 97 107 context.read<AuthProvider>().addListener(_onAuthChanged); ··· 151 161 super.didChangeMetrics(); 152 162 // Guard against being called after widget is deactivated 153 163 // (can happen during keyboard animation while navigating away) 154 - if (!mounted) return; 164 + if (!mounted || _cachedView == null) return; 155 165 156 - final keyboardHeight = View.of(context).viewInsets.bottom; 166 + final keyboardHeight = _cachedView!.viewInsets.bottom; 157 167 158 168 // Detect keyboard closing and unfocus text field 159 169 if (_lastKeyboardHeight > 0 && keyboardHeight == 0) { ··· 495 505 with WidgetsBindingObserver { 496 506 final ValueNotifier<double> _keyboardMarginNotifier = ValueNotifier(0); 497 507 final ValueNotifier<double> _safeAreaBottomNotifier = ValueNotifier(0); 508 + FlutterView? _cachedView; 498 509 499 510 @override 500 511 void initState() { ··· 505 516 @override 506 517 void didChangeDependencies() { 507 518 super.didChangeDependencies(); 519 + // Cache view reference for safe access in didChangeMetrics 520 + _cachedView = View.of(context); 508 521 _updateMargins(); 509 522 } 510 523 ··· 518 531 519 532 @override 520 533 void didChangeMetrics() { 521 - _updateMargins(); 534 + // Schedule update after frame to ensure context is valid 535 + WidgetsBinding.instance.addPostFrameCallback((_) { 536 + _updateMargins(); 537 + }); 522 538 } 523 539 524 540 void _updateMargins() { 525 - if (!mounted) { 541 + if (!mounted || _cachedView == null) { 526 542 return; 527 543 } 528 - final view = View.of(context); 544 + final view = _cachedView!; 529 545 final devicePixelRatio = view.devicePixelRatio; 530 546 final keyboardInset = view.viewInsets.bottom / devicePixelRatio; 531 547 final viewPaddingBottom = view.viewPadding.bottom / devicePixelRatio;