When refreshing a timeline, instead of calling fetch() again, call
update() and prepend the new events to the event_list.
This has three benefits:
- it's quicker
- it creates less network traffic
- it leaves the EventList cursor where it is, so the user can easily see
which statuses are new.
---
tooi/tabs/timeline.py | 19 +++++++++++++++++--tooi/widgets/event_list.py | 20 ++++++++++++++++----
2 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/tooi/tabs/timeline.py b/tooi/tabs/timeline.py
index 59cee63..5cc56c4 100644
--- a/tooi/tabs/timeline.py+++ b/tooi/tabs/timeline.py
@@ -65,7 +65,7 @@ class TimelineTab(TabPane):
async def on_mount(self, message):
self.event_detail.focus()
- await self.refresh_timeline()+ await self.fetch_timeline() if self.initial_focus:
self.event_list.focus_event(self.initial_focus)
@@ -81,6 +81,21 @@ class TimelineTab(TabPane):
return make_event_detail(event)
async def refresh_timeline(self):
+ # Handle timelines that don't support updating.+ if not hasattr(self.timeline, 'update'):+ await self.fetch_timeline()+ return++ newevents = []++ async for eventslist in self.timeline.update():+ newevents += eventslist++ # The updates are returned in inverse chronological order, so reverse them before adding.+ newevents.reverse()+ self.event_list.prepend_events(newevents)++ async def fetch_timeline(self): self.generator = self.timeline.fetch()
events = await anext(self.generator)
self.event_list.replace(events)
@@ -151,7 +166,7 @@ class TimelineTab(TabPane):
# TODO: handle exceptions
try:
next_events = await anext(self.generator)
- self.event_list.update(next_events)+ self.event_list.append_events(next_events) finally:
self.fetching = False
self.status_bar.update()
diff --git a/tooi/widgets/event_list.py b/tooi/widgets/event_list.py
index e6bf8e6..d7dee17 100644
--- a/tooi/widgets/event_list.py+++ b/tooi/widgets/event_list.py
@@ -31,15 +31,14 @@ class EventList(ListView):
super().__init__()
self.events = []
self.current = None
- self.update(events)+ self.append_events(events) def replace(self, next_events: list[Event]):
- self.events = [] self.clear()
self.current = None
- self.update(next_events)+ self.append_events(next_events)- def update(self, next_events: list[Event]):+ def append_events(self, next_events: list[Event]): self.events += next_events
for event in next_events:
@@ -52,6 +51,19 @@ class EventList(ListView):
if self.current is not None:
self.post_message(EventHighlighted(self.current))
+ def prepend_events(self, next_events: list[Event]):+ self.events = next_events + self.events++ for event in next_events:+ self.mount(EventListItem(event), before=0)++ if self.current is None and len(self.events) > 0:+ self.index = 0+ self.current = self.events[0]++ if self.current is not None:+ self.post_message(EventHighlighted(self.current))+ def focus_event(self, event_id: str):
for i, event in enumerate(self.events):
if event.id == event_id:
--
2.43.0
[PATCH tooi 2/5] api.timeline: rename create_generator() to fetch()
update() is similar to fetch(), but it only fetches events which are new
since the previous call to update() (or the initial call to fetch()).
---
tooi/api/timeline.py | 97 +++++++++++++++++++++++++++++++++++++-------
1 file changed, 83 insertions(+), 14 deletions(-)
diff --git a/tooi/api/timeline.py b/tooi/api/timeline.py
index af5b105..d494e64 100644
--- a/tooi/api/timeline.py+++ b/tooi/api/timeline.py
@@ -50,8 +50,13 @@ async def fetch_timeline(
next_path = path
while next_path:
response = await request("GET", next_path, params=_params)
- yield response.json()- next_path = _get_next_path(response.headers)+ events = response.json()++ if len(events) == 0:+ next_path = None+ else:+ yield events+ next_path = _get_next_path(response.headers)class Timeline(ABC):
@@ -77,10 +82,36 @@ class StatusTimeline(Timeline):
super().__init__(name, instance)
self.path = path
self.params = params
+ self._most_recent_id = None async def fetch(self, limit: int | None = None) -> EventGenerator:
- async for events in fetch_timeline(self.instance, self.path, self.params, limit):- yield [StatusEvent(self.instance, from_dict(Status, s)) for s in events]+ async for eventlist in fetch_timeline(self.instance, self.path, self.params, limit):+ events = [StatusEvent(self.instance, from_dict(Status, s)) for s in eventlist]++ # Track the most recent id we've fetched, which will be the first, for update().+ if self._most_recent_id is None and len(events) > 0:+ self._most_recent_id = events[0].status.id++ yield events++ async def update(self, limit: int | None = None) -> EventGenerator:+ eventslist = fetch_timeline(+ self.instance,+ self.path,+ self.params,+ limit,+ self._most_recent_id)++ updated_most_recent = False++ async for eventlist in eventslist:+ events = [StatusEvent(self.instance, from_dict(Status, s)) for s in eventlist]++ if updated_most_recent == False and len(events) > 0:+ updated_most_recent = True+ self._most_recent_id = events[0].status.id++ yield eventsclass HomeTimeline(StatusTimeline):
@@ -159,8 +190,13 @@ class NotificationTimeline(Timeline):
NotificationTimeline loads events from the user's notifications.
https://docs.joinmastodon.org/methods/notifications/
"""
++ # TODO: not included: follow_request, poll, update, admin.sign_up, admin.report+ TYPES = [ "mention", "follow", "favourite", "reblog"]+ def __init__(self, instance: InstanceInfo):
super().__init__("Notifications", instance)
+ self._most_recent_id = None def make_notification_event(self, response: dict) -> Event | None:
notification = from_dict(Notification, response)
@@ -185,13 +221,35 @@ class NotificationTimeline(Timeline):
path = "/api/v1/notifications"
async for events in fetch_timeline(self.instance, path, params, limit):
- yield [e for e in map(self.make_notification_event, events) if e is not None]+ events = [e for e in map(self.make_notification_event, events) if e is not None]++ # Track the most recent id we've fetched, which will be the first, for update().+ if self._most_recent_id is None and len(events) > 0:+ self._most_recent_id = events[0].notification.id++ yield events def fetch(self, limit: int | None = None):
- # TODO: not included: follow_request, poll, update, admin.sign_up, admin.report- types = ["mention", "status", "reblog", "favourite", "follow"]- params = {"types[]": types}- return self.notification_generator(params, limit)+ return self.notification_generator({ "types[]": self.TYPES}, limit)++ async def update(self, limit: int | None = None) -> EventGenerator:+ eventslist = fetch_timeline(+ self.instance,+ "/api/v1/notifications",+ params={ "types[]": self.TYPES },+ limit=limit,+ since_id=self._most_recent_id)++ updated_most_recent = False++ async for eventlist in eventslist:+ events = [e for e in map(self.make_notification_event, eventlist) if e is not None]++ if updated_most_recent == False and len(events) > 0:+ updated_most_recent = True+ self._most_recent_id = events[0].notification.id++ yield eventsclass TagTimeline(StatusTimeline):
@@ -199,7 +257,14 @@ class TagTimeline(StatusTimeline):
TagTimeline loads events from the given hashtag.
This timeline only ever returns events of type StatusEvent.
"""
- def __init__(self, instance: InstanceInfo, hashtag: str, local: bool = False):+ def __init__(+ self,+ instance:+ InstanceInfo,+ hashtag: str,+ local: bool = False,+ remote: bool=False):+ self.local = local
# Normalise the hashtag to not begin with a hash
@@ -210,11 +275,15 @@ class TagTimeline(StatusTimeline):
raise (ValueError("TagTimeline: tag is empty"))
self.hashtag = hashtag
- super().__init__(f"#{self.hashtag}", instance)- def fetch(self, limit: int = 40):- path = f"/api/v1/timelines/tag/{quote(self.hashtag)}"- return self.status_generator(path, limit=limit)+ super().__init__(+ f"#{self.hashtag}",+ instance,+ f"/api/v1/timelines/tag/{quote(self.hashtag)}",+ params={+ "local": local,+ "remote": remote,+ })class ContextTimeline(Timeline):
--
2.43.0