diff --git a/flutter_client/android/app/src/main/AndroidManifest.xml b/flutter_client/android/app/src/main/AndroidManifest.xml
index 294073a0..cfa07960 100644
--- a/flutter_client/android/app/src/main/AndroidManifest.xml
+++ b/flutter_client/android/app/src/main/AndroidManifest.xml
@@ -9,6 +9,10 @@
for the media notification (now-playing tile) to appear. Audio
playback works without it; only the system-tray surface breaks. -->
+
+
+
+
+
+
+
+
+
diff --git a/flutter_client/lib/shared/routing.dart b/flutter_client/lib/shared/routing.dart
index 189884f0..9d99b5df 100644
--- a/flutter_client/lib/shared/routing.dart
+++ b/flutter_client/lib/shared/routing.dart
@@ -18,6 +18,7 @@ import '../playlists/playlists_list_screen.dart';
import '../requests/requests_screen.dart';
import '../search/search_screen.dart';
import '../settings/settings_screen.dart';
+import '../update/update_banner.dart';
import '../admin/admin_landing_screen.dart';
import '../admin/admin_requests_screen.dart';
import '../admin/admin_quarantine_screen.dart';
@@ -94,6 +95,7 @@ class _ShellWithPlayerBar extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
+ const UpdateBanner(),
Expanded(child: child),
const PlayerBar(),
],
diff --git a/flutter_client/lib/update/client_update_provider.dart b/flutter_client/lib/update/client_update_provider.dart
new file mode 100644
index 00000000..f249922f
--- /dev/null
+++ b/flutter_client/lib/update/client_update_provider.dart
@@ -0,0 +1,102 @@
+import 'dart:async';
+
+import 'package:dio/dio.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+import 'package:pub_semver/pub_semver.dart';
+
+import '../library/library_providers.dart' show dioProvider;
+import 'installer.dart';
+import 'update_info.dart';
+
+/// Tracks the bundled-server APK version vs. the installed app version.
+/// Polls /api/client/version on startup + every 24h. Returns null when
+/// no update is available (or the channel is unreachable / disabled).
+///
+/// Server returns 404 when no APK is bundled (dev environments, pre-CI
+/// images) — we treat that as "no update channel" and stay silent.
+class ClientUpdateController extends AsyncNotifier {
+ static const Duration _pollInterval = Duration(hours: 24);
+
+ Timer? _pollTimer;
+
+ @override
+ Future build() async {
+ ref.onDispose(() {
+ _pollTimer?.cancel();
+ _pollTimer = null;
+ });
+ _pollTimer ??= Timer.periodic(_pollInterval, (_) => ref.invalidateSelf());
+ return _check();
+ }
+
+ Future _check() async {
+ final dio = await ref.read(dioProvider.future);
+ final Response