Implemented per stream subscription recovery with polling fallback

This commit is contained in:
2026-03-01 17:24:04 +08:00
parent e91e7b43d2
commit c9479f01f0
19 changed files with 894 additions and 494 deletions
+55 -28
View File
@@ -6,6 +6,7 @@ import '../models/duty_schedule.dart';
import '../models/swap_request.dart';
import 'profile_provider.dart';
import 'supabase_provider.dart';
import 'stream_recovery.dart';
final geofenceProvider = FutureProvider<GeofenceConfig?>((ref) async {
final client = ref.watch(supabaseClientProvider);
@@ -28,16 +29,30 @@ final dutySchedulesProvider = StreamProvider<List<DutySchedule>>((ref) {
}
final isAdmin = profile.role == 'admin' || profile.role == 'dispatcher';
final base = client.from('duty_schedules').stream(primaryKey: ['id']);
if (isAdmin) {
return base
.order('start_time')
.map((rows) => rows.map(DutySchedule.fromMap).toList());
}
return base
.eq('user_id', profile.id)
.order('start_time')
.map((rows) => rows.map(DutySchedule.fromMap).toList());
final wrapper = StreamRecoveryWrapper<DutySchedule>(
stream: isAdmin
? client
.from('duty_schedules')
.stream(primaryKey: ['id'])
.order('start_time')
: client
.from('duty_schedules')
.stream(primaryKey: ['id'])
.eq('user_id', profile.id)
.order('start_time'),
onPollData: () async {
final query = client.from('duty_schedules').select();
final data = isAdmin
? await query.order('start_time')
: await query.eq('user_id', profile.id).order('start_time');
return data.map(DutySchedule.fromMap).toList();
},
fromMap: DutySchedule.fromMap,
);
ref.onDispose(wrapper.dispose);
return wrapper.stream.map((result) => result.data);
});
/// Fetch duty schedules by a list of IDs (used by UI when swap requests reference
@@ -88,24 +103,36 @@ final swapRequestsProvider = StreamProvider<List<SwapRequest>>((ref) {
}
final isAdmin = profile.role == 'admin' || profile.role == 'dispatcher';
final base = client.from('swap_requests').stream(primaryKey: ['id']);
if (isAdmin) {
return base
.order('created_at', ascending: false)
.map((rows) => rows.map(SwapRequest.fromMap).toList());
}
return base
.order('created_at', ascending: false)
.map(
(rows) => rows
.where(
(row) =>
row['requester_id'] == profile.id ||
row['recipient_id'] == profile.id,
)
.map(SwapRequest.fromMap)
.toList(),
);
final wrapper = StreamRecoveryWrapper<SwapRequest>(
stream: isAdmin
? client
.from('swap_requests')
.stream(primaryKey: ['id'])
.order('created_at', ascending: false)
: client
.from('swap_requests')
.stream(primaryKey: ['id'])
.order('created_at', ascending: false),
onPollData: () async {
final data = await client
.from('swap_requests')
.select()
.order('created_at', ascending: false);
return data.map(SwapRequest.fromMap).toList();
},
fromMap: SwapRequest.fromMap,
);
ref.onDispose(wrapper.dispose);
return wrapper.stream.map((result) {
return result.data
.where(
(row) =>
row.requesterId == profile.id || row.recipientId == profile.id,
)
.toList();
});
});
final workforceControllerProvider = Provider<WorkforceController>((ref) {