@@ -17,10 +17,11 @@ import 'autocomplete.dart';
17
17
import 'dialog.dart' ;
18
18
import 'icons.dart' ;
19
19
import 'store.dart' ;
20
+ import 'text.dart' ;
20
21
import 'theme.dart' ;
21
22
22
- const double _inputVerticalPadding = 8 ;
23
- const double _sendButtonSize = 36 ;
23
+ const double _composeButtonWidth = 44 ;
24
+ const double _composeButtonHeight = 42 ;
24
25
25
26
/// A [TextEditingController] for use in the compose box.
26
27
///
@@ -285,32 +286,39 @@ class _ContentInput extends StatelessWidget {
285
286
286
287
@override
287
288
Widget build (BuildContext context) {
288
- ColorScheme colorScheme = Theme .of (context).colorScheme;
289
-
290
- return InputDecorator (
291
- decoration: const InputDecoration (),
292
- child: ConstrainedBox (
293
- constraints: const BoxConstraints (
294
- minHeight: _sendButtonSize - 2 * _inputVerticalPadding,
295
-
296
- // TODO constrain this adaptively (i.e. not hard-coded 200)
297
- maxHeight: 200 ,
298
- ),
299
- child: ComposeAutocomplete (
300
- narrow: narrow,
301
- controller: controller,
302
- focusNode: focusNode,
303
- fieldViewBuilder: (context) {
304
- return TextField (
289
+ final designVariables = DesignVariables .of (context);
290
+ const topPadding = 8.0 ;
291
+ const contentLineHeight = 22.0 ;
292
+
293
+ return ConstrainedBox (
294
+ constraints: const BoxConstraints (
295
+ // Reserve space to fully show the first 7th lines and just partially
296
+ // clip the 8th line, where the height matches the spec of 178 logical
297
+ // pixels. The partial line hints that the content input is scrollable.
298
+ maxHeight: topPadding + contentLineHeight * 7 + contentLineHeight * 0.727 ),
299
+ child: ComposeAutocomplete (
300
+ narrow: narrow,
301
+ controller: controller,
302
+ focusNode: focusNode,
303
+ fieldViewBuilder: (context) => SingleChildScrollView (
304
+ // While the [TextField] is scrollable, we need to wrap it with
305
+ // [SingleChildScrollView] to prepend a fixed-height padding that can
306
+ // be scrolled along with the text.
307
+ child: Padding (
308
+ padding: const EdgeInsets .only (top: topPadding),
309
+ child: TextField (
305
310
controller: controller,
306
311
focusNode: focusNode,
307
- style: TextStyle (color: colorScheme.onSurface),
308
- decoration: InputDecoration .collapsed (hintText: hintText),
312
+ decoration: InputDecoration .collapsed (
313
+ hintText: hintText,
314
+ hintStyle: TextStyle (color: designVariables.textInput.withOpacity (0.5 ))),
315
+ minLines: 2 ,
309
316
maxLines: null ,
310
317
textCapitalization: TextCapitalization .sentences,
311
- );
312
- }),
313
- ));
318
+ style: TextStyle (
319
+ fontSize: 17 ,
320
+ height: (contentLineHeight / 17 ),
321
+ color: designVariables.textInput))))));
314
322
}
315
323
}
316
324
@@ -391,20 +399,42 @@ class _TopicInput extends StatelessWidget {
391
399
392
400
@override
393
401
Widget build (BuildContext context) {
402
+ const textFieldHeight = 42 ;
403
+ const lineHeight = 22 ;
394
404
final zulipLocalizations = ZulipLocalizations .of (context);
395
- ColorScheme colorScheme = Theme .of (context).colorScheme;
405
+ final designVariables = DesignVariables .of (context);
406
+ TextStyle topicTextStyle = TextStyle (
407
+ fontSize: 22 ,
408
+ height: lineHeight / 22 ,
409
+ color: designVariables.textInput,
410
+ ).merge (weightVariableTextStyle (context, wght: 600 ));
396
411
397
412
return TopicAutocomplete (
398
413
streamId: streamId,
399
414
controller: controller,
400
415
focusNode: focusNode,
401
416
contentFocusNode: contentFocusNode,
402
- fieldViewBuilder: (context) => TextField (
403
- controller: controller,
404
- focusNode: focusNode,
405
- textInputAction: TextInputAction .next,
406
- style: TextStyle (color: colorScheme.onSurface),
407
- decoration: InputDecoration (hintText: zulipLocalizations.composeBoxTopicHintText),
417
+ fieldViewBuilder: (context) => Stack (
418
+ children: [
419
+ TextField (
420
+ controller: controller,
421
+ focusNode: focusNode,
422
+ textInputAction: TextInputAction .next,
423
+ style: topicTextStyle,
424
+ decoration: InputDecoration (
425
+ isDense: true ,
426
+ contentPadding: const EdgeInsets .symmetric (
427
+ vertical: (textFieldHeight - lineHeight) / 2 ),
428
+ border: InputBorder .none,
429
+ hintText: zulipLocalizations.composeBoxTopicHintText,
430
+ hintStyle: topicTextStyle.copyWith (
431
+ color: designVariables.textInput.withOpacity (0.5 )))),
432
+ Positioned (bottom: 0 , left: 0 , right: 0 ,
433
+ child: Container (height: 1 , decoration: BoxDecoration (
434
+ border: Border (
435
+ bottom: BorderSide (width: 1 ,
436
+ color: designVariables.foreground.withOpacity (0.2 )))))),
437
+ ],
408
438
));
409
439
}
410
440
}
@@ -578,10 +608,13 @@ abstract class _AttachUploadsButton extends StatelessWidget {
578
608
@override
579
609
Widget build (BuildContext context) {
580
610
final zulipLocalizations = ZulipLocalizations .of (context);
581
- return IconButton (
582
- icon: Icon (icon),
583
- tooltip: tooltip (zulipLocalizations),
584
- onPressed: () => _handlePress (context));
611
+ return SizedBox (
612
+ width: _composeButtonWidth,
613
+ child: IconButton (
614
+ icon: Icon (icon),
615
+ tooltip: tooltip (zulipLocalizations),
616
+ onPressed: () => _handlePress (context),
617
+ style: const ButtonStyle (splashFactory: NoSplash .splashFactory)));
585
618
}
586
619
}
587
620
@@ -841,39 +874,20 @@ class _SendButtonState extends State<_SendButton> {
841
874
842
875
@override
843
876
Widget build (BuildContext context) {
844
- final disabled = _hasValidationErrors;
845
- final colorScheme = Theme .of (context).colorScheme;
877
+ final designVariables = DesignVariables .of (context);
846
878
final zulipLocalizations = ZulipLocalizations .of (context);
847
879
848
- // Copy FilledButton defaults (_FilledButtonDefaultsM3.backgroundColor)
849
- final backgroundColor = disabled
850
- ? colorScheme.onSurface.withOpacity (0.12 )
851
- : colorScheme.primary;
852
-
853
- // Copy FilledButton defaults (_FilledButtonDefaultsM3.foregroundColor)
854
- final foregroundColor = disabled
855
- ? colorScheme.onSurface.withOpacity (0.38 )
856
- : colorScheme.onPrimary;
857
-
858
- return Ink (
859
- decoration: BoxDecoration (
860
- borderRadius: const BorderRadius .all (Radius .circular (8.0 )),
861
- color: backgroundColor,
862
- ),
880
+ return SizedBox (
881
+ width: _composeButtonWidth,
863
882
child: IconButton (
864
883
tooltip: zulipLocalizations.composeBoxSendTooltip,
865
- style: const ButtonStyle (
866
- // Match the height of the content input.
867
- minimumSize: WidgetStatePropertyAll (Size .square (_sendButtonSize)),
868
- // With the default of [MaterialTapTargetSize.padded], not just the
869
- // tap target but the visual button would get padded to 48px square.
870
- // It would be nice if the tap target extended invisibly out from the
871
- // button, to make a 48px square, but that's not the behavior we get.
872
- tapTargetSize: MaterialTapTargetSize .shrinkWrap,
873
- ),
874
- color: foregroundColor,
884
+ color: _hasValidationErrors
885
+ // TODO(design): need send button color when disabled
886
+ ? designVariables.icon.withOpacity (0.5 )
887
+ : designVariables.icon,
875
888
icon: const Icon (ZulipIcons .send),
876
- onPressed: _send));
889
+ onPressed: _send,
890
+ style: const ButtonStyle (splashFactory: NoSplash .splashFactory)));
877
891
}
878
892
}
879
893
@@ -884,18 +898,16 @@ class _ComposeBoxContainer extends StatelessWidget {
884
898
885
899
@override
886
900
Widget build (BuildContext context) {
887
- ColorScheme colorScheme = Theme .of (context).colorScheme ;
901
+ final designVariables = DesignVariables .of (context);
888
902
889
903
// TODO(design): Maybe put a max width on the compose box, like we do on
890
904
// the message list itself
891
- return SizedBox (width: double .infinity,
905
+ return Container (width: double .infinity,
906
+ decoration: BoxDecoration (
907
+ border: Border (top: BorderSide (color: designVariables.borderBar))),
892
908
child: Material (
893
- color: colorScheme.surfaceContainerHighest,
894
- child: SafeArea (
895
- minimum: const EdgeInsets .fromLTRB (8 , 0 , 8 , 8 ),
896
- child: Padding (
897
- padding: const EdgeInsets .only (top: 8.0 ),
898
- child: child))));
909
+ color: designVariables.bgComposeBox,
910
+ child: SafeArea (child: child)));
899
911
}
900
912
}
901
913
@@ -916,45 +928,32 @@ class _ComposeBoxLayout extends StatelessWidget {
916
928
917
929
@override
918
930
Widget build (BuildContext context) {
919
- ThemeData themeData = Theme .of (context);
920
- ColorScheme colorScheme = themeData.colorScheme;
921
-
922
- final inputThemeData = themeData.copyWith (
923
- inputDecorationTheme: InputDecorationTheme (
924
- // Both [contentPadding] and [isDense] combine to make the layout compact.
925
- isDense: true ,
926
- contentPadding: const EdgeInsets .symmetric (
927
- horizontal: 12.0 , vertical: _inputVerticalPadding),
928
- border: const OutlineInputBorder (
929
- borderRadius: BorderRadius .all (Radius .circular (4.0 )),
930
- borderSide: BorderSide .none),
931
- filled: true ,
932
- fillColor: colorScheme.surface,
933
- ),
934
- );
931
+ final themeData = Theme .of (context);
932
+ final designVariables = DesignVariables .of (context);
935
933
936
934
return _ComposeBoxContainer (
937
935
child: Column (children: [
938
- Row (crossAxisAlignment: CrossAxisAlignment .end, children: [
939
- Expanded (
940
- child: Theme (
941
- data: inputThemeData,
942
- child: Column (children: [
943
- if (topicInput != null ) topicInput! ,
944
- if (topicInput != null ) const SizedBox (height: 8 ),
945
- contentInput,
946
- ]))),
947
- const SizedBox (width: 8 ),
948
- sendButton,
949
- ]),
950
- Theme (
951
- data: themeData.copyWith (
952
- iconTheme: themeData.iconTheme.copyWith (color: colorScheme.onSurface.withOpacity (0.5 ))),
953
- child: Row (children: [
954
- _AttachFileButton (contentController: contentController, contentFocusNode: contentFocusNode),
955
- _AttachMediaButton (contentController: contentController, contentFocusNode: contentFocusNode),
956
- _AttachFromCameraButton (contentController: contentController, contentFocusNode: contentFocusNode),
957
- ])),
936
+ if (topicInput != null )
937
+ Padding (padding: const EdgeInsets .symmetric (horizontal: 16 ),
938
+ child: topicInput! ),
939
+ Padding (padding: const EdgeInsets .symmetric (horizontal: 16 ),
940
+ child: contentInput),
941
+ Container (
942
+ padding: const EdgeInsets .symmetric (horizontal: 8 ),
943
+ height: _composeButtonHeight,
944
+ child: Row (mainAxisAlignment: MainAxisAlignment .spaceBetween,
945
+ children: [
946
+ Theme (
947
+ data: themeData.copyWith (
948
+ iconTheme: themeData.iconTheme.copyWith (
949
+ color: designVariables.foreground.withOpacity (0.5 ))),
950
+ child: Row (children: [
951
+ _AttachFileButton (contentController: contentController, contentFocusNode: contentFocusNode),
952
+ _AttachMediaButton (contentController: contentController, contentFocusNode: contentFocusNode),
953
+ _AttachFromCameraButton (contentController: contentController, contentFocusNode: contentFocusNode),
954
+ ])),
955
+ sendButton,
956
+ ])),
958
957
]));
959
958
}
960
959
}
0 commit comments