import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:flutter/cupertino.dart'; import 'package:http/http.dart' as http; import 'package:open_filex/open_filex.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; class UpdateInfo { final bool update; final String version; final String notes; final bool mandatory; final String sha256; final String download; UpdateInfo({ required this.update, required this.version, required this.notes, required this.mandatory, required this.sha256, required this.download, }); factory UpdateInfo.fromJson(Map json) { return UpdateInfo( update: json["update"] ?? false, version: json["version"] ?? "", notes: json["notes"] ?? "", mandatory: json["mandatory"] ?? false, sha256: json["sha256"] ?? "", download: json["download"] ?? "", ); } } class AppUpdateUtil { final String serverUrl; AppUpdateUtil({required this.serverUrl}); /// Gets the installed app version Future currentVersion() async { final info = await PackageInfo.fromPlatform(); debugPrint("Current app version: ${info.version}"); return info.version; } /// Checks the update server Future checkForUpdate() async { final version = await currentVersion(); final url = Uri.parse("$serverUrl/api/update?version=$version"); final response = await http.get(url); if (response.statusCode != 200) { throw Exception("Update server unavailable"); } final data = jsonDecode(response.body); if (data["update"] != true) { return null; } return UpdateInfo.fromJson(data); } /// Downloads the APK Future downloadApk( UpdateInfo update, { Function(double progress)? onProgress, }) async { final url = update.download.startsWith("http") ? update.download : "$serverUrl${update.download}"; final request = http.Request("GET", Uri.parse(url)); final response = await request.send(); if (response.statusCode != 200) { throw Exception("APK download failed"); } final total = response.contentLength ?? 0; int received = 0; final directory = await getTemporaryDirectory(); final file = File("${directory.path}/update.apk"); final sink = file.openWrite(); await for (final chunk in response.stream) { sink.add(chunk); received += chunk.length; if (total > 0 && onProgress != null) { onProgress(received / total); } } await sink.close(); return file; } /// Verifies APK checksum Future verifySha256(File file, String expectedHash) async { final bytes = await file.readAsBytes(); final digest = sha256.convert(bytes); return digest.toString().toLowerCase() == expectedHash.toLowerCase(); } /// Opens Android APK installer Future installApk(File file) async { // final result = // await OpenFilex.open( // file.path, // ); // // // if (result.type != ResultType.done) { // throw Exception( // "Could not open APK installer", // ); // } } /// Full update flow /// /// Returns: /// - null if no update exists /// - UpdateInfo if update is available /// Future update({Function(double progress)? onProgress}) async { final info = await checkForUpdate(); if (info == null) { return null; } final apk = await downloadApk(info, onProgress: onProgress); final valid = await verifySha256(apk, info.sha256); if (!valid) { throw Exception("APK checksum mismatch"); } await installApk(apk); return info; } }