Skip to content

Commit 9492046

Browse files
committed
wip: Support the redesigned layout for the compose box.
TODO: - Figure out a way to modify the underline for topic input. - Check if there are other changes left to be implemented, other than the drop shadow and compose box icons. Signed-off-by: Zixuan James Li <[email protected]>
1 parent c65347e commit 9492046

File tree

2 files changed

+100
-97
lines changed

2 files changed

+100
-97
lines changed

lib/widgets/compose_box.dart

Lines changed: 84 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import 'autocomplete.dart';
1717
import 'dialog.dart';
1818
import 'icons.dart';
1919
import 'store.dart';
20+
import 'text.dart';
2021
import 'theme.dart';
2122

22-
const double _inputVerticalPadding = 8;
2323
const double _sendButtonSize = 36;
2424

2525
/// A [TextEditingController] for use in the compose box.
@@ -285,32 +285,40 @@ class _ContentInput extends StatelessWidget {
285285

286286
@override
287287
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(
288+
final designVariables = DesignVariables.of(context);
289+
const topPadding = 8.0;
290+
const contentLineHeight = 22.0;
291+
292+
return ConstrainedBox(
293+
constraints: const BoxConstraints(
294+
// Reserve space to fully show the first 7th lines and just partially
295+
// clip the 8th line, where the height matches the spec of 170 logical
296+
// pixels. This hints that the content input is scrollable.
297+
maxHeight: topPadding + contentLineHeight * 7 + contentLineHeight * 0.727),
298+
child: ComposeAutocomplete(
299+
narrow: narrow,
300+
controller: controller,
301+
focusNode: focusNode,
302+
fieldViewBuilder: (context) => SingleChildScrollView(
303+
// While the [TextField] is scrollable, we need to wrap it with
304+
// [SingleChildScrollView] to insert a fixed-height padding that can
305+
// be scrolled along with the text.
306+
child: Padding(
307+
padding: const EdgeInsets.only(top: topPadding),
308+
child: TextField(
305309
controller: controller,
306310
focusNode: focusNode,
307-
style: TextStyle(color: colorScheme.onSurface),
308-
decoration: InputDecoration.collapsed(hintText: hintText),
311+
decoration: InputDecoration.collapsed(
312+
border: InputBorder.none,
313+
hintText: hintText,
314+
hintStyle: TextStyle(color: designVariables.textInput.withOpacity(0.5))),
315+
minLines: 2,
309316
maxLines: null,
310317
textCapitalization: TextCapitalization.sentences,
311-
);
312-
}),
313-
));
318+
style: TextStyle(
319+
fontSize: 17,
320+
height: (contentLineHeight / 17),
321+
color: designVariables.textInput))))));
314322
}
315323
}
316324

@@ -392,7 +400,11 @@ class _TopicInput extends StatelessWidget {
392400
@override
393401
Widget build(BuildContext context) {
394402
final zulipLocalizations = ZulipLocalizations.of(context);
395-
ColorScheme colorScheme = Theme.of(context).colorScheme;
403+
final designVariables = DesignVariables.of(context);
404+
TextStyle topicTextStyle = TextStyle(
405+
fontSize: 22,
406+
color: designVariables.textInput,
407+
).merge(weightVariableTextStyle(context, wght: 600));
396408

397409
return TopicAutocomplete(
398410
streamId: streamId,
@@ -403,9 +415,13 @@ class _TopicInput extends StatelessWidget {
403415
controller: controller,
404416
focusNode: focusNode,
405417
textInputAction: TextInputAction.next,
406-
style: TextStyle(color: colorScheme.onSurface),
407-
decoration: InputDecoration(hintText: zulipLocalizations.composeBoxTopicHintText),
408-
));
418+
style: topicTextStyle,
419+
decoration: InputDecoration(
420+
isDense: true,
421+
border: const UnderlineInputBorder(),
422+
contentPadding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
423+
hintStyle: topicTextStyle.copyWith(color: designVariables.textInput.withOpacity(0.5)),
424+
hintText: zulipLocalizations.composeBoxTopicHintText)));
409425
}
410426
}
411427

@@ -842,38 +858,27 @@ class _SendButtonState extends State<_SendButton> {
842858
@override
843859
Widget build(BuildContext context) {
844860
final disabled = _hasValidationErrors;
845-
final colorScheme = Theme.of(context).colorScheme;
846861
final zulipLocalizations = ZulipLocalizations.of(context);
862+
final designVariables = DesignVariables.of(context);
847863

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;
864+
final buttonColor = disabled
865+
? designVariables.icon.withOpacity(0.5)
866+
: designVariables.icon;
857867

858-
return Ink(
859-
decoration: BoxDecoration(
860-
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
861-
color: backgroundColor,
868+
return IconButton(
869+
tooltip: zulipLocalizations.composeBoxSendTooltip,
870+
style: const ButtonStyle(
871+
// Match the height of the content input.
872+
minimumSize: WidgetStatePropertyAll(Size.square(_sendButtonSize)),
873+
// With the default of [MaterialTapTargetSize.padded], not just the
874+
// tap target but the visual button would get padded to 48px square.
875+
// It would be nice if the tap target extended invisibly out from the
876+
// button, to make a 48px square, but that's not the behavior we get.
877+
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
862878
),
863-
child: IconButton(
864-
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,
875-
icon: const Icon(ZulipIcons.send),
876-
onPressed: _send));
879+
color: buttonColor,
880+
icon: const Icon(ZulipIcons.send),
881+
onPressed: _send);
877882
}
878883
}
879884

@@ -884,18 +889,21 @@ class _ComposeBoxContainer extends StatelessWidget {
884889

885890
@override
886891
Widget build(BuildContext context) {
887-
ColorScheme colorScheme = Theme.of(context).colorScheme;
892+
final designVariables = DesignVariables.of(context);
888893

889894
// TODO(design): Maybe put a max width on the compose box, like we do on
890895
// the message list itself
891-
return SizedBox(width: double.infinity,
896+
return Container(width: double.infinity,
897+
decoration: BoxDecoration(
898+
border: Border(top: BorderSide(color: designVariables.borderBar))),
892899
child: Material(
893-
color: colorScheme.surfaceContainerHighest,
900+
color: designVariables.bgComposeBox,
894901
child: SafeArea(
895-
minimum: const EdgeInsets.fromLTRB(8, 0, 8, 8),
896902
child: Padding(
897-
padding: const EdgeInsets.only(top: 8.0),
898-
child: child))));
903+
padding: const EdgeInsets.symmetric(horizontal: 16.0),
904+
child: child,
905+
)),
906+
));
899907
}
900908
}
901909

@@ -916,45 +924,26 @@ class _ComposeBoxLayout extends StatelessWidget {
916924

917925
@override
918926
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-
);
935-
936927
return _ComposeBoxContainer(
937928
child: Column(children: [
938929
Row(crossAxisAlignment: CrossAxisAlignment.end, children: [
939930
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,
931+
child: Column(children: [
932+
if (topicInput != null) topicInput!,
933+
contentInput,
934+
])),
949935
]),
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+
Row(
937+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
938+
children: [
939+
Row(children: [
940+
_AttachFileButton(contentController: contentController, contentFocusNode: contentFocusNode),
941+
_AttachMediaButton(contentController: contentController, contentFocusNode: contentFocusNode),
942+
_AttachFromCameraButton(contentController: contentController, contentFocusNode: contentFocusNode),
943+
]),
944+
sendButton,
945+
],
946+
),
958947
]));
959948
}
960949
}

lib/widgets/theme.dart

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,13 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
110110
bgCounterUnread: const Color(0xff666699).withOpacity(0.15),
111111
bgTopBar: const Color(0xfff5f5f5),
112112
borderBar: const Color(0x33000000),
113-
icon: const Color(0xff666699),
113+
icon: const Color(0xff6159e1),
114114
labelCounterUnread: const Color(0xff222222),
115115
labelMenuButton: const Color(0xff222222),
116116
mainBackground: const Color(0xfff0f0f0),
117117
title: const Color(0xff1a1a1a),
118+
bgComposeBox: const Color(0xffffffff),
119+
textInput: const Color(0xff000000),
118120
channelColorSwatches: ChannelColorSwatches.light,
119121
atMentionMarker: const HSLColor.fromAHSL(0.5, 0, 0, 0.2).toColor(),
120122
dmHeaderBg: const HSLColor.fromAHSL(1, 46, 0.35, 0.93).toColor(),
@@ -138,11 +140,13 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
138140
bgCounterUnread: const Color(0xff666699).withOpacity(0.37),
139141
bgTopBar: const Color(0xff242424),
140142
borderBar: Colors.black.withOpacity(0.41),
141-
icon: const Color(0xff7070c2),
143+
icon: const Color(0xff7977fe),
142144
labelCounterUnread: const Color(0xffffffff).withOpacity(0.7),
143145
labelMenuButton: const Color(0xffffffff).withOpacity(0.85),
144146
mainBackground: const Color(0xff1d1d1d),
145147
title: const Color(0xffffffff),
148+
bgComposeBox: const Color(0xff0f0f0f),
149+
textInput: const Color(0xffffffff).withOpacity(0.9),
146150
channelColorSwatches: ChannelColorSwatches.dark,
147151
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
148152
atMentionMarker: const HSLColor.fromAHSL(0.4, 0, 0, 1).toColor(),
@@ -177,6 +181,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
177181
required this.labelMenuButton,
178182
required this.mainBackground,
179183
required this.title,
184+
required this.bgComposeBox,
185+
required this.textInput,
180186
required this.channelColorSwatches,
181187
required this.atMentionMarker,
182188
required this.dmHeaderBg,
@@ -213,6 +219,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
213219
final Color labelMenuButton;
214220
final Color mainBackground;
215221
final Color title;
222+
final Color bgComposeBox;
223+
final Color textInput;
216224

217225
// Not exactly from the Figma design, but from Vlad anyway.
218226
final ChannelColorSwatches channelColorSwatches;
@@ -244,6 +252,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
244252
Color? labelMenuButton,
245253
Color? mainBackground,
246254
Color? title,
255+
Color? bgComposeBox,
256+
Color? textInput,
247257
ChannelColorSwatches? channelColorSwatches,
248258
Color? atMentionMarker,
249259
Color? dmHeaderBg,
@@ -270,6 +280,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
270280
labelMenuButton: labelMenuButton ?? this.labelMenuButton,
271281
mainBackground: mainBackground ?? this.mainBackground,
272282
title: title ?? this.title,
283+
bgComposeBox: bgComposeBox ?? this.bgComposeBox,
284+
textInput: textInput ?? this.textInput,
273285
channelColorSwatches: channelColorSwatches ?? this.channelColorSwatches,
274286
atMentionMarker: atMentionMarker ?? this.atMentionMarker,
275287
dmHeaderBg: dmHeaderBg ?? this.dmHeaderBg,
@@ -303,6 +315,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
303315
labelMenuButton: Color.lerp(labelMenuButton, other.labelMenuButton, t)!,
304316
mainBackground: Color.lerp(mainBackground, other.mainBackground, t)!,
305317
title: Color.lerp(title, other.title, t)!,
318+
bgComposeBox: Color.lerp(bgComposeBox, other.bgComposeBox, t)!,
319+
textInput: Color.lerp(textInput, other.textInput, t)!,
306320
channelColorSwatches: ChannelColorSwatches.lerp(channelColorSwatches, other.channelColorSwatches, t),
307321
atMentionMarker: Color.lerp(atMentionMarker, other.atMentionMarker, t)!,
308322
dmHeaderBg: Color.lerp(dmHeaderBg, other.dmHeaderBg, t)!,

0 commit comments

Comments
 (0)