Skip to content

Commit 8c2469a

Browse files
committed
compose_box: Cast inset shadow for scrollable contents
The shadow is always present, but the overlay is only visible when there is text under it. This only happens when the TextField is long enough to be scrollable. See also: - https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3954-13395 - https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3862-14350 Fixes: zulip#915 Signed-off-by: Zixuan James Li <[email protected]>
1 parent 52e2495 commit 8c2469a

File tree

1 file changed

+48
-19
lines changed

1 file changed

+48
-19
lines changed

lib/widgets/compose_box.dart

+48-19
Original file line numberDiff line numberDiff line change
@@ -308,25 +308,54 @@ class _ContentInput extends StatelessWidget {
308308
narrow: narrow,
309309
controller: controller,
310310
focusNode: focusNode,
311-
fieldViewBuilder: (context) => TextField(
312-
controller: controller,
313-
focusNode: focusNode,
314-
// Not clipping content input with [TextField] gives us fine
315-
// control over the clipping behavior. Otherwise, the non-zero
316-
// vertical `contentPadding` would cause the text to be clipped
317-
// by a rectangle shorter than the compose box.
318-
clipBehavior: Clip.none,
319-
style: TextStyle(
320-
fontSize: 17,
321-
height: (contentLineHeight / 17),
322-
color: designVariables.textInput),
323-
maxLines: null,
324-
textCapitalization: TextCapitalization.sentences,
325-
decoration: InputDecoration(
326-
contentPadding: const EdgeInsets.symmetric(vertical: verticalPadding),
327-
hintText: hintText,
328-
hintStyle: TextStyle(
329-
color: designVariables.textInput.withValues(alpha: 0.5)))))));
311+
fieldViewBuilder: (context) => _InsetShadowBox(
312+
color: designVariables.composeBoxBg,
313+
child: TextField(
314+
controller: controller,
315+
focusNode: focusNode,
316+
// Not clipping content input with [TextField] gives us fine
317+
// control over the clipping behavior. Otherwise, the non-zero
318+
// vertical `contentPadding` would cause the text to be clipped
319+
// by a rectangle shorter than the compose box.
320+
clipBehavior: Clip.none,
321+
style: TextStyle(
322+
fontSize: 17,
323+
height: (contentLineHeight / 17),
324+
color: designVariables.textInput),
325+
maxLines: null,
326+
textCapitalization: TextCapitalization.sentences,
327+
decoration: InputDecoration(
328+
contentPadding: const EdgeInsets.symmetric(vertical: verticalPadding),
329+
hintText: hintText,
330+
hintStyle: TextStyle(
331+
color: designVariables.textInput.withValues(alpha: 0.5))))))));
332+
}
333+
}
334+
335+
/// A widget that overlays inset shadows on a child.
336+
class _InsetShadowBox extends StatelessWidget {
337+
const _InsetShadowBox({required this.color, required this.child});
338+
339+
/// The shadow color to fade into transparency from the top and bottom borders.
340+
final Color color;
341+
final Widget child;
342+
343+
BoxDecoration _shadowFrom(AlignmentGeometry begin) {
344+
return BoxDecoration(gradient: LinearGradient(
345+
begin: begin, end: -begin,
346+
colors: [color, color.withValues(alpha: 0)]));
347+
}
348+
349+
@override
350+
Widget build(BuildContext context) {
351+
return Stack(
352+
children: [
353+
child,
354+
Positioned(top: 0, left: 0, right: 0,
355+
child: Container(height: 8, decoration: _shadowFrom(Alignment.topCenter))),
356+
Positioned(bottom: 0, left: 0, right: 0,
357+
child: Container(height: 8, decoration: _shadowFrom(Alignment.bottomCenter))),
358+
]);
330359
}
331360
}
332361

0 commit comments

Comments
 (0)