feat(block2): global 'you're offline' banner (network state)

Feedback: offline, the social layer stops working with no clear reason. A slim
amber strip now shows above every screen when there's no network (connectivity_
plus), so it's obvious WHY sharing is paused — the inventory keeps working.
Wrapped once via the MaterialApp builder; connectivity check is guarded so it
no-ops where the plugin is absent (tests/unsupported platforms). i18n en/es/pt.
Analyzer clean.
This commit is contained in:
vjrj 2026-07-10 15:18:16 +02:00
parent 5741d6bde8
commit 1ac954a09e
11 changed files with 135 additions and 5 deletions

View file

@ -0,0 +1,87 @@
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import '../i18n/strings.g.dart';
/// Wraps the app and shows a slim strip at the very top when there's no network,
/// so it's clear WHY the social layer (market, chat, profile) is paused — the
/// inventory keeps working offline. Wrap the whole app once (MaterialApp builder).
class OfflineBanner extends StatefulWidget {
const OfflineBanner({required this.child, super.key});
final Widget child;
@override
State<OfflineBanner> createState() => _OfflineBannerState();
}
class _OfflineBannerState extends State<OfflineBanner> {
bool _offline = false;
StreamSubscription<List<ConnectivityResult>>? _sub;
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
try {
final current = await Connectivity().checkConnectivity();
if (mounted) setState(() => _offline = _isOffline(current));
_sub = Connectivity().onConnectivityChanged.listen((results) {
if (mounted) setState(() => _offline = _isOffline(results));
});
} catch (_) {
// Platform without connectivity support assume online, hide the banner.
}
}
bool _isOffline(List<ConnectivityResult> results) =>
results.isEmpty ||
results.every((r) => r == ConnectivityResult.none);
@override
void dispose() {
_sub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (_offline)
Material(
color: const Color(0xFFE8A200), // amber not an error, just paused
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.cloud_off, size: 16, color: Colors.white),
const SizedBox(width: 8),
Flexible(
child: Text(
context.t.common.offline,
style: const TextStyle(
color: Colors.white,
fontSize: 12.5,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
),
),
Expanded(child: widget.child),
],
);
}
}