import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:workmanager/workmanager.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; const _taskCheckUpdates = 'checkUpdates'; InitializationSettings _buildInitSettings() { const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); return const InitializationSettings(android: androidSettings); } @pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { if (task == _taskCheckUpdates) { await _checkForUpdates(); } return true; }); } Future _checkForUpdates() async { const storage = FlutterSecureStorage(); final serverUrl = await storage.read(key: 'server_url'); final token = await storage.read(key: 'auth_token'); if (serverUrl == null || token == null) return; try { final response = await http.get( Uri.parse('$serverUrl/api/v1/apps'), headers: { 'Authorization': 'Bearer $token', 'Accept': 'application/json', }, ); if (response.statusCode != 200) return; final data = jsonDecode(response.body); final teams = (data['teams'] as List).cast>(); final plugin = FlutterLocalNotificationsPlugin(); await plugin.initialize(settings: _buildInitSettings()); int notifId = 0; for (final team in teams) { final apps = (team['apps'] as List).cast>(); for (final app in apps) { final tracks = ['release', 'debug', 'profile']; for (final trackName in tracks) { final track = app[trackName] as Map?; if (track == null) continue; final current = track['current'] as Map?; if (current == null || current['hasFile'] != true) continue; final installedVersion = await _getInstalledVersion(app['packageName']); if (installedVersion == null) continue; final apiVersion = current['version'] as String; if (_isNewer(apiVersion, installedVersion)) { await plugin.show( id: notifId++, title: 'Update available: ${app['title']}', body: 'Version $apiVersion is available on $trackName track', notificationDetails: NotificationDetails( android: AndroidNotificationDetails( 'update_available', 'App Updates', channelDescription: 'Notifications for available app updates', importance: Importance.high, priority: Priority.high, ), ), payload: app['packageName'], ); } } } } } catch (_) {} } Future _getInstalledVersion(String packageName) async { const channel = MethodChannel('xyz.nuark.update_forge_companion/device'); try { return await channel.invokeMethod( 'getInstalledVersion', {'packageName': packageName}, ); } on PlatformException { return null; } } bool _isNewer(String apiVersion, String installedVersion) { final apiParts = apiVersion.split('.').map((s) => int.tryParse(s.split('-').first) ?? 0).toList(); final installedParts = installedVersion.split('.').map((s) => int.tryParse(s.split('-').first) ?? 0).toList(); final maxLen = apiParts.length > installedParts.length ? apiParts.length : installedParts.length; for (var i = 0; i < maxLen; i++) { final a = i < apiParts.length ? apiParts[i] : 0; final b = i < installedParts.length ? installedParts[i] : 0; if (a > b) return true; if (a < b) return false; } return false; } class BackgroundService { final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin(); Future initialize() async { await _notifications.initialize(settings: _buildInitSettings()); await Workmanager().initialize(callbackDispatcher); } Future schedulePeriodicCheck() async { await Workmanager().registerPeriodicTask( 'update-check', _taskCheckUpdates, frequency: const Duration(hours: 1), constraints: Constraints( networkType: NetworkType.connected, ), ); } Future cancelAll() async { await Workmanager().cancelAll(); } }