Show offline notice in settings related screens (#576)

* refactor: wrap offline notice translations

* feat: show offline notice in network screen

* feat: show no internet notice in settings screen

* feat: show no internet notice in bug report screen

* feat: show no internet notice in key packages screen

* feat: show no internet notice in relay control screen

* docs: update changelog
This commit is contained in:
Pepi
2026-04-20 09:46:44 -04:00
committed by GitHub
parent 7293a5debd
commit 6cb653d12b
17 changed files with 527 additions and 129 deletions
+2 -6
View File
@@ -20,6 +20,7 @@ import 'package:whitenoise/utils/bubble_grouping.dart';
import 'package:whitenoise/utils/metadata.dart';
import 'package:whitenoise/widgets/chat_message_bubble.dart';
import 'package:whitenoise/widgets/chat_scroll_down_button.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_avatar.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_chat_system_message.dart';
@@ -133,12 +134,7 @@ class ChatInviteScreen extends HookConsumerWidget {
WnSlate(
tag: 'wn-slate-invite-header',
systemNotice: isOffline
? WnSystemNotice(
key: const Key('offline_notice'),
title: context.l10n.waitingForInternet,
type: WnSystemNoticeType.warning,
variant: WnSystemNoticeVariant.expanded,
)
? const OfflineSystemNotice()
: (noticeMessage.value != null
? WnSystemNotice(
key: ValueKey(noticeMessage.value),
+3 -7
View File
@@ -15,6 +15,7 @@ import 'package:whitenoise/theme.dart';
import 'package:whitenoise/utils/chat_search.dart';
import 'package:whitenoise/widgets/chat_list_header.dart';
import 'package:whitenoise/widgets/chat_list_tile.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_chat_list.dart';
import 'package:whitenoise/widgets/wn_filter_chip.dart';
@@ -88,7 +89,7 @@ class ChatListScreen extends HookConsumerWidget {
);
}
WnSystemNotice? _buildSystemNotice(
Widget? _buildSystemNotice(
BuildContext context,
AppTypography typography,
SemanticColors colors, {
@@ -99,12 +100,7 @@ class ChatListScreen extends HookConsumerWidget {
required VoidCallback onWelcomeDismiss,
}) {
if (isOffline) {
return WnSystemNotice(
key: const Key('offline_notice'),
title: context.l10n.waitingForInternet,
type: WnSystemNoticeType.warning,
variant: WnSystemNoticeVariant.expanded,
);
return const OfflineSystemNotice();
}
if (updateVersion != null) {
return WnSystemNotice(
+2 -6
View File
@@ -39,6 +39,7 @@ import 'package:whitenoise/widgets/chat_media_upload_preview.dart';
import 'package:whitenoise/widgets/chat_message_bubble.dart';
import 'package:whitenoise/widgets/chat_message_quote.dart';
import 'package:whitenoise/widgets/chat_scroll_down_button.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_chat_message_input.dart';
import 'package:whitenoise/widgets/wn_icon.dart';
@@ -521,12 +522,7 @@ class ChatScreen extends HookConsumerWidget {
: null,
),
systemNotice: isOffline
? WnSystemNotice(
key: const Key('offline_notice'),
title: context.l10n.waitingForInternet,
type: WnSystemNoticeType.warning,
variant: WnSystemNoticeVariant.expanded,
)
? const OfflineSystemNotice()
: (noticeMessage.value != null
? WnSystemNotice(
key: ValueKey(noticeMessage.value),
+22 -13
View File
@@ -5,9 +5,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whitenoise/hooks/use_key_packages.dart';
import 'package:whitenoise/l10n/l10n.dart';
import 'package:whitenoise/providers/account_pubkey_provider.dart';
import 'package:whitenoise/providers/offline_provider.dart';
import 'package:whitenoise/routes.dart';
import 'package:whitenoise/src/rust/api/accounts.dart' show FlutterEvent;
import 'package:whitenoise/theme.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_key_package_card.dart';
import 'package:whitenoise/widgets/wn_scroll_edge_effect.dart';
@@ -23,6 +25,7 @@ class KeyPackageManagementScreen extends HookConsumerWidget {
final colors = context.colors;
final typography = context.typographyScaled;
final pubkey = ref.watch(accountPubkeyProvider);
final isOffline = ref.watch(offlineProvider).value ?? false;
final (:state, :fetch, :publish, :delete, :deleteAll) = useKeyPackages(pubkey);
final noticeMessage = useState<String?>(null);
@@ -68,6 +71,7 @@ class KeyPackageManagementScreen extends HookConsumerWidget {
Future<void> handleDelete(String id) => handleAction(() => delete(id));
useEffect(() {
if (isOffline) return;
fetch().then((result) {
if (!context.mounted) return;
if (!result.success) {
@@ -75,7 +79,7 @@ class KeyPackageManagementScreen extends HookConsumerWidget {
}
});
return null;
}, [pubkey]);
}, [pubkey, isOffline]);
return Scaffold(
backgroundColor: colors.backgroundPrimary,
@@ -87,14 +91,16 @@ class KeyPackageManagementScreen extends HookConsumerWidget {
title: context.l10n.keyPackageManagementTitle,
onNavigate: () => Routes.goBack(context),
),
systemNotice: noticeMessage.value != null
? WnSystemNotice(
key: ValueKey(noticeMessage.value),
title: noticeMessage.value!,
type: noticeType.value,
onDismiss: dismissNotice,
)
: null,
systemNotice: isOffline
? const OfflineSystemNotice()
: (noticeMessage.value != null
? WnSystemNotice(
key: ValueKey(noticeMessage.value),
title: noticeMessage.value!,
type: noticeType.value,
onDismiss: dismissNotice,
)
: null),
child: Padding(
padding: EdgeInsets.fromLTRB(14.w, 0, 14.w, 14.h),
child: Column(
@@ -103,6 +109,7 @@ class KeyPackageManagementScreen extends HookConsumerWidget {
SizedBox(height: 16.h),
_KeyPackageActionButtons(
isLoading: state.isLoading,
isOffline: isOffline,
activeAction: state.activeAction,
onPublish: () => handleAction(publish),
onFetch: () => handleAction(fetch),
@@ -120,7 +127,7 @@ class KeyPackageManagementScreen extends HookConsumerWidget {
child: _KeyPackageList(
packages: state.packages,
onDelete: handleDelete,
disabled: state.isLoading,
disabled: state.isLoading || isOffline,
deletingId: state.deletingId,
),
),
@@ -137,6 +144,7 @@ class KeyPackageManagementScreen extends HookConsumerWidget {
class _KeyPackageActionButtons extends StatelessWidget {
const _KeyPackageActionButtons({
required this.isLoading,
required this.isOffline,
required this.activeAction,
required this.onPublish,
required this.onFetch,
@@ -144,6 +152,7 @@ class _KeyPackageActionButtons extends StatelessWidget {
});
final bool isLoading;
final bool isOffline;
final KeyPackageAction? activeAction;
final VoidCallback onPublish;
final VoidCallback onFetch;
@@ -158,7 +167,7 @@ class _KeyPackageActionButtons extends StatelessWidget {
WnButton(
text: context.l10n.refreshKeyPackages,
onPressed: onFetch,
disabled: isLoading,
disabled: isLoading || isOffline,
loading: activeAction == KeyPackageAction.fetch,
type: WnButtonType.outline,
size: WnButtonSize.medium,
@@ -166,14 +175,14 @@ class _KeyPackageActionButtons extends StatelessWidget {
WnButton(
text: context.l10n.publishNewKeyPackage,
onPressed: onPublish,
disabled: isLoading,
disabled: isLoading || isOffline,
loading: activeAction == KeyPackageAction.publish,
size: WnButtonSize.medium,
),
WnButton(
text: context.l10n.deleteAllKeyPackages,
onPressed: onDeleteAll,
disabled: isLoading,
disabled: isLoading || isOffline,
loading: activeAction == KeyPackageAction.deleteAll,
type: WnButtonType.destructive,
size: WnButtonSize.medium,
+50 -31
View File
@@ -8,8 +8,10 @@ import 'package:whitenoise/hooks/use_network_relays.dart';
import 'package:whitenoise/hooks/use_system_notice.dart';
import 'package:whitenoise/l10n/l10n.dart';
import 'package:whitenoise/providers/account_pubkey_provider.dart';
import 'package:whitenoise/providers/offline_provider.dart';
import 'package:whitenoise/routes.dart';
import 'package:whitenoise/theme.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_confirmation_slate.dart';
import 'package:whitenoise/widgets/wn_icon.dart';
@@ -17,7 +19,7 @@ import 'package:whitenoise/widgets/wn_list.dart';
import 'package:whitenoise/widgets/wn_list_item.dart';
import 'package:whitenoise/widgets/wn_slate.dart';
import 'package:whitenoise/widgets/wn_slate_navigation_header.dart';
import 'package:whitenoise/widgets/wn_system_notice.dart' show WnSystemNotice;
import 'package:whitenoise/widgets/wn_system_notice.dart';
import 'package:whitenoise/widgets/wn_tooltip.dart';
class NetworkScreen extends HookConsumerWidget {
@@ -31,6 +33,7 @@ class NetworkScreen extends HookConsumerWidget {
final (:state, :fetchAll, :addRelay, :removeRelay, :restoreDefaultRelays) = useNetworkRelays(
pubkey,
);
final isOffline = ref.watch(offlineProvider).value ?? false;
final listItemController = useListItemController();
final (:noticeMessage, :noticeType, :showErrorNotice, :showSuccessNotice, :dismissNotice) =
useSystemNotice();
@@ -128,24 +131,28 @@ class NetworkScreen extends HookConsumerWidget {
);
}
final systemNotice = isOffline
? const OfflineSystemNotice()
: noticeMessage != null
? WnSystemNotice(
key: ValueKey(noticeMessage),
title: noticeMessage,
type: noticeType,
onDismiss: dismissNotice,
)
: null;
return Scaffold(
backgroundColor: colors.backgroundPrimary,
body: SafeArea(
child: WnSlate(
showTopScrollEffect: true,
showBottomScrollEffect: true,
systemNotice: systemNotice,
header: WnSlateNavigationHeader(
title: context.l10n.networkRelaysTitle,
onNavigate: () => Routes.goBack(context),
),
systemNotice: noticeMessage != null
? WnSystemNotice(
key: ValueKey(noticeMessage),
title: noticeMessage,
type: noticeType,
onDismiss: dismissNotice,
)
: null,
footer: Padding(
padding: EdgeInsets.fromLTRB(16.w, 24.h, 16.w, 16.h),
child: SizedBox(
@@ -155,25 +162,28 @@ class NetworkScreen extends HookConsumerWidget {
text: context.l10n.restoreDefaultRelays,
size: WnButtonSize.medium,
trailingIcon: WnIcons.reset,
onPressed: () => WnConfirmationSlate.show(
context: context,
title: context.l10n.restoreDefaultRelaysConfirmationTitle,
message: context.l10n.restoreDefaultRelaysConfirmationMessage,
confirmText: context.l10n.restoreDefaultRelays,
cancelText: context.l10n.cancel,
isDestructive: true,
onConfirmAsync: () async {
try {
await restoreDefaultRelays();
return true;
} catch (_) {
if (context.mounted) {
showErrorNotice(context.l10n.restoreDefaultRelaysError);
}
return false;
}
},
),
disabled: isOffline,
onPressed: isOffline
? null
: () => WnConfirmationSlate.show(
context: context,
title: context.l10n.restoreDefaultRelaysConfirmationTitle,
message: context.l10n.restoreDefaultRelaysConfirmationMessage,
confirmText: context.l10n.restoreDefaultRelays,
cancelText: context.l10n.cancel,
isDestructive: true,
onConfirmAsync: () async {
try {
await restoreDefaultRelays();
return true;
} catch (_) {
if (context.mounted) {
showErrorNotice(context.l10n.restoreDefaultRelaysError);
}
return false;
}
},
),
),
),
),
@@ -216,7 +226,10 @@ class NetworkScreen extends HookConsumerWidget {
type: WnButtonType.overlay,
size: WnButtonSize.medium,
trailingIcon: WnIcons.addLarge,
onPressed: () => showAddRelaySheet(RelayCategory.normal),
disabled: isOffline,
onPressed: isOffline
? null
: () => showAddRelaySheet(RelayCategory.normal),
),
),
],
@@ -243,7 +256,10 @@ class NetworkScreen extends HookConsumerWidget {
type: WnButtonType.overlay,
size: WnButtonSize.medium,
trailingIcon: WnIcons.addLarge,
onPressed: () => showAddRelaySheet(RelayCategory.inbox),
disabled: isOffline,
onPressed: isOffline
? null
: () => showAddRelaySheet(RelayCategory.inbox),
),
),
],
@@ -273,7 +289,10 @@ class NetworkScreen extends HookConsumerWidget {
type: WnButtonType.overlay,
size: WnButtonSize.medium,
trailingIcon: WnIcons.addLarge,
onPressed: () => showAddRelaySheet(RelayCategory.keyPackage),
disabled: isOffline,
onPressed: isOffline
? null
: () => showAddRelaySheet(RelayCategory.keyPackage),
),
),
],
+48 -43
View File
@@ -2,30 +2,45 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:whitenoise/hooks/use_system_notice.dart';
import 'package:whitenoise/l10n/l10n.dart';
import 'package:whitenoise/providers/offline_provider.dart';
import 'package:whitenoise/routes.dart';
import 'package:whitenoise/src/rust/api/relays.dart';
import 'package:whitenoise/theme.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_slate.dart';
import 'package:whitenoise/widgets/wn_slate_navigation_header.dart';
import 'package:whitenoise/widgets/wn_system_notice.dart';
final _logger = Logger('RelayControlStateScreen');
class RelayControlStateScreen extends HookWidget {
class RelayControlStateScreen extends HookConsumerWidget {
const RelayControlStateScreen({super.key});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final colors = context.colors;
final typography = context.typographyScaled;
final isOffline = ref.watch(offlineProvider).value ?? false;
final isLoading = useState(false);
final result = useState<String?>(null);
final error = useState<String?>(null);
final (
:noticeMessage,
:noticeType,
:showErrorNotice,
:dismissNotice,
:showSuccessNotice,
) = useSystemNotice();
Future<void> loadState() async {
if (isOffline) return;
isLoading.value = true;
error.value = null;
dismissNotice();
try {
final dump = await debugRelayControlState();
@@ -38,7 +53,7 @@ class RelayControlStateScreen extends HookWidget {
if (!context.mounted) {
return;
}
error.value = context.l10n.relayControlStateLoadError;
showErrorNotice(context.l10n.relayControlStateLoadError);
result.value = null;
} finally {
if (context.mounted) {
@@ -55,19 +70,16 @@ class RelayControlStateScreen extends HookWidget {
await Clipboard.setData(ClipboardData(text: dump));
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.rawDebugViewCopied),
duration: const Duration(seconds: 2),
),
);
showSuccessNotice(context.l10n.rawDebugViewCopied);
}
}
useEffect(() {
loadState();
if (!isOffline) {
loadState();
}
return null;
}, const []);
}, [isOffline]);
return Scaffold(
backgroundColor: colors.backgroundPrimary,
@@ -79,6 +91,16 @@ class RelayControlStateScreen extends HookWidget {
title: context.l10n.relayStateTitle,
onNavigate: () => Routes.goBack(context),
),
systemNotice: isOffline
? const OfflineSystemNotice()
: (noticeMessage != null
? WnSystemNotice(
key: ValueKey(noticeMessage),
title: noticeMessage,
type: noticeType,
onDismiss: dismissNotice,
)
: null),
child: ListView(
padding: EdgeInsets.fromLTRB(14.w, 12.h, 14.w, 14.h),
children: [
@@ -92,14 +114,6 @@ class RelayControlStateScreen extends HookWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.relayControlStateDumpLabel,
style: typography.semiBold10.copyWith(
color: colors.backgroundContentSecondary,
fontFamily: 'monospace',
),
),
SizedBox(height: 8.h),
Text(
context.l10n.relayControlStateSnapshotDescription,
style: typography.medium10.copyWith(
@@ -113,34 +127,25 @@ class RelayControlStateScreen extends HookWidget {
spacing: 8.w,
runSpacing: 8.h,
children: [
TextButton(
WnButton(
key: const Key('relay_control_state_refresh_button'),
onPressed: isLoading.value ? null : loadState,
child: Text(
isLoading.value
? context.l10n.relayControlStateLoading
: context.l10n.relayControlStateRefreshButton,
),
text: isLoading.value
? context.l10n.relayControlStateLoading
: context.l10n.relayControlStateRefreshButton,
onPressed: isLoading.value || isOffline ? null : loadState,
loading: isLoading.value,
type: WnButtonType.outline,
size: WnButtonSize.small,
),
TextButton(
WnButton(
key: const Key('relay_control_state_copy_button'),
onPressed: result.value == null ? null : copyDump,
child: Text(context.l10n.relayControlStateCopyButton),
text: context.l10n.relayControlStateCopyButton,
onPressed: (result.value?.trim().isEmpty ?? true) ? null : copyDump,
type: WnButtonType.outline,
size: WnButtonSize.small,
),
],
),
if (error.value != null) ...[
SizedBox(height: 6.h),
SelectableText(
key: const Key('relay_control_state_error'),
error.value!,
style: typography.medium10.copyWith(
color: colors.fillDestructive,
fontFamily: 'monospace',
height: 1.4,
),
),
],
if (result.value != null) ...[
SizedBox(height: 6.h),
SelectableText(
+16 -9
View File
@@ -9,11 +9,13 @@ import 'package:whitenoise/hooks/use_system_notice.dart';
import 'package:whitenoise/l10n/l10n.dart';
import 'package:whitenoise/providers/app_version_provider.dart';
import 'package:whitenoise/providers/auth_provider.dart';
import 'package:whitenoise/providers/offline_provider.dart';
import 'package:whitenoise/routes.dart';
import 'package:whitenoise/src/rust/api/bug_report.dart';
import 'package:whitenoise/src/rust/api/utils.dart';
import 'package:whitenoise/theme.dart';
import 'package:whitenoise/widgets/keyboard_dismiss_on_tap.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_checkbox.dart';
import 'package:whitenoise/widgets/wn_dropdown_selector.dart';
@@ -32,6 +34,7 @@ class ReportBugScreen extends HookConsumerWidget {
final colors = context.colors;
final typography = context.typographyScaled;
final l10n = context.l10n;
final isOffline = ref.watch(offlineProvider).value ?? false;
final description = useTextEditingController();
final stepsToReproduce = useTextEditingController();
@@ -54,6 +57,7 @@ class ReportBugScreen extends HookConsumerWidget {
];
Future<void> handleSend() async {
if (isOffline) return;
if (description.text.trim().isEmpty) {
descriptionError.value = l10n.reportBugWhatWentWrongRequired;
return;
@@ -103,14 +107,16 @@ class ReportBugScreen extends HookConsumerWidget {
title: l10n.reportBug,
onNavigate: () => Routes.goBack(context),
),
systemNotice: notice.noticeMessage != null
? WnSystemNotice(
key: ValueKey(notice.noticeMessage),
title: notice.noticeMessage!,
type: notice.noticeType,
onDismiss: notice.dismissNotice,
)
: null,
systemNotice: isOffline
? const OfflineSystemNotice()
: (notice.noticeMessage != null
? WnSystemNotice(
key: ValueKey(notice.noticeMessage),
title: notice.noticeMessage!,
type: notice.noticeType,
onDismiss: notice.dismissNotice,
)
: null),
child: KeyboardDismissOnTap(
child: SingleChildScrollView(
padding: EdgeInsets.fromLTRB(14.w, 0, 14.w, 14.h),
@@ -161,7 +167,8 @@ class ReportBugScreen extends HookConsumerWidget {
child: WnButton(
text: l10n.reportBugSend,
loading: isSending.value,
onPressed: isSending.value ? null : handleSend,
disabled: isOffline || isSending.value,
onPressed: isOffline || isSending.value ? null : handleSend,
),
),
],
+5
View File
@@ -7,10 +7,12 @@ import 'package:whitenoise/hooks/use_user_metadata.dart';
import 'package:whitenoise/l10n/l10n.dart';
import 'package:whitenoise/providers/app_version_provider.dart';
import 'package:whitenoise/providers/auth_provider.dart';
import 'package:whitenoise/providers/offline_provider.dart';
import 'package:whitenoise/routes.dart';
import 'package:whitenoise/theme.dart';
import 'package:whitenoise/utils/formatting.dart';
import 'package:whitenoise/utils/metadata.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_avatar.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_icon.dart';
@@ -28,6 +30,7 @@ class SettingsScreen extends HookConsumerWidget {
final colors = context.colors;
final typography = context.typographyScaled;
final pubkey = ref.watch(authProvider).value;
final isOffline = ref.watch(offlineProvider).value ?? false;
final helpState = useSupportChat(accountPubkey: pubkey);
final metadataSnapshot = useUserMetadata(context, pubkey);
final appVersion = ref.watch(appVersionProvider);
@@ -45,6 +48,7 @@ class SettingsScreen extends HookConsumerWidget {
child: WnSlate(
showTopScrollEffect: true,
showBottomScrollEffect: true,
systemNotice: isOffline ? const OfflineSystemNotice() : null,
header: WnSlateNavigationHeader(
title: context.l10n.settings,
onNavigate: () => Routes.goBack(context),
@@ -174,6 +178,7 @@ class SettingsScreen extends HookConsumerWidget {
WnMenu(
children: [
WnMenuItem(
key: const Key('report_bug_menu_item'),
icon: WnIcons.flag,
label: context.l10n.reportBug,
type: WnMenuItemType.secondary,
+2 -6
View File
@@ -12,6 +12,7 @@ import 'package:whitenoise/providers/account_pubkey_provider.dart';
import 'package:whitenoise/providers/offline_provider.dart';
import 'package:whitenoise/routes.dart';
import 'package:whitenoise/theme.dart';
import 'package:whitenoise/widgets/offline_system_notice.dart';
import 'package:whitenoise/widgets/wn_button.dart';
import 'package:whitenoise/widgets/wn_icon.dart';
import 'package:whitenoise/widgets/wn_slate.dart';
@@ -89,12 +90,7 @@ class StartSupportChatScreen extends HookConsumerWidget {
onDismiss: dismissNotice,
)
: isOffline
? WnSystemNotice(
key: const Key('offline_notice'),
title: context.l10n.waitingForInternet,
type: WnSystemNoticeType.warning,
variant: WnSystemNoticeVariant.expanded,
)
? const OfflineSystemNotice()
: null),
child: Padding(
padding: EdgeInsets.fromLTRB(14.w, 0, 14.w, 14.h),
+17
View File
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:whitenoise/l10n/l10n.dart';
import 'package:whitenoise/widgets/wn_system_notice.dart';
class OfflineSystemNotice extends StatelessWidget {
const OfflineSystemNotice({super.key});
@override
Widget build(BuildContext context) {
return WnSystemNotice(
key: const Key('offline_notice'),
title: context.l10n.waitingForInternet,
type: WnSystemNoticeType.warning,
variant: WnSystemNoticeVariant.expanded,
);
}
}