Signing, Installer, New Workflows
Some checks failed
Flutter Release / get-version (push) Successful in 7s
Flutter Release / build-windows (push) Failing after 9s
Flutter Release / create-release (push) Has been cancelled
Flutter Release / build-android (push) Has been cancelled

This commit is contained in:
2025-12-15 00:05:29 -05:00
parent 9ff0d62651
commit 110c5d99a1
25 changed files with 2647 additions and 268 deletions

View File

@@ -143,33 +143,31 @@ class UpdateService {
final baseUrl = 'https://git.hudsonriggs.systems/LambdaBankingConglomerate/rmtPocketWatcher/releases/download/v$version';
return [
// Windows Full Package
ReleaseAsset(
name: 'rmtPocketWatcher-windows-x64.exe',
downloadUrl: '$baseUrl/rmtPocketWatcher-windows-x64.exe',
name: 'rmtPocketWatcher-Windows-v$version.zip',
downloadUrl: '$baseUrl/rmtPocketWatcher-Windows-v$version.zip',
size: 0, // Unknown size from RSS
contentType: 'application/octet-stream',
contentType: 'application/zip',
),
// Windows Portable (single exe)
ReleaseAsset(
name: 'rmtPocketWatcher-windows-x64.msi',
downloadUrl: '$baseUrl/rmtPocketWatcher-windows-x64.msi',
name: 'rmtPocketWatcher-Windows-Portable-v$version.zip',
downloadUrl: '$baseUrl/rmtPocketWatcher-Windows-Portable-v$version.zip',
size: 0,
contentType: 'application/octet-stream',
contentType: 'application/zip',
),
// Windows MSIX Installer
ReleaseAsset(
name: 'rmtPocketWatcher-macos.dmg',
downloadUrl: '$baseUrl/rmtPocketWatcher-macos.dmg',
name: 'rmtPocketWatcher-v$version.msix',
downloadUrl: '$baseUrl/rmtPocketWatcher-v$version.msix',
size: 0,
contentType: 'application/octet-stream',
contentType: 'application/msix',
),
// Android APK
ReleaseAsset(
name: 'rmtPocketWatcher-linux.appimage',
downloadUrl: '$baseUrl/rmtPocketWatcher-linux.appimage',
size: 0,
contentType: 'application/octet-stream',
),
ReleaseAsset(
name: 'rmtPocketWatcher-android.apk',
downloadUrl: '$baseUrl/rmtPocketWatcher-android.apk',
name: 'rmtPocketWatcher-Android-v$version.apk',
downloadUrl: '$baseUrl/rmtPocketWatcher-Android-v$version.apk',
size: 0,
contentType: 'application/vnd.android.package-archive',
),
@@ -200,36 +198,38 @@ class UpdateInfo {
ReleaseAsset? getAssetForCurrentPlatform() {
if (kIsWeb) return null;
String platformPattern;
switch (defaultTargetPlatform) {
case TargetPlatform.windows:
platformPattern = r'windows|win|\.exe$|\.msi$';
break;
// Prefer portable version for Windows
var portable = assets.where((asset) => asset.name.contains('Portable')).firstOrNull;
if (portable != null) return portable;
// Fall back to full Windows package
var windows = assets.where((asset) => asset.name.contains('Windows') && asset.name.endsWith('.zip')).firstOrNull;
if (windows != null) return windows;
// Last resort: MSIX installer
return assets.where((asset) => asset.name.endsWith('.msix')).firstOrNull;
case TargetPlatform.macOS:
platformPattern = r'macos|mac|darwin|\.dmg$|\.pkg$';
break;
return assets.where((asset) =>
RegExp(r'macOS|macos|mac|darwin|\.dmg$|\.pkg$', caseSensitive: false).hasMatch(asset.name)
).firstOrNull;
case TargetPlatform.linux:
platformPattern = r'linux|\.deb$|\.rpm$|\.appimage$';
break;
return assets.where((asset) =>
RegExp(r'Linux|linux|\.deb$|\.rpm$|\.appimage$', caseSensitive: false).hasMatch(asset.name)
).firstOrNull;
case TargetPlatform.android:
platformPattern = r'android|\.apk$';
break;
return assets.where((asset) => asset.name.endsWith('.apk')).firstOrNull;
case TargetPlatform.iOS:
platformPattern = r'ios|\.ipa$';
break;
return assets.where((asset) => asset.name.endsWith('.ipa')).firstOrNull;
default:
return null;
}
final regex = RegExp(platformPattern, caseSensitive: false);
for (final asset in assets) {
if (regex.hasMatch(asset.name)) {
return asset;
}
}
return null;
}
}

View File

@@ -0,0 +1,315 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:window_manager/window_manager.dart';
import 'package:tray_manager/tray_manager.dart';
class WindowService with WindowListener, TrayListener {
static final WindowService _instance = WindowService._internal();
factory WindowService() => _instance;
WindowService._internal();
bool _isInitialized = false;
bool _isMinimizedToTray = false;
BuildContext? _context;
Future<void> initialize({BuildContext? context}) async {
if (_isInitialized) return;
_context = context;
// Initialize window manager
windowManager.addListener(this);
// Initialize system tray
await _initializeTray();
_isInitialized = true;
}
Future<void> _initializeTray() async {
try {
await trayManager.setIcon(
kIsWeb ? '' : 'icon.ico',
isTemplate: false,
);
await trayManager.setToolTip('rmtPocketWatcher - AUEC Price Tracker');
// Create tray menu
Menu menu = Menu(
items: [
MenuItem(
key: 'show_window',
label: 'Show rmtPocketWatcher',
),
MenuItem.separator(),
MenuItem(
key: 'check_updates',
label: 'Check for Updates',
),
MenuItem.separator(),
MenuItem(
key: 'about',
label: 'About',
),
MenuItem.separator(),
MenuItem(
key: 'exit_app',
label: 'Exit',
),
],
);
await trayManager.setContextMenu(menu);
trayManager.addListener(this);
if (kDebugMode) {
print('System tray initialized successfully');
}
} catch (e) {
if (kDebugMode) {
print('Failed to initialize system tray: $e');
}
}
}
// Window controls
Future<void> minimizeWindow() async {
await windowManager.minimize();
}
Future<void> minimizeToTray() async {
await windowManager.hide();
_isMinimizedToTray = true;
if (kDebugMode) {
print('Window minimized to system tray');
}
}
Future<void> showWindow() async {
await windowManager.show();
await windowManager.focus();
_isMinimizedToTray = false;
if (kDebugMode) {
print('Window restored from system tray');
}
}
Future<void> maximizeWindow() async {
bool isMaximized = await windowManager.isMaximized();
if (isMaximized) {
await windowManager.unmaximize();
} else {
await windowManager.maximize();
}
}
Future<void> closeWindow() async {
// Close button should exit the app
await exitApp();
}
Future<void> exitApp() async {
await trayManager.destroy();
await windowManager.destroy();
SystemNavigator.pop();
}
// Window event handlers
@override
void onWindowClose() async {
// Prevent default close behavior
await closeWindow();
}
@override
void onWindowMinimize() async {
// When window is minimized (via minimize button or taskbar), go to tray
await minimizeToTray();
if (kDebugMode) {
print('Window minimized to tray');
}
}
@override
void onWindowRestore() {
_isMinimizedToTray = false;
if (kDebugMode) {
print('Window restored');
}
}
@override
void onWindowMaximize() {
if (kDebugMode) {
print('Window maximized');
}
}
@override
void onWindowUnmaximize() {
if (kDebugMode) {
print('Window unmaximized');
}
}
// Tray event handlers
@override
void onTrayIconMouseDown() async {
// Single click to show/hide window
if (_isMinimizedToTray) {
await showWindow();
} else {
await minimizeToTray();
}
}
@override
void onTrayIconRightMouseDown() async {
// Right click shows context menu (handled automatically)
}
@override
void onTrayMenuItemClick(MenuItem menuItem) async {
switch (menuItem.key) {
case 'show_window':
await showWindow();
break;
case 'check_updates':
await showWindow();
// The update check will be handled by the UI
if (kDebugMode) {
print('Checking for updates from tray menu');
}
break;
case 'about':
await showWindow();
_showAboutDialog();
break;
case 'exit_app':
await exitApp();
break;
}
}
// Update tray tooltip with current status
Future<void> updateTrayTooltip(String status) async {
try {
await trayManager.setToolTip('rmtPocketWatcher - $status');
} catch (e) {
if (kDebugMode) {
print('Failed to update tray tooltip: $e');
}
}
}
// Update tray menu with dynamic content
Future<void> updateTrayMenu({bool hasUpdate = false}) async {
try {
Menu menu = Menu(
items: [
MenuItem(
key: 'show_window',
label: 'Show rmtPocketWatcher',
),
MenuItem.separator(),
MenuItem(
key: 'check_updates',
label: hasUpdate ? '🔄 Update Available!' : 'Check for Updates',
),
MenuItem.separator(),
MenuItem(
key: 'about',
label: 'About',
),
MenuItem.separator(),
MenuItem(
key: 'exit_app',
label: 'Exit',
),
],
);
await trayManager.setContextMenu(menu);
} catch (e) {
if (kDebugMode) {
print('Failed to update tray menu: $e');
}
}
}
// Getters
bool get isMinimizedToTray => _isMinimizedToTray;
bool get isInitialized => _isInitialized;
void _showAboutDialog() {
if (_context != null) {
// Import the about dialog dynamically to avoid circular imports
showDialog(
context: _context!,
builder: (context) {
// We'll create a simple about dialog here to avoid import issues
return AlertDialog(
backgroundColor: const Color(0xFF1A1F3A),
title: const Row(
children: [
Icon(Icons.analytics, color: Color(0xFF50E3C2), size: 32),
SizedBox(width: 12),
Text('rmtPocketWatcher', style: TextStyle(color: Colors.white)),
],
),
content: const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Star Citizen AUEC Price Tracker',
style: TextStyle(color: Colors.white70, fontSize: 14),
),
SizedBox(height: 8),
Text(
'Developed by Lambda Banking Conglomerate',
style: TextStyle(color: Colors.white70, fontSize: 12),
),
SizedBox(height: 16),
Text(
'Features:',
style: TextStyle(
color: Color(0xFF50E3C2),
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text('• Real-time AUEC price tracking', style: TextStyle(color: Colors.white70, fontSize: 12)),
Text('• Multiple vendor monitoring', style: TextStyle(color: Colors.white70, fontSize: 12)),
Text('• Historical price charts', style: TextStyle(color: Colors.white70, fontSize: 12)),
Text('• Price alerts & notifications', style: TextStyle(color: Colors.white70, fontSize: 12)),
Text('• System tray integration', style: TextStyle(color: Colors.white70, fontSize: 12)),
],
),
actions: [
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF50E3C2),
foregroundColor: Colors.black,
),
child: const Text('Close'),
),
],
);
},
);
}
}
// Cleanup
void dispose() {
windowManager.removeListener(this);
trayManager.removeListener(this);
}
}