Skip to content

Commit c7f6db4

Browse files
[Super Editor] - Prevent NaN and negative bottom insets in KeyboardScaffoldSafeArea (Resolves #2535) (#2536)
1 parent c62263e commit c7f6db4

File tree

1 file changed

+69
-43
lines changed

1 file changed

+69
-43
lines changed

super_editor/lib/src/infrastructure/keyboard_panel_scaffold.dart

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,49 +1077,7 @@ class _KeyboardScaffoldSafeAreaState extends State<KeyboardScaffoldSafeArea> {
10771077
return widget.child;
10781078
}
10791079

1080-
// There's no ancestor KeyboardScaffoldSafeArea, but there might be an ancestor
1081-
// KeyboardScaffoldSafeAreaScope, whose insets we should use.
1082-
final inheritedGeometry = _ancestorSafeAreaScope?.geometry;
1083-
1084-
// Either use the ancestor geometry, or use our own.
1085-
final keyboardSafeArea = inheritedGeometry ?? KeyboardScaffoldSafeAreaScope.of(safeAreaContext).geometry;
1086-
1087-
// Get the current keyboard safe area bottom insets, and then adjust that
1088-
// value based on our global bottom y-value. When this widget appears at
1089-
// the very bottom of the screen, this adjustment will be zero (no change),
1090-
// but when this widget sits somewhere above the bottom of the screen, we
1091-
// need to account for that extra space between us and the keyboard that's
1092-
// coming up from the bottom of the screen.
1093-
var bottomInsets = keyboardSafeArea.bottomInsets;
1094-
if (_myBoxKey.currentContext != null && _myBoxKey.currentContext!.findRenderObject() != null) {
1095-
final myBox = _myBoxKey.currentContext!.findRenderObject() as RenderBox;
1096-
final myGlobalBottom = myBox.localToGlobal(Offset(0, myBox.size.height)).dy;
1097-
final spaceBelowMe = MediaQuery.sizeOf(safeAreaContext).height - myGlobalBottom;
1098-
1099-
// The bottom insets are measured from the bottom of the screen. But we might not
1100-
// be sitting at the bottom of the screen. There might be some space beneath us.
1101-
// In that case, we don't need to push as far up. Remove the space below us from
1102-
// the bottom insets.
1103-
bottomInsets = max(bottomInsets - spaceBelowMe, 0);
1104-
} else {
1105-
// This is our first widget build and we need to adjust our insets
1106-
// after initial layout.
1107-
//
1108-
// Note: We have a frame of lag because our inset spacing is based on other
1109-
// layout results. As a result, if the content below us animates a height
1110-
// change, such as a widget in a `SafeArea` where bottom `padding` animates
1111-
// up/down, our content will jitter as it plays catchup one frame behind.
1112-
//
1113-
// The only solution I can think of that might truly solve this is to use
1114-
// a Leader and Follower in some way. That way positioning occurs as late
1115-
// as possible.
1116-
WidgetsBinding.instance.addPostFrameCallback((_) {
1117-
setState(() {
1118-
// Re-run build.
1119-
});
1120-
});
1121-
}
1122-
1080+
final bottomInsets = _chooseBottomInsets(safeAreaContext);
11231081
return Padding(
11241082
padding: EdgeInsets.only(bottom: bottomInsets),
11251083
// ^ We inject `bottomInsets` to push content above the keyboard. However, we don't
@@ -1134,6 +1092,74 @@ class _KeyboardScaffoldSafeAreaState extends State<KeyboardScaffoldSafeArea> {
11341092
}),
11351093
);
11361094
}
1095+
1096+
double _chooseBottomInsets(BuildContext safeAreaContext) {
1097+
// There's no ancestor KeyboardScaffoldSafeArea, but there might be an ancestor
1098+
// KeyboardScaffoldSafeAreaScope, whose insets we should use.
1099+
final inheritedGeometry = _ancestorSafeAreaScope?.geometry;
1100+
1101+
// Either use the ancestor geometry, or use our own.
1102+
final keyboardSafeArea = inheritedGeometry ?? KeyboardScaffoldSafeAreaScope.of(safeAreaContext).geometry;
1103+
1104+
// Get the current keyboard safe area bottom insets, and then adjust that
1105+
// value based on our global bottom y-value. When this widget appears at
1106+
// the very bottom of the screen, this adjustment will be zero (no change),
1107+
// but when this widget sits somewhere above the bottom of the screen, we
1108+
// need to account for that extra space between us and the keyboard that's
1109+
// coming up from the bottom of the screen.
1110+
var bottomInsets = keyboardSafeArea.bottomInsets;
1111+
if (_myBoxKey.currentContext != null && _myBoxKey.currentContext!.findRenderObject() != null) {
1112+
final myBox = _myBoxKey.currentContext!.findRenderObject() as RenderBox;
1113+
final myGlobalBottom = myBox.localToGlobal(Offset(0, myBox.size.height)).dy;
1114+
if (myGlobalBottom.isNaN) {
1115+
// We've found in a client app that under some unknown circumstances we get NaN
1116+
// from localToGlobal(). We're not sure why. In that case, log a warning and return zero.
1117+
keyboardPanelLog.warning(
1118+
"KeyboardScaffoldSafeArea (${widget.debugLabel}) - Tried to measure our global bottom offset on the screen but received NaN from localToGlobal(). If you're able to consistently reproduce this problem, please report it to Super Editor with the repro steps.",
1119+
);
1120+
return 0;
1121+
}
1122+
if (myGlobalBottom.isNegative) {
1123+
// We haven't seen negative values here, but if we ever did receive one then our
1124+
// Padding widget would blow up. Return zero to be base.
1125+
keyboardPanelLog.warning(
1126+
"KeyboardScaffoldSafeArea (${widget.debugLabel}) - Tried to measure our global bottom offset on the screen but received a negative y-value from localToGlobal(). If you're able to consistently reproduce this problem, please report it to Super Editor with the repro steps.",
1127+
);
1128+
return 0;
1129+
}
1130+
1131+
final spaceBelowMe = MediaQuery.sizeOf(safeAreaContext).height - myGlobalBottom;
1132+
1133+
// The bottom insets are measured from the bottom of the screen. But we might not
1134+
// be sitting at the bottom of the screen. There might be some space beneath us.
1135+
// In that case, we don't need to push as far up. Remove the space below us from
1136+
// the bottom insets.
1137+
bottomInsets = max(bottomInsets - spaceBelowMe, 0);
1138+
} else {
1139+
// This is our first widget build and we need to adjust our insets
1140+
// after initial layout.
1141+
//
1142+
// Note: We have a frame of lag because our inset spacing is based on other
1143+
// layout results. As a result, if the content below us animates a height
1144+
// change, such as a widget in a `SafeArea` where bottom `padding` animates
1145+
// up/down, our content will jitter as it plays catchup one frame behind.
1146+
//
1147+
// The only solution I can think of that might truly solve this is to use
1148+
// a Leader and Follower in some way. That way positioning occurs as late
1149+
// as possible.
1150+
WidgetsBinding.instance.addPostFrameCallback((_) {
1151+
if (!mounted) {
1152+
return;
1153+
}
1154+
1155+
setState(() {
1156+
// Re-run build.
1157+
});
1158+
});
1159+
}
1160+
1161+
return bottomInsets;
1162+
}
11371163
}
11381164

11391165
abstract interface class KeyboardScaffoldSafeAreaMutator {

0 commit comments

Comments
 (0)