diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 90c6da22..5e391df7 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -96,7 +96,7 @@ jobs:
security import certificate.p12 -k build.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
security find-identity
- /usr/bin/codesign --force --deep --options runtime -s "$MACOS_SIGN_IDENTITY" build/macos/Build/Products/Release/Wispar.app
+ /usr/bin/codesign --force --deep --options runtime --entitlements macos/Runner/Release.entitlements -s "$MACOS_SIGN_IDENTITY" build/macos/Build/Products/Release/Wispar.app
/usr/bin/codesign --verify --deep --strict --verbose=2 build/macos/Build/Products/Release/Wispar.app
- name: Notarize app
env:
diff --git a/PRIVACY.md b/PRIVACY.md
index 3a30742f..c897277b 100644
--- a/PRIVACY.md
+++ b/PRIVACY.md
@@ -1,15 +1,45 @@
-Wispar is an open-source mobile app that prioritizes user privacy. Our app does not collect any data or access personal information.
+# Privacy policy
-However, Wispar integrates with external services to enhance functionality. Users should be aware that these services could be collecting information such as IP addresses, device-related data and other data. We encourage users to review the privacy policies of each service for a comprehensive understanding of their data collection practices.
+Wispar is an open-source mobile app that prioritizes user privacy. By default, our app does not collect any data or access personal information.
-Third-party services used by Wispar:
+Wispar does not include any third-party trackers, advertisements, or telemetry (e.g., Google Analytics or Firebase)
-Core services:
+## Wispar Sync (Optional)
+
+Wispar offers an optional cloud sync feature called **Wispar Sync**. When this feature is enabled, the following data is collected solely for authentication and synchronization purposes:
+
+- Email address
+- App data necessary for syncing which includes:
+ - Article metadata for favorited and hidden publications
+ - Journal metadata for followed and other journals
+ - Custom feeds parameters
+ - Saved search queries
+ - EZproxy known URLs
+
+This data is **not shared, sold, or used for any other purpose**.
+
+The Wispar Sync server is hosted on a VPS provided by **Hetzner** in Germany.
+
+Users retain full control over their data:
+
+- You can permanently delete your data at any time using the delete cloud account button in the sync settings. While deletion is immediate, your data may persist for up to **7 days** within daily server backups before being permanently overwritten.
+- You may also choose to self-host the sync backend, allowing you to keep full ownership and control of your data.
+
+**If you do not enable Wispar Sync, no data is collected by Wispar.**
+
+
+## Third-party services:
+
+Wispar integrates with external services to enhance functionality. These services may collect information such as IP addresses, device-related data, and other usage data.
+
+We encourage users to review the privacy policies of these services for a comprehensive understanding of their data collection practices.
+
+**Core services:**
- Crossref: https://www.crossref.org/operations-and-sustainability/privacy
- OpenAlex: https://openalex.org/OpenAlex_privacy_policy.pdf
-Optional services (used only when enabled and/or an API key is provided):
+**Optional services** (used only when enabled and/or an API key is provided):
- Unpaywall (enabled by default): https://unpaywall.org/legal/privacy
- Zotero: https://www.zotero.org/support/privacy
@@ -19,4 +49,8 @@ Optional services (used only when enabled and/or an API key is provided):
Please review the privacy policies of these services to understand how they handle data. Wispar does not have control over the data collection practices of these external services.
-Inquiries can be submitted to wispar-app@protonmail.com
\ No newline at end of file
+## Contact
+For any inquiries, please contact: support@wispar.app
+
+---
+Last Updated: March 2026
\ No newline at end of file
diff --git a/README.md b/README.md
index 45111f2d..8ad413c6 100644
--- a/README.md
+++ b/README.md
@@ -1,97 +1,113 @@
-
+
+
Stay up-to-date with academic journals and the latest research articles!
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
---
## Description
-
-Wispar is a user-friendly and privacy-friendly Android/iOS app that seamlessly searches scientific journals and articles using the Crossref and OpenAlex APIs. 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 app for Android, iOS, Windows, MacOS and Linux that seamlessly searches scientific journals and articles using the Crossref and OpenAlex APIs. 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.
## Features overview
-
- - [x] Search and follow journals
- - [x] Search for articles and save the queries for easy access later. You can even include them in your feed!
- - [x] Download articles for offline reading
- - [x] EZproxy and Unpaywall integration
- - [x] Send articles to Zotero
- - [x] Share articles
- - [x] Scrape missing abstracts
- - [x] Scrape graphical abstracts
- - [x] Export/Import the local database
- - [x] Notifications and background journals updates
- - [x] Create custom feeds
- - [x] Customizable swipe gestures
- - [x] Translate title and abstracts (requires an AI API key)
- - [x] Chat with your papers using AI
-
+
+- [x] Search and follow journals
+- [x] Search for articles and save the queries for easy access later. You can even include them in your feed!
+- [x] Sync your database across devices. You can also self-host the sync backend!
+- [x] Download articles for offline reading
+- [x] EZproxy and Unpaywall integration
+- [x] Send articles to Zotero
+- [x] Share articles
+- [x] Scrape missing abstracts
+- [x] Scrape graphical abstracts
+- [x] Export/Import the local database
+- [x] Notifications and background journals updates
+- [x] Create custom feeds
+- [x] Customizable swipe gestures
+- [x] Translate title and abstracts (requires an AI API key)
+- [x] Chat with your papers using AI
+
## Translations
-
-Wispar uses Weblate to manage translations. You can find the hosted instance at https://hosted.weblate.org/engage/wispar/
+Wispar uses Weblate to manage translations. You can find the hosted instance at [https://hosted.weblate.org/engage/wispar/](https://hosted.weblate.org/engage/wispar/)
A huge thank you to Weblate for hosting the translations for free :heart:.
Translation status:
-
-
-
-
+
+[](https://hosted.weblate.org/engage/wispar/)
## Contribute
-
-
- - There are many ways you can contribute to improving Wispar—and it's not just about writing code!
- - You can help translate Wispar into your language by using our hosted Weblate instance.
- - Additionally, providing feedback and reporting bugs are invaluable ways to contribute!
-
- If you contribute to the project, feel free to add yourself to the .zenodo.json file to be credited!
-
+
+There are many ways you can contribute to improving Wispar, and it's not just about writing code!
+
+* **Translations:** Help translate Wispar into your language using the [hosted Weblate instance](https://hosted.weblate.org/engage/wispar/).
+* **Documentation:** Help me expand and finish the [official docs](https://wispar.app/docs/intro). It is currently a work in progress and any help is greatly appreciated!
+* **Feedback:** Reporting bugs and suggesting features via [GitHub Issues](https://github.com/Scriptbash/Wispar/issues) is also an invaluable way to contribute!
+
+**If you contribute to the project, feel free to add yourself to the `.zenodo.json` file to be credited!**
+
## Help
-
-If you run into any issue while using Wispar, have a question or want to share your feedback, please open an issue here : https://github.com/Scriptbash/Wispar/issues
-
+
+If you run into any issue while using Wispar, have a question or want to share your feedback, please [open an issue here](https://github.com/Scriptbash/Wispar/issues).
+
+If you have an issue with your Wispar Sync account, you can send an email at [support[at]wispar.app](mailto:support@wispar.app) and I will try to help as soon as possible.
## Credits
-
-## Screenshots
+- Thank you [Sergio](https://github.com/reds2401) for the original app icon and [Lingling](https://github.com/Meigane) for the updated app icon!
+- [Library Proxy URL Database](https://libproxy-db.org/)
+- [Unpaywall](https://unpaywall.org/)
+- [Crossref](https://www.crossref.org/)
+- [OpenAlex](https://openalex.org/)
+- [PocketBase](https://pocketbase.io/)
+## Screenshots
|  |  |  |
|---|---|---|
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index 0c67376e..1f2c0ab4 100644
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -1,5 +1,10 @@
-
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)app.wispar.wispar
+
+
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 9a6aa83c..05b652cc 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -777,5 +777,95 @@
"downloadStarting": "Download starting.",
"downloadFoundPdf": "Download found PDF",
"downloadToApp": "Download in app",
- "downloadToAppSubtitle": "Attempt to download and view PDF directly in Wispar for better integration."
+ "downloadToAppSubtitle": "Attempt to download and view PDF directly in Wispar for better integration.",
+ "loginToSyncDevices":"Login to sync across devices",
+ "@loginToSyncDevices":{},
+ "syncLongDescription":"Wispar offers an optional sync service to keep your data consistent across devices. You can use Wispar Sync (see Privacy Policy) or self-host for full data control (see Documentation). You can also skip sync and continue.",
+ "@syncLongDescription":{},
+ "documentation":"Documentation",
+ "@documentation":{},
+ "cloudSync": "Cloud sync",
+ "@cloudSync":{},
+ "selfHosted":"Self-Hosted",
+ "@selfHosted":{},
+ "syncNow": "Sync now",
+ "@syncNow":{},
+ "syncing":"Syncing...",
+ "@syncing":{},
+ "lastSync":"Last sync: {time}",
+ "@lastSync":{},
+ "backgroundSync": "Background syncing",
+ "@backgroundSync":{},
+ "backgroundSyncDescription": "If enabled, syncing will occur automatically while the app is in use.",
+ "@backgroundSyncDescription":{},
+ "authFailed":"Authentication failed: {error}",
+ "@authFailed":{},
+ "accountAlreadyExists":"This account already exists.",
+ "@accountAlreadyExists":{},
+ "pleaseEnterEmail":"Please enter your email.",
+ "@pleaseEnterEmail":{},
+ "pleaseEnterValidEmail":"Please enter a valid email address.",
+ "@pleaseEnterValidEmail":{},
+ "pleaseEnterPassword":"Please enter your password.",
+ "@pleaseEnterPassword":{},
+ "passwordTooShort":"Password must be at least 8 characters.",
+ "@passwordTooShort":{},
+ "checkdetailAndTryAgain":"Check your details and try again.",
+ "@checkdetailAndTryAgain":{},
+ "invalidEmailOrPassword":"Invalid email or password.",
+ "@invalidEmailOrPassword":{},
+ "cantConnectServer":"Could not connect to the server.",
+ "@cantConnectServer":{},
+ "forgotPassword":"Forgot password?",
+ "@forgotPassword":{},
+ "passwordResetSent": "Password reset email sent! Check your inbox and your spam folder.",
+ "@passwordResetSent":{},
+ "deleteAccountFailed":"Failed to delete account: {error}",
+ "@deleteAccountFailed":{},
+ "syncFailed":"Sync failed: {e}",
+ "@syncFailed":{},
+ "syncSuccess":"Sync completed successfully!",
+ "@syncSuccess":{},
+ "login":"Login",
+ "@login":{},
+ "userIsLoggedIn":"You are now logged in! Your data will stay in sync.",
+ "@userIsLoggedIn":{},
+ "logout":"Logout",
+ "@logout":{},
+ "user":"User: {name}",
+ "@user":{},
+ "syncServer":"Sync server: {address}",
+ "@syncServer":{},
+ "serverUrl": "Server URL",
+ "@serverUrl":{},
+ "checkEmail":"Check your email.",
+ "@checkEmail":{},
+ "checkEmailDescription":"A verification link has been sent. Please verify your account before logging in.",
+ "@checkEmailDescription":{},
+ "emailNotVerifiedError":"Please verify your email address before syncing.",
+ "@emailNotVerifiedError":{},
+ "waitResendEmail":"Wait {cooldown}s to resend the email.",
+ "@waitResendEmail":{},
+ "resendEmail":"Resend verification email",
+ "@resendEmail":{},
+ "close":"Close",
+ "@close":{},
+ "email":"Email",
+ "@email":{},
+ "password":"Password",
+ "@password":{},
+ "signUp":"Sign up",
+ "@signUp":{},
+ "haveAnAccount":"Have an account? Login",
+ "@haveAnAccount":{},
+ "needAnAccount":"Need an account? Sign up",
+ "@needAnAccount":{},
+ "deleteCloudAccount":"Delete cloud account",
+ "@deleteCloudAccount":{},
+ "deleteAccountQmark":"Permenantly delete account?",
+ "@deleteAccountQmark":{},
+ "deleteAccountExplanation": "This will permanently delete your account and cloud data. Your local data on this device will remain.",
+ "@deleteAccountExplanation":{},
+ "accountAndDataDeleted":"Account and cloud data deleted",
+ "@accountAndDataDeleted":{}
}
diff --git a/lib/main.dart b/lib/main.dart
index 00dffb0a..b9cffd34 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -14,11 +14,13 @@ import 'package:wispar/screens/downloads_screen.dart';
import 'package:google_nav_bar/google_nav_bar.dart';
import 'package:wispar/services/background_service.dart';
import 'package:wispar/services/logs_helper.dart';
+import 'package:wispar/services/pocketbase_service.dart';
import 'package:background_fetch/background_fetch.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:path_provider/path_provider.dart';
import 'package:window_manager/window_manager.dart';
+import 'package:wispar/services/sync_service.dart';
import 'package:wispar/webview_env.dart';
import 'dart:io' show Platform;
@@ -45,6 +47,7 @@ void main() async {
databaseFactory = databaseFactoryFfi;
}
LogsService();
+ await PocketBaseService().init();
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
await windowManager.ensureInitialized();
WindowOptions windowOptions = const WindowOptions(
@@ -78,19 +81,24 @@ void main() async {
class Wispar extends StatefulWidget {
static const title = 'Wispar';
- const Wispar({Key? key}) : super(key: key);
+ const Wispar({super.key});
@override
- _WisparState createState() => _WisparState();
+ WisparState createState() => WisparState();
}
-class _WisparState extends State {
+class WisparState extends State {
bool _hasSeenIntro = false;
+ final pbService = PocketBaseService();
+ final syncManager = SyncManager();
@override
void initState() {
super.initState();
_checkIntroPreference();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ syncManager.triggerBackgroundSync();
+ });
}
// Load the intro preference
@@ -173,14 +181,13 @@ class _WisparState extends State {
class HomeScreenNavigator extends StatefulWidget {
final bool skipToSearch;
- const HomeScreenNavigator({Key? key, this.skipToSearch = false})
- : super(key: key);
+ const HomeScreenNavigator({super.key, this.skipToSearch = false});
@override
- _HomeScreenNavigatorState createState() => _HomeScreenNavigatorState();
+ HomeScreenNavigatorState createState() => HomeScreenNavigatorState();
}
-class _HomeScreenNavigatorState extends State {
+class HomeScreenNavigatorState extends State {
var _currentIndex = 0;
@override
diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart
index a6d830df..b2f27ef8 100644
--- a/lib/screens/home_screen.dart
+++ b/lib/screens/home_screen.dart
@@ -7,6 +7,7 @@ import 'package:wispar/services/database_helper.dart';
import 'package:wispar/services/feed_service.dart';
import 'package:wispar/services/abstract_helper.dart';
import 'package:wispar/models/feed_filter_entity.dart';
+import 'package:wispar/services/sync_service.dart';
import 'package:wispar/widgets/publication_card/publication_card.dart';
import 'package:wispar/screens/publication_card_settings_screen.dart';
import 'package:wispar/widgets/sort_dialog.dart';
@@ -46,6 +47,7 @@ class HomeScreenState extends State {
List _activeFeed = [];
final FeedService _feedService = FeedService();
+ final syncManager = SyncManager();
List