Skip to content

Commit

Permalink
Implement cursor and lazy loading for articles
Browse files Browse the repository at this point in the history
  • Loading branch information
Scriptbash committed Jan 1, 2025
1 parent 5816e78 commit 5b7938a
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 18 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@
## Screenshots


| Feed (light) | Search screen (light) | Journals (dark) |
| Home screen (light) | Search screen (light) | Journals screen(dark) |
|---------------------------------------------------|------------------------------------------------------|---------------------------------------------------------|
| ![Feed](screenshots/light_ios_feed.png) | ![Search](screenshots/light_ios_search_screen.png) | ![Journals](screenshots/dark_ios_library_journals.png) |

| Example Query (dark) | Journal details (dark) | Abstract (dark) |
| Queries screen (dark) | Journal latest works (dark) | Abstract (dark) |
|---------------------------------------------------|------------------------------------------------------|---------------------------------------------------------|
| ![Query](screenshots/dark_ios_library_queries.png) | ![JournalDetails](screenshots/dark_ios_journal_details.png) | ![Abstract](screenshots/dark_android_abstract.png) |


## Description
<p align="justify">
Wispar is a user-friendly and privacy-friendly Android/iOS app that seamlessly searches scientific journals using the Crossref API. Stay updated on your preferred journals by following them and receive new article abstracts in your main feed. No account required. The integration of Unpaywall ensures convenient access to open-access articles, while EZproxy helps overcome subscription barriers.
Wispar is a user-friendly and privacy-friendly Android/iOS app that seamlessly searches scientific journals and articles using the Crossref API. Stay updated on your preferred journals by following them and receive new article abstracts in your main feed. No account required. The integration of Unpaywall ensures convenient access to open-access articles, while EZproxy helps overcome subscription barriers.

<b>Wispar is still under development and is not ready yet. APK files can be obtained from the workflow artifacts (must be signed in).</b>
</p>
Expand Down
87 changes: 79 additions & 8 deletions lib/screens/article_search_results_screen.dart
Original file line number Diff line number Diff line change
@@ -1,27 +1,98 @@
import 'package:flutter/material.dart';
import '../models/crossref_journals_works_models.dart';
import '../widgets/publication_card.dart';
import '../models/crossref_journals_works_models.dart' as journalsWorks;
import '../services/crossref_api.dart';

class ArticleSearchResultsScreen extends StatelessWidget {
final List<journalsWorks.Item> searchResults;
class ArticleSearchResultsScreen extends StatefulWidget {
final List<journalsWorks.Item> initialSearchResults;
final bool initialHasMore;
final Map<String, dynamic> queryParams;

const ArticleSearchResultsScreen({
Key? key,
required this.searchResults,
required this.initialSearchResults,
required this.initialHasMore,
required this.queryParams,
}) : super(key: key);

@override
_ArticleSearchResultsScreenState createState() =>
_ArticleSearchResultsScreenState();
}

class _ArticleSearchResultsScreenState
extends State<ArticleSearchResultsScreen> {
late List<journalsWorks.Item> _searchResults;
final ScrollController _scrollController = ScrollController();
bool _isLoadingMore = false;
bool _hasMoreResults = true;

@override
void initState() {
super.initState();
_searchResults = widget.initialSearchResults;

_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 100 &&
!_isLoadingMore &&
_hasMoreResults) {
_loadMoreResults();
}
});
}

Future<void> _loadMoreResults() async {
setState(() {
_isLoadingMore = true;
});

try {
final ListAndMore<journalsWorks.Item> newResults =
await CrossRefApi.getWorksByQuery(widget.queryParams);

setState(() {
_searchResults.addAll(newResults.list);
_hasMoreResults = newResults.hasMore;
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to load more results')),
);
} finally {
setState(() {
_isLoadingMore = false;
});
}
}

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Search Results'),
title: Text('Search results'),
),
body: searchResults.isNotEmpty
body: _searchResults.isNotEmpty
? ListView.builder(
itemCount: searchResults.length,
controller: _scrollController,
itemCount: _searchResults.length + (_hasMoreResults ? 1 : 0),
itemBuilder: (context, index) {
final item = searchResults[index];
if (index == _searchResults.length) {
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}

final item = _searchResults[index];
return PublicationCard(
title: item.title,
abstract: item.abstract,
Expand Down
20 changes: 15 additions & 5 deletions lib/services/crossref_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class CrossRefApi {
static const String email = '[email protected]';
static String? _journalCursor = '*';
static String? _journalWorksCursor = '*';
static String? _worksQueryCursor = '*';
static String? _currentQuery;

// Query journals by name
Expand Down Expand Up @@ -111,6 +112,10 @@ class CrossRefApi {
_journalWorksCursor = '*';
}

static void resetWorksQueryCursor() {
_worksQueryCursor = '*';
}

static String? getCurrentQuery() {
return _currentQuery;
}
Expand All @@ -132,24 +137,29 @@ class CrossRefApi {
}
}

static Future<List<journalsWorks.Item>> getWorksByQuery(
static Future<ListAndMore<journalsWorks.Item>> getWorksByQuery(
Map<String, dynamic> queryParams) async {
String url = '$baseUrl$worksEndpoint';
// Construct the query parameters string by iterating over the queryParams map
String queryString = queryParams.entries
.map((entry) =>
'${Uri.encodeQueryComponent(entry.key)}=${Uri.encodeQueryComponent(entry.value.toString())}')
.join('&');

final response =
await http.get(Uri.parse('$url?$queryString&rows=50&$email'));
String apiUrl = '$url?$queryString&rows=50&$email';
if (_worksQueryCursor != null) {
apiUrl += '&cursor=$_worksQueryCursor';
}
final response = await http.get(Uri.parse(apiUrl));
//print('$url?$queryString');

if (response.statusCode == 200) {
final responseData =
journalsWorks.JournalWork.fromJson(json.decode(response.body));
List<journalsWorks.Item> feedItems = responseData.message.items;
return feedItems;
_worksQueryCursor = responseData.message.nextCursor;
bool hasMoreResults =
_worksQueryCursor != null && _worksQueryCursor != "";
return ListAndMore(feedItems, hasMoreResults);
} else {
throw Exception('Failed to fetch results');
}
Expand Down
5 changes: 4 additions & 1 deletion lib/widgets/article_query_search_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ class QuerySearchFormState extends State<QuerySearchForm> {
try {
final dbHelper = DatabaseHelper();
late final response;
CrossRefApi.resetWorksQueryCursor(); // Reset the cursor on new search
if (saveQuery) {
final queryName = queryNameController.text.trim();
if (queryName != '') {
Expand Down Expand Up @@ -333,7 +334,9 @@ class QuerySearchFormState extends State<QuerySearchForm> {
context,
MaterialPageRoute(
builder: (context) => ArticleSearchResultsScreen(
searchResults: response,
initialSearchResults: response.list,
initialHasMore: response.hasMore,
queryParams: queryParams,
),
),
);
Expand Down
4 changes: 3 additions & 1 deletion lib/widgets/search_query_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ class SearchQueryCard extends StatelessWidget {
context,
MaterialPageRoute(
builder: (context) => ArticleSearchResultsScreen(
searchResults: response,
initialSearchResults: response.list,
initialHasMore: response.hasMore,
queryParams: queryMap,
),
),
);
Expand Down

0 comments on commit 5b7938a

Please sign in to comment.