Skip to content

Commit

Permalink
Add datestamps to message widgets when day changes
Browse files Browse the repository at this point in the history
When creating a message widget, show the date if the previous message
wasn't from today.

Fixes tdryer#27.
  • Loading branch information
tdryer committed Jan 3, 2015
1 parent c0289ce commit 4ec21d5
Showing 1 changed file with 48 additions and 20 deletions.
68 changes: 48 additions & 20 deletions hangups/ui/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@


LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
MESSAGE_TIME_FORMAT = '%I:%M:%S %p'
MESSAGE_DATETIME_FORMAT = '%y-%m-%d %I:%M:%S %p'
COL_SCHEMES = {
# Very basic scheme with no colour
'default': {
Expand Down Expand Up @@ -260,11 +262,12 @@ class MessageWidget(urwid.WidgetWrap):

"""Widget for displaying a single message in a conversation."""

def __init__(self, timestamp, text, user=None):
def __init__(self, timestamp, text, user=None, show_date=False):
# Save the timestamp as an attribute for sorting.
self.timestamp = timestamp
text = [
('msg_date', '(' + self._get_date_str(timestamp) + ') '),
('msg_date', '(' + self._get_date_str(timestamp,
show_date=show_date) + ') '),
('msg_text', text)
]
if user is not None:
Expand All @@ -273,31 +276,41 @@ def __init__(self, timestamp, text, user=None):
super().__init__(self._widget)

@staticmethod
def _get_date_str(timestamp):
def _get_date_str(timestamp, show_date=False):
"""Convert UTC datetime into user interface string."""
return timestamp.astimezone(tz=None).strftime('%I:%M:%S %p')
fmt = MESSAGE_DATETIME_FORMAT if show_date else MESSAGE_TIME_FORMAT
return timestamp.astimezone(tz=None).strftime(fmt)

def __lt__(self, other):
return self.timestamp < other.timestamp

@staticmethod
def from_conversation_event(conversation, conv_event):
def from_conversation_event(conversation, conv_event, prev_conv_event):
"""Return MessageWidget representing a ConversationEvent.
Returns None if the ConversationEvent does not have a widget
representation.
"""
user = conversation.get_user(conv_event.user_id)
# Check whether the previous event occurred on the same day as this
# event.
if prev_conv_event is not None:
is_new_day = (conv_event.timestamp.astimezone(tz=None).date() !=
prev_conv_event.timestamp.astimezone(tz=None).date())
else:
is_new_day = False
if isinstance(conv_event, hangups.ChatMessageEvent):
return MessageWidget(conv_event.timestamp, conv_event.text, user)
return MessageWidget(conv_event.timestamp, conv_event.text, user,
show_date=is_new_day)
elif isinstance(conv_event, hangups.RenameEvent):
if conv_event.new_name == '':
text = ('{} cleared the conversation name'
.format(user.first_name))
else:
text = ('{} renamed the conversation to {}'
.format(user.first_name, conv_event.new_name))
return MessageWidget(conv_event.timestamp, text)
return MessageWidget(conv_event.timestamp, text,
show_date=is_new_day)
elif isinstance(conv_event, hangups.MembershipChangeEvent):
event_users = [conversation.get_user(user_id) for user_id
in conv_event.participant_ids]
Expand All @@ -307,7 +320,8 @@ def from_conversation_event(conversation, conv_event):
.format(user.first_name, names))
else: # LEAVE
text = ('{} left the conversation'.format(names))
return MessageWidget(conv_event.timestamp, text)
return MessageWidget(conv_event.timestamp, text,
show_date=is_new_day)
else:
return None

Expand All @@ -327,15 +341,19 @@ def __init__(self, conversation):
self._first_loaded = False # Whether the first event is loaded

# Focus position is the first displayable event ID, or None.
is_displayable = lambda ev: (
MessageWidget.from_conversation_event(conversation, ev) is not None
)
self._focus_position = next((ev.id_ for ev in
reversed(conversation.events)
if is_displayable(ev)), None)
self._focus_position = next((
ev.id_ for ev in reversed(conversation.events)
if self._is_event_displayable(ev)
), None)

self._conversation.on_event.add_observer(self._handle_event)

def _is_event_displayable(self, conv_event):
"""Return True if the ConversationWidget is displayable."""
widget = MessageWidget.from_conversation_event(self._conversation,
conv_event, None)
return widget is not None

def _handle_event(self, conv_event):
"""Handle updating and scrolling when a new event is added.
Expand Down Expand Up @@ -384,14 +402,26 @@ def __getitem__(self, position):
"""Return widget at position or raise IndexError."""
if position == self.POSITION_LOADING:
if self._first_loaded:
# TODO: Show the full date the conversation was created.
return urwid.Text('No more messages', align='center')
future = asyncio.async(self._load())
future.add_done_callback(lambda future: future.result())
return urwid.Text('Loading...', align='center')
# May return None if the event doesn't have a widget representation.
try:
# Get the previous displayable event, or None if it isn't loaded or
# doesn't exist.
prev_position = self._get_position(position, prev=True)
if prev_position == self.POSITION_LOADING:
prev_event = None
else:
prev_event = self._conversation.get_event(prev_position)

# When creating the widget, also pass the previous event so a
# timestamp can be shown if this event occurred on a different day.
widget = MessageWidget.from_conversation_event(
self._conversation, self._conversation.get_event(position)
self._conversation, self._conversation.get_event(position),
prev_event
)
except KeyError:
raise IndexError('Invalid position: {}'.format(position))
Expand All @@ -415,12 +445,10 @@ def _get_position(self, position, prev=False):
raise IndexError('Reached first position')
# Skip events that aren't represented by a widget and try the next
# one.
try:
self[ev.id_]
except IndexError:
position = ev.id_
else:
if self._is_event_displayable(ev):
return ev.id_
else:
position = ev.id_

def next_position(self, position):
"""Return the position below position or raise IndexError."""
Expand Down

0 comments on commit 4ec21d5

Please sign in to comment.