diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a52e5bc4..b6302a16 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -269,16 +269,34 @@ jobs:
ln -s /Applications dmg-staging/Applications
hdiutil create -format ULMO -srcfolder dmg-staging -volname "Plezy" plezy-macos.dmg
+ - name: Sign DMG for Sparkle (EdDSA)
+ if: ${{ secrets.SPARKLE_PRIVATE_KEY != '' }}
+ env:
+ SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
+ run: |
+ SIGN_BIN="macos/Pods/Sparkle/bin/sign_update"
+ SIGNATURE=$(echo "$SPARKLE_PRIVATE_KEY" | "$SIGN_BIN" plezy-macos.dmg --ed-key-file - -p)
+ echo "MACOS_ED_SIGNATURE=$SIGNATURE" >> $GITHUB_ENV
+ echo "MACOS_DMG_SIZE=$(stat -f%z plezy-macos.dmg)" >> $GITHUB_ENV
+
- name: Attest macOS DMG
uses: actions/attest-build-provenance@v2
with:
subject-path: plezy-macos.dmg
+ - name: Write macOS signature metadata
+ run: |
+ echo "${{ env.MACOS_ED_SIGNATURE }}" > macos-ed-signature.txt
+ echo "${{ env.MACOS_DMG_SIZE }}" > macos-dmg-size.txt
+
- name: Upload macOS DMG
uses: actions/upload-artifact@v4
with:
name: macos-dmg
- path: plezy-macos.dmg
+ path: |
+ plezy-macos.dmg
+ macos-ed-signature.txt
+ macos-dmg-size.txt
build-windows-x64:
runs-on: windows-latest
@@ -361,6 +379,15 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Setup Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: "stable"
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
- name: Download x64 build
uses: actions/download-artifact@v4
with:
@@ -376,6 +403,18 @@ jobs:
- name: Build installer and portables
run: .\windows\build-installer.ps1 -X64BuildDir "build-x64" -Arm64BuildDir "build-arm64"
+ - name: Sign installer for WinSparkle (DSA)
+ if: ${{ secrets.WINSPARKLE_DSA_PRIVATE_KEY != '' }}
+ env:
+ WINSPARKLE_DSA_PRIVATE_KEY: ${{ secrets.WINSPARKLE_DSA_PRIVATE_KEY }}
+ shell: pwsh
+ run: |
+ $env:WINSPARKLE_DSA_PRIVATE_KEY | Out-File -Encoding ascii dsa_priv.pem
+ $output = dart run auto_updater:sign_update plezy-windows-installer.exe
+ Remove-Item dsa_priv.pem
+ $output | Out-File -Encoding ascii win-dsa-signature.txt
+ (Get-Item plezy-windows-installer.exe).Length | Out-File -Encoding ascii win-installer-size.txt
+
- name: Attest Windows artifacts
uses: actions/attest-build-provenance@v2
with:
@@ -400,7 +439,10 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: windows-installer
- path: plezy-windows-installer.exe
+ path: |
+ plezy-windows-installer.exe
+ win-dsa-signature.txt
+ win-installer-size.txt
build-linux-x64:
runs-on: ubuntu-latest
@@ -634,6 +676,11 @@ jobs:
permissions:
contents: write
steps:
+ - uses: actions/checkout@v4
+ with:
+ sparse-checkout: pubspec.yaml
+ sparse-checkout-cone-mode: false
+
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
@@ -642,6 +689,82 @@ jobs:
- name: Display structure of downloaded files
run: ls -R artifacts
+ - name: Read version from pubspec.yaml
+ id: version
+ run: |
+ VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //' | sed 's/+.*//')
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Generate appcast.xml
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+
+ # Read macOS signature metadata (may be empty if secrets not configured)
+ MACOS_ED_SIG=""
+ MACOS_DMG_SIZE="0"
+ if [ -f artifacts/macos-dmg/macos-ed-signature.txt ]; then
+ MACOS_ED_SIG=$(cat artifacts/macos-dmg/macos-ed-signature.txt)
+ fi
+ if [ -f artifacts/macos-dmg/macos-dmg-size.txt ]; then
+ MACOS_DMG_SIZE=$(cat artifacts/macos-dmg/macos-dmg-size.txt)
+ fi
+
+ # Read Windows signature metadata (may be empty if secrets not configured)
+ WIN_DSA_SIG=""
+ WIN_INSTALLER_SIZE="0"
+ if [ -f artifacts/windows-installer/win-dsa-signature.txt ]; then
+ WIN_DSA_SIG=$(cat artifacts/windows-installer/win-dsa-signature.txt)
+ fi
+ if [ -f artifacts/windows-installer/win-installer-size.txt ]; then
+ WIN_INSTALLER_SIZE=$(cat artifacts/windows-installer/win-installer-size.txt)
+ fi
+
+ cat > appcast.xml << 'XMLEOF'
+
+
+
+ Plezy Updates
+ XMLEOF
+
+ # macOS entry
+ if [ -n "$MACOS_ED_SIG" ]; then
+ cat >> appcast.xml << XMLEOF
+ -
+ Version ${VERSION}
+ ${VERSION}
+ macos
+
+
+ XMLEOF
+ fi
+
+ # Windows entry
+ if [ -n "$WIN_DSA_SIG" ]; then
+ cat >> appcast.xml << XMLEOF
+ -
+ Version ${VERSION}
+ ${VERSION}
+ windows-x64
+
+
+ XMLEOF
+ fi
+
+ cat >> appcast.xml << 'XMLEOF'
+
+
+ XMLEOF
+
+ # Clean up leading whitespace from heredoc indentation
+ sed -i 's/^ //' appcast.xml
+
+ echo "Generated appcast.xml:"
+ cat appcast.xml
+
- name: Create Release
uses: softprops/action-gh-release@v1
with:
@@ -656,6 +779,7 @@ jobs:
artifacts/windows-installer/plezy-windows-installer.exe
artifacts/linux-x64/*
artifacts/linux-arm64/*
+ appcast.xml
draft: true
prerelease: false
generate_release_notes: true
diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart
index fd59bd0b..449150dd 100644
--- a/lib/screens/main_screen.dart
+++ b/lib/screens/main_screen.dart
@@ -195,6 +195,12 @@ class _MainScreenState extends State with RouteAware, WindowListener
if (!mounted) return;
+ // Native updater (Sparkle/WinSparkle) handles everything — skip Flutter dialog
+ if (UpdateService.useNativeUpdater) {
+ await UpdateService.checkForUpdatesNative(inBackground: true);
+ return;
+ }
+
try {
final updateInfo = await UpdateService.checkForUpdatesOnStartup();
diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart
index e178786c..a7dc66a0 100644
--- a/lib/screens/settings/settings_screen.dart
+++ b/lib/screens/settings/settings_screen.dart
@@ -931,6 +931,31 @@ class _SettingsScreenState extends State with FocusableTab {
}
Widget _buildUpdateSection() {
+ // Native updater: simple tile that triggers Sparkle/WinSparkle native dialog
+ if (UpdateService.useNativeUpdater) {
+ return Card(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(16),
+ child: Text(
+ t.settings.updates,
+ style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
+ ),
+ ),
+ ListTile(
+ focusNode: _focusTracker.get(_kCheckForUpdates),
+ leading: const AppIcon(Symbols.system_update_rounded, fill: 1),
+ title: Text(t.settings.checkForUpdates),
+ trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
+ onTap: () => UpdateService.checkForUpdatesNative(inBackground: false),
+ ),
+ ],
+ ),
+ );
+ }
+
final hasUpdate = _updateInfo != null && _updateInfo!['hasUpdate'] == true;
return Card(
diff --git a/lib/services/update_service.dart b/lib/services/update_service.dart
index 2cb52419..0b0d7dc6 100644
--- a/lib/services/update_service.dart
+++ b/lib/services/update_service.dart
@@ -1,3 +1,6 @@
+import 'dart:io';
+
+import 'package:auto_updater/auto_updater.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:logger/logger.dart';
@@ -5,9 +8,14 @@ import 'package:shared_preferences/shared_preferences.dart';
/// Service to check for new versions on GitHub
/// Only enabled when ENABLE_UPDATE_CHECK build flag is set
+///
+/// On macOS (non-Homebrew) and installed Windows: delegates to Sparkle/WinSparkle
+/// via auto_updater for native update dialogs and in-app installs.
+/// On all other platforms: falls back to GitHub API check + browser link dialog.
class UpdateService {
static final Logger _logger = Logger();
static const String _githubRepo = 'edde746/plezy';
+ static const String _feedUrl = 'https://github.com/edde746/plezy/releases/latest/download/appcast.xml';
// SharedPreferences keys
static const String _keySkippedVersion = 'update_skipped_version';
@@ -16,11 +24,70 @@ class UpdateService {
// Check cooldown: 6 hours
static const Duration _checkCooldown = Duration(hours: 6);
+ static bool _nativeUpdaterInitialized = false;
+
/// Check if update checking is enabled via build flag
static bool get isUpdateCheckEnabled {
return const bool.fromEnvironment('ENABLE_UPDATE_CHECK', defaultValue: false);
}
+ /// Whether the native auto_updater (Sparkle/WinSparkle) should be used.
+ /// True on macOS (non-Homebrew) and installed Windows (has uninstaller).
+ static bool get useNativeUpdater {
+ if (!isUpdateCheckEnabled) return false;
+ if (Platform.isMacOS) return !_isHomebrewInstall();
+ if (Platform.isWindows) return _isInstalledApp();
+ return false;
+ }
+
+ /// Initialize the native auto_updater (Sparkle/WinSparkle).
+ /// Call once at startup if [useNativeUpdater] is true.
+ static Future initNativeUpdater() async {
+ if (_nativeUpdaterInitialized) return;
+ _nativeUpdaterInitialized = true;
+
+ try {
+ await autoUpdater.setFeedURL(_feedUrl);
+ // 6 hours in seconds
+ await autoUpdater.setScheduledCheckInterval(_checkCooldown.inSeconds);
+ } catch (e) {
+ _logger.e('Failed to initialize native auto updater: $e');
+ }
+ }
+
+ /// Trigger a background update check via Sparkle/WinSparkle.
+ /// Only shows UI if an update is found.
+ static Future checkForUpdatesNative({bool inBackground = true}) async {
+ if (!_nativeUpdaterInitialized) await initNativeUpdater();
+ try {
+ await autoUpdater.checkForUpdates(inBackground: inBackground);
+ } catch (e) {
+ _logger.e('Native update check failed: $e');
+ }
+ }
+
+ /// Check if the macOS app was installed via Homebrew.
+ /// Homebrew casks live under /opt/homebrew/Caskroom/ or /usr/local/Caskroom/.
+ static bool _isHomebrewInstall() {
+ try {
+ final execPath = Platform.resolvedExecutable;
+ return execPath.contains('/Caskroom/') || execPath.contains('/homebrew/');
+ } catch (_) {
+ return false;
+ }
+ }
+
+ /// Check if the Windows app is an installed copy (not portable).
+ /// The Inno Setup installer places unins000.exe next to the executable.
+ static bool _isInstalledApp() {
+ try {
+ final exeDir = File(Platform.resolvedExecutable).parent.path;
+ return File('$exeDir\\unins000.exe').existsSync();
+ } catch (_) {
+ return false;
+ }
+ }
+
/// Skip a specific version
static Future skipVersion(String version) async {
final prefs = await SharedPreferences.getInstance();
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 718fe9a5..a9d35d16 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
+import auto_updater_macos
import connectivity_plus
import device_info_plus
import file_picker
@@ -23,6 +24,7 @@ import wakelock_plus
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ AutoUpdaterMacosPlugin.register(with: registry.registrar(forPlugin: "AutoUpdaterMacosPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
diff --git a/macos/Podfile.lock b/macos/Podfile.lock
index d0b49723..650efab9 100644
--- a/macos/Podfile.lock
+++ b/macos/Podfile.lock
@@ -1,4 +1,7 @@
PODS:
+ - auto_updater_macos (0.0.1):
+ - FlutterMacOS
+ - Sparkle
- connectivity_plus (0.0.1):
- FlutterMacOS
- device_info_plus (0.0.1):
@@ -22,6 +25,7 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
+ - Sparkle (2.9.0)
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
@@ -60,6 +64,7 @@ PODS:
- FlutterMacOS
DEPENDENCIES:
+ - auto_updater_macos (from `Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos`)
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
@@ -80,9 +85,12 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
+ - Sparkle
- sqlite3
EXTERNAL SOURCES:
+ auto_updater_macos:
+ :path: Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos
connectivity_plus:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
device_info_plus:
@@ -119,6 +127,7 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
+ auto_updater_macos: 3a42f1a06be6981f1a18be37e6e7bf86aa732118
connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
@@ -130,6 +139,7 @@ SPEC CHECKSUMS:
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
+ Sparkle: f4355f9ebbe9b7d932df4980d70f13922ac97b2a
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist
index bcd707b5..b9238fc3 100644
--- a/macos/Runner/Info.plist
+++ b/macos/Runner/Info.plist
@@ -30,5 +30,9 @@
NSApplication
FLTEnableMergedPlatformUIThread
+ SUFeedURL
+ https://github.com/edde746/plezy/releases/latest/download/appcast.xml
+ SUPublicEDKey
+ q7t6eu1txRrqv1PeWDCRV95rFxnmD/D3GZWbUPMwUyU=
diff --git a/pubspec.lock b/pubspec.lock
index acb9df56..bccc1d78 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -65,6 +65,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.0"
+ auto_updater:
+ dependency: "direct main"
+ description:
+ name: auto_updater
+ sha256: "74fd008b021d15e4fc50fb5f1923870e6374ae831bb1168241c23bc35a4e898b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ auto_updater_macos:
+ dependency: transitive
+ description:
+ name: auto_updater_macos
+ sha256: ef9de0daf38d782d2243fe131503a9404d62df762f5386e5360020e43c01867f
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ auto_updater_platform_interface:
+ dependency: transitive
+ description:
+ name: auto_updater_platform_interface
+ sha256: ed5dff8cea18c58bc5ac787ff9122fedd1644272e02015d28c9b592f8078c5dc
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ auto_updater_windows:
+ dependency: transitive
+ description:
+ name: auto_updater_windows
+ sha256: "2bba20a71eee072f49b7267fedd5c4f1406c4b1b1e5b83932c634dbab75b80c9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
background_downloader:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index bf1c46b5..f8c24a9a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -54,6 +54,7 @@ dependencies:
mobile_scanner: ^6.0.2
android_intent_plus: ^5.0.2
background_downloader: ^9.5.2
+ auto_updater: ^1.0.0
dev_dependencies:
flutter_test:
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index bc3ebff2..6eeee254 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
+#include
#include
#include
#include
@@ -15,6 +16,8 @@
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
+ AutoUpdaterWindowsPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("AutoUpdaterWindowsPluginCApi"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
OsMediaControlsPluginCApiRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index db77f229..85ff0401 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ auto_updater_windows
connectivity_plus
os_media_controls
screen_retriever_windows
diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc
index 265245f9..7f732849 100644
--- a/windows/runner/Runner.rc
+++ b/windows/runner/Runner.rc
@@ -105,6 +105,12 @@ BEGIN
END
END
+/////////////////////////////////////////////////////////////////////////////
+//
+// WinSparkle DSA public key for update signature verification
+//
+DSAPub DSAPEM "resources\\dsa_pub.pem"
+
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
diff --git a/windows/runner/resources/dsa_pub.pem b/windows/runner/resources/dsa_pub.pem
new file mode 100644
index 00000000..88668a46
--- /dev/null
+++ b/windows/runner/resources/dsa_pub.pem
@@ -0,0 +1,36 @@
+-----BEGIN PUBLIC KEY-----
+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
+IdoSn7v/pXbD9haYAkYFhXifYUUE/g==
+-----END PUBLIC KEY-----