Skip to content

Commit 536d970

Browse files
authored
Merge pull request #361 from Scriptbash/openalex-filters
Add ISSN and open access filters to OpenAlex
2 parents 8fbd78f + 2e5bc1e commit 536d970

File tree

5 files changed

+122
-12
lines changed

5 files changed

+122
-12
lines changed

lib/l10n/app_en.arb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@
193193
"@publishedBetween":{},
194194
"noFilter": "No filter",
195195
"@noFilter":{},
196+
"issnFilter":"ISSN filter",
197+
"@issnFilter":{},
198+
"openAccessOnly":"Open access only",
199+
"@openAccessOnly":{},
196200
"selectStartDate": "Select start date",
197201
"@selectStartDate":{},
198202
"selectEndDate": "Select end date",

lib/screens/article_search_results_screen.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class _ArticleSearchResultsScreenState
8989
widget.queryParams['sortField'],
9090
widget.queryParams['sortOrder'],
9191
widget.queryParams['dateFilter'],
92+
widget.queryParams['issnFilter'],
93+
widget.queryParams['isOpenAccess'] ?? false,
9294
page: pageKey,
9395
);
9496

lib/services/openAlex_api.dart

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ class OpenAlexApi {
1111
static const String worksEndpoint = '/works?';
1212
static String? apiKey;
1313

14-
static Future<List<journalWorks.Item>> getOpenAlexWorksByQuery(String query,
15-
int scope, String? sortField, String? sortOrder, String? dateFilter,
14+
static Future<List<journalWorks.Item>> getOpenAlexWorksByQuery(
15+
String query,
16+
int scope,
17+
String? sortField,
18+
String? sortOrder,
19+
String? dateFilter,
20+
String? issnFilter,
21+
bool isOpenAccess,
1622
{int page = 1}) async {
1723
final prefs = await SharedPreferences.getInstance();
1824
apiKey = prefs.getString('openalex_api_key');
@@ -41,6 +47,21 @@ class OpenAlexApi {
4147
filterPart += ',$dateFilter';
4248
}
4349
}
50+
if (issnFilter != null && issnFilter.isNotEmpty) {
51+
if (filterPart.isEmpty) {
52+
filterPart = 'filter=$issnFilter';
53+
} else {
54+
filterPart += ',$issnFilter';
55+
}
56+
}
57+
58+
if (isOpenAccess) {
59+
if (filterPart.isEmpty) {
60+
filterPart = 'filter=is_oa:true';
61+
} else {
62+
filterPart += ',is_oa:true';
63+
}
64+
}
4465

4566
String sortPart = '';
4667
if (sortField != null && sortOrder != null) {

lib/widgets/article_openAlex_search_form.dart

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
1919
String selectedSortOrder = '-';
2020
DateTime? _publishedAfter;
2121
DateTime? _publishedBefore;
22+
final TextEditingController issnController = TextEditingController();
23+
bool _isOpenAccess = false;
2224

2325
String _dateMode = 'none';
2426
// bool _filtersExpanded = false;
@@ -33,6 +35,13 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
3335
_checkApiKey();
3436
}
3537

38+
@override
39+
void dispose() {
40+
issnController.dispose();
41+
queryNameController.dispose();
42+
super.dispose();
43+
}
44+
3645
void _addQueryPart(String type) {
3746
setState(() {
3847
if (queryParts.isNotEmpty && queryParts.last['type'] != 'operator') {
@@ -125,6 +134,7 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
125134
String? sortField = selectedSortField == '-' ? null : selectedSortField;
126135
String? sortOrder = selectedSortOrder == '-' ? null : selectedSortOrder;
127136
String? dateFilter;
137+
String? issnFilter;
128138

129139
String formatDate(DateTime d) => d.toIso8601String().split('T')[0];
130140

@@ -139,6 +149,16 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
139149
"to_publication_date:${formatDate(_publishedBefore!)}";
140150
}
141151

152+
if (issnController.text.trim().isNotEmpty) {
153+
final issns = issnController.text
154+
.split(',')
155+
.map((e) => e.trim())
156+
.where((e) => e.isNotEmpty)
157+
.join('|');
158+
159+
issnFilter = "locations.source.issn:$issns";
160+
}
161+
142162
try {
143163
showDialog(
144164
context: context,
@@ -182,8 +202,33 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
182202
}
183203
}
184204

205+
String issnPart = '';
206+
207+
if (issnFilter != null && issnFilter.isNotEmpty) {
208+
if (searchField.startsWith('filter=')) {
209+
issnPart = ',$issnFilter';
210+
} else if (datePart.isNotEmpty) {
211+
issnPart = ',$issnFilter';
212+
} else {
213+
issnPart = '&filter=$issnFilter';
214+
}
215+
}
216+
217+
String oaPart = '';
218+
if (_isOpenAccess) {
219+
if (searchField.startsWith('filter=')) {
220+
oaPart = ',is_oa:true';
221+
} else if (datePart.isNotEmpty || issnPart.isNotEmpty) {
222+
oaPart = ',is_oa:true';
223+
} else {
224+
oaPart = '&filter=is_oa:true';
225+
}
226+
}
227+
185228
queryString = '$searchField$query'
186229
'$datePart'
230+
'$issnPart'
231+
'$oaPart'
187232
'$selectedSortBy'
188233
'$selectedSortOrder';
189234
await dbHelper.saveSearchQuery(queryName, queryString, 'OpenAlex');
@@ -208,6 +253,8 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
208253
if (sortField != null) 'sortField': sortField,
209254
if (sortOrder != null) 'sortOrder': sortOrder,
210255
if (dateFilter != null) 'dateFilter': dateFilter,
256+
if (issnFilter != null) 'issnFilter': issnFilter,
257+
'isOpenAccess': _isOpenAccess,
211258
},
212259
source: 'OpenAlex',
213260
),
@@ -281,6 +328,16 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
281328
border: OutlineInputBorder(),
282329
),
283330
),
331+
SizedBox(height: 16),
332+
TextFormField(
333+
controller: issnController,
334+
decoration: InputDecoration(
335+
labelText: AppLocalizations.of(context)!.issnFilter,
336+
hintText: "0022-1694, 1234-5678",
337+
border: OutlineInputBorder(),
338+
),
339+
),
340+
284341
SizedBox(height: 16),
285342

286343
DropdownButtonFormField<String>(
@@ -416,8 +473,24 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
416473
),
417474
],
418475
),
476+
SizedBox(height: 16),
477+
SwitchListTile(
478+
title: Text(
479+
AppLocalizations.of(context)!.openAccessOnly,
480+
style: TextStyle(fontWeight: FontWeight.w500),
481+
),
482+
value: _isOpenAccess,
483+
onChanged: (bool value) {
484+
setState(() {
485+
_isOpenAccess = value;
486+
});
487+
},
488+
),
489+
SizedBox(height: 10),
490+
Divider(
491+
height: 5,
492+
),
419493
SizedBox(height: 10),
420-
421494
// Dynamic query builder
422495
Column(
423496
children: () {
@@ -548,27 +621,29 @@ class OpenAlexSearchFormState extends State<OpenAlexSearchForm> {
548621
],
549622
),*/
550623
SizedBox(height: 20),
551-
Text(
552-
AppLocalizations.of(context)!.saveQuery,
553-
style: TextStyle(fontWeight: FontWeight.bold),
554-
),
555-
Switch(
624+
SwitchListTile(
625+
title: Text(
626+
AppLocalizations.of(context)!.saveQuery,
627+
style: TextStyle(fontWeight: FontWeight.w600),
628+
),
556629
value: saveQuery,
557630
onChanged: (bool value) {
558631
setState(() {
559632
saveQuery = value;
560633
});
561634
},
562635
),
563-
SizedBox(height: 8),
564-
if (saveQuery)
636+
if (saveQuery) ...[
637+
const SizedBox(height: 8),
565638
TextFormField(
566639
controller: queryNameController,
567640
decoration: InputDecoration(
568641
labelText: AppLocalizations.of(context)!.queryName,
569-
border: OutlineInputBorder(),
642+
border: const OutlineInputBorder(),
570643
),
571644
),
645+
],
646+
572647
SizedBox(height: 70),
573648
],
574649
),

lib/widgets/search_query_card.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ class SearchQueryCardState extends State<SearchQueryCard> {
6868
String? sortField;
6969
String? sortOrder;
7070
String? dateFilter;
71+
String? issnFilter;
7172
String? filterValue;
73+
bool isOpenAccess = false;
7274

7375
if (widget.queryProvider == 'Crossref') {
7476
// Convert the params string to the needed mapstring
@@ -77,7 +79,7 @@ class SearchQueryCardState extends State<SearchQueryCard> {
7779
queryMap = Uri.splitQueryString(widget.queryParams);
7880

7981
String? sortParam = queryMap['sort'];
80-
String? filterValue = queryMap['filter'];
82+
filterValue = queryMap['filter'];
8183
String? searchValue = queryMap['search'];
8284

8385
sortField = null;
@@ -109,6 +111,10 @@ class SearchQueryCardState extends State<SearchQueryCard> {
109111
} else if (f.startsWith('from_publication_date:') ||
110112
f.startsWith('to_publication_date:')) {
111113
remainingFilters.add(f);
114+
} else if (f == 'is_oa:true') {
115+
isOpenAccess = true;
116+
} else if (f.startsWith('locations.source.issn:')) {
117+
issnFilter = f;
112118
}
113119
}
114120
if (remainingFilters.isNotEmpty) {
@@ -136,6 +142,8 @@ class SearchQueryCardState extends State<SearchQueryCard> {
136142
'sortField': sortField,
137143
'sortOrder': sortOrder,
138144
'dateFilter': dateFilter,
145+
'issnFilter': issnFilter,
146+
'isOpenAccess': isOpenAccess,
139147
'filter': filterValue,
140148
},
141149
source: widget.queryProvider,

0 commit comments

Comments
 (0)