feat: implement real-time database synchronization with SSE and update backend modules
Test Suite / backend-pr-quick (24.15.0) (push) Has been cancelled
Test Suite / backend-full (24.15.0) (push) Has been cancelled
Test Suite / flutter-quality (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-12 16:57:05 +02:00
parent 2dda34d4d2
commit 98ee8a3ad6
11 changed files with 400 additions and 7 deletions
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:async';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/l10n/l10n.dart';
import '../../../core/realtime/realtime_sync.dart';
import 'admin_ai_panel.dart';
import 'admin_aliases_panel.dart';
import 'admin_inventory_panel.dart';
@@ -38,6 +40,32 @@ class AdminDatabasePanel extends ConsumerStatefulWidget {
class _AdminDatabasePanelState extends ConsumerState<AdminDatabasePanel> {
_DatabaseTab _activeTab = _DatabaseTab.inventory;
bool _isRefreshingCategories = false;
int _panelRefreshVersion = 0;
ProviderSubscription<int>? _realtimeTickSubscription;
Timer? _realtimeDebounce;
@override
void initState() {
super.initState();
_realtimeTickSubscription = ref.listenManual<int>(
realtimeRefreshTickProvider,
(_, __) {
if (!mounted) return;
_realtimeDebounce?.cancel();
_realtimeDebounce = Timer(const Duration(milliseconds: 600), () {
if (!mounted) return;
setState(() => _panelRefreshVersion++);
});
},
);
}
@override
void dispose() {
_realtimeDebounce?.cancel();
_realtimeTickSubscription?.close();
super.dispose();
}
List<_DatabaseTabConfig> get _tabConfigs => [
_DatabaseTabConfig(
@@ -153,7 +181,12 @@ class _AdminDatabasePanelState extends ConsumerState<AdminDatabasePanel> {
children: [
header,
const SizedBox(height: 12),
Expanded(child: currentTab.panel),
Expanded(
child: KeyedSubtree(
key: ValueKey('admin-db-${_activeTab.name}-$_panelRefreshVersion'),
child: currentTab.panel,
),
),
],
),
);
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:async';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/l10n/l10n.dart';
import '../../../core/realtime/realtime_sync.dart';
import '../data/admin_repository.dart';
import '../domain/user_admin.dart';
@@ -28,6 +30,8 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
bool _filterPremiumOnly = false;
bool _filterSharingOffOnly = false;
List<UserAdmin> _users = [];
ProviderSubscription<int>? _realtimeTickSubscription;
Timer? _realtimeDebounce;
String _sortLabel(_UserSort sort) => switch (sort) {
_UserSort.newest => 'Nyast',
@@ -71,11 +75,24 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
void initState() {
super.initState();
_searchCtrl = TextEditingController();
_realtimeTickSubscription = ref.listenManual<int>(
realtimeRefreshTickProvider,
(_, __) {
if (!mounted) return;
_realtimeDebounce?.cancel();
_realtimeDebounce = Timer(const Duration(milliseconds: 600), () {
if (!mounted) return;
_load();
});
},
);
_load();
}
@override
void dispose() {
_realtimeDebounce?.cancel();
_realtimeTickSubscription?.close();
_searchCtrl.dispose();
super.dispose();
}
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/realtime/realtime_sync.dart';
import '../../admin/data/admin_repository.dart';
import '../../admin/domain/receipt_alias.dart';
@@ -16,13 +17,27 @@ class _UserAliasesScreenState extends ConsumerState<UserAliasesScreen> {
List<ReceiptAlias> _aliases = [];
bool _isLoading = true;
String? _error;
ProviderSubscription<int>? _realtimeTickSubscription;
@override
void initState() {
super.initState();
_realtimeTickSubscription = ref.listenManual<int>(
realtimeRefreshTickProvider,
(_, __) {
if (!mounted) return;
_load();
},
);
_load();
}
@override
void dispose() {
_realtimeTickSubscription?.close();
super.dispose();
}
Future<void> _load() async {
setState(() {
_isLoading = true;