Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Ninja
run: sudo apt-get install -y ninja-build
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,27 @@

## Screenshots

| Followed journals (dark) | Search results (light) | Journal details (light) | Abstract (dark) |
|----------------------------------------------------|---------------------------------------------------------|--------------------------------------------------------------|----------------------------------------------------|
| ![Journals](screenshots/dark_android_journals.png) | ![Search](screenshots/light_android_search_results.png) | ![JournalDetails](screenshots/light_ios_journal_details.png) | ![abstract](screenshots/dark_android_abstract.png) |

| 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) |

| 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>

## Features overview
<ul>
<li> [x] Search and follow journals</li>
<li> [x] Search for articles and save the queries for easy access later</li>
<li> [x] Download articles for offline access *</li>
<li> [x] EZproxy and Unpaywall integration</li>
<li> [x] Send articles to Zotero</li>
Expand All @@ -45,9 +51,11 @@ Wispar is a user-friendly and privacy-friendly Android/iOS app that seamlessly s

<p align ="justify">
Wispar uses Weblate to manage translations. You can find the hosted instance at <a href="https://hosted.weblate.org/engage/wispar/">https://hosted.weblate.org/engage/wispar/</a>
</br></br>Translation status:
</p>

A huge thank you to Weblate for hosting the translations for free :heart:

Translation status:
</p>
<a href="https://hosted.weblate.org/engage/wispar/">
<img src="https://hosted.weblate.org/widget/wispar/multi-auto.svg" alt="Translation status" />
</a>
Expand Down
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ if (flutterVersionName == null) {
android {
namespace "app.wispar.wispar"
compileSdkVersion 34
ndkVersion "25.1.8937393"
ndkVersion "27.0.12077973"

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand Down
8 changes: 4 additions & 4 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand All @@ -24,6 +26,8 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -41,9 +45,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
9 changes: 7 additions & 2 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@
"@unfollow": {
"description": "The button text shown on journal cards when it is followed."
},
"library": "Library",
"@library": {
"description": "The library menu button and the app bar title when in the library screen."
},

"journals": "Journals",
"@journals": {
"description": "The journals menu button and the app bar title when in the journals screen."
},
"search": "Search",
"search": "Search",
"@search": {
"description": "Placeholder text shown inside the search bar."
"description": "Text shown inside the search screen app bar and for the seach button."
},
"publisher": "Publisher",
"@publisher": {},
Expand Down
19 changes: 14 additions & 5 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:salomon_bottom_bar/salomon_bottom_bar.dart';
import 'theme_provider.dart';
import 'screens/home_screen.dart';
import 'screens/search_screen.dart';
import 'screens/favorites_screen.dart';
import 'screens/journals_screen.dart';
import 'screens/library_screen.dart';
import 'screens/downloads_screen.dart';

void main() {
Expand Down Expand Up @@ -34,6 +35,7 @@ class _WisparState extends State<Wispar> {
var _currentIndex = 0;
final List<Widget> _pages = [
const HomeScreen(),
const SearchScreen(),
const LibraryScreen(),
const FavoritesScreen(),
const DownloadsScreen(),
Expand All @@ -42,8 +44,10 @@ class _WisparState extends State<Wispar> {
switch (key) {
case 'home':
return AppLocalizations.of(context)!.home;
case 'journals':
return AppLocalizations.of(context)!.journals;
case 'search':
return AppLocalizations.of(context)!.search;
case 'library':
return AppLocalizations.of(context)!.library;
case 'favorites':
return AppLocalizations.of(context)!.favorites;
case 'downloads':
Expand Down Expand Up @@ -99,8 +103,13 @@ class _WisparState extends State<Wispar> {
selectedColor: Colors.deepPurpleAccent,
),
SalomonBottomBarItem(
icon: const Icon(Icons.library_books_outlined),
title: Text(getLocalizedText(bottomBarContext, 'journals')),
icon: const Icon(Icons.search_outlined),
title: Text(getLocalizedText(bottomBarContext, 'search')),
selectedColor: Colors.deepPurpleAccent,
),
SalomonBottomBarItem(
icon: const Icon(Icons.my_library_books_outlined),
title: Text(getLocalizedText(bottomBarContext, 'library')),
selectedColor: Colors.deepPurpleAccent,
),
SalomonBottomBarItem(
Expand Down
82 changes: 30 additions & 52 deletions lib/models/crossref_journals_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ class Item {
String publisher;
Map<String, double> coverage;
String title;
List<Subject> subjects;
CoverageType coverageType;
Map<String, bool> flags;
List<String> issn;
Expand All @@ -91,29 +90,27 @@ class Item {
required this.publisher,
required this.coverage,
required this.title,
required this.subjects,
required this.coverageType,
required this.flags,
required this.issn,
required this.issnType,
});

factory Item.fromJson(Map<String, dynamic> json) => Item(
lastStatusCheckTime: json["last-status-check-time"],
counts: Counts.fromJson(json["counts"]),
breakdowns: Breakdowns.fromJson(json["breakdowns"]),
publisher: json["publisher"],
coverage: Map.from(json["coverage"])
.map((k, v) => MapEntry<String, double>(k, v?.toDouble())),
title: json["title"],
subjects: List<Subject>.from(
json["subjects"].map((x) => Subject.fromJson(x))),
coverageType: CoverageType.fromJson(json["coverage-type"]),
flags:
Map.from(json["flags"]).map((k, v) => MapEntry<String, bool>(k, v)),
issn: List<String>.from(json["ISSN"].map((x) => x)),
lastStatusCheckTime: json["last-status-check-time"] ?? 0,
counts: Counts.fromJson(json["counts"] ?? {}),
breakdowns: Breakdowns.fromJson(json["breakdowns"] ?? {}),
publisher: json["publisher"] ?? "Unknown",
coverage: Map.from(json["coverage"] ?? {})
.map((k, v) => MapEntry<String, double>(k, (v ?? 0).toDouble())),
title: json["title"] ?? "Untitled",
coverageType: CoverageType.fromJson(json["coverage-type"] ?? {}),
flags: Map.from(json["flags"] ?? {})
.map((k, v) => MapEntry<String, bool>(k, v ?? false)),
issn: List<String>.from(json["ISSN"]?.map((x) => x) ?? []),
issnType: List<IssnType>.from(
json["issn-type"].map((x) => IssnType.fromJson(x))),
(json["issn-type"] ?? []).map((x) => IssnType.fromJson(x)),
),
);

Map<String, dynamic> toJson() => {
Expand All @@ -124,7 +121,6 @@ class Item {
"coverage":
Map.from(coverage).map((k, v) => MapEntry<String, dynamic>(k, v)),
"title": title,
"subjects": List<dynamic>.from(subjects.map((x) => x.toJson())),
"coverage-type": coverageType.toJson(),
"flags": Map.from(flags).map((k, v) => MapEntry<String, dynamic>(k, v)),
"ISSN": List<dynamic>.from(issn.map((x) => x)),
Expand All @@ -140,8 +136,10 @@ class Breakdowns {
});

factory Breakdowns.fromJson(Map<String, dynamic> json) => Breakdowns(
doisByIssuedYear: List<List<int>>.from(json["dois-by-issued-year"]
.map((x) => List<int>.from(x.map((x) => x)))),
doisByIssuedYear: List<List<int>>.from(
(json["dois-by-issued-year"] ?? [])
.map((x) => List<int>.from(x.map((x) => x ?? 0))),
),
);

Map<String, dynamic> toJson() => {
Expand All @@ -162,9 +160,9 @@ class Counts {
});

factory Counts.fromJson(Map<String, dynamic> json) => Counts(
currentDois: json["current-dois"],
backfileDois: json["backfile-dois"],
totalDois: json["total-dois"],
currentDois: json["current-dois"] ?? 0,
backfileDois: json["backfile-dois"] ?? 0,
totalDois: json["total-dois"] ?? 0,
);

Map<String, dynamic> toJson() => {
Expand All @@ -186,12 +184,12 @@ class CoverageType {
});

factory CoverageType.fromJson(Map<String, dynamic> json) => CoverageType(
all: Map.from(json["all"])
.map((k, v) => MapEntry<String, double>(k, v?.toDouble())),
backfile: Map.from(json["backfile"])
.map((k, v) => MapEntry<String, double>(k, v?.toDouble())),
current: Map.from(json["current"])
.map((k, v) => MapEntry<String, double>(k, v?.toDouble())),
all: Map.from(json["all"] ?? {})
.map((k, v) => MapEntry<String, double>(k, (v ?? 0).toDouble())),
backfile: Map.from(json["backfile"] ?? {})
.map((k, v) => MapEntry<String, double>(k, (v ?? 0).toDouble())),
current: Map.from(json["current"] ?? {})
.map((k, v) => MapEntry<String, double>(k, (v ?? 0).toDouble())),
);

Map<String, dynamic> toJson() => {
Expand All @@ -213,8 +211,8 @@ class IssnType {
});

factory IssnType.fromJson(Map<String, dynamic> json) => IssnType(
value: json["value"],
type: typeValues.map[json["type"]]!,
value: json["value"] ?? "",
type: typeValues.map[json["type"]] ?? Type.ELECTRONIC,
);

Map<String, dynamic> toJson() => {
Expand All @@ -228,26 +226,6 @@ enum Type { ELECTRONIC, PRINT }
final typeValues =
EnumValues({"electronic": Type.ELECTRONIC, "print": Type.PRINT});

class Subject {
int asjc;
String name;

Subject({
required this.asjc,
required this.name,
});

factory Subject.fromJson(Map<String, dynamic> json) => Subject(
asjc: json["ASJC"],
name: json["name"],
);

Map<String, dynamic> toJson() => {
"ASJC": asjc,
"name": name,
};
}

class Query {
int startIndex;
String searchTerms;
Expand All @@ -258,8 +236,8 @@ class Query {
});

factory Query.fromJson(Map<String, dynamic> json) => Query(
startIndex: json["start-index"],
searchTerms: json["search-terms"],
startIndex: json["start-index"] ?? 0,
searchTerms: json["search-terms"] ?? "",
);

Map<String, dynamic> toJson() => {
Expand Down
Loading
Loading