fix(ui): preserve desktop platform listeners (#1483)

This commit is contained in:
Levi authored and GitHub committed 2026-09-06 20:36:48 +08:00
1 parent d511e66d56
commit 53d13dfe28
2 files changed
+130 -15

No files matched your search

@@ -54,6 +54,9 @@ class AppPlatformState {
enum AppUpdateStatus { idle, checking, upToDate, available, failed }
class AppPlatformController extends AsyncNotifier<AppPlatformState> with WindowListener, TrayListener {
@protected
bool get isDesktop => Util.isDesktop();
bool _started = false;
String? _trayLocaleConfig;
bool _updateInitialized = false;
@@ -74,30 +77,54 @@ class AppPlatformController extends AsyncNotifier<AppPlatformState> with WindowL
@override
Future<AppPlatformState> build() async {
final runtime = ref.watch(appRuntimeControllerProvider).value;
if (runtime == null || kIsWeb) {
return _snapshot();
}
if (!Util.isDesktop()) {
_scheduleInitialUpdateCheck();
return _snapshot();
}
if (_started) {
final localeConfig = runtime.downloaderConfig.extra.locale;
if (_trayLocaleConfig != localeConfig) await _initTray();
return _snapshot();
}
await _start();
_scheduleInitialUpdateCheck();
ref.listen(appRuntimeControllerProvider, (_, next) {
unawaited(
_handleRuntimeChange(next.value).catchError((Object error, StackTrace stackTrace) {
if (ref.mounted) state = AsyncValue.error(error, stackTrace);
}),
);
});
ref.onDispose(() {
if (!_started) return;
_started = false;
windowManager.removeListener(this);
trayManager.removeListener(this);
unawaited(HostRpcService.instance.stop());
unawaited(AppWindowCapabilityHost.instance.stop());
});
final runtime = ref.read(appRuntimeControllerProvider).value;
if (runtime == null || kIsWeb) {
return _snapshot();
}
if (!isDesktop) {
_scheduleInitialUpdateCheck();
return _snapshot();
}
if (_started) {
return _snapshot();
}
await _start();
_scheduleInitialUpdateCheck();
return _snapshot();
}
Future<void> _handleRuntimeChange(AppRuntimeState? runtime) async {
if (runtime == null || kIsWeb) return;
if (!isDesktop) {
_scheduleInitialUpdateCheck();
return;
}
if (!_started) {
await _start();
_scheduleInitialUpdateCheck();
_emit();
return;
}
if (_trayLocaleConfig != runtime.downloaderConfig.extra.locale) {
await _initTray();
}
}
void _scheduleInitialUpdateCheck() {
if (_updateInitialized) return;
_updateInitialized = true;
@@ -0,0 +1,88 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gopeed/api/model/downloader_config.dart';
import 'package:gopeed/app/application/app_platform_controller.dart';
import 'package:gopeed/app/application/app_runtime_controller.dart';
import 'package:gopeed/core/common/api_server_state.dart';
import 'package:gopeed/core/common/start_config.dart';
import 'package:gopeed/util/updater.dart';
void main() {
test('runtime updates do not rebuild the platform controller', () async {
final container = ProviderContainer(
overrides: [
appRuntimeControllerProvider.overrideWith(_TestRuntimeController.new),
appPlatformControllerProvider.overrideWith(_TestPlatformController.new),
],
);
addTearDown(container.dispose);
final subscription = container.listen(appPlatformControllerProvider, (_, _) {}, fireImmediately: true);
addTearDown(subscription.close);
await container.read(appPlatformControllerProvider.future);
final platformController = container.read(appPlatformControllerProvider.notifier) as _TestPlatformController;
final runtimeController = container.read(appRuntimeControllerProvider.notifier) as _TestRuntimeController;
expect(platformController.buildCount, 1);
runtimeController.completeRuntime();
await container.read(appRuntimeControllerProvider.future);
await Future<void>.delayed(Duration.zero);
expect(platformController.updateCheckCount, 1);
final config = DownloaderConfig.fromJson(runtimeController.state.requireValue.downloaderConfig.toJson());
config.extra.locale = 'zh';
runtimeController.replaceDownloaderConfig(config);
await container.pump();
expect(platformController.buildCount, 1);
});
}
class _TestPlatformController extends AppPlatformController {
int buildCount = 0;
int updateCheckCount = 0;
@override
bool get isDesktop => false;
@override
Future<AppPlatformState> build() {
buildCount++;
return super.build();
}
@override
Future<VersionInfo?> checkForUpdate() async {
updateCheckCount++;
return null;
}
}
class _TestRuntimeController extends AppRuntimeController {
final _pendingRuntime = Completer<AppRuntimeState>();
@override
Future<AppRuntimeState> build() => _pendingRuntime.future;
void completeRuntime() {
_pendingRuntime.complete(
AppRuntimeState(
startConfig: StartConfig(),
apiServerState: const ApiServerState(
enabled: false,
mcpEnabled: false,
running: false,
network: '',
address: '',
runningPort: 0,
pendingApply: false,
lastError: '',
),
downloaderConfig: DownloaderConfig(),
),
);
}
}