import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:package_info_plus/package_info_plus.dart'; class ReleaseInfo { final String version; final String changelog; final String url; final bool isNewerThanCurrent; final Map downloads; ReleaseInfo({ required this.version, required this.changelog, required this.url, required this.isNewerThanCurrent, required this.downloads, }); String? downloadUrlFor(String platform) => downloads[platform]; Map get preferredDownloads { final group = _platformGroup(); final entries = downloads.entries.where((e) => e.key.contains(group)); return Map.fromEntries(entries); } Map get otherDownloads { final group = _platformGroup(); final entries = downloads.entries.where((e) => !e.key.contains(group)); return Map.fromEntries(entries); } String _platformGroup() { switch (defaultTargetPlatform) { case TargetPlatform.android: return 'android'; case TargetPlatform.iOS: return 'ios'; case TargetPlatform.windows: return 'windows'; case TargetPlatform.macOS: return 'macos'; case TargetPlatform.linux: return 'linux'; default: return ''; } } } extension DownloadLabelFormatter on String { String prettifyKey() { final parts = split('_'); if (parts.isEmpty) return this; final base = parts.first.capitalize(); if (parts.length == 1) return base; final variant = parts.sublist(1).join(' ').capitalize(); return '$base ($variant)'; } String capitalize() => isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}'; } class UpdateChecker { final String owner = 'DonutWare'; final String repo = 'Fladder'; Future> fetchRecentReleases({int count = 5}) async { final info = await PackageInfo.fromPlatform(); final currentVersion = info.version; final url = Uri.parse('https://api.github.com/repos/$owner/$repo/releases?per_page=$count'); final response = await http.get(url); if (response.statusCode != 200) { print('Failed to fetch releases: ${response.statusCode}'); return []; } final List releases = jsonDecode(response.body); return releases.map((json) { final tag = (json['tag_name'] as String?)?.replaceFirst(RegExp(r'^v'), ''); final changelog = json['body'] as String? ?? ''; final htmlUrl = json['html_url'] as String? ?? ''; final assets = json['assets'] as List? ?? []; final Map downloads = {}; for (final asset in assets) { final name = asset['name'] as String? ?? ''; final downloadUrl = asset['browser_download_url'] as String? ?? ''; if (name.contains('Android') && name.endsWith('.apk')) { downloads['android'] = downloadUrl; } else if (name.contains('iOS') && name.endsWith('.ipa')) { downloads['ios'] = downloadUrl; } else if (name.contains('Windows') && name.endsWith('Setup.exe')) { downloads['windows_installer'] = downloadUrl; } else if (name.contains('Windows') && name.endsWith('.zip')) { downloads['windows_portable'] = downloadUrl; } else if (name.contains('macOS') && name.endsWith('.dmg')) { downloads['macos'] = downloadUrl; } else if (name.contains('Linux') && name.endsWith('.AppImage')) { downloads['linux_appimage'] = downloadUrl; } else if (name.contains('Linux') && name.endsWith('.flatpak')) { downloads['linux_flatpak'] = downloadUrl; } else if (name.contains('Linux') && name.endsWith('.zip')) { downloads['linux_zip'] = downloadUrl; } else if (name.contains('Linux') && name.endsWith('.zsync')) { downloads['linux_zsync'] = downloadUrl; } else if (name.contains('Web') && name.endsWith('.zip')) { downloads['web'] = downloadUrl; } } bool isNewer = tag != null && _compareVersions(tag, currentVersion) > 0; return ReleaseInfo( version: tag ?? 'unknown', changelog: changelog.trim(), url: htmlUrl, isNewerThanCurrent: isNewer, downloads: downloads, ); }).toList(); } Future isUpToDate() async { final releases = await fetchRecentReleases(count: 1); if (releases.isEmpty) return true; return !releases.first.isNewerThanCurrent; } static int _compareVersions(String a, String b) { final aParts = a.split('.').map(int.tryParse).toList(); final bParts = b.split('.').map(int.tryParse).toList(); for (var i = 0; i < aParts.length || i < bParts.length; i++) { final aVal = i < aParts.length ? (aParts[i] ?? 0) : 0; final bVal = i < bParts.length ? (bParts[i] ?? 0) : 0; if (aVal != bVal) return aVal.compareTo(bVal); } return 0; } }