141 lines
4.4 KiB
Dart
141 lines
4.4 KiB
Dart
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<void> _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<Map<String, dynamic>>();
|
|
|
|
final plugin = FlutterLocalNotificationsPlugin();
|
|
await plugin.initialize(settings: _buildInitSettings());
|
|
|
|
int notifId = 0;
|
|
for (final team in teams) {
|
|
final apps = (team['apps'] as List).cast<Map<String, dynamic>>();
|
|
for (final app in apps) {
|
|
final tracks = ['release', 'debug', 'profile'];
|
|
for (final trackName in tracks) {
|
|
final track = app[trackName] as Map<String, dynamic>?;
|
|
if (track == null) continue;
|
|
|
|
final current = track['current'] as Map<String, dynamic>?;
|
|
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<String?> _getInstalledVersion(String packageName) async {
|
|
const channel = MethodChannel('xyz.nuark.update_forge_companion/device');
|
|
try {
|
|
return await channel.invokeMethod<String>(
|
|
'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<void> initialize() async {
|
|
await _notifications.initialize(settings: _buildInitSettings());
|
|
|
|
await Workmanager().initialize(callbackDispatcher);
|
|
}
|
|
|
|
Future<void> schedulePeriodicCheck() async {
|
|
await Workmanager().registerPeriodicTask(
|
|
'update-check',
|
|
_taskCheckUpdates,
|
|
frequency: const Duration(hours: 1),
|
|
constraints: Constraints(
|
|
networkType: NetworkType.connected,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> cancelAll() async {
|
|
await Workmanager().cancelAll();
|
|
}
|
|
}
|