@@ -14,9 +14,12 @@ import '../model/compose.dart';
14
14
import '../model/narrow.dart' ;
15
15
import '../model/store.dart' ;
16
16
import 'autocomplete.dart' ;
17
+ import 'color.dart' ;
17
18
import 'dialog.dart' ;
18
19
import 'icons.dart' ;
20
+ import 'inset_shadow.dart' ;
19
21
import 'store.dart' ;
22
+ import 'text.dart' ;
20
23
import 'theme.dart' ;
21
24
22
25
const double _composeButtonWidth = 44 ;
@@ -364,32 +367,76 @@ class _ContentInputState extends State<_ContentInput> with WidgetsBindingObserve
364
367
}
365
368
}
366
369
370
+ static double maxHeight (BuildContext context) {
371
+ final clampingTextScaler = MediaQuery .textScalerOf (context)
372
+ .clamp (maxScaleFactor: 1.5 );
373
+ final scaledLineHeight = clampingTextScaler.scale (fontSize) * lineHeightRatio;
374
+
375
+ // Reserve space to fully show the first 7th lines and just partially
376
+ // clip the 8th line, where the height matches the spec at
377
+ // https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3960-5147&node-type=text&m=dev
378
+ // > Maximum size of the compose box is suggested to be 178px. Which
379
+ // > has 7 fully visible lines of text
380
+ //
381
+ // The partial line hints that the content input is scrollable.
382
+ //
383
+ // Using the ambient TextScale means this works for different values of the
384
+ // system text-size setting. We clamp to a max scale factor to limit
385
+ // how tall the content input can get; that's to save room for the message
386
+ // list. The user can still scroll the input to see everything.
387
+ return verticalPadding + 7.727 * scaledLineHeight;
388
+ }
389
+
390
+ static const verticalPadding = 8.0 ;
391
+ static const fontSize = 17.0 ;
392
+ static const lineHeight = 22.0 ;
393
+ static const lineHeightRatio = lineHeight / fontSize;
394
+
367
395
@override
368
396
Widget build (BuildContext context) {
369
- ColorScheme colorScheme = Theme .of (context).colorScheme;
370
-
371
- return InputDecorator (
372
- decoration: const InputDecoration (),
373
- child: ConstrainedBox (
374
- constraints: const BoxConstraints (
375
- // TODO constrain this adaptively (i.e. not hard-coded 200)
376
- maxHeight: 200 ,
377
- ),
378
- child: ComposeAutocomplete (
379
- narrow: widget.narrow,
380
- controller: widget.controller,
381
- focusNode: widget.focusNode,
382
- fieldViewBuilder: (context) {
383
- return TextField (
397
+ final designVariables = DesignVariables .of (context);
398
+
399
+ return ComposeAutocomplete (
400
+ narrow: widget.narrow,
401
+ controller: widget.controller,
402
+ focusNode: widget.focusNode,
403
+ fieldViewBuilder: (context) => ConstrainedBox (
404
+ constraints: BoxConstraints (maxHeight: maxHeight (context)),
405
+ child: ClipRect (
406
+ child: InsetShadowBox (
407
+ top: verticalPadding, bottom: verticalPadding,
408
+ color: designVariables.composeBoxBg,
409
+ child: TextField (
384
410
controller: widget.controller,
385
411
focusNode: widget.focusNode,
386
- style: TextStyle (color: colorScheme.onSurface),
387
- decoration: InputDecoration .collapsed (hintText: widget.hintText),
412
+ // Let the content show through the `contentPadding` so that
413
+ // our [InsetShadowBox] can fade it smoothly there.
414
+ clipBehavior: Clip .none,
415
+ style: TextStyle (
416
+ fontSize: fontSize,
417
+ height: lineHeightRatio,
418
+ color: designVariables.textInput),
419
+ // From the spec at
420
+ // https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3960-5147&node-type=text&m=dev
421
+ // > Compose box has the height to fit 2 lines. This is [done] to
422
+ // > have a bigger hit area for the user to start the input. […]
423
+ minLines: 2 ,
388
424
maxLines: null ,
389
425
textCapitalization: TextCapitalization .sentences,
390
- );
391
- }),
392
- ));
426
+ decoration: InputDecoration (
427
+ // This padding ensures that the user can always scroll long
428
+ // content entirely out of the top or bottom shadow if desired.
429
+ // With this and the `minLines: 2` above, an empty content input
430
+ // gets 60px vertical distance between the top of the top shadow
431
+ // and the bottom of the bottom shadow (with no text-size
432
+ // scaling). That's a bit more than the 54px given in the Figma,
433
+ // and we can revisit if needed, but it's tricky to get that
434
+ // 54px distance while also making the scrolling work like this
435
+ // and offering two lines of touchable area.
436
+ contentPadding: const EdgeInsets .symmetric (vertical: verticalPadding),
437
+ hintText: widget.hintText,
438
+ hintStyle: TextStyle (
439
+ color: designVariables.textInput.withFadedAlpha (0.5 ))))))));
393
440
}
394
441
}
395
442
@@ -472,20 +519,39 @@ class _TopicInput extends StatelessWidget {
472
519
@override
473
520
Widget build (BuildContext context) {
474
521
final zulipLocalizations = ZulipLocalizations .of (context);
475
- ColorScheme colorScheme = Theme .of (context).colorScheme;
522
+ final designVariables = DesignVariables .of (context);
523
+ TextStyle topicTextStyle = TextStyle (
524
+ fontSize: 20 ,
525
+ height: 22 / 20 ,
526
+ color: designVariables.textInput.withFadedAlpha (0.9 ),
527
+ ).merge (weightVariableTextStyle (context, wght: 600 ));
476
528
477
529
return TopicAutocomplete (
478
530
streamId: streamId,
479
531
controller: controller,
480
532
focusNode: focusNode,
481
533
contentFocusNode: contentFocusNode,
482
- fieldViewBuilder: (context) => TextField (
483
- controller: controller,
484
- focusNode: focusNode,
485
- textInputAction: TextInputAction .next,
486
- style: TextStyle (color: colorScheme.onSurface),
487
- decoration: InputDecoration (hintText: zulipLocalizations.composeBoxTopicHintText),
488
- ));
534
+ fieldViewBuilder: (context) => Column (
535
+ mainAxisSize: MainAxisSize .min,
536
+ children: [
537
+ Padding (
538
+ padding: const EdgeInsets .symmetric (vertical: 10 ),
539
+ child: TextField (
540
+ controller: controller,
541
+ focusNode: focusNode,
542
+ textInputAction: TextInputAction .next,
543
+ style: topicTextStyle,
544
+ decoration: InputDecoration (
545
+ hintText: zulipLocalizations.composeBoxTopicHintText,
546
+ hintStyle: topicTextStyle.copyWith (
547
+ color: designVariables.textInput.withFadedAlpha (0.5 ))))),
548
+ SizedBox (height: 0 , width: double .infinity,
549
+ child: DecoratedBox (decoration: BoxDecoration (
550
+ border: Border (
551
+ bottom: BorderSide (
552
+ width: 1 ,
553
+ color: designVariables.foreground.withFadedAlpha (0.2 )))))),
554
+ ]));
489
555
}
490
556
}
491
557
0 commit comments