diff --git a/lib/api/api.dart b/lib/api/api.dart index 37a32c5..c47e441 100644 --- a/lib/api/api.dart +++ b/lib/api/api.dart @@ -103,6 +103,7 @@ class APIRequestBuilder { return this; } + /// Mark single item as read, unread, saved, unsaved APIRequestBuilder markItem(Item item, ItemMarkType markAs) { _addArg('mark=item'); _addArg('as=${markAs.name}'); @@ -112,6 +113,22 @@ class APIRequestBuilder { return this; } + /// Use the `since_id` argument with the highest id of locally cached items to + /// request 50 additional items. Repeat until the items array in the response + /// is empty. + APIRequestBuilder sinceId(int id) { + _addArg('since_id=$id'); + return this; + } + + /// Use the `max_id` argument with the lowest id of locally cached items (or + /// 0 initially) to request 50 previous items. Repeat until the items array + /// in the response is empty. + APIRequestBuilder maxId(int id) { + _addArg('max_id=$id'); + return this; + } + /// Executes the API request and returns the JSON data Future> fetch() async { if (api.apiKey == null || api.apiUrl == null) { @@ -126,7 +143,7 @@ class APIRequestBuilder { } /// Executes the API request and populates the local cache with the response data - Future execute() async { + Future> execute() async { final data = await fetch(); if (data.containsKey('groups')) { @@ -185,6 +202,8 @@ class APIRequestBuilder { } api.cache.items.set(_markedItem!.id, _markedItem!); } + + return data; } } diff --git a/lib/api/cache.dart b/lib/api/cache.dart index e29af69..66ef7a6 100644 --- a/lib/api/cache.dart +++ b/lib/api/cache.dart @@ -42,6 +42,8 @@ class ObjectCache { void set(K key, T value) { _items[key] = value; } + + int get size => _items.length; } class ItemCache extends ObjectCache { diff --git a/lib/main.dart b/lib/main.dart index d37d606..88cdd0c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:feet/pages/login.dart'; import 'package:feet/widgets/centered_page_hint.dart'; import 'package:feet/widgets/homepage_post.dart'; import 'package:feet/widgets/login_prompt.dart'; @@ -10,6 +9,10 @@ import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'api/api.dart'; +// Limit the maximum amount of posts to fetch so we don't wait forever when +// fetching a large list +const FETCH_MAX_POSTS = 1000; + void main() { runApp(MyApp()); } @@ -78,9 +81,47 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { var _index = 0; var _fetched = false; + var _fetchedCount = 0; + var _knownPostsSize = -1; - Future refresh() => - widget.api.request().withFeeds().withGroups().withItems().execute(); + Future refresh() async { + widget.api.cache.clear(); + + print("Starting refresh"); + + var data = await widget.api + .request() + .withFeeds() + .withGroups() + .withItems() + .sinceId(0) + .execute(); + + while ( + (data['items'] as List).isNotEmpty && + _fetchedCount < FETCH_MAX_POSTS + ) { + print("Fetching more items"); + + var ids = widget.api.cache.items + .getAll() + .values + .map((value) => value.id) + .toList() + ..sort((a, b) => b - a); + + setState(() { + _fetchedCount = ids.length; + _knownPostsSize = data['total_items'] ?? -1; + }); + + data = + await widget.api.request().withItems().sinceId(ids.first).execute(); + + print( + "Item cache size is now ${widget.api.cache.items.size} ${ids.first} ${ids.last}"); + } + } @override void initState() { @@ -233,8 +274,10 @@ class _MyHomePageState extends State { body: loggedIn ? (_fetched ? pages[_index] - : const CenteredPageHint( - icon: Icons.downloading, text: "Fetching your feeds...")) + : CenteredPageHint( + icon: Icons.downloading, + text: + "Fetching your feeds...\n${_fetchedCount > 0 && _knownPostsSize > -1 ? "$_fetchedCount / $_knownPostsSize" : ""}")) : Center(child: LoginPrompt(api: widget.api)), ); } diff --git a/lib/widgets/centered_page_hint.dart b/lib/widgets/centered_page_hint.dart index a475911..1b93190 100644 --- a/lib/widgets/centered_page_hint.dart +++ b/lib/widgets/centered_page_hint.dart @@ -15,7 +15,7 @@ class CenteredPageHint extends StatelessWidget { padding: const EdgeInsets.all(12.0), child: Icon(icon, size: 48), ), - Text(text), + Text(text, textAlign: TextAlign.center), ], ), );