worldhopper/lib/widgets/worldhopper_app_bar.dart
Felipe M. 64eee6b81b
feat: implement internet connectivity tracking with offline indicators
Adds real-time connectivity monitoring and visual feedback across the app. Offline icon appears in all app bars when disconnected, cached content shows download badges, and access control prevents browsing servers or opening uncached content without internet connection.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 20:13:21 +01:00

112 lines
3.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:worldhopper/providers/connectivity_provider.dart';
/// Custom AppBar that automatically shows offline indicator
///
/// This widget wraps the standard AppBar and adds an offline icon
/// (cloud_off) when the device is not connected to the internet.
///
/// Usage: Replace standard AppBar with WorldhopperAppBar
/// ```dart
/// WorldhopperAppBar(
/// title: Text('My Screen'),
/// actions: [
/// IconButton(icon: Icon(Icons.search), onPressed: () {}),
/// ],
/// )
/// ```
class WorldhopperAppBar extends ConsumerWidget implements PreferredSizeWidget {
final Widget? title;
final List<Widget>? actions;
final Widget? leading;
final bool automaticallyImplyLeading;
final PreferredSizeWidget? bottom;
final double? elevation;
final Color? backgroundColor;
final IconThemeData? iconTheme;
final bool centerTitle;
final double? leadingWidth;
final TextStyle? titleTextStyle;
final Color? foregroundColor;
final Color? shadowColor;
final Color? surfaceTintColor;
final ShapeBorder? shape;
final double? scrolledUnderElevation;
final bool forceMaterialTransparency;
final Clip? clipBehavior;
const WorldhopperAppBar({
super.key,
this.title,
this.actions,
this.leading,
this.automaticallyImplyLeading = true,
this.bottom,
this.elevation,
this.backgroundColor,
this.iconTheme,
this.centerTitle = false,
this.leadingWidth,
this.titleTextStyle,
this.foregroundColor,
this.shadowColor,
this.surfaceTintColor,
this.shape,
this.scrolledUnderElevation,
this.forceMaterialTransparency = false,
this.clipBehavior,
});
@override
Size get preferredSize => Size.fromHeight(
kToolbarHeight + (bottom?.preferredSize.height ?? 0.0),
);
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch connectivity state
final connectivityState = ref.watch(connectivityStateProvider);
// Check if offline (default to online while loading)
final isOffline = connectivityState.whenOrNull(
data: (isOnline) => !isOnline,
) ??
false;
// Build actions list with offline icon prepended if needed
final effectiveActions = <Widget>[
if (isOffline)
Padding(
padding: const EdgeInsets.only(right: 8),
child: Icon(
Icons.cloud_off,
color: Theme.of(context).colorScheme.error,
semanticLabel: 'Offline',
),
),
...?actions,
];
return AppBar(
title: title,
actions: effectiveActions.isEmpty ? null : effectiveActions,
leading: leading,
automaticallyImplyLeading: automaticallyImplyLeading,
bottom: bottom,
elevation: elevation,
backgroundColor: backgroundColor,
iconTheme: iconTheme,
centerTitle: centerTitle,
leadingWidth: leadingWidth,
titleTextStyle: titleTextStyle,
foregroundColor: foregroundColor,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
shape: shape,
scrolledUnderElevation: scrolledUnderElevation,
forceMaterialTransparency: forceMaterialTransparency,
clipBehavior: clipBehavior,
);
}
}