From d3c7398da0a569cf57234d3c0a6c92dccc5ed3f4 Mon Sep 17 00:00:00 2001 From: Scriptbash <98601298+Scriptbash@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:29:26 -0500 Subject: [PATCH 1/5] store db in app documents directory --- lib/screens/database_settings_screen.dart | 71 ++++++++++------------- lib/services/database_helper.dart | 26 +++++---- 2 files changed, 45 insertions(+), 52 deletions(-) diff --git a/lib/screens/database_settings_screen.dart b/lib/screens/database_settings_screen.dart index 84fba0a4..3efc2448 100644 --- a/lib/screens/database_settings_screen.dart +++ b/lib/screens/database_settings_screen.dart @@ -118,8 +118,7 @@ class DatabaseSettingsScreenState extends State { String? targetPath; if (Platform.isIOS) { - targetPath = - await DatabaseHelper.resolveCustomDatabasePath(pickedFolder); + targetPath = await DatabaseHelper.resolveBookmarkPath(pickedFolder); if (targetPath == null) { throw Exception('Failed to resolve custom database bookmark on iOS.'); } @@ -150,22 +149,21 @@ class DatabaseSettingsScreenState extends State { try { await _showLoadingDialog( AppLocalizations.of(context)!.movingDatabase); - final dbHelper = DatabaseHelper(); await dbHelper.closeDatabase(); + final oldDbPath = await dbHelper.getDbPath(); + final oldBaseDir = Directory(p.dirname(oldDbPath)); - final defaultAppDir = await getApplicationDocumentsDirectory(); - final oldDBPath = p.join(await getDatabasesPath(), 'wispar.db'); - final newDBPath = p.join(targetPath, 'wispar.db'); + final newDbPath = p.join(targetPath, 'wispar.db'); // Move database - final oldDBFile = File(oldDBPath); + final oldDBFile = File(oldDbPath); if (await oldDBFile.exists()) { - await oldDBFile.copy(newDBPath); + await oldDBFile.copy(newDbPath); await oldDBFile.delete(); } // Move PDFs - await for (final file in defaultAppDir.list()) { + await for (final file in oldBaseDir.list()) { if (file is File && file.path.endsWith('.pdf')) { final newFile = File(p.join(targetPath, p.basename(file.path))); await file.copy(newFile.path); @@ -175,7 +173,7 @@ class DatabaseSettingsScreenState extends State { // Move graphical abstracts final oldGraphicalDir = - Directory(p.join(defaultAppDir.path, 'graphical_abstracts')); + Directory(p.join(oldBaseDir.path, 'graphical_abstracts')); final newGraphicalDir = Directory(p.join(targetPath, 'graphical_abstracts')); @@ -251,14 +249,24 @@ class DatabaseSettingsScreenState extends State { if (customPath == null) { throw Exception("Custom DB folder not accessible"); } + String defaultDBPath; + Directory defaultBaseDir; + + if (Platform.isWindows) { + final appDir = await getApplicationDocumentsDirectory(); + defaultDBPath = p.join(appDir.path, 'wispar.db'); + defaultBaseDir = appDir; + } else { + final defaultPath = await getDatabasesPath(); + defaultDBPath = p.join(defaultPath, 'wispar.db'); + defaultBaseDir = Directory(defaultPath); + } - final defaultDBPath = p.join(await getDatabasesPath(), 'wispar.db'); - final appDir = await getApplicationDocumentsDirectory(); final oldDBPath = p.join(customPath, 'wispar.db'); final oldGraphicalDir = Directory(p.join(customPath, 'graphical_abstracts')); final newGraphicalDir = - Directory(p.join(appDir.path, 'graphical_abstracts')); + Directory(p.join(defaultBaseDir.path, 'graphical_abstracts')); // Move DB final oldDBFile = File(oldDBPath); @@ -271,7 +279,8 @@ class DatabaseSettingsScreenState extends State { final customDir = Directory(customPath); await for (final file in customDir.list()) { if (file is File && file.path.endsWith('.pdf')) { - final newFile = File(p.join(appDir.path, p.basename(file.path))); + final newFile = + File(p.join(defaultBaseDir.path, p.basename(file.path))); await file.copy(newFile.path); await file.delete(); } @@ -372,22 +381,9 @@ class DatabaseSettingsScreenState extends State { final String outputFile = p.join(outputDirectory, 'wispar_backup_$timestamp.zip'); - final customSourcePath = await _getUsableCustomPath(); - String sourceBasePath; - String dbDirectoryPath; - - if (customSourcePath != null) { - sourceBasePath = customSourcePath; - dbDirectoryPath = customSourcePath; - } else { - // Use default paths - final defaultAppDir = await getApplicationDocumentsDirectory(); - sourceBasePath = defaultAppDir.path; - dbDirectoryPath = await getDatabasesPath(); - } - - String dbPath = p.join(dbDirectoryPath, "wispar.db"); - File dbFile = File(dbPath); + final dbPath = await DatabaseHelper().getDbPath(); + final dbFile = File(dbPath); + final sourceBasePath = p.dirname(dbPath); final sourceDir = Directory(sourceBasePath); @@ -453,17 +449,12 @@ class DatabaseSettingsScreenState extends State { await dbHelper.closeDatabase(); File selectedFile = File(result.files.single.path!); + final dbPath = await dbHelper.getDbPath(); + final dbDirectoryPath = p.dirname(dbPath); + final prefs = await SharedPreferences.getInstance(); final useCustomPath = prefs.getBool('useCustomDatabasePath') ?? false; final customPath = prefs.getString('customDatabasePath'); - - String dbDestinationPath; - if (useCustomPath && customPath != null) { - dbDestinationPath = customPath; - } else { - dbDestinationPath = await getDatabasesPath(); - } - String docsDestinationPath; if (useCustomPath && customPath != null) { docsDestinationPath = customPath; @@ -478,7 +469,7 @@ class DatabaseSettingsScreenState extends State { for (final file in archive) { final destinationBasePath = - file.name == 'wispar.db' ? dbDestinationPath : docsDestinationPath; + file.name == 'wispar.db' ? dbDirectoryPath : docsDestinationPath; final filePath = p.join(destinationBasePath, file.name); final outFile = File(filePath); @@ -502,7 +493,7 @@ class DatabaseSettingsScreenState extends State { } logger.info( - 'The database was successfully imported to $dbDestinationPath from ${selectedFile.path}'); + 'The database was successfully imported to $dbDirectoryPath from ${selectedFile.path}'); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/services/database_helper.dart b/lib/services/database_helper.dart index 5bfca69a..b610ea19 100644 --- a/lib/services/database_helper.dart +++ b/lib/services/database_helper.dart @@ -17,7 +17,7 @@ import 'package:path_provider/path_provider.dart'; class DatabaseHelper { static const platform = MethodChannel('app.wispar.wispar/database_access'); - static Future resolveCustomDatabasePath(String? path) async { + static Future resolveBookmarkPath(String? path) async { if (path == null) return null; if (Platform.isIOS) { @@ -29,7 +29,6 @@ class DatabaseHelper { return null; } } else { - // Android can use the path directly return path; } } @@ -44,23 +43,26 @@ class DatabaseHelper { return _database!; } - Future initDatabase() async { + Future getDbPath() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String? customPath = prefs.getString('customDatabasePath'); String? bookmark = prefs.getString('customDatabaseBookmark'); if (Platform.isIOS && bookmark != null) { - final resolvedPath = await DatabaseHelper.platform - .invokeMethod('resolveCustomPath', bookmark); - if (resolvedPath != null) { - customPath = resolvedPath; - } else { - customPath = null; - } + final resolved = await resolveBookmarkPath(bookmark); + if (resolved != null) customPath = resolved; + } + String defaultPath = await getDatabasesPath(); + if (Platform.isWindows) { + final dir = await getApplicationDocumentsDirectory(); + defaultPath = dir.path; } - - final defaultPath = await getDatabasesPath(); final databasePath = join(customPath ?? defaultPath, 'wispar.db'); + return databasePath; + } + + Future initDatabase() async { + String databasePath = await getDbPath(); return openDatabase(databasePath, version: 9, onOpen: (db) async { await db.execute('PRAGMA foreign_keys = ON'); From bc8e472ecf58875b21ef8a298c68aac8f71a727c Mon Sep 17 00:00:00 2001 From: Scriptbash <98601298+Scriptbash@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:36:29 -0500 Subject: [PATCH 2/5] patch version in iss file --- .github/workflows/publish.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6970a897..a64b0e53 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -155,6 +155,17 @@ jobs: run: | choco install innosetup -y refreshenv + - name: Extract version from pubspec.yaml + id: extract_version + run: | + version=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2 | tr -d '\r') + echo "VERSION=$version" >> $GITHUB_ENV + - name: Patch ISS with correct version + shell: powershell + run: | + $issPath = ".\windows\wispar_setup.iss" + (Get-Content $issPath) -replace '#define MyAppVersion ".*"', '#define MyAppVersion "${{ env.VERSION }}"' | Set-Content $issPath + Write-Host "Patched ISS file with version ${{ env.VERSION }}" - name: Create Windows installer shell: powershell run: | From 9567f1f49e32eff63b19064849371b06528acf67 Mon Sep 17 00:00:00 2001 From: Scriptbash <98601298+Scriptbash@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:55:37 -0500 Subject: [PATCH 3/5] fix windows version extract --- .github/workflows/publish.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a64b0e53..eb0c190d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -156,10 +156,18 @@ jobs: choco install innosetup -y refreshenv - name: Extract version from pubspec.yaml + shell: pwsh id: extract_version run: | - version=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2 | tr -d '\r') - echo "VERSION=$version" >> $GITHUB_ENV + $pubspec = Get-Content pubspec.yaml + foreach ($line in $pubspec) { + if ($line -match '^version:\s*(.+)$') { + $version = $matches[1] + break + } + } + Write-Host "VERSION=$version" + echo "VERSION=$version" >> $env:GITHUB_ENV - name: Patch ISS with correct version shell: powershell run: | From 302df0c4808a114508a9e58e4b06378cf3348503 Mon Sep 17 00:00:00 2001 From: Scriptbash <98601298+Scriptbash@users.noreply.github.com> Date: Sat, 24 Jan 2026 14:19:53 -0500 Subject: [PATCH 4/5] Save webview cache in writable directory --- lib/main.dart | 21 +++++++++++++++++++ lib/screens/article_website.dart | 4 ++++ lib/screens/database_settings_screen.dart | 5 ++++- lib/screens/pdf_reader.dart | 4 ++++ lib/services/database_helper.dart | 5 ++++- lib/services/graphical_abstract_manager.dart | 3 +++ .../publication_card/publication_card.dart | 4 ++++ 7 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 3fbc738e..d5598047 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,10 +16,31 @@ import './services/background_service.dart'; import './services/logs_helper.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:flutter/foundation.dart'; +import 'package:path_provider/path_provider.dart'; import 'dart:io' show Platform; +WebViewEnvironment? webViewEnvironment; + void main() async { WidgetsFlutterBinding.ensureInitialized(); + + if (Platform.isWindows) { + final availableVersion = await WebViewEnvironment.getAvailableVersion(); + if (availableVersion == null) { + return; + } + + final appDataDir = await getApplicationSupportDirectory(); + + webViewEnvironment = await WebViewEnvironment.create( + settings: WebViewEnvironmentSettings( + userDataFolder: '${appDataDir.path}\\WisparWebView', + ), + ); + } + if (Platform.isLinux || Platform.isWindows) { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; diff --git a/lib/screens/article_website.dart b/lib/screens/article_website.dart index 9f110e14..42822c55 100644 --- a/lib/screens/article_website.dart +++ b/lib/screens/article_website.dart @@ -979,6 +979,10 @@ class ArticleWebsiteState extends State { if (useCustomPath && customPath != null) { baseDirPath = customPath; + } else if (Platform.isWindows) { + final defaultAppDir = + await getApplicationSupportDirectory(); + baseDirPath = defaultAppDir.path; } else { final defaultAppDir = await getApplicationDocumentsDirectory(); diff --git a/lib/screens/database_settings_screen.dart b/lib/screens/database_settings_screen.dart index 3efc2448..d16731e7 100644 --- a/lib/screens/database_settings_screen.dart +++ b/lib/screens/database_settings_screen.dart @@ -253,7 +253,7 @@ class DatabaseSettingsScreenState extends State { Directory defaultBaseDir; if (Platform.isWindows) { - final appDir = await getApplicationDocumentsDirectory(); + final appDir = await getApplicationSupportDirectory(); defaultDBPath = p.join(appDir.path, 'wispar.db'); defaultBaseDir = appDir; } else { @@ -458,6 +458,9 @@ class DatabaseSettingsScreenState extends State { String docsDestinationPath; if (useCustomPath && customPath != null) { docsDestinationPath = customPath; + } else if (Platform.isWindows) { + final appDir = await getApplicationSupportDirectory(); + docsDestinationPath = appDir.path; } else { final appDir = await getApplicationDocumentsDirectory(); docsDestinationPath = appDir.path; diff --git a/lib/screens/pdf_reader.dart b/lib/screens/pdf_reader.dart index f6f54ea3..856f2e22 100644 --- a/lib/screens/pdf_reader.dart +++ b/lib/screens/pdf_reader.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:flutter/material.dart'; import '../generated_l10n/app_localizations.dart'; import 'package:pdfrx/pdfrx.dart'; @@ -80,6 +81,9 @@ class PdfReaderState extends State { String basePath; if (_useCustomPath && _customPath != null) { basePath = _customPath!; + } else if (Platform.isWindows) { + final defaultDirectory = await getApplicationSupportDirectory(); + basePath = defaultDirectory.path; } else { final defaultDirectory = await getApplicationDocumentsDirectory(); basePath = defaultDirectory.path; diff --git a/lib/services/database_helper.dart b/lib/services/database_helper.dart index b610ea19..d943bd1b 100644 --- a/lib/services/database_helper.dart +++ b/lib/services/database_helper.dart @@ -54,7 +54,7 @@ class DatabaseHelper { } String defaultPath = await getDatabasesPath(); if (Platform.isWindows) { - final dir = await getApplicationDocumentsDirectory(); + final dir = await getApplicationSupportDirectory(); defaultPath = dir.path; } final databasePath = join(customPath ?? defaultPath, 'wispar.db'); @@ -1117,6 +1117,9 @@ class DatabaseHelper { String baseDirPath; if (useCustomPath && customPath != null) { baseDirPath = customPath; + } else if (Platform.isWindows) { + final dir = await getApplicationSupportDirectory(); + baseDirPath = dir.path; } else { final dir = await getApplicationDocumentsDirectory(); baseDirPath = dir.path; diff --git a/lib/services/graphical_abstract_manager.dart b/lib/services/graphical_abstract_manager.dart index e1cd13f7..2c526b75 100644 --- a/lib/services/graphical_abstract_manager.dart +++ b/lib/services/graphical_abstract_manager.dart @@ -17,6 +17,9 @@ class GraphicalAbstractManager { if (useCustomPath && customPath != null) { basePath = customPath; + } else if (Platform.isWindows) { + final dir = await getApplicationSupportDirectory(); + basePath = dir.path; } else { final dir = await getApplicationDocumentsDirectory(); basePath = dir.path; diff --git a/lib/widgets/publication_card/publication_card.dart b/lib/widgets/publication_card/publication_card.dart index 8531c448..94784689 100644 --- a/lib/widgets/publication_card/publication_card.dart +++ b/lib/widgets/publication_card/publication_card.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:wispar/generated_l10n/app_localizations.dart'; @@ -401,6 +402,9 @@ class PublicationCardState extends State String basePath; if (useCustomPath && customPath != null) { basePath = customPath; + } else if (Platform.isWindows) { + final defaultDirectory = await getApplicationSupportDirectory(); + basePath = defaultDirectory.path; } else { final defaultDirectory = await getApplicationDocumentsDirectory(); basePath = defaultDirectory.path; From e2ebb623911ddeb888a6b1c2ca37a5e3e1375c8c Mon Sep 17 00:00:00 2001 From: Scriptbash <98601298+Scriptbash@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:25:43 -0500 Subject: [PATCH 5/5] Apply webview environment --- lib/main.dart | 4 +--- lib/screens/article_website.dart | 5 ++++- lib/services/abstract_scraper.dart | 6 ++++-- lib/webview_env.dart | 5 +++++ 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 lib/webview_env.dart diff --git a/lib/main.dart b/lib/main.dart index d5598047..acd933bc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,12 +17,10 @@ import './services/logs_helper.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:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:wispar/webview_env.dart'; import 'dart:io' show Platform; -WebViewEnvironment? webViewEnvironment; - void main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/screens/article_website.dart b/lib/screens/article_website.dart index 42822c55..1e663516 100644 --- a/lib/screens/article_website.dart +++ b/lib/screens/article_website.dart @@ -11,7 +11,8 @@ import 'package:path_provider/path_provider.dart'; import 'dart:io'; import 'package:http/http.dart' as http; import 'package:wispar/services/logs_helper.dart'; -import '../services/database_helper.dart'; +import 'package:wispar/webview_env.dart'; +import 'package:wispar/services/database_helper.dart'; class ArticleWebsite extends StatefulWidget { final PublicationCard publicationCard; @@ -307,6 +308,8 @@ class ArticleWebsiteState extends State { isReadyToLoad ? InAppWebView( key: webViewKey, + webViewEnvironment: + Platform.isWindows ? webViewEnvironment : null, initialUrlRequest: URLRequest(url: WebUri(pdfUrl)), initialSettings: settings, pullToRefreshController: pullToRefreshController, diff --git a/lib/services/abstract_scraper.dart b/lib/services/abstract_scraper.dart index d8b4fd1f..3913148c 100644 --- a/lib/services/abstract_scraper.dart +++ b/lib/services/abstract_scraper.dart @@ -2,8 +2,9 @@ import 'dart:async'; import 'dart:io' show Platform; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import './string_format_helper.dart'; -import './logs_helper.dart'; +import 'package:wispar/webview_env.dart'; +import 'package:wispar/services/string_format_helper.dart'; +import 'package:wispar/services/logs_helper.dart'; class AbstractScraper { Completer> _completer = @@ -47,6 +48,7 @@ class AbstractScraper { initialSettings: InAppWebViewSettings( userAgent: userAgent, ), + webViewEnvironment: Platform.isWindows ? webViewEnvironment : null, initialUrlRequest: URLRequest(url: WebUri(url)), onLoadStop: (controller, loadedUrl) async { await Future.delayed(const Duration(seconds: 3)); diff --git a/lib/webview_env.dart b/lib/webview_env.dart new file mode 100644 index 00000000..7ff5c5f6 --- /dev/null +++ b/lib/webview_env.dart @@ -0,0 +1,5 @@ +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + +/* Global variable to specify where to save the inappwebview cache + on Windows*/ +WebViewEnvironment? webViewEnvironment;