diff --git a/lib/widgets/compose_box.dart b/lib/widgets/compose_box.dart index 72792e6796..e21d00f2e4 100644 --- a/lib/widgets/compose_box.dart +++ b/lib/widgets/compose_box.dart @@ -17,6 +17,7 @@ import 'autocomplete.dart'; import 'color.dart'; import 'dialog.dart'; import 'icons.dart'; +import 'inset_shadow.dart'; import 'store.dart'; import 'text.dart'; import 'theme.dart'; @@ -287,30 +288,65 @@ class _ContentInput extends StatelessWidget { @override Widget build(BuildContext context) { - ColorScheme colorScheme = Theme.of(context).colorScheme; - - return InputDecorator( - decoration: const InputDecoration(), - child: ConstrainedBox( - constraints: const BoxConstraints( - // TODO constrain this adaptively (i.e. not hard-coded 200) - maxHeight: 200, - ), - child: ComposeAutocomplete( - narrow: narrow, - controller: controller, - focusNode: focusNode, - fieldViewBuilder: (context) { - return TextField( + const verticalPadding = 8.0; + const contentLineHeight = 22.0; + // Prevent the content input from getting too tall, + // otherwise it might overflow at the bottom, + // pushing compose buttons and the send button out of screen. + final textScaler = MediaQuery.textScalerOf(context).clamp(maxScaleFactor: 1.5); + + final designVariables = DesignVariables.of(context); + + return ComposeAutocomplete( + narrow: narrow, + controller: controller, + focusNode: focusNode, + fieldViewBuilder: (context) => ConstrainedBox( + constraints: BoxConstraints( + // Reserve space to fully show the first 7th lines and just partially + // clip the 8th line, where the height matches the spec at + // https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3960-5147&node-type=text&m=dev + // > Maximum size of the compose box is suggested to be 178px. Which + // > has 7 fully visible lines of text + // + // The partial line hints that the content input is scrollable. + maxHeight: verticalPadding + textScaler.scale( + contentLineHeight * 7 + contentLineHeight * 0.727)), + child: ClipRect( + child: InsetShadowBox( + top: verticalPadding, bottom: verticalPadding, + color: designVariables.composeBoxBg, + child: TextField( controller: controller, focusNode: focusNode, - style: TextStyle(color: colorScheme.onSurface), - decoration: InputDecoration.collapsed(hintText: hintText), + // Let the content show through the `contentPadding` so that + // our [InsetShadowBox] can fade it smoothly there. + clipBehavior: Clip.none, + style: TextStyle( + fontSize: 17, + height: (contentLineHeight / 17), + color: designVariables.textInput), + // From the spec at + // https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3960-5147&node-type=text&m=dev + // > Compose box has the height to fit 2 lines. This is [done] to + // > have a bigger hit area for the user to start the input. […] + minLines: 2, maxLines: null, textCapitalization: TextCapitalization.sentences, - ); - }), - )); + decoration: InputDecoration( + // This padding ensures that the user can always scroll long + // content entirely out of the top or bottom shadow if desired. + // With this and the `minLines: 2` above, an empty content input + // gets 60px vertical distance between the top of the top shadow + // and the bottom of the bottom shadow (with no text-size + // scaling). That's a bit more than the 54px given in the Figma, + // and we can revisit if needed, but it's tricky to get that + // 54px distance while also making the scrolling work like this + // and offering two lines of touchable area. + contentPadding: const EdgeInsets.symmetric(vertical: verticalPadding), + hintText: hintText, + hintStyle: TextStyle( + color: designVariables.textInput.withValues(alpha: 0.5)))))))); } }