Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

action_sheet: Add "Mark topic as read" button #1274

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/icons/ZulipIcons.ttf
Binary file not shown.
4 changes: 4 additions & 0 deletions assets/icons/message_checked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@
"@actionSheetOptionUnstarMessage": {
"description": "Label for unstar button on action sheet."
},
"actionSheetOptionMarkTopicAsRead": "Mark topic as read",
"@actionSheetOptionMarkTopicAsRead": {
"description": "Option to mark a specific topic as read in the action sheet."
},
"errorWebAuthOperationalErrorTitle": "Something went wrong",
"@errorWebAuthOperationalErrorTitle": {
"description": "Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials)."
Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ abstract class ZulipLocalizations {
/// **'Unstar message'**
String get actionSheetOptionUnstarMessage;

/// Option to mark a specific topic as read in the action sheet.
///
/// In en, this message translates to:
/// **'Mark topic as read'**
String get actionSheetOptionMarkTopicAsRead;

/// Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials).
///
/// In en, this message translates to:
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get actionSheetOptionUnstarMessage => 'Unstar message';

@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get actionSheetOptionUnstarMessage => 'Unstar message';

@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_ja.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get actionSheetOptionUnstarMessage => 'Unstar message';

@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_nb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
@override
String get actionSheetOptionUnstarMessage => 'Unstar message';

@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_pl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
@override
String get actionSheetOptionUnstarMessage => 'Odbierz gwiazdkę';

@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get errorWebAuthOperationalErrorTitle => 'Coś poszło nie tak';

Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_ru.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
@override
String get actionSheetOptionUnstarMessage => 'Снять отметку с сообщения';

@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get errorWebAuthOperationalErrorTitle => 'Что-то пошло не так';

Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_sk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
@override
String get actionSheetOptionUnstarMessage => 'Odhviezdičkovať správu';

@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get errorWebAuthOperationalErrorTitle => 'Niečo sa pokazilo';

Expand Down
31 changes: 31 additions & 0 deletions lib/widgets/action_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ void showTopicActionSheet(BuildContext context, {
someMessageIdInTopic: someMessageIdInTopic));
}

final unreadCount = store.unreads.countInTopicNarrow(channelId, topic);
if (unreadCount > 0) {
optionButtons.add(MarkTopicAsReadButton(
channelId: channelId,
topic: topic,
pageContext: context));
}

if (optionButtons.isEmpty) {
// TODO(a11y): This case makes a no-op gesture handler; as a consequence,
// we're presenting some UI (to people who use screen-reader software) as
Expand Down Expand Up @@ -461,6 +469,29 @@ class ResolveUnresolveButton extends ActionSheetMenuItemButton {
}
}

class MarkTopicAsReadButton extends ActionSheetMenuItemButton {
const MarkTopicAsReadButton({
super.key,
required this.channelId,
required this.topic,
required super.pageContext,
});

final int channelId;
final TopicName topic;

@override IconData get icon => ZulipIcons.message_checked;

@override
String label(ZulipLocalizations zulipLocalizations) {
return zulipLocalizations.actionSheetOptionMarkTopicAsRead;
}

@override void onPressed() async {
await ZulipAction.markNarrowAsRead(pageContext, TopicNarrow(channelId, topic));
}
}

/// Show a sheet of actions you can take on a message in the message list.
///
/// Must have a [MessageListPage] ancestor.
Expand Down
29 changes: 16 additions & 13 deletions lib/widgets/icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,44 +99,47 @@ abstract final class ZulipIcons {
/// The Zulip custom icon "menu".
static const IconData menu = IconData(0xf119, fontFamily: "Zulip Icons");

/// The Zulip custom icon "message_checked".
static const IconData message_checked = IconData(0xf11a, fontFamily: "Zulip Icons");

/// The Zulip custom icon "message_feed".
static const IconData message_feed = IconData(0xf11a, fontFamily: "Zulip Icons");
static const IconData message_feed = IconData(0xf11b, fontFamily: "Zulip Icons");

/// The Zulip custom icon "mute".
static const IconData mute = IconData(0xf11b, fontFamily: "Zulip Icons");
static const IconData mute = IconData(0xf11c, fontFamily: "Zulip Icons");

/// The Zulip custom icon "read_receipts".
static const IconData read_receipts = IconData(0xf11c, fontFamily: "Zulip Icons");
static const IconData read_receipts = IconData(0xf11d, fontFamily: "Zulip Icons");

/// The Zulip custom icon "send".
static const IconData send = IconData(0xf11d, fontFamily: "Zulip Icons");
static const IconData send = IconData(0xf11e, fontFamily: "Zulip Icons");

/// The Zulip custom icon "share".
static const IconData share = IconData(0xf11e, fontFamily: "Zulip Icons");
static const IconData share = IconData(0xf11f, fontFamily: "Zulip Icons");

/// The Zulip custom icon "share_ios".
static const IconData share_ios = IconData(0xf11f, fontFamily: "Zulip Icons");
static const IconData share_ios = IconData(0xf120, fontFamily: "Zulip Icons");

/// The Zulip custom icon "smile".
static const IconData smile = IconData(0xf120, fontFamily: "Zulip Icons");
static const IconData smile = IconData(0xf121, fontFamily: "Zulip Icons");

/// The Zulip custom icon "star".
static const IconData star = IconData(0xf121, fontFamily: "Zulip Icons");
static const IconData star = IconData(0xf122, fontFamily: "Zulip Icons");

/// The Zulip custom icon "star_filled".
static const IconData star_filled = IconData(0xf122, fontFamily: "Zulip Icons");
static const IconData star_filled = IconData(0xf123, fontFamily: "Zulip Icons");

/// The Zulip custom icon "three_person".
static const IconData three_person = IconData(0xf123, fontFamily: "Zulip Icons");
static const IconData three_person = IconData(0xf124, fontFamily: "Zulip Icons");

/// The Zulip custom icon "topic".
static const IconData topic = IconData(0xf124, fontFamily: "Zulip Icons");
static const IconData topic = IconData(0xf125, fontFamily: "Zulip Icons");

/// The Zulip custom icon "unmute".
static const IconData unmute = IconData(0xf125, fontFamily: "Zulip Icons");
static const IconData unmute = IconData(0xf126, fontFamily: "Zulip Icons");

/// The Zulip custom icon "user".
static const IconData user = IconData(0xf126, fontFamily: "Zulip Icons");
static const IconData user = IconData(0xf127, fontFamily: "Zulip Icons");

// END GENERATED ICON DATA
}
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ class _MarkAsReadWidgetState extends State<MarkAsReadWidget> {
backgroundColor: WidgetStatePropertyAll(messageListTheme.unreadMarker),
),
onPressed: _loading ? null : () => _handlePress(context),
icon: const Icon(Icons.playlist_add_check),
icon: const Icon(ZulipIcons.message_checked),
label: Text(zulipLocalizations.markAllAsReadLabel))))));
}
}
Expand Down
42 changes: 42 additions & 0 deletions test/widgets/action_sheet_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:http/http.dart' as http;
import 'package:zulip/api/model/events.dart';
import 'package:zulip/api/model/initial_snapshot.dart';
import 'package:zulip/api/model/model.dart';
import 'package:zulip/api/model/narrow.dart';
import 'package:zulip/api/route/channels.dart';
import 'package:zulip/api/route/messages.dart';
import 'package:zulip/model/binding.dart';
Expand Down Expand Up @@ -562,6 +563,47 @@ void main() {
expectedTitle: 'Failed to mark topic as unresolved');
});
});

group('MarkTopicAsReadButton', () {
testWidgets('visible if topic has unread messages', (tester) async {
await prepare();
final message = eg.streamMessage(stream: someChannel, topic: someTopic, flags: []);
await store.addMessage(message);
await showFromAppBar(tester, messages: [message]);
check(find.text('Mark topic as read')).findsOne();
});

testWidgets('not visible if topic has no unread messages', (tester) async {
await prepare();
final message = eg.streamMessage(stream: someChannel, topic: someTopic, flags: [MessageFlag.read]);
await store.addMessage(message);
await showFromAppBar(tester, messages: [message]);
check(find.text('Mark topic as read')).findsNothing();
});

testWidgets('marks topic as read when pressed', (tester) async {
await prepare();
final message = eg.streamMessage(stream: someChannel, topic: someTopic);
await store.handleEvent(MessageEvent(id: 0, message: message));
await showFromAppBar(tester, messages: [message]);

connection.prepare(json: UpdateMessageFlagsForNarrowResult(
processedCount: 1, updatedCount: 1,
firstProcessedId: message.id, lastProcessedId: message.id,
foundOldest: true, foundNewest: true).toJson());
await tester.tap(find.text('Mark topic as read'));
await tester.pumpAndSettle();

check(connection.lastRequest).isA<http.Request>()
..url.path.equals('/api/v1/messages/flags/narrow')
..bodyFields['narrow'].equals(jsonEncode([
...eg.topicNarrow(someChannel.streamId, someTopic).apiEncode(),
ApiNarrowIs(IsOperand.unread),
]))
..bodyFields['op'].equals('add')
..bodyFields['flag'].equals('read');
});
});
});

group('message action sheet', () {
Expand Down