wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.) als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
@@ -55,7 +56,27 @@ bool _isNewer((int, int, int) remote, (int, int, int) local) {
|
||||
return remote.$3 > local.$3;
|
||||
}
|
||||
|
||||
String? _findDownloadUrl(List<dynamic> assets) {
|
||||
// Rewrite the host in a Gitea asset URL to always use the configured base,
|
||||
// so downloads work from outside the home network once nginx is set up.
|
||||
String? _rewriteHost(String? url) {
|
||||
if (url == null) return null;
|
||||
return url.replaceFirst(RegExp(r'https?://[^/]+'), _kGiteaBase);
|
||||
}
|
||||
|
||||
// Device ABIs in preference order (e.g. [arm64-v8a, armeabi-v7a, armeabi]),
|
||||
// fetched once from the platform. Used to pick the matching split APK.
|
||||
Future<List<String>> _deviceAbis() async {
|
||||
if (!Platform.isAndroid) return const [];
|
||||
try {
|
||||
final abis = await const MethodChannel('chat.pyramid.pyramid/install')
|
||||
.invokeListMethod<String>('getAbis');
|
||||
return abis?.map((a) => a.toLowerCase()).toList() ?? const [];
|
||||
} catch (_) {
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
String? _findDownloadUrl(List<dynamic> assets, List<String> abis) {
|
||||
if (assets.isEmpty) return null;
|
||||
final list = assets.cast<Map<String, dynamic>>();
|
||||
|
||||
@@ -71,6 +92,12 @@ String? _findDownloadUrl(List<dynamic> assets) {
|
||||
return pick((n) => n.endsWith('.exe')) ?? pick((n) => n.endsWith('.msix'));
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
// Prefer the split APK matching the device's best ABI, then weaker ABIs.
|
||||
// Fall back to any APK so fat-APK releases (pre-split) keep working.
|
||||
for (final abi in abis) {
|
||||
final url = pick((n) => n.endsWith('.apk') && n.contains(abi));
|
||||
if (url != null) return url;
|
||||
}
|
||||
return pick((n) => n.endsWith('.apk'));
|
||||
}
|
||||
return null;
|
||||
@@ -78,38 +105,53 @@ String? _findDownloadUrl(List<dynamic> assets) {
|
||||
|
||||
// ─── Fetch ────────────────────────────────────────────────────────────────────
|
||||
|
||||
Future<UpdateInfo?> _fetchLatestRelease() async {
|
||||
Future<UpdateInfo?> _fetchLatestRelease(List<String> abis) async {
|
||||
// Fetch enough releases to find the highest version even if they were
|
||||
// created out of chronological order (Gitea sorts by created_at, not semver).
|
||||
final uri = Uri.parse(
|
||||
'$_kGiteaBase/api/v1/repos/$_kGiteaRepo/releases?limit=20',
|
||||
);
|
||||
// Let network/timeout exceptions propagate so the provider shows an error
|
||||
// state instead of silently appearing as "up to date".
|
||||
final response = await http
|
||||
.get(uri, headers: {'Accept': 'application/json'})
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Gitea antwortete mit ${response.statusCode}');
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
'$_kGiteaBase/api/v1/repos/$_kGiteaRepo/releases?limit=1',
|
||||
);
|
||||
final response = await http
|
||||
.get(uri, headers: {'Accept': 'application/json'})
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) return null;
|
||||
|
||||
final list = json.decode(response.body) as List<dynamic>;
|
||||
if (list.isEmpty) return null;
|
||||
final data = list.first as Map<String, dynamic>;
|
||||
|
||||
final tagName = data['tag_name'] as String? ?? '';
|
||||
if (tagName.isEmpty) return null;
|
||||
// Find the release with the highest version number, ignoring drafts and
|
||||
// pre-releases. This is robust even when releases are created out of order.
|
||||
UpdateInfo? best;
|
||||
for (final item in list) {
|
||||
final data = item as Map<String, dynamic>;
|
||||
if (data['draft'] == true || data['prerelease'] == true) continue;
|
||||
|
||||
final remoteVer = _parseVersion(tagName);
|
||||
if (remoteVer == null) return null;
|
||||
final tagName = data['tag_name'] as String? ?? '';
|
||||
if (tagName.isEmpty) continue;
|
||||
|
||||
final assets = (data['assets'] as List<dynamic>?) ?? [];
|
||||
final downloadUrl = _findDownloadUrl(assets);
|
||||
final remoteVer = _parseVersion(tagName);
|
||||
if (remoteVer == null) continue;
|
||||
|
||||
return UpdateInfo(
|
||||
tagName: tagName,
|
||||
releaseName: data['name'] as String? ?? tagName,
|
||||
body: data['body'] as String? ?? '',
|
||||
htmlUrl: '$_kGiteaBase/$_kGiteaRepo/releases/tag/$tagName',
|
||||
remoteVersion: remoteVer,
|
||||
downloadUrl: downloadUrl,
|
||||
);
|
||||
if (best == null || _isNewer(remoteVer, best.remoteVersion)) {
|
||||
final assets = (data['assets'] as List<dynamic>?) ?? [];
|
||||
final downloadUrl = _rewriteHost(_findDownloadUrl(assets, abis));
|
||||
best = UpdateInfo(
|
||||
tagName: tagName,
|
||||
releaseName: data['name'] as String? ?? tagName,
|
||||
body: data['body'] as String? ?? '',
|
||||
htmlUrl: '$_kGiteaBase/$_kGiteaRepo/releases/tag/$tagName',
|
||||
remoteVersion: remoteVer,
|
||||
downloadUrl: downloadUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
return best;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
@@ -128,7 +170,7 @@ final updateInfoProvider = FutureProvider<UpdateInfo?>((ref) async {
|
||||
|
||||
await prefs.setInt(_kPrefLastCheck, DateTime.now().millisecondsSinceEpoch);
|
||||
|
||||
final remote = await _fetchLatestRelease();
|
||||
final remote = await _fetchLatestRelease(await _deviceAbis());
|
||||
if (remote == null) return null;
|
||||
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
|
||||
Reference in New Issue
Block a user