Files
tasq/lib/app.dart
T
redz1029 3cb980b629 QA hardening: security RLS fixes, Flutter 3.47.5 upgrade, UI/validation fixes
Security — enforce write authorization server-side (was UI/RPC-only):
- it_service_requests RLS: block cross-office read/edit + self-approve (QA-015)
- pass_slips RLS: owner can complete but not self-approve (QA-046)
- swap_requests RLS: scope select/update to participants + admin (QA-047)
- storage: tighten it_service_attachments + task_attachments write/delete (QA-027)
- admin_user_management edge function: allow programmers to manage users (QA-016)

Fixes:
- workforce generator "uncovered shifts" false alarms (QA-043/044)
- network-map VLAN + New-location dialog validation, disabled-until-valid (QA-048)
- de-flake time-of-day-dependent dashboard metrics test (QA-045)

Toolchain:
- upgrade to Flutter 3.47.5 / Dart 3.13.4; font_awesome_flutter 11.0.0,
  flutter_quill 11.6.0, pdfrx 2.6.5; clear resulting deprecations (QA-002)

analyze clean; 139 tests pass; web build succeeds. Report + evidence in docs/qa/.

Note: also carries the in-progress Brick model cleanup already present in the
working tree. QA-001 (AI keys public in the build) is deferred by owner decision.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-26 11:52:16 +08:00

78 lines
2.8 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'routing/app_router.dart';
import 'models/profile.model.dart';
import 'providers/connectivity_provider.dart';
import 'providers/profile_provider.dart';
import 'services/background_location_service.dart';
import 'theme/app_theme.dart';
import 'utils/snackbar.dart';
import 'widgets/ios_install_prompt.dart';
import 'widgets/offline_banner.dart';
class TasqApp extends ConsumerWidget {
const TasqApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Keep the connectivity monitor alive at the root so the global online
// check is wired into StreamRecoveryWrapper before any provider subscribes
// to a Supabase realtime stream. Without this watch the autoDispose
// provider only stays alive while OfflineBanner is mounted, which is too
// late for offline-cold-launch detection.
ref.watch(connectivityMonitorProvider);
final router = ref.watch(appRouterProvider);
// Ensure background service is running if the profile indicates tracking.
// This handles the case where the app restarts while tracking was already
// enabled (service should persist, but this guarantees it). It also stops
// the service when tracking is disabled externally.
ref.listen<AsyncValue<Profile?>>(currentProfileProvider, (
previous,
next,
) async {
final profile = next.valueOrNull;
final allow = profile?.allowTracking ?? false;
if (allow) {
await startBackgroundLocationUpdates();
} else {
await stopBackgroundLocationUpdates();
}
// Pre-warm the enrolled-face cache so offline face matching has bytes
// to compare against. No-op on web, or when cache is up-to-date.
if (profile != null && profile.hasFaceEnrolled) {
unawaited(
prewarmEnrolledFaceCache(
controller: ref.read(profileControllerProvider),
userId: profile.id,
enrolledAt: profile.faceEnrolledAt?.toIso8601String(),
),
);
}
});
return MaterialApp.router(
title: 'TasQ',
routerConfig: router,
scaffoldMessengerKey: scaffoldMessengerKey,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
themeMode: ThemeMode.system,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
FlutterQuillLocalizations.delegate,
],
builder: (context, child) => OfflineBanner(
child: IosInstallPrompt(child: child ?? const SizedBox.shrink()),
),
);
}
}