feat: enhance routing and connectivity handling

- Added a new route for connection issues, allowing users to navigate to a dedicated page when the server is unreachable.
- Updated the RouterNotifier to manage navigation based on server connectivity status and authentication state.
- Improved the handling of offline scenarios by integrating connectivity checks into the routing logic.
- Enhanced localization support for connection issue messages in multiple languages.
- Refactored the OfflineIndicator widget to streamline the display of connectivity status without unnecessary complexity.
This commit is contained in:
cogwheel0
2025-10-01 23:26:12 +05:30
parent d899ca5f70
commit ebe6cec17c
16 changed files with 523 additions and 148 deletions

View File

@@ -15,13 +15,8 @@ part 'offline_indicator.g.dart';
class OfflineIndicator extends ConsumerWidget {
final Widget child;
final bool showBanner;
const OfflineIndicator({
super.key,
required this.child,
this.showBanner = true,
});
const OfflineIndicator({super.key, required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -33,26 +28,22 @@ class OfflineIndicator extends ConsumerWidget {
orElse: () => false,
);
return Stack(
children: [
child,
if (showBanner)
connectivityStatus.when(
data: (status) {
if (status == ConnectivityStatus.offline || socketOffline) {
return _OfflineBanner();
}
// Announce back-online briefly if we were previously offline
if (wasOffline) {
return const _BackOnlineToast();
}
return const SizedBox.shrink();
},
loading: () => const SizedBox.shrink(),
error: (_, _) => _OfflineBanner(),
),
],
final overlay = connectivityStatus.when(
data: (status) {
if ((status == ConnectivityStatus.offline || socketOffline) &&
!wasOffline) {
return const SizedBox.shrink();
}
if (wasOffline) {
return const _BackOnlineToast();
}
return const SizedBox.shrink();
},
loading: () => const SizedBox.shrink(),
error: (unusedError, unusedStackTrace) => const SizedBox.shrink(),
);
return Stack(children: [child, overlay]);
}
}
@@ -137,65 +128,6 @@ class _BackOnlineToast extends StatelessWidget {
}
}
class _OfflineBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
bottom: false,
child:
Semantics(
container: true,
liveRegion: true,
label: AppLocalizations.of(context)!.offlineBanner,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.xs,
),
decoration: BoxDecoration(
color: context.conduitTheme.warning,
boxShadow: ConduitShadows.low,
),
child: Row(
children: [
Icon(
Platform.isIOS
? CupertinoIcons.wifi_slash
: Icons.wifi_off,
color: context.conduitTheme.textInverse,
size: AppTypography.headlineMedium,
),
const SizedBox(width: Spacing.xs),
Expanded(
child: Text(
AppLocalizations.of(context)!.offlineBanner,
style: TextStyle(
color: context.conduitTheme.textInverse,
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
)
.animate(onPlay: (controller) => controller.forward())
.slideY(
begin: -1,
end: 0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
),
),
);
}
}
// Inline offline indicator for specific features
class InlineOfflineIndicator extends ConsumerWidget {
final String message;
@@ -217,16 +149,20 @@ class InlineOfflineIndicator extends ConsumerWidget {
return const SizedBox.shrink();
}
final theme = context.conduitTheme;
final surfaceColor = backgroundColor ?? theme.warningBackground;
final borderAlpha = Theme.of(context).brightness == Brightness.dark
? 0.45
: 0.3;
return Container(
margin: const EdgeInsets.all(Spacing.md),
padding: const EdgeInsets.all(Spacing.md),
decoration: BoxDecoration(
color:
backgroundColor ??
context.conduitTheme.warning.withValues(alpha: 0.1),
color: surfaceColor,
borderRadius: BorderRadius.circular(AppBorderRadius.md),
border: Border.all(
color: context.conduitTheme.warning.withValues(alpha: 0.3),
color: theme.warning.withValues(alpha: borderAlpha),
width: BorderWidth.regular,
),
),
@@ -235,7 +171,7 @@ class InlineOfflineIndicator extends ConsumerWidget {
Icon(
icon ??
(Platform.isIOS ? CupertinoIcons.wifi_slash : Icons.wifi_off),
color: context.conduitTheme.warning,
color: theme.warning,
size: Spacing.lg,
),
const SizedBox(width: Spacing.xs),
@@ -245,7 +181,7 @@ class InlineOfflineIndicator extends ConsumerWidget {
? message
: AppLocalizations.of(context)!.featureRequiresInternet,
style: TextStyle(
color: context.conduitTheme.warning,
color: theme.warning,
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
),
@@ -299,16 +235,22 @@ class ChatOfflineOverlay extends ConsumerWidget {
return const SizedBox.shrink();
}
final theme = context.conduitTheme;
final surfaceColor = theme.warningBackground;
final borderAlpha = Theme.of(context).brightness == Brightness.dark
? 0.5
: 0.35;
return Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
decoration: BoxDecoration(
color: context.conduitTheme.warning.withValues(alpha: 0.2),
color: surfaceColor,
border: Border(
top: BorderSide(
color: context.conduitTheme.warning.withValues(alpha: 0.5),
color: theme.warning.withValues(alpha: borderAlpha),
width: BorderWidth.regular,
),
),
@@ -318,14 +260,14 @@ class ChatOfflineOverlay extends ConsumerWidget {
children: [
Icon(
Platform.isIOS ? CupertinoIcons.wifi_slash : Icons.wifi_off,
color: context.conduitTheme.warning,
color: theme.warning,
size: Spacing.md,
),
const SizedBox(width: Spacing.sm),
Text(
AppLocalizations.of(context)!.messagesWillSendWhenOnline,
style: TextStyle(
color: context.conduitTheme.warning,
color: theme.warning,
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w500,
),