diff --git a/docs/hotkeys.md b/docs/hotkeys.md
index f9c8b9fd8a..a70ca572e9 100644
--- a/docs/hotkeys.md
+++ b/docs/hotkeys.md
@@ -64,6 +64,11 @@
|Show/hide stream information & modify settings|i|
|Show/hide stream members (from stream information)|m|
+## Topic list actions
+|Command|Key Combination|
+| :--- | :---: |
+|Mute/unmute Topics|M|
+
## Composing a Message
|Command|Key Combination|
| :--- | :---: |
diff --git a/tests/conftest.py b/tests/conftest.py
index b93b2c90df..0326355a3a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,7 +7,11 @@
from zulipterminal.config.keys import keys_for_command
from zulipterminal.helper import initial_index as helper_initial_index
from zulipterminal.ui_tools.boxes import MessageBox
-from zulipterminal.ui_tools.buttons import StreamButton, UserButton
+from zulipterminal.ui_tools.buttons import (
+ StreamButton,
+ TopicButton,
+ UserButton,
+)
from zulipterminal.version import (
MINIMUM_SUPPORTED_SERVER_VERSION,
SUPPORTED_SERVER_VERSIONS,
@@ -51,6 +55,21 @@ def stream_button(mocker):
return button
+@pytest.fixture
+def topic_button(mocker):
+ """
+ Mocked topic button.
+ """
+ button = TopicButton(
+ stream_id=1,
+ topic='party',
+ controller=mocker.patch('zulipterminal.core.Controller'),
+ width=27,
+ count=30
+ )
+ return button
+
+
@pytest.fixture
def user_button(mocker, width=38):
"""
diff --git a/tests/core/test_core.py b/tests/core/test_core.py
index 7c18c19330..e73da0c6d2 100644
--- a/tests/core/test_core.py
+++ b/tests/core/test_core.py
@@ -302,6 +302,34 @@ def test_stream_muting_confirmation_popup(self, mocker, controller,
+ "' ?"), "center")
pop_up.assert_called_once_with(controller, text(), partial())
+ @pytest.mark.parametrize('muted_topics, action', [
+ ({
+ ('Stream 1', 'delhi'): 1
+ }, 'muting'),
+ ({
+ ('Stream 1', 'delhi'): 1,
+ ('Stream 1', 'party'): 2
+ }, 'unmuting'),
+ ])
+ def test_topic_muting_confirmation_popup(self, mocker, controller,
+ stream_dict, topic_button,
+ muted_topics, action):
+ pop_up = mocker.patch(CORE + '.PopUpConfirmationView')
+ text = mocker.patch(CORE + '.urwid.Text')
+ partial = mocker.patch(CORE + '.partial')
+ controller.model._muted_topics = muted_topics
+ controller.model.stream_dict = stream_dict
+ controller.loop = mocker.Mock()
+
+ controller.topic_muting_confirmation_popup(topic_button)
+ text.assert_called_with(
+ ("",
+ f"Confirm {action} of topic '{topic_button.topic_name}' "
+ f"under the '{topic_button.stream_name}' stream ?"),
+ "center"
+ )
+ pop_up.assert_called_once_with(controller, text(), partial())
+
@pytest.mark.parametrize('initial_narrow, final_narrow', [
([], [['search', 'FOO']]),
([['search', 'BOO']], [['search', 'FOO']]),
diff --git a/tests/helper/test_helper.py b/tests/helper/test_helper.py
index 785c8dbbd0..62754c961a 100644
--- a/tests/helper/test_helper.py
+++ b/tests/helper/test_helper.py
@@ -200,15 +200,20 @@ def test_powerset(iterable, map_func, expected_powerset):
'all_msg': 8,
'streams': {99: 1},
'unread_topics': {(99, 'Some private unread topic'): 1},
+ 'unread_muted_topics': {(1000, 'Some general unread topic'): 3},
'all_mentions': 0,
}),
([1000], [['Secret stream', 'Some private unread topic']], {
'all_msg': 8,
'streams': {1000: 3},
'unread_topics': {(1000, 'Some general unread topic'): 3},
+ 'unread_muted_topics': {(99, 'Some private unread topic'): 1},
+ 'all_mentions': 0,
+ }),
+ ([1], [], {
+ 'unread_muted_topics': {},
'all_mentions': 0,
}),
- ([1], [], {'all_mentions': 0})
], ids=['mute_private_stream_mute_general_stream_topic',
'mute_general_stream_mute_private_stream_topic',
'no_mute_some_other_stream_muted']
diff --git a/tests/model/test_model.py b/tests/model/test_model.py
index 0eeb583ea8..f8ffef2ad2 100644
--- a/tests/model/test_model.py
+++ b/tests/model/test_model.py
@@ -167,6 +167,7 @@ def test_register_initial_desired_events(self, mocker, initial_data):
'message',
'update_message',
'reaction',
+ 'muted_topics',
'subscription',
'typing',
'update_message_flags',
@@ -706,6 +707,34 @@ def test_toggle_stream_muted_status(self, mocker, model,
self.display_error_if_present.assert_called_once_with(response,
self.controller)
+ @pytest.mark.parametrize(['initial_muted_topics', 'op',
+ 'topic_to_be_toggled'], [
+ ({
+ ('Stream 1', 'delhi'): 1
+ }, 'add', ('Stream 1', 'party')),
+ ({
+ ('Stream 1', 'delhi'): 1,
+ ('Stream 1', 'party'): 2
+ }, 'remove', ('Stream 1', 'delhi'))
+ ], ids=['muting_party', 'unmuting_delhi'])
+ def test_toggle_topic_muted_status(self, mocker, model,
+ stream_dict, topic_to_be_toggled,
+ initial_muted_topics, op,
+ response={'result': 'success'}):
+ model._muted_topics = initial_muted_topics
+ model.stream_dict = stream_dict
+ model.client.mute_topic.return_value = response
+ model.toggle_topic_muted_status(1, topic_to_be_toggled[0],
+ topic_to_be_toggled[1])
+ request = {
+ 'stream': topic_to_be_toggled[0],
+ 'topic': topic_to_be_toggled[1],
+ 'op': op
+ }
+ model.client.mute_topic.assert_called_once_with(request)
+ self.display_error_if_present.assert_called_once_with(response,
+ self.controller)
+
@pytest.mark.parametrize('flags_before, expected_operator', [
([], 'add'),
(['starred'], 'remove'),
@@ -965,6 +994,55 @@ def test__handle_message_event(self, mocker, user_profile, response,
# LOG REMAINS THE SAME IF UPDATE IS FALSE
assert self.controller.view.message_view.log == log
+ @pytest.mark.parametrize(['event', 'initial_muted_topics',
+ 'toggled_topic_status'], [
+ ({
+ "type": 'muted_topics',
+ "muted_topics": [
+ ['Stream 1', 'delhi', 3],
+ ['Stream 1', 'kerala', 2],
+ ['Stream 2', 'bangalore', 1]
+ ]
+ },
+ {
+ ('Stream 1', 'kerala'): 2,
+ ('Stream 2', 'bangalore'): 1
+ }, ('delhi', True)),
+ ({
+ "type": 'muted_topics',
+ "muted_topics": [
+ ['Stream 1', 'delhi', 3],
+ ['Stream 2', 'bangalore', 1]
+ ]
+ },
+ {
+ ('Stream 1', 'delhi'): 3,
+ ('Stream 1', 'kerala'): 2,
+ ('Stream 2', 'bangalore'): 1
+ }, ('kerala', False))
+ ], ids=['muting_topic_delhi', 'unmuting_topic_kerala'])
+ def test__handle_topic_muting_event(self, mocker, model,
+ event, initial_muted_topics,
+ toggled_topic_status):
+ model._muted_topics = initial_muted_topics
+ model._get_muted_topic = (
+ mocker.Mock(return_value=toggled_topic_status)
+ )
+ mark_muted = mocker.patch(
+ 'zulipterminal.ui_tools.buttons.TopicButton.mark_muted')
+ mark_unmuted = mocker.patch(
+ 'zulipterminal.ui_tools.buttons.TopicButton.mark_unmuted')
+ model.handle_topic_muting_event(event)
+
+ if toggled_topic_status[1]:
+ assert ('Stream 1', 'delhi') in model._muted_topics
+ mark_muted.called
+ else:
+ assert ('Stream 1', 'kerala') not in model._muted_topics
+ mark_unmuted.called
+ model._get_muted_topic.assert_called
+ assert model.controller.update_screen.called
+
@pytest.mark.parametrize(['topic_name', 'topic_order_initial',
'topic_order_final'], [
('TOPIC3', ['TOPIC2', 'TOPIC3', 'TOPIC1'],
@@ -2012,6 +2090,32 @@ def test_is_muted_topic(self, topic, is_muted, stream_dict, model,
assert return_value == is_muted
+ @pytest.mark.parametrize('muted_topics', [
+ [
+ ['Stream 1', 'delhi', 4],
+ ['Stream 1', 'kerala', 2],
+ ['Stream 2', 'bangalore', 1]
+ ]
+ ])
+ @pytest.mark.parametrize(['initial_muted_topics', 'toggle_topic_status'], [
+ ({
+ ('Stream 1', 'kerala'): 2,
+ ('Stream 2', 'bangalore'): 1
+ }, (['Stream 1', 'delhi', 4], True)),
+ ({
+ ('Stream 1', 'delhi'): 4,
+ ('Stream 1', 'chennai'): 3,
+ ('Stream 1', 'kerala'): 2,
+ ('Stream 2', 'bangalore'): 1
+ }, (['Stream 1', 'chennai', 3], False)),
+ ], ids=['muting_delhi', 'unmuting_chennai'])
+ def test__get_muted_topic(self, model, initial_muted_topics,
+ muted_topics, toggle_topic_status):
+ model._muted_topics = initial_muted_topics
+ return_value = model._get_muted_topic(muted_topics)
+
+ assert return_value == toggle_topic_status
+
@pytest.mark.parametrize('stream_id, expected_response', [
(1, True),
(462, False),
diff --git a/tests/ui_tools/test_buttons.py b/tests/ui_tools/test_buttons.py
index 9f1419a090..4c33d82b94 100644
--- a/tests/ui_tools/test_buttons.py
+++ b/tests/ui_tools/test_buttons.py
@@ -314,10 +314,19 @@ def test_init_calls_mark_muted(self, mocker, stream_name, title,
topic=title, controller=controller,
width=40, count=0)
if is_muted_called:
- mark_muted.assert_called_once_with()
+ mark_muted.called
else:
mark_muted.assert_not_called()
+ @pytest.mark.parametrize('key', keys_for_command('TOGGLE_MUTE_TOPIC'))
+ def test_keypress_TOGGLE_MUTE_TOPIC(self, mocker, topic_button, key,
+ widget_size):
+ size = widget_size(topic_button)
+ pop_up = mocker.patch(
+ 'zulipterminal.core.Controller.topic_muting_confirmation_popup')
+ topic_button.keypress(size, key)
+ pop_up.assert_called_once_with(topic_button)
+
class TestMessageLinkButton:
@pytest.fixture(autouse=True)
diff --git a/zulipterminal/api_types.py b/zulipterminal/api_types.py
index 6ca30b5540..7712c2987b 100644
--- a/zulipterminal/api_types.py
+++ b/zulipterminal/api_types.py
@@ -112,6 +112,11 @@ class ReactionEvent(TypedDict):
message_id: int
+class TopicMutingEvent(TypedDict):
+ type: Literal['muted_topics']
+ muted_topics: List[List[str]]
+
+
class SubscriptionEvent(TypedDict):
type: Literal['subscription']
op: str
@@ -152,6 +157,7 @@ class UpdateDisplaySettings(TypedDict):
MessageEvent,
UpdateMessageEvent,
ReactionEvent,
+ TopicMutingEvent,
SubscriptionEvent,
TypingEvent,
UpdateMessageFlagsEvent,
diff --git a/zulipterminal/config/keys.py b/zulipterminal/config/keys.py
index 887fa8f107..3f3c4bf285 100644
--- a/zulipterminal/config/keys.py
+++ b/zulipterminal/config/keys.py
@@ -209,6 +209,11 @@ class KeyBinding(TypedDict, total=False):
'help_text': 'Mute/unmute Streams',
'key_category': 'stream_list',
}),
+ ('TOGGLE_MUTE_TOPIC', {
+ 'keys': ['M'],
+ 'help_text': 'Mute/unmute Topics',
+ 'key_category': 'topic_list',
+ }),
('ENTER', {
'keys': ['enter'],
'help_text': 'Perform current action',
@@ -339,6 +344,7 @@ class KeyBinding(TypedDict, total=False):
('searching', 'Searching'),
('msg_actions', 'Message actions'),
('stream_list', 'Stream list actions'),
+ ('topic_list', 'Topic list actions'),
('msg_compose', 'Composing a Message'),
])
diff --git a/zulipterminal/core.py b/zulipterminal/core.py
index 346e8a8b42..df4e457421 100644
--- a/zulipterminal/core.py
+++ b/zulipterminal/core.py
@@ -287,6 +287,22 @@ def stream_muting_confirmation_popup(self, button: Any) -> None:
self.loop.widget = PopUpConfirmationView(self, question,
mute_this_stream)
+ def topic_muting_confirmation_popup(self, button: Any) -> None:
+ currently_muted = self.model.is_muted_topic(button.stream_id,
+ button.topic_name)
+ type_of_action = "unmuting" if currently_muted else "muting"
+ question = urwid.Text(
+ ("",
+ f"Confirm {type_of_action} of topic '{button.topic_name}' under "
+ f"the '{button.stream_name}' stream ?"),
+ "center"
+ )
+ mute_this_topic = partial(self.model.toggle_topic_muted_status,
+ button.stream_id, button.stream_name,
+ button.topic_name)
+ self.loop.widget = PopUpConfirmationView(self, question,
+ mute_this_topic)
+
def _narrow_to(self, anchor: Optional[int], **narrow: Any) -> None:
already_narrowed = self.model.set_narrow(**narrow)
if already_narrowed:
diff --git a/zulipterminal/helper.py b/zulipterminal/helper.py
index 821cc63b3f..972b67ad24 100644
--- a/zulipterminal/helper.py
+++ b/zulipterminal/helper.py
@@ -89,6 +89,7 @@ class UnreadCounts(TypedDict):
all_pms: int
all_mentions: int
unread_topics: Dict[Tuple[int, str], int] # stream_id, topic
+ unread_muted_topics: Dict[Tuple[int, str], int]
unread_pms: Dict[int, int] # sender_id
unread_huddles: Dict[FrozenSet[int], int] # Group pms
streams: Dict[int, int] # stream_id
@@ -118,7 +119,8 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
return wrapper
-def _set_count_in_model(new_count: int, changed_messages: List[Message],
+def _set_count_in_model(controller: Any, new_count: int,
+ changed_messages: List[Message],
unread_counts: UnreadCounts) -> None:
"""
This function doesn't explicitly set counts in model,
@@ -140,9 +142,16 @@ def update_unreads(unreads: Dict[KeyT, int], key: KeyT) -> None:
for message in changed_messages:
if message['type'] == 'stream':
stream_id = message['stream_id']
- update_unreads(unread_counts['unread_topics'],
- (stream_id, message['subject']))
- update_unreads(unread_counts['streams'], stream_id)
+ topic = message['subject']
+ # If topic is muted, update unread_counts['unread_muted_topics']
+ # Don't need to change stream unread count
+ if controller.model.is_muted_topic(stream_id, topic):
+ update_unreads(unread_counts['unread_muted_topics'],
+ (stream_id, topic))
+ else:
+ update_unreads(unread_counts['unread_topics'],
+ (stream_id, topic))
+ update_unreads(unread_counts['streams'], stream_id)
# self-pm has only one display_recipient
# 1-1 pms have 2 display_recipient
elif len(message['display_recipient']) <= 2:
@@ -187,24 +196,27 @@ def _set_count_in_view(controller: Any, new_count: int,
if msg_type == 'stream':
stream_id = message['stream_id']
msg_topic = message['subject']
- if controller.model.is_muted_stream(stream_id):
- add_to_counts = False # if muted, don't add to eg. all_msg
- else:
- for stream_button in stream_buttons_log:
- if stream_button.stream_id == stream_id:
- stream_button.update_count(stream_button.count
- + new_count)
- break
- # FIXME: Update unread_counts['unread_topics']?
+ # If the topic has been muted, we don't need to change
+ # stream/topic button count.
if controller.model.is_muted_topic(stream_id, msg_topic):
add_to_counts = False
- if is_open_topic_view and stream_id == toggled_stream_id:
- # If topic_view is open for incoming messages's stream,
- # We update the respective TopicButton count accordingly.
- for topic_button in topic_buttons_log:
- if topic_button.topic_name == msg_topic:
- topic_button.update_count(topic_button.count
- + new_count)
+ else:
+ if controller.model.is_muted_stream(stream_id):
+ add_to_counts = False # if muted, don't add to eg. all_msg
+ else:
+ for stream_button in stream_buttons_log:
+ if stream_button.stream_id == stream_id:
+ stream_button.update_count(stream_button.count
+ + new_count)
+ break
+
+ if is_open_topic_view and stream_id == toggled_stream_id:
+ # If topic_view is open for incoming messages's stream,
+ # We update the respective TopicButton count accordingly.
+ for topic_button in topic_buttons_log:
+ if topic_button.topic_name == msg_topic:
+ topic_button.update_count(topic_button.count
+ + new_count)
else:
for user_button in user_buttons_log:
if user_button.user_id == user_id:
@@ -225,7 +237,7 @@ def set_count(id_list: List[int], controller: Any, new_count: int) -> None:
messages = controller.model.index['messages']
unread_counts: UnreadCounts = controller.model.unread_counts
changed_messages = [messages[id] for id in id_list]
- _set_count_in_model(new_count, changed_messages, unread_counts)
+ _set_count_in_model(controller, new_count, changed_messages, unread_counts)
# if view is not yet loaded. Usually the case when first message is read.
while not hasattr(controller, 'view'):
@@ -429,6 +441,7 @@ def classify_unread_counts(model: Any) -> UnreadCounts:
all_pms=0,
all_mentions=0,
unread_topics=dict(),
+ unread_muted_topics=dict(),
unread_pms=dict(),
unread_huddles=dict(),
streams=defaultdict(int),
@@ -446,12 +459,13 @@ def classify_unread_counts(model: Any) -> UnreadCounts:
for stream in unread_msg_counts['streams']:
count = len(stream['unread_message_ids'])
stream_id = stream['stream_id']
+ stream_topic = (stream_id, stream['topic'])
# unsubscribed streams may be in raw unreads, but are not tracked
if not model.is_user_subscribed_to_stream(stream_id):
continue
if model.is_muted_topic(stream_id, stream['topic']):
+ unread_counts['unread_muted_topics'][stream_topic] = count
continue
- stream_topic = (stream_id, stream['topic'])
unread_counts['unread_topics'][stream_topic] = count
if not unread_counts['streams'].get(stream_id):
unread_counts['streams'][stream_id] = count
diff --git a/zulipterminal/model.py b/zulipterminal/model.py
index e8a59e5212..c550fe6eb9 100644
--- a/zulipterminal/model.py
+++ b/zulipterminal/model.py
@@ -110,6 +110,7 @@ def __init__(self, controller: Any) -> None:
('message', self._handle_message_event),
('update_message', self._handle_update_message_event),
('reaction', self._handle_reaction_event),
+ ('muted_topics', self.handle_topic_muting_event),
('subscription', self._handle_subscription_event),
('typing', self._handle_typing_event),
('update_message_flags',
@@ -144,7 +145,7 @@ def __init__(self, controller: Any) -> None:
# feature level 1, server version 3.0.
muted_topics = self.initial_data['muted_topics']
assert set(map(len, muted_topics)) in (set(), {2}, {3})
- self._muted_topics: Dict[Tuple[str, str], Optional[int]] = {
+ self._muted_topics: Dict[Tuple[str, str], Any] = {
(stream_name, topic): (None if self.server_feature_level is None
else date_muted[0])
for stream_name, topic, *date_muted in muted_topics
@@ -838,6 +839,52 @@ def _group_info_from_realm_user_groups(self,
user_group_names.sort(key=str.lower)
return user_group_names
+ def handle_topic_muting_event(self, event: Event) -> None:
+ """
+ Handle Topic muting events
+ """
+ assert event['type'] == "muted_topics"
+ if hasattr(self.controller, 'view'):
+ if 'muted_topics' in event:
+ new_muted_topics = event['muted_topics']
+ added_topic, is_mute_topic = self._get_muted_topic(
+ new_muted_topics)
+ self._muted_topics = {
+ (stream_name, topic): (None
+ if self.server_feature_level is None
+ else date_muted)
+ for stream_name, topic, date_muted in new_muted_topics
+ }
+ if (self.controller.view.left_panel.is_in_topic_view
+ and added_topic[0] == self.controller.view.
+ topic_w.stream_button.stream_name):
+ topic_button = self.controller.view.topic_name_to_button[
+ added_topic[1]]
+ if is_mute_topic:
+ topic_button.mark_muted()
+ else:
+ topic_button.mark_unmuted()
+ self.controller.update_screen()
+
+ def _get_muted_topic(self,
+ muted_topics: List[List[str]]) -> Tuple[List[str],
+ bool]:
+ """
+ We figure out which topic has been muted/unmuted and return the extra
+ topic and whether it is muting/unmuting.
+ """
+ for topic in muted_topics:
+ if (topic[0], topic[1]) not in self._muted_topics.keys():
+ return (topic, True)
+ # If it reaches here, then we must have unmuted a topic.
+ for unmuted_topic in self._muted_topics.keys():
+ muting_timestamp = self._muted_topics[unmuted_topic]
+ formatted_topic = list(unmuted_topic)
+ formatted_topic.append(muting_timestamp)
+ if formatted_topic not in muted_topics:
+ return (formatted_topic, False)
+ return ([], True)
+
def toggle_stream_muted_status(self, stream_id: int) -> None:
request = [{
'stream_id': stream_id,
@@ -848,6 +895,18 @@ def toggle_stream_muted_status(self, stream_id: int) -> None:
response = self.client.update_subscription_settings(request)
display_error_if_present(response, self.controller)
+ def toggle_topic_muted_status(self, stream_id: int,
+ stream_name: str, topic_name: str) -> None:
+ request = {
+ 'stream': stream_name,
+ 'topic': topic_name,
+ 'op': 'remove'
+ if self.is_muted_topic(stream_id, topic_name)
+ else 'add'
+ }
+ response = self.client.mute_topic(request)
+ display_error_if_present(response, self.controller)
+
def stream_id_from_name(self, stream_name: str) -> int:
for stream_id, stream in self.stream_dict.items():
if stream['name'] == stream_name:
diff --git a/zulipterminal/ui_tools/buttons.py b/zulipterminal/ui_tools/buttons.py
index f21af7754d..6c7a1dea9d 100644
--- a/zulipterminal/ui_tools/buttons.py
+++ b/zulipterminal/ui_tools/buttons.py
@@ -278,6 +278,7 @@ def __init__(self, stream_id: int, topic: str,
self.topic_name = topic
self.stream_id = stream_id
self.model = controller.model
+ self.view = controller.view
narrow_function = partial(
controller.narrow_to_topic,
@@ -294,11 +295,57 @@ def __init__(self, stream_id: int, topic: str,
)
if controller.model.is_muted_topic(self.stream_id, self.topic_name):
- self.mark_muted()
+ self.update_widget(('muted', MUTE_MARKER), 'muted')
+ # TODO: Handle event-based approach for topic-muting.
+
+ def set_button_counts(self, unmuted_count: int) -> None:
+ self.update_count(unmuted_count)
+ self.model.unread_counts['streams'][self.stream_id] += unmuted_count
+ stream_button = self.view.stream_id_to_button[self.stream_id]
+ stream_button.update_count(
+ self.model.unread_counts['streams'][self.stream_id])
+ self.model.unread_counts['all_msg'] += unmuted_count
+ self.view.home_button.update_count(
+ self.model.unread_counts['all_msg'])
def mark_muted(self) -> None:
+ """
+ We do not decrease the count of topic button (or
+ unread_counts['unread_topics']) but only mark it as M since
+ we would require the correct count while unmuting.
+ However, we decrease the all msg count and the topic's stream
+ count by the unread counts.
+ """
self.update_widget(('muted', MUTE_MARKER), 'muted')
- # TODO: Handle event-based approach for topic-muting.
+ if self.stream_id in self.model.unread_counts['streams']:
+ self.model.unread_counts['streams'][self.stream_id] -= self.count
+ stream_button = self.view.stream_id_to_button[self.stream_id]
+ stream_button.update_count(
+ self.model.unread_counts['streams'][self.stream_id])
+ self.model.unread_counts['unread_muted_topics'][(
+ self.stream_id, self.topic_name)] = self.count
+ self.model.unread_counts['unread_topics'].pop(
+ (self.stream_id, self.topic_name), None)
+ self.model.unread_counts['all_msg'] -= self.count
+ self.view.home_button.update_count(
+ self.model.unread_counts['all_msg'])
+
+ def mark_unmuted(self) -> None:
+ key = (self.stream_id, self.topic_name)
+ if key in self.model.unread_counts['unread_muted_topics']:
+ unmuted_count = self.model.unread_counts[
+ 'unread_muted_topics'][key]
+ self.model.unread_counts['unread_muted_topics'].pop(key, None)
+ self.model.unread_counts['unread_topics'][key] = unmuted_count
+ self.set_button_counts(unmuted_count)
+ else:
+ # All messages in this topic are read.
+ self.set_button_counts(0)
+
+ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
+ if is_command_key('TOGGLE_MUTE_TOPIC', key):
+ self.controller.topic_muting_confirmation_popup(self)
+ return super().keypress(size, key)
class DecodedStream(TypedDict):
diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py
index 8851a0edfb..83a604d882 100644
--- a/zulipterminal/ui_tools/views.py
+++ b/zulipterminal/ui_tools/views.py
@@ -834,6 +834,8 @@ def topics_view(self, stream_button: Any) -> Any:
for topic in topics
]
+ self.view.topic_name_to_button = {topic.topic_name: topic
+ for topic in topics_btn_list}
self.view.topic_w = TopicsView(topics_btn_list, self.view,
stream_button)
w = urwid.LineBox(