diff --git a/assets/icons/ZulipIcons.ttf b/assets/icons/ZulipIcons.ttf
index eb79620203..0f902b96fc 100644
Binary files a/assets/icons/ZulipIcons.ttf and b/assets/icons/ZulipIcons.ttf differ
diff --git a/assets/icons/copy.svg b/assets/icons/copy.svg
new file mode 100644
index 0000000000..074b231add
--- /dev/null
+++ b/assets/icons/copy.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/format_quote.svg b/assets/icons/format_quote.svg
new file mode 100644
index 0000000000..0de3a768ce
--- /dev/null
+++ b/assets/icons/format_quote.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/share.svg b/assets/icons/share.svg
new file mode 100644
index 0000000000..5031943a1c
--- /dev/null
+++ b/assets/icons/share.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/share_ios.svg b/assets/icons/share_ios.svg
new file mode 100644
index 0000000000..45cf0c81dd
--- /dev/null
+++ b/assets/icons/share_ios.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/smile.svg b/assets/icons/smile.svg
new file mode 100644
index 0000000000..2f9c5b5e74
--- /dev/null
+++ b/assets/icons/smile.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/star.svg b/assets/icons/star.svg
new file mode 100644
index 0000000000..9c5e4069d3
--- /dev/null
+++ b/assets/icons/star.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart
index 8442160d40..d06a57995f 100644
--- a/lib/widgets/action_sheet.dart
+++ b/lib/widgets/action_sheet.dart
@@ -1,5 +1,6 @@
import 'dart:async';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
@@ -14,10 +15,12 @@ import 'actions.dart';
import 'clipboard.dart';
import 'compose_box.dart';
import 'dialog.dart';
-import 'draggable_scrollable_modal_bottom_sheet.dart';
import 'icons.dart';
+import 'inset_shadow.dart';
import 'message_list.dart';
import 'store.dart';
+import 'text.dart';
+import 'theme.dart';
/// Show a sheet of actions you can take on a message in the message list.
///
@@ -43,21 +46,48 @@ void showMessageActionSheet({required BuildContext context, required Message mes
&& reactionWithVotes.userIds.contains(store.selfUserId))
?? false;
- showDraggableScrollableModalBottomSheet(
+ final optionButtons = [
+ if (!hasThumbsUpReactionVote)
+ AddThumbsUpButton(message: message, messageListContext: context),
+ StarButton(message: message, messageListContext: context),
+ if (isComposeBoxOffered)
+ QuoteAndReplyButton(message: message, messageListContext: context),
+ if (showMarkAsUnreadButton)
+ MarkAsUnreadButton(message: message, messageListContext: context, narrow: narrow),
+ CopyMessageTextButton(message: message, messageListContext: context),
+ CopyMessageLinkButton(message: message, messageListContext: context),
+ ShareButton(message: message, messageListContext: context),
+ ];
+
+ showModalBottomSheet(
context: context,
+ // Clip.hardEdge looks bad; Clip.antiAliasWithSaveLayer looks pixel-perfect
+ // on my iPhone 13 Pro but is marked as "much slower":
+ // https://api.flutter.dev/flutter/dart-ui/Clip.html
+ clipBehavior: Clip.antiAlias,
+ useSafeArea: true,
+ isScrollControlled: true,
builder: (BuildContext _) {
- return Column(children: [
- if (!hasThumbsUpReactionVote)
- AddThumbsUpButton(message: message, messageListContext: context),
- StarButton(message: message, messageListContext: context),
- if (isComposeBoxOffered)
- QuoteAndReplyButton(message: message, messageListContext: context),
- if (showMarkAsUnreadButton)
- MarkAsUnreadButton(message: message, messageListContext: context, narrow: narrow),
- CopyMessageTextButton(message: message, messageListContext: context),
- CopyMessageLinkButton(message: message, messageListContext: context),
- ShareButton(message: message, messageListContext: context),
- ]);
+ return SafeArea(
+ minimum: const EdgeInsets.only(bottom: 16),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // TODO(#217): show message text
+ Flexible(child: InsetShadowBox(
+ top: 8, bottom: 8,
+ color: DesignVariables.of(context).bgContextMenu,
+ child: SingleChildScrollView(
+ padding: const EdgeInsets.only(top: 16, bottom: 8),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(7),
+ child: Column(spacing: 1,
+ children: optionButtons))))),
+ const MessageActionSheetCancelButton(),
+ ])));
});
}
@@ -77,11 +107,47 @@ abstract class MessageActionSheetMenuItemButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final designVariables = DesignVariables.of(context);
final zulipLocalizations = ZulipLocalizations.of(context);
return MenuItemButton(
- leadingIcon: Icon(icon),
+ trailingIcon: Icon(icon, color: designVariables.contextMenuItemText),
+ style: MenuItemButton.styleFrom(
+ padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
+ foregroundColor: designVariables.contextMenuItemText,
+ splashFactory: NoSplash.splashFactory,
+ ).copyWith(backgroundColor: WidgetStateColor.resolveWith((states) =>
+ designVariables.contextMenuItemBg.withValues(
+ alpha: states.contains(WidgetState.pressed) ? 0.20 : 0.12))),
onPressed: () => onPressed(context),
- child: Text(label(zulipLocalizations)));
+ child: Text(label(zulipLocalizations),
+ style: const TextStyle(fontSize: 20, height: 24 / 20)
+ .merge(weightVariableTextStyle(context, wght: 600)),
+ ));
+ }
+}
+
+class MessageActionSheetCancelButton extends StatelessWidget {
+ const MessageActionSheetCancelButton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final designVariables = DesignVariables.of(context);
+ return TextButton(
+ style: TextButton.styleFrom(
+ padding: const EdgeInsets.all(10),
+ foregroundColor: designVariables.contextMenuCancelText,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(7)),
+ splashFactory: NoSplash.splashFactory,
+ ).copyWith(backgroundColor: WidgetStateColor.resolveWith((states) =>
+ designVariables.contextMenuCancelBg.withValues(
+ alpha: states.contains(WidgetState.pressed) ? 0.20 : 0.15))),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ child: Text(ZulipLocalizations.of(context).dialogCancel,
+ style: const TextStyle(fontSize: 20, height: 24 / 20)
+ .merge(weightVariableTextStyle(context, wght: 600))),
+ );
}
}
@@ -94,7 +160,7 @@ class AddThumbsUpButton extends MessageActionSheetMenuItemButton {
required super.messageListContext,
});
- @override IconData get icon => Icons.add_reaction_outlined;
+ @override IconData get icon => ZulipIcons.smile;
@override
String label(ZulipLocalizations zulipLocalizations) {
@@ -135,11 +201,13 @@ class StarButton extends MessageActionSheetMenuItemButton {
required super.messageListContext,
});
- @override IconData get icon => ZulipIcons.star_filled;
+ @override IconData get icon => _isStarred ? ZulipIcons.star_filled : ZulipIcons.star;
+
+ bool get _isStarred => message.flags.contains(MessageFlag.starred);
@override
String label(ZulipLocalizations zulipLocalizations) {
- return message.flags.contains(MessageFlag.starred)
+ return _isStarred
? zulipLocalizations.actionSheetOptionUnstarMessage
: zulipLocalizations.actionSheetOptionStarMessage;
}
@@ -231,7 +299,7 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton {
required super.messageListContext,
});
- @override IconData get icon => Icons.format_quote_outlined;
+ @override IconData get icon => ZulipIcons.format_quote;
@override
String label(ZulipLocalizations zulipLocalizations) {
@@ -316,7 +384,7 @@ class CopyMessageTextButton extends MessageActionSheetMenuItemButton {
required super.messageListContext,
});
- @override IconData get icon => Icons.copy;
+ @override IconData get icon => ZulipIcons.copy;
@override
String label(ZulipLocalizations zulipLocalizations) {
@@ -384,7 +452,10 @@ class ShareButton extends MessageActionSheetMenuItemButton {
required super.messageListContext,
});
- @override IconData get icon => Icons.adaptive.share;
+ @override
+ IconData get icon => defaultTargetPlatform == TargetPlatform.iOS
+ ? ZulipIcons.share_ios
+ : ZulipIcons.share;
@override
String label(ZulipLocalizations zulipLocalizations) {
diff --git a/lib/widgets/draggable_scrollable_modal_bottom_sheet.dart b/lib/widgets/draggable_scrollable_modal_bottom_sheet.dart
deleted file mode 100644
index 27f8bfd654..0000000000
--- a/lib/widgets/draggable_scrollable_modal_bottom_sheet.dart
+++ /dev/null
@@ -1,118 +0,0 @@
-import 'package:flutter/material.dart';
-
-class _DraggableScrollableLayer extends StatelessWidget {
- const _DraggableScrollableLayer({required this.builder});
-
- final WidgetBuilder builder;
-
- @override
- Widget build(BuildContext context) {
- return DraggableScrollableSheet(
- // Match `initial…` to `min…` so that a slight drag downward dismisses
- // the sheet instead of just resizing it. Making them equal gives a
- // buggy experience for some reason
- // ( https://github.com/zulip/zulip-flutter/pull/12#discussion_r1116423455 )
- // so we work around by make `initial…` a bit bigger.
- minChildSize: 0.25,
- initialChildSize: 0.26,
-
- // With `expand: true`, the bottom sheet would then start out occupying
- // the whole screen, as if `initialChildSize` was 1.0. That doesn't seem
- // like what the docs call for. Maybe a bug. Or maybe it's somehow
- // related to the `Stack`?
- expand: false,
-
- builder: (BuildContext context, ScrollController scrollController) {
- return SingleChildScrollView(
- // Prevent overscroll animation on swipe down; it looks
- // sloppy when you're swiping to dismiss the sheet.
- physics: const ClampingScrollPhysics(),
-
- controller: scrollController,
-
- child: Padding(
- // Avoid the drag handle. See comment on
- // _DragHandleLayer's SizedBox.height.
- padding: const EdgeInsets.only(top: kMinInteractiveDimension),
-
- // Extend DraggableScrollableSheet to full width so the whole
- // sheet responds to drag/scroll uniformly.
- child: FractionallySizedBox(
- widthFactor: 1.0,
- child: Builder(builder: builder))));
- });
- }
-}
-
-class _DragHandleLayer extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- ColorScheme colorScheme = Theme.of(context).colorScheme;
- return SizedBox(
- // In the spec, this is expressed as 22 logical pixels of top/bottom
- // padding on the drag handle:
- // https://m3.material.io/components/bottom-sheets/specs#e69f3dfb-e443-46ba-b4a8-aabc718cf335
- // The drag handle is specified with height 4 logical pixels, so we can
- // get the same result by vertically centering the handle in a box with
- // height 22 + 4 + 22 = 48. We have another way to say 48 --
- // kMinInteractiveDimension -- which is actually not a bad way to
- // express it, since the feature was announced as "an optional drag
- // handle with an accessible 48dp hit target":
- // https://m3.material.io/components/bottom-sheets/overview#2cce5bae-eb83-40b0-8e52-5d0cfaa9b795
- // As a bonus, that constant is easy to use at the other layer in the
- // Stack where we set the starting position of the sheet's content to
- // avoid the drag handle.
- height: kMinInteractiveDimension,
-
- child: Center(
- child: ClipRRect(
- clipBehavior: Clip.hardEdge,
- borderRadius: const BorderRadius.all(Radius.circular(2)),
- child: SizedBox(
- // height / width / color (including opacity) from this table:
- // https://m3.material.io/components/bottom-sheets/specs#7c093473-d9e1-48f3-9659-b75519c2a29d
- height: 4,
- width: 32,
- child: ColoredBox(color: colorScheme.onSurfaceVariant.withValues(alpha: 0.40))))));
- }
-}
-
-/// Show a modal bottom sheet that drags and scrolls to present lots of content.
-///
-/// Aims to follow Material 3's "bottom sheet" with a drag handle:
-/// https://m3.material.io/components/bottom-sheets/overview
-Future showDraggableScrollableModalBottomSheet({
- required BuildContext context,
- required WidgetBuilder builder,
-}) {
- return showModalBottomSheet(
- context: context,
-
- // Clip.hardEdge looks bad; Clip.antiAliasWithSaveLayer looks pixel-perfect
- // on my iPhone 13 Pro but is marked as "much slower":
- // https://api.flutter.dev/flutter/dart-ui/Clip.html
- clipBehavior: Clip.antiAlias,
-
- // The spec:
- // https://m3.material.io/components/bottom-sheets/specs
- // defines the container's shape with the design token
- // `md.sys.shape.corner.extra-large.top`, which in the table at
- // https://m3.material.io/styles/shape/shape-scale-tokens#6f668ba1-b671-4ea2-bcf3-c1cff4f4099e
- // maps to:
- // 28dp,28dp,0dp,0dp
- // SHAPE_FAMILY_ROUNDED_CORNERS
- shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(28.0))),
-
- useSafeArea: true,
- isScrollControlled: true,
- builder: (BuildContext context) {
- // Make the content start below the drag handle in the y-direction, but
- // when the content is scrollable, let it scroll under the drag handle in
- // the z-direction.
- return Stack(
- children: [
- _DraggableScrollableLayer(builder: builder),
- _DragHandleLayer(),
- ]);
- });
-}
diff --git a/lib/widgets/icons.dart b/lib/widgets/icons.dart
index 23080a2257..ebdb6a362c 100644
--- a/lib/widgets/icons.dart
+++ b/lib/widgets/icons.dart
@@ -42,38 +42,56 @@ abstract final class ZulipIcons {
/// The Zulip custom icon "clock".
static const IconData clock = IconData(0xf106, fontFamily: "Zulip Icons");
+ /// The Zulip custom icon "copy".
+ static const IconData copy = IconData(0xf107, fontFamily: "Zulip Icons");
+
+ /// The Zulip custom icon "format_quote".
+ static const IconData format_quote = IconData(0xf108, fontFamily: "Zulip Icons");
+
/// The Zulip custom icon "globe".
- static const IconData globe = IconData(0xf107, fontFamily: "Zulip Icons");
+ static const IconData globe = IconData(0xf109, fontFamily: "Zulip Icons");
/// The Zulip custom icon "group_dm".
- static const IconData group_dm = IconData(0xf108, fontFamily: "Zulip Icons");
+ static const IconData group_dm = IconData(0xf10a, fontFamily: "Zulip Icons");
/// The Zulip custom icon "hash_sign".
- static const IconData hash_sign = IconData(0xf109, fontFamily: "Zulip Icons");
+ static const IconData hash_sign = IconData(0xf10b, fontFamily: "Zulip Icons");
/// The Zulip custom icon "language".
- static const IconData language = IconData(0xf10a, fontFamily: "Zulip Icons");
+ static const IconData language = IconData(0xf10c, fontFamily: "Zulip Icons");
/// The Zulip custom icon "lock".
- static const IconData lock = IconData(0xf10b, fontFamily: "Zulip Icons");
+ static const IconData lock = IconData(0xf10d, fontFamily: "Zulip Icons");
/// The Zulip custom icon "mute".
- static const IconData mute = IconData(0xf10c, fontFamily: "Zulip Icons");
+ static const IconData mute = IconData(0xf10e, fontFamily: "Zulip Icons");
/// The Zulip custom icon "read_receipts".
- static const IconData read_receipts = IconData(0xf10d, fontFamily: "Zulip Icons");
+ static const IconData read_receipts = IconData(0xf10f, fontFamily: "Zulip Icons");
+
+ /// The Zulip custom icon "share".
+ static const IconData share = IconData(0xf110, fontFamily: "Zulip Icons");
+
+ /// The Zulip custom icon "share_ios".
+ static const IconData share_ios = IconData(0xf111, fontFamily: "Zulip Icons");
+
+ /// The Zulip custom icon "smile".
+ static const IconData smile = IconData(0xf112, fontFamily: "Zulip Icons");
+
+ /// The Zulip custom icon "star".
+ static const IconData star = IconData(0xf113, fontFamily: "Zulip Icons");
/// The Zulip custom icon "star_filled".
- static const IconData star_filled = IconData(0xf10e, fontFamily: "Zulip Icons");
+ static const IconData star_filled = IconData(0xf114, fontFamily: "Zulip Icons");
/// The Zulip custom icon "topic".
- static const IconData topic = IconData(0xf10f, fontFamily: "Zulip Icons");
+ static const IconData topic = IconData(0xf115, fontFamily: "Zulip Icons");
/// The Zulip custom icon "unmute".
- static const IconData unmute = IconData(0xf110, fontFamily: "Zulip Icons");
+ static const IconData unmute = IconData(0xf116, fontFamily: "Zulip Icons");
/// The Zulip custom icon "user".
- static const IconData user = IconData(0xf111, fontFamily: "Zulip Icons");
+ static const IconData user = IconData(0xf117, fontFamily: "Zulip Icons");
// END GENERATED ICON DATA
}
diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart
index 9f1c732f5c..0fe1ac6cfd 100644
--- a/lib/widgets/theme.dart
+++ b/lib/widgets/theme.dart
@@ -88,6 +88,13 @@ ThemeData zulipThemeData(BuildContext context) {
),
scaffoldBackgroundColor: designVariables.mainBackground,
tooltipTheme: const TooltipThemeData(preferBelow: false),
+ bottomSheetTheme: BottomSheetThemeData(
+ clipBehavior: Clip.antiAlias,
+ backgroundColor: designVariables.bgContextMenu,
+ modalBarrierColor: designVariables.modalBarrierColor,
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.vertical(top: Radius.circular(20.0))),
+ ),
);
}
@@ -107,9 +114,13 @@ class DesignVariables extends ThemeExtension {
DesignVariables.light() :
this._(
background: const Color(0xffffffff),
+ bgContextMenu: const Color(0xfff2f2f2),
bgCounterUnread: const Color(0xff666699).withValues(alpha: 0.15),
bgTopBar: const Color(0xfff5f5f5),
borderBar: const Color(0x33000000),
+ contextMenuCancelText: const Color(0xff222222),
+ contextMenuItemBg: const Color(0xff6159e1),
+ contextMenuItemText: const Color(0xff381da7),
icon: const Color(0xff666699),
labelCounterUnread: const Color(0xff222222),
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 0).toColor(),
@@ -118,6 +129,7 @@ class DesignVariables extends ThemeExtension {
title: const Color(0xff1a1a1a),
channelColorSwatches: ChannelColorSwatches.light,
atMentionMarker: const HSLColor.fromAHSL(0.5, 0, 0, 0.2).toColor(),
+ contextMenuCancelBg: const Color(0xff797986),
dmHeaderBg: const HSLColor.fromAHSL(1, 46, 0.35, 0.93).toColor(),
errorBannerBackground: const HSLColor.fromAHSL(1, 4, 0.33, 0.90).toColor(),
errorBannerBorder: const HSLColor.fromAHSL(0.4, 3, 0.57, 0.33).toColor(),
@@ -126,6 +138,7 @@ class DesignVariables extends ThemeExtension {
groupDmConversationIconBg: const Color(0x33808080),
loginOrDivider: const Color(0xffdedede),
loginOrDividerText: const Color(0xff575757),
+ modalBarrierColor: const Color(0xff000000).withValues(alpha: 0.3),
mutedUnreadBadge: const HSLColor.fromAHSL(0.5, 0, 0, 0.8).toColor(),
sectionCollapseIcon: const Color(0x7f1e2e48),
star: const HSLColor.fromAHSL(0.5, 47, 1, 0.41).toColor(),
@@ -137,9 +150,13 @@ class DesignVariables extends ThemeExtension {
DesignVariables.dark() :
this._(
background: const Color(0xff000000),
+ bgContextMenu: const Color(0xff262626),
bgCounterUnread: const Color(0xff666699).withValues(alpha: 0.37),
bgTopBar: const Color(0xff242424),
borderBar: Colors.black.withValues(alpha: 0.41),
+ contextMenuCancelText: const Color(0xffffffff).withValues(alpha: 0.75),
+ contextMenuItemBg: const Color(0xff7977fe),
+ contextMenuItemText: const Color(0xff9398fd),
icon: const Color(0xff7070c2),
labelCounterUnread: const Color(0xffffffff).withValues(alpha: 0.7),
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 1).toColor(),
@@ -147,6 +164,7 @@ class DesignVariables extends ThemeExtension {
mainBackground: const Color(0xff1d1d1d),
title: const Color(0xffffffff),
channelColorSwatches: ChannelColorSwatches.dark,
+ contextMenuCancelBg: const Color(0xff797986), // the same as the light mode in Figma
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
atMentionMarker: const HSLColor.fromAHSL(0.4, 0, 0, 1).toColor(),
dmHeaderBg: const HSLColor.fromAHSL(1, 46, 0.15, 0.2).toColor(),
@@ -159,6 +177,7 @@ class DesignVariables extends ThemeExtension {
groupDmConversationIconBg: const Color(0x33cccccc),
loginOrDivider: const Color(0xff424242),
loginOrDividerText: const Color(0xffa8a8a8),
+ modalBarrierColor: const Color(0xff000000).withValues(alpha: 0.5),
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
mutedUnreadBadge: const HSLColor.fromAHSL(0.5, 0, 0, 0.6).toColor(),
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
@@ -174,9 +193,13 @@ class DesignVariables extends ThemeExtension {
DesignVariables._({
required this.background,
+ required this.bgContextMenu,
required this.bgCounterUnread,
required this.bgTopBar,
required this.borderBar,
+ required this.contextMenuCancelText,
+ required this.contextMenuItemBg,
+ required this.contextMenuItemText,
required this.icon,
required this.labelCounterUnread,
required this.labelEdited,
@@ -185,6 +208,7 @@ class DesignVariables extends ThemeExtension {
required this.title,
required this.channelColorSwatches,
required this.atMentionMarker,
+ required this.contextMenuCancelBg,
required this.dmHeaderBg,
required this.errorBannerBackground,
required this.errorBannerBorder,
@@ -193,6 +217,7 @@ class DesignVariables extends ThemeExtension {
required this.groupDmConversationIconBg,
required this.loginOrDivider,
required this.loginOrDividerText,
+ required this.modalBarrierColor,
required this.mutedUnreadBadge,
required this.sectionCollapseIcon,
required this.star,
@@ -212,9 +237,13 @@ class DesignVariables extends ThemeExtension {
}
final Color background;
+ final Color bgContextMenu;
final Color bgCounterUnread;
final Color bgTopBar;
final Color borderBar;
+ final Color contextMenuCancelText;
+ final Color contextMenuItemBg;
+ final Color contextMenuItemText;
final Color icon;
final Color labelCounterUnread;
final Color labelEdited;
@@ -227,6 +256,7 @@ class DesignVariables extends ThemeExtension {
// Not named variables in Figma; taken from older Figma drafts, or elsewhere.
final Color atMentionMarker;
+ final Color contextMenuCancelBg; // In Figma, but unnamed.
final Color dmHeaderBg;
final Color errorBannerBackground;
final Color errorBannerBorder;
@@ -235,6 +265,7 @@ class DesignVariables extends ThemeExtension {
final Color groupDmConversationIconBg;
final Color loginOrDivider; // TODO(design-dark) need proper dark-theme color (this is ad hoc)
final Color loginOrDividerText; // TODO(design-dark) need proper dark-theme color (this is ad hoc)
+ final Color modalBarrierColor;
final Color mutedUnreadBadge;
final Color sectionCollapseIcon;
final Color star;
@@ -245,9 +276,13 @@ class DesignVariables extends ThemeExtension {
@override
DesignVariables copyWith({
Color? background,
+ Color? bgContextMenu,
Color? bgCounterUnread,
Color? bgTopBar,
Color? borderBar,
+ Color? contextMenuCancelText,
+ Color? contextMenuItemBg,
+ Color? contextMenuItemText,
Color? icon,
Color? labelCounterUnread,
Color? labelEdited,
@@ -256,6 +291,7 @@ class DesignVariables extends ThemeExtension {
Color? title,
ChannelColorSwatches? channelColorSwatches,
Color? atMentionMarker,
+ Color? contextMenuCancelBg,
Color? dmHeaderBg,
Color? errorBannerBackground,
Color? errorBannerBorder,
@@ -264,6 +300,7 @@ class DesignVariables extends ThemeExtension {
Color? groupDmConversationIconBg,
Color? loginOrDivider,
Color? loginOrDividerText,
+ Color? modalBarrierColor,
Color? mutedUnreadBadge,
Color? sectionCollapseIcon,
Color? star,
@@ -273,9 +310,13 @@ class DesignVariables extends ThemeExtension {
}) {
return DesignVariables._(
background: background ?? this.background,
+ bgContextMenu: bgContextMenu ?? this.bgContextMenu,
bgCounterUnread: bgCounterUnread ?? this.bgCounterUnread,
bgTopBar: bgTopBar ?? this.bgTopBar,
borderBar: borderBar ?? this.borderBar,
+ contextMenuCancelText: contextMenuCancelText ?? this.contextMenuCancelText,
+ contextMenuItemBg: contextMenuItemBg ?? this.contextMenuItemBg,
+ contextMenuItemText: contextMenuItemText ?? this.contextMenuItemBg,
icon: icon ?? this.icon,
labelCounterUnread: labelCounterUnread ?? this.labelCounterUnread,
labelEdited: labelEdited ?? this.labelEdited,
@@ -284,6 +325,7 @@ class DesignVariables extends ThemeExtension {
title: title ?? this.title,
channelColorSwatches: channelColorSwatches ?? this.channelColorSwatches,
atMentionMarker: atMentionMarker ?? this.atMentionMarker,
+ contextMenuCancelBg: contextMenuCancelBg ?? this.contextMenuCancelBg,
dmHeaderBg: dmHeaderBg ?? this.dmHeaderBg,
errorBannerBackground: errorBannerBackground ?? this.errorBannerBackground,
errorBannerBorder: errorBannerBorder ?? this.errorBannerBorder,
@@ -292,6 +334,7 @@ class DesignVariables extends ThemeExtension {
groupDmConversationIconBg: groupDmConversationIconBg ?? this.groupDmConversationIconBg,
loginOrDivider: loginOrDivider ?? this.loginOrDivider,
loginOrDividerText: loginOrDividerText ?? this.loginOrDividerText,
+ modalBarrierColor: modalBarrierColor ?? this.modalBarrierColor,
mutedUnreadBadge: mutedUnreadBadge ?? this.mutedUnreadBadge,
sectionCollapseIcon: sectionCollapseIcon ?? this.sectionCollapseIcon,
star: star ?? this.star,
@@ -308,9 +351,13 @@ class DesignVariables extends ThemeExtension {
}
return DesignVariables._(
background: Color.lerp(background, other.background, t)!,
+ bgContextMenu: Color.lerp(bgContextMenu, other.bgContextMenu, t)!,
bgCounterUnread: Color.lerp(bgCounterUnread, other.bgCounterUnread, t)!,
bgTopBar: Color.lerp(bgTopBar, other.bgTopBar, t)!,
borderBar: Color.lerp(borderBar, other.borderBar, t)!,
+ contextMenuCancelText: Color.lerp(contextMenuCancelText, other.contextMenuCancelText, t)!,
+ contextMenuItemBg: Color.lerp(contextMenuItemBg, other.contextMenuItemBg, t)!,
+ contextMenuItemText: Color.lerp(contextMenuItemText, other.contextMenuItemBg, t)!,
icon: Color.lerp(icon, other.icon, t)!,
labelCounterUnread: Color.lerp(labelCounterUnread, other.labelCounterUnread, t)!,
labelEdited: Color.lerp(labelEdited, other.labelEdited, t)!,
@@ -319,6 +366,7 @@ class DesignVariables extends ThemeExtension {
title: Color.lerp(title, other.title, t)!,
channelColorSwatches: ChannelColorSwatches.lerp(channelColorSwatches, other.channelColorSwatches, t),
atMentionMarker: Color.lerp(atMentionMarker, other.atMentionMarker, t)!,
+ contextMenuCancelBg: Color.lerp(contextMenuCancelBg, other.contextMenuCancelBg, t)!,
dmHeaderBg: Color.lerp(dmHeaderBg, other.dmHeaderBg, t)!,
errorBannerBackground: Color.lerp(errorBannerBackground, other.errorBannerBackground, t)!,
errorBannerBorder: Color.lerp(errorBannerBorder, other.errorBannerBorder, t)!,
@@ -327,6 +375,7 @@ class DesignVariables extends ThemeExtension {
groupDmConversationIconBg: Color.lerp(groupDmConversationIconBg, other.groupDmConversationIconBg, t)!,
loginOrDivider: Color.lerp(loginOrDivider, other.loginOrDivider, t)!,
loginOrDividerText: Color.lerp(loginOrDividerText, other.loginOrDividerText, t)!,
+ modalBarrierColor: Color.lerp(modalBarrierColor, other.modalBarrierColor, t)!,
mutedUnreadBadge: Color.lerp(mutedUnreadBadge, other.mutedUnreadBadge, t)!,
sectionCollapseIcon: Color.lerp(sectionCollapseIcon, other.sectionCollapseIcon, t)!,
star: Color.lerp(star, other.star, t)!,
diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart
index b37ca66bc5..42fca2edae 100644
--- a/test/widgets/action_sheet_test.dart
+++ b/test/widgets/action_sheet_test.dart
@@ -100,8 +100,8 @@ void main() {
group('AddThumbsUpButton', () {
Future tapButton(WidgetTester tester) async {
- await tester.ensureVisible(find.byIcon(Icons.add_reaction_outlined, skipOffstage: false));
- await tester.tap(find.byIcon(Icons.add_reaction_outlined));
+ await tester.ensureVisible(find.byIcon(ZulipIcons.smile, skipOffstage: false));
+ await tester.tap(find.byIcon(ZulipIcons.smile));
await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e
}
@@ -147,15 +147,15 @@ void main() {
});
group('StarButton', () {
- Future tapButton(WidgetTester tester) async {
+ Future tapButton(WidgetTester tester, {bool starred = false}) async {
// Starred messages include the same icon so we need to
// match only by descendants of [BottomSheet].
await tester.ensureVisible(find.descendant(
of: find.byType(BottomSheet),
- matching: find.byIcon(ZulipIcons.star_filled, skipOffstage: false)));
+ matching: find.byIcon(starred ? ZulipIcons.star_filled : ZulipIcons.star, skipOffstage: false)));
await tester.tap(find.descendant(
of: find.byType(BottomSheet),
- matching: find.byIcon(ZulipIcons.star_filled)));
+ matching: find.byIcon(starred ? ZulipIcons.star_filled : ZulipIcons.star)));
await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e
}
@@ -186,7 +186,7 @@ void main() {
final connection = store.connection as FakeApiConnection;
connection.prepare(json: {});
- await tapButton(tester);
+ await tapButton(tester, starred: true);
await tester.pump(Duration.zero);
check(connection.lastRequest).isA()
@@ -233,7 +233,7 @@ void main() {
'msg': 'Invalid message(s)',
'result': 'error',
});
- await tapButton(tester);
+ await tapButton(tester, starred: true);
await tester.pump(Duration.zero); // error arrives; error dialog shows
await tester.tap(find.byWidget(checkErrorDialog(tester,
@@ -249,14 +249,14 @@ void main() {
}
Widget? findQuoteAndReplyButton(WidgetTester tester) {
- return tester.widgetList(find.byIcon(Icons.format_quote_outlined)).singleOrNull;
+ return tester.widgetList(find.byIcon(ZulipIcons.format_quote)).singleOrNull;
}
/// Simulates tapping the quote-and-reply button in the message action sheet.
///
/// Checks that there is a quote-and-reply button.
Future tapQuoteAndReplyButton(WidgetTester tester) async {
- await tester.ensureVisible(find.byIcon(Icons.format_quote_outlined, skipOffstage: false));
+ await tester.ensureVisible(find.byIcon(ZulipIcons.format_quote, skipOffstage: false));
final quoteAndReplyButton = findQuoteAndReplyButton(tester);
check(quoteAndReplyButton).isNotNull();
await tester.tap(find.byWidget(quoteAndReplyButton!));
@@ -468,8 +468,8 @@ void main() {
});
Future tapCopyMessageTextButton(WidgetTester tester) async {
- await tester.ensureVisible(find.byIcon(Icons.copy, skipOffstage: false));
- await tester.tap(find.byIcon(Icons.copy));
+ await tester.ensureVisible(find.byIcon(ZulipIcons.copy, skipOffstage: false));
+ await tester.tap(find.byIcon(ZulipIcons.copy));
await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e
}
@@ -565,8 +565,8 @@ void main() {
}
Future tapShareButton(WidgetTester tester) async {
- await tester.ensureVisible(find.byIcon(Icons.adaptive.share, skipOffstage: false));
- await tester.tap(find.byIcon(Icons.adaptive.share));
+ await tester.ensureVisible(find.byIcon(ZulipIcons.share, skipOffstage: false));
+ await tester.tap(find.byIcon(ZulipIcons.share));
await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e
}
@@ -616,4 +616,27 @@ void main() {
check(mockSharePlus.sharedString).isNull();
});
});
+
+ group('MessageActionSheetCancelButton', () {
+ final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
+
+ void checkActionSheet(WidgetTester tester, {required bool isShown}) {
+ check(find.text(zulipLocalizations.actionSheetOptionStarMessage)
+ .evaluate().length).equals(isShown ? 1 : 0);
+
+ final findCancelButton = find.text(zulipLocalizations.dialogCancel);
+ check(findCancelButton.evaluate().length).equals(isShown ? 1 : 0);
+ }
+
+ testWidgets('pressing the button dismisses the action sheet', (tester) async {
+ final message = eg.streamMessage();
+ await setupToMessageActionSheet(tester, message: message, narrow: TopicNarrow.ofMessage(message));
+ checkActionSheet(tester, isShown: true);
+
+ final findCancelButton = find.text(zulipLocalizations.dialogCancel);
+ await tester.tap(findCancelButton);
+ await tester.pumpAndSettle();
+ checkActionSheet(tester, isShown: false);
+ });
+ });
}