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:
parent
a070d866e6
commit
3fe5397675
11 changed files with 135 additions and 5 deletions
|
|
@ -26,6 +26,7 @@ import 'ui/home_screen.dart';
|
|||
import 'ui/intro_screen.dart';
|
||||
import 'ui/inventory_list_screen.dart';
|
||||
import 'ui/market_screen.dart';
|
||||
import 'ui/offline_banner.dart';
|
||||
import 'ui/profile_screen.dart';
|
||||
import 'ui/settings_screen.dart';
|
||||
import 'ui/theme.dart';
|
||||
|
|
@ -212,6 +213,10 @@ class TaneApp extends StatelessWidget {
|
|||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
// A global "you're offline" strip above every screen — makes it clear
|
||||
// why the social layer is paused (the inventory still works).
|
||||
builder: (context, child) =>
|
||||
OfflineBanner(child: child ?? const SizedBox.shrink()),
|
||||
routerConfig: _router,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"type": "Type",
|
||||
"comingSoon": "Coming soon"
|
||||
"comingSoon": "Coming soon",
|
||||
"offline": "You're offline — sharing is paused"
|
||||
},
|
||||
"home": {
|
||||
"tagline": "Share and grow local seeds",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"delete": "Eliminar",
|
||||
"edit": "Editar",
|
||||
"type": "Tipo",
|
||||
"comingSoon": "Pronto"
|
||||
"comingSoon": "Pronto",
|
||||
"offline": "Sin conexión — el compartir está en pausa"
|
||||
},
|
||||
"home": {
|
||||
"tagline": "Comparte y cultiva semillas locales",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"delete": "Eliminar",
|
||||
"edit": "Editar",
|
||||
"type": "Tipo",
|
||||
"comingSoon": "Em breve"
|
||||
"comingSoon": "Em breve",
|
||||
"offline": "Sem ligação — a partilha está em pausa"
|
||||
},
|
||||
"home": {
|
||||
"tagline": "Partilha e cultiva sementes locais",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 3
|
||||
/// Strings: 1073 (357 per locale)
|
||||
/// Strings: 1076 (358 per locale)
|
||||
///
|
||||
/// Built on 2026-07-10 at 11:35 UTC
|
||||
/// Built on 2026-07-10 at 13:17 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
|
|
|||
|
|
@ -113,6 +113,9 @@ class Translations$common$en {
|
|||
|
||||
/// en: 'Coming soon'
|
||||
String get comingSoon => 'Coming soon';
|
||||
|
||||
/// en: 'You're offline — sharing is paused'
|
||||
String get offline => 'You\'re offline — sharing is paused';
|
||||
}
|
||||
|
||||
// Path: home
|
||||
|
|
@ -1755,6 +1758,7 @@ extension on Translations {
|
|||
'common.edit' => 'Edit',
|
||||
'common.type' => 'Type',
|
||||
'common.comingSoon' => 'Coming soon',
|
||||
'common.offline' => 'You\'re offline — sharing is paused',
|
||||
'home.tagline' => 'Share and grow local seeds',
|
||||
'home.openMarket' => 'Market',
|
||||
'home.openMarketSubtitle' => 'Discover and share seeds nearby',
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ class _Translations$common$es extends Translations$common$en {
|
|||
@override String get edit => 'Editar';
|
||||
@override String get type => 'Tipo';
|
||||
@override String get comingSoon => 'Pronto';
|
||||
@override String get offline => 'Sin conexión — el compartir está en pausa';
|
||||
}
|
||||
|
||||
// Path: home
|
||||
|
|
@ -1057,6 +1058,7 @@ extension on TranslationsEs {
|
|||
'common.edit' => 'Editar',
|
||||
'common.type' => 'Tipo',
|
||||
'common.comingSoon' => 'Pronto',
|
||||
'common.offline' => 'Sin conexión — el compartir está en pausa',
|
||||
'home.tagline' => 'Comparte y cultiva semillas locales',
|
||||
'home.openMarket' => 'Mercado',
|
||||
'home.openMarketSubtitle' => 'Descubre y comparte semillas cerca',
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ class _Translations$common$pt extends Translations$common$en {
|
|||
@override String get edit => 'Editar';
|
||||
@override String get type => 'Tipo';
|
||||
@override String get comingSoon => 'Em breve';
|
||||
@override String get offline => 'Sem ligação — a partilha está em pausa';
|
||||
}
|
||||
|
||||
// Path: home
|
||||
|
|
@ -1053,6 +1054,7 @@ extension on TranslationsPt {
|
|||
'common.edit' => 'Editar',
|
||||
'common.type' => 'Tipo',
|
||||
'common.comingSoon' => 'Em breve',
|
||||
'common.offline' => 'Sem ligação — a partilha está em pausa',
|
||||
'home.tagline' => 'Partilha e cultiva sementes locais',
|
||||
'home.openMarket' => 'Mercado',
|
||||
'home.openMarketSubtitle' => 'Descobre e partilha sementes por perto',
|
||||
|
|
|
|||
87
apps/app_seeds/lib/ui/offline_banner.dart
Normal file
87
apps/app_seeds/lib/ui/offline_banner.dart
Normal 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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +71,9 @@ dependencies:
|
|||
# typing a code. Low accuracy only; the value is immediately reduced to a
|
||||
# low-precision geohash. Behind an interface so it's fakeable and pluggable.
|
||||
geolocator: ^13.0.1
|
||||
# Network reachability (BSD-3) to show a global "you're offline" banner — the
|
||||
# social layer needs a connection; the inventory works regardless.
|
||||
connectivity_plus: ^6.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue