feat(legacy): replace CircularProgressIndicator with animated GIF loading

Add LegacyLoadingIndicator widget using a transparent GIF animation
for all full-page loading states across legacy modules. Also fix
HealthController crash by deferring provider mutation during build.
This commit is contained in:
2026-04-23 14:54:38 +02:00
parent ac493725cf
commit 5be9136e06
26 changed files with 60 additions and 30 deletions

View File

@@ -40,7 +40,7 @@ class AppUsersScreen extends ConsumerWidget {
SizedBox(height: SizeUtils.getByScreen(small: 18, big: 17)), SizedBox(height: SizeUtils.getByScreen(small: 18, big: 17)),
itemCount: users.length, itemCount: users.length,
), ),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => const SizedBox.shrink(), error: (_, __) => const SizedBox.shrink(),
), ),
), ),

View File

@@ -60,7 +60,7 @@ class LinkedDevicesScreen extends ConsumerWidget {
itemCount: devices.length, itemCount: devices.length,
), ),
), ),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => const SizedBox.shrink(), error: (_, __) => const SizedBox.shrink(),
), ),
); );

View File

@@ -20,7 +20,7 @@ class PersonalDataScreen extends ConsumerWidget {
data: (user) => _PersonalDataForm(user: user), data: (user) => _PersonalDataForm(user: user),
loading: () => Scaffold( loading: () => Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
), ),
error: (_, __) => LegacyPageLayout( error: (_, __) => LegacyPageLayout(
title: context.translate(I18n.personalData), title: context.translate(I18n.personalData),

View File

@@ -40,7 +40,7 @@ class ControlPanelScreen extends ConsumerWidget {
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: asyncState.when( body: asyncState.when(
skipLoadingOnReload: true, skipLoadingOnReload: true,
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (error, _) => RefreshableErrorState( error: (error, _) => RefreshableErrorState(
message: formatErrorMessage(error), message: formatErrorMessage(error),
onRefresh: () async { onRefresh: () async {

View File

@@ -10,6 +10,7 @@ import 'package:device_management/src/features/activity_meter/presentation/widge
import 'package:device_management/src/features/activity_meter/presentation/widgets/steps_bar_chart.dart'; import 'package:device_management/src/features/activity_meter/presentation/widgets/steps_bar_chart.dart';
import 'package:device_management/src/features/activity_meter/presentation/widgets/steps_history_section.dart'; import 'package:device_management/src/features/activity_meter/presentation/widgets/steps_history_section.dart';
import 'package:device_management/src/features/activity_meter/presentation/widgets/steps_progress_ring.dart'; import 'package:device_management/src/features/activity_meter/presentation/widgets/steps_progress_ring.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_ui/legacy_ui.dart'; import 'package:legacy_ui/legacy_ui.dart';
@@ -27,7 +28,7 @@ class ActivityMeterScreen extends ConsumerWidget {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.activityMeter), title: context.translate(I18n.activityMeter),
body: isLoading body: isLoading
? const Center(child: CircularProgressIndicator()) ? const LegacyLoadingIndicator()
: const _ActivityMeterBody(), : const _ActivityMeterBody(),
); );
} }

View File

@@ -2,6 +2,7 @@ import 'package:device_management/src/core/presentation/widgets/time_range_selec
import 'package:device_management/src/features/apps_use/presentation/providers/apps_use_controller.dart'; import 'package:device_management/src/features/apps_use/presentation/providers/apps_use_controller.dart';
import 'package:device_management/src/features/apps_use/presentation/widgets/daily_app_usage_section.dart'; import 'package:device_management/src/features/apps_use/presentation/widgets/daily_app_usage_section.dart';
import 'package:device_management/src/features/apps_use/presentation/widgets/top_apps_section.dart'; import 'package:device_management/src/features/apps_use/presentation/widgets/top_apps_section.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_ui/legacy_ui.dart'; import 'package:legacy_ui/legacy_ui.dart';
@@ -29,7 +30,7 @@ class AppsUseScreen extends ConsumerWidget {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.appsUse), title: context.translate(I18n.appsUse),
body: state.isLoading body: state.isLoading
? const Center(child: CircularProgressIndicator()) ? const LegacyLoadingIndicator()
: SingleChildScrollView( : SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@@ -105,13 +105,13 @@ class BackgroundImageScreen extends ConsumerWidget {
body: SafeArea( body: SafeArea(
top: false, top: false,
child: photosAsync.when( child: photosAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.errorBackgroundImageLoad)), child: Text(context.translate(I18n.errorBackgroundImageLoad)),
), ),
data: (photos) { data: (photos) {
if (isSaving) { if (isSaving) {
return const Center(child: CircularProgressIndicator()); return const LegacyLoadingIndicator();
} }
if (photos.isEmpty) { if (photos.isEmpty) {
return _EmptyState( return _EmptyState(

View File

@@ -1,6 +1,7 @@
import 'package:device_management/src/features/call_history/data/call_history_entity.dart'; import 'package:device_management/src/features/call_history/data/call_history_entity.dart';
import 'package:device_management/src/features/call_history/presentation/providers/call_history_filter_provider.dart'; import 'package:device_management/src/features/call_history/presentation/providers/call_history_filter_provider.dart';
import 'package:device_management/src/features/call_history/presentation/providers/call_history_provider.dart'; import 'package:device_management/src/features/call_history/presentation/providers/call_history_provider.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_theme/legacy_theme.dart'; import 'package:legacy_theme/legacy_theme.dart';
@@ -28,7 +29,7 @@ class CallHistoryScreen extends ConsumerWidget {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.callHistory), title: context.translate(I18n.callHistory),
body: callsAsync.when( body: callsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (err, _) => Center( error: (err, _) => Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@@ -57,14 +57,14 @@ class ContactsScreen extends ConsumerWidget {
showEdit: true, showEdit: true,
onEditChange: ref.read(contactsEditingModeProvider.notifier).toggle, onEditChange: ref.read(contactsEditingModeProvider.notifier).toggle,
body: userAsync.when( body: userAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.errorGeneric)), child: Text(context.translate(I18n.errorGeneric)),
), ),
data: (user) { data: (user) {
final contactsAsync = ref.watch(contactsProvider(user.id)); final contactsAsync = ref.watch(contactsProvider(user.id));
return contactsAsync.when( return contactsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.errorGeneric)), child: Text(context.translate(I18n.errorGeneric)),
), ),

View File

@@ -26,7 +26,7 @@ class EditContactScreen extends ConsumerWidget {
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: SafeArea( body: SafeArea(
child: userAsync.when( child: userAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.errorGeneric)), child: Text(context.translate(I18n.errorGeneric)),
), ),
@@ -34,7 +34,7 @@ class EditContactScreen extends ConsumerWidget {
final contactsAsync = ref.watch(contactsProvider(user.id)); final contactsAsync = ref.watch(contactsProvider(user.id));
return contactsAsync.when( return contactsAsync.when(
loading: () => loading: () =>
const Center(child: CircularProgressIndicator()), const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.errorGeneric)), child: Text(context.translate(I18n.errorGeneric)),
), ),

View File

@@ -35,7 +35,7 @@ class DoNotDisturbScreen extends ConsumerWidget {
if (device == null) { if (device == null) {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.doNotDisturb), title: context.translate(I18n.doNotDisturb),
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
); );
} }
@@ -50,7 +50,7 @@ class DoNotDisturbScreen extends ConsumerWidget {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.doNotDisturb), title: context.translate(I18n.doNotDisturb),
body: periodsAsync.when( body: periodsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.doNotDisturbError)), child: Text(context.translate(I18n.doNotDisturbError)),
), ),

View File

@@ -80,7 +80,7 @@ class _HealthScreenState extends ConsumerState<HealthScreen>
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.healthTitle), title: context.translate(I18n.healthTitle),
body: state.isLoading body: state.isLoading
? const Center(child: CircularProgressIndicator()) ? const LegacyLoadingIndicator()
: state.isMeasuringCountdown : state.isMeasuringCountdown
? _MeasuringOverlay( ? _MeasuringOverlay(
remainingSeconds: state.measureRemainingSeconds, remainingSeconds: state.measureRemainingSeconds,

View File

@@ -36,7 +36,9 @@ class HealthController extends _$HealthController {
final remaining = endTime.difference(DateTime.now()).inSeconds; final remaining = endTime.difference(DateTime.now()).inSeconds;
if (remaining <= 0) { if (remaining <= 0) {
ref.read(measureEndTimeProvider.notifier).set(null); Future.microtask(() {
ref.read(measureEndTimeProvider.notifier).set(null);
});
return; return;
} }

View File

@@ -27,7 +27,7 @@ class RemoteCameraScreen extends ConsumerWidget {
if (device == null) { if (device == null) {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.remoteCamera), title: context.translate(I18n.remoteCamera),
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
); );
} }
@@ -37,14 +37,14 @@ class RemoteCameraScreen extends ConsumerWidget {
Widget body; Widget body;
if (cameraState.isTakingPicture) { if (cameraState.isTakingPicture) {
body = const Center(child: CircularProgressIndicator()); body = const LegacyLoadingIndicator();
} else if (cameraState.isWaitingForPhoto) { } else if (cameraState.isWaitingForPhoto) {
body = _WaitingForPhotoOverlay( body = _WaitingForPhotoOverlay(
remainingSeconds: cameraState.photoCountdown, remainingSeconds: cameraState.photoCountdown,
); );
} else { } else {
body = picturesAsync.when( body = picturesAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => error: (_, __) =>
Center(child: Text(context.translate(I18n.errorFetchPhotos))), Center(child: Text(context.translate(I18n.errorFetchPhotos))),
data: (pictures) => _GallerySection(pictures: pictures), data: (pictures) => _GallerySection(pictures: pictures),

View File

@@ -2,6 +2,7 @@ import 'package:device_management/src/features/scheduled_activities/presentation
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_provider.dart'; import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_provider.dart';
import 'package:device_management/src/features/scheduled_activities/presentation/widgets/activity_form_sheet.dart'; import 'package:device_management/src/features/scheduled_activities/presentation/widgets/activity_form_sheet.dart';
import 'package:device_management/src/features/scheduled_activities/presentation/widgets/day_timeline.dart'; import 'package:device_management/src/features/scheduled_activities/presentation/widgets/day_timeline.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_device_state/legacy_device_state.dart'; import 'package:legacy_device_state/legacy_device_state.dart';
@@ -82,7 +83,7 @@ class _ScheduledActivitiesScreenState
if (device == null) { if (device == null) {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.activityScheduleTitle), title: context.translate(I18n.activityScheduleTitle),
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
); );
} }
@@ -92,7 +93,7 @@ class _ScheduledActivitiesScreenState
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.activityScheduleTitle), title: context.translate(I18n.activityScheduleTitle),
body: activitiesAsync.when( body: activitiesAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (err, _) => Center(child: Text(err.toString())), error: (err, _) => Center(child: Text(err.toString())),
data: (activities) => Column( data: (activities) => Column(
children: [ children: [

View File

@@ -31,7 +31,7 @@ class VolumeControlScreen extends ConsumerWidget {
if (device == null) { if (device == null) {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.volumeControl), title: context.translate(I18n.volumeControl),
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
); );
} }

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_device_state/legacy_device_state.dart'; import 'package:legacy_device_state/legacy_device_state.dart';
@@ -48,7 +49,7 @@ class LocationScreen extends ConsumerWidget {
title: context.translate(I18n.mapTitle), title: context.translate(I18n.mapTitle),
showBack: false, showBack: false,
body: isLoading body: isLoading
? const Center(child: CircularProgressIndicator()) ? const LegacyLoadingIndicator()
: deviceState == null : deviceState == null
? RefreshableErrorState( ? RefreshableErrorState(
onRefresh: () async { onRefresh: () async {

View File

@@ -50,7 +50,7 @@ class AlarmScreen extends ConsumerWidget {
return alarmsAsync.when( return alarmsAsync.when(
loading: () => Scaffold( loading: () => Scaffold(
appBar: _appBar(context, navigationContract, primaryColor), appBar: _appBar(context, navigationContract, primaryColor),
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
), ),
error: (_, __) => Scaffold( error: (_, __) => Scaffold(
appBar: _appBar(context, navigationContract, primaryColor), appBar: _appBar(context, navigationContract, primaryColor),

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_device_state/legacy_device_state.dart'; import 'package:legacy_device_state/legacy_device_state.dart';
@@ -46,7 +47,7 @@ class BlockPhoneScreen extends ConsumerWidget {
return contactsAsync.when( return contactsAsync.when(
loading: () => Scaffold( loading: () => Scaffold(
appBar: _appBar(context, navigationContract, primaryColor), appBar: _appBar(context, navigationContract, primaryColor),
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
), ),
error: (_, __) => Scaffold( error: (_, __) => Scaffold(
appBar: _appBar(context, navigationContract, primaryColor), appBar: _appBar(context, navigationContract, primaryColor),

View File

@@ -40,7 +40,7 @@ class DisableFunctionsScreen extends ConsumerWidget {
return LegacyPageLayout( return LegacyPageLayout(
title: context.translate(I18n.disableFunctions), title: context.translate(I18n.disableFunctions),
body: device == null body: device == null
? const Center(child: CircularProgressIndicator()) ? const LegacyLoadingIndicator()
: Padding( : Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 16, big: 14), horizontal: SizeUtils.getByScreen(small: 16, big: 14),

View File

@@ -2,6 +2,7 @@ import 'package:settings/src/core/domain/entities/notification_entity.dart';
import 'package:settings/src/features/notifications/presentation/providers/notifications_feed_provider.dart'; import 'package:settings/src/features/notifications/presentation/providers/notifications_feed_provider.dart';
import 'package:settings/src/features/notifications/presentation/providers/notifications_filter_provider.dart'; import 'package:settings/src/features/notifications/presentation/providers/notifications_filter_provider.dart';
import 'package:settings/src/features/notifications/presentation/widgets/notification_card.dart'; import 'package:settings/src/features/notifications/presentation/widgets/notification_card.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_theme/legacy_theme.dart'; import 'package:legacy_theme/legacy_theme.dart';
@@ -216,7 +217,7 @@ class _FilteredNotificationsScreen extends ConsumerWidget {
), ),
), ),
body: feedAsync.when( body: feedAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.alertsLoadError)), child: Text(context.translate(I18n.alertsLoadError)),
), ),

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_theme/legacy_theme.dart'; import 'package:legacy_theme/legacy_theme.dart';
@@ -48,7 +49,7 @@ class SosContactsScreen extends ConsumerWidget {
return contactsAsync.when( return contactsAsync.when(
loading: () => Scaffold( loading: () => Scaffold(
appBar: _appBar(context, navigationContract, primaryColor), appBar: _appBar(context, navigationContract, primaryColor),
body: const Center(child: CircularProgressIndicator()), body: const LegacyLoadingIndicator(),
), ),
error: (_, __) => Scaffold( error: (_, __) => Scaffold(
appBar: _appBar(context, navigationContract, primaryColor), appBar: _appBar(context, navigationContract, primaryColor),

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_device_state/legacy_device_state.dart'; import 'package:legacy_device_state/legacy_device_state.dart';
@@ -97,7 +98,7 @@ class _WifiSettingsScreenState extends ConsumerState<WifiSettingsScreen> {
body: SafeArea( body: SafeArea(
top: false, top: false,
child: savedAsync.when( child: savedAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const LegacyLoadingIndicator(),
error: (_, __) => Center( error: (_, __) => Center(
child: Text(context.translate(I18n.wifiLoadError)), child: Text(context.translate(I18n.wifiLoadError)),
), ),

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

View File

@@ -15,5 +15,6 @@ export 'src/containers/section_container.dart';
export 'src/containers/footer_container.dart'; export 'src/containers/footer_container.dart';
export 'src/rows/editable_row.dart'; export 'src/rows/editable_row.dart';
export 'src/loading/app_loading_indicator.dart'; export 'src/loading/app_loading_indicator.dart';
export 'src/loading/legacy_loading_indicator.dart';
export 'src/confetti/confetti_overlay.dart'; export 'src/confetti/confetti_overlay.dart';
export 'src/dialogs/contacts_permission_dialog.dart'; export 'src/dialogs/contacts_permission_dialog.dart';

View File

@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
class LegacyLoadingIndicator extends StatelessWidget {
final double size;
const LegacyLoadingIndicator({super.key, this.size = 120});
@override
Widget build(BuildContext context) {
return Center(
child: Image.asset(
'packages/design_system/assets/animations/loading_legacy.gif',
width: size,
height: size,
),
);
}
}