Files
xianyan/lib/shared/widgets/feedback/offline_banner.dart
Developer ae1df22732 feat: v6.10.3 多语言翻译补全 + 17项功能修复
- 引导页协议多语言支持(languageId传递)
- 登录页双书名号修复 + 注册页协议勾选
- 个人中心页面多语言(18个翻译键)
- 网络断开提示增加关闭/刷新按钮
- 了解我们:新增秋叶qy开发者 + ayk签名修改 + 贡献者精简 + 微风暴微信搜索
- iOS快捷按钮重复修复(删除Info.plist静态定义)
- 测试账号123456警告提示
- 扫码登录自动跳转(HTTP轮询+WebSocket双通道)
- 登录页老用户按钮改次要色
- Syncfusion图表崩溃修复(DeferredBuilder+animationDuration:0)
- macOS标题栏跟随软件夜间模式
- 平台兼容分发渠道弹窗
- 软件著作权图片+交叉水印
- 桌面小部件平台兼容说明默认收起
- iOS/macOS图标更新+名称确认为闲言
- 12个语言文件补全roleNative+7个分发渠道翻译字段
2026-06-02 04:50:32 +08:00

249 lines
7.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言APP — 网络状态横幅组件
/// 创建时间: 2026-05-04
/// 更新时间: 2026-06-02
/// 作用: 在页面顶部显示网络状态提示(离线/网络异常),支持关闭和网络恢复自动隐藏
/// 上次更新: OfflineBanner增加刷新/关闭按钮改为ConsumerStatefulWidget
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/network/connectivity_provider.dart';
import '../../../core/services/network/connectivity_provider.dart'
show connectivityCheckProvider;
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_typography.dart';
import 'app_toast.dart';
/// 网络状态横幅 — 显示在页面顶部
///
/// 两种状态:
/// - 离线:网络完全断开,显示"网络未连接"
/// - 异常有网络但DNS不通VPN等问题显示"网络异常"
///
/// 特性:
/// - 可手动关闭(关闭后本次会话不再显示,直到网络状态变化)
/// - 网络恢复时自动隐藏
/// - iOS 风格,使用主题系统
class NetworkStatusBanner extends ConsumerStatefulWidget {
const NetworkStatusBanner({super.key});
@override
ConsumerState<NetworkStatusBanner> createState() =>
_NetworkStatusBannerState();
}
class _NetworkStatusBannerState extends ConsumerState<NetworkStatusBanner> {
bool _dismissed = false;
bool _wasOffline = false;
@override
Widget build(BuildContext context) {
final isOffline = ref.isOffline;
final dnsCheck = ref.watch(connectivityCheckProvider);
final hasDnsIssue = dnsCheck.value == false;
final shouldShow = (isOffline || hasDnsIssue) && !_dismissed;
if (_wasOffline && !isOffline && !hasDnsIssue) {
_dismissed = false;
}
_wasOffline = isOffline || hasDnsIssue;
if (!shouldShow) return const SizedBox.shrink();
final isNoNetwork = isOffline;
final icon = isNoNetwork
? CupertinoIcons.wifi_slash
: CupertinoIcons.exclamationmark_triangle_fill;
final message = isNoNetwork ? '📡 网络未连接' : '⚠️ 网络异常,部分功能可能不可用';
final tintColor = isNoNetwork
? CupertinoColors.systemOrange
: CupertinoColors.systemYellow;
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: tintColor.withValues(alpha: 0.12),
border: Border(
bottom: BorderSide(
color: tintColor.withValues(alpha: 0.2),
width: 0.5,
),
),
),
child: Row(
children: [
Icon(icon, size: 14, color: tintColor.darkColor),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
message,
style: AppTypography.caption1.copyWith(
color: tintColor.darkColor,
fontWeight: FontWeight.w600,
),
),
),
GestureDetector(
onTap: () {
if (mounted) {
setState(() {
_dismissed = true;
});
}
},
child: Padding(
padding: const EdgeInsets.only(left: AppSpacing.sm),
child: Icon(
CupertinoIcons.xmark_circle_fill,
size: 16,
color: tintColor.withValues(alpha: 0.6),
),
),
),
],
),
).animate().fadeIn(duration: 300.ms).slideY(begin: -0.5, end: 0);
}
}
/// 简洁离线横幅 — 显示离线状态,支持刷新和关闭
class OfflineBanner extends ConsumerStatefulWidget {
const OfflineBanner({super.key});
@override
ConsumerState<OfflineBanner> createState() => _OfflineBannerState();
}
class _OfflineBannerState extends ConsumerState<OfflineBanner> {
bool _dismissed = false;
bool _wasOffline = false;
@override
Widget build(BuildContext context) {
final isOffline = ref.isOffline;
if (_wasOffline && !isOffline) {
_dismissed = false;
}
_wasOffline = isOffline;
if (!isOffline || _dismissed) return const SizedBox.shrink();
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: CupertinoColors.systemOrange.withValues(alpha: 0.12),
border: Border(
bottom: BorderSide(
color: CupertinoColors.systemOrange.withValues(alpha: 0.2),
width: 0.5,
),
),
),
child: Row(
children: [
Icon(
CupertinoIcons.wifi_slash,
size: 14,
color: CupertinoColors.systemOrange.darkColor,
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
'离线模式 · 使用缓存数据',
style: AppTypography.caption1.copyWith(
color: CupertinoColors.systemOrange.darkColor,
fontWeight: FontWeight.w600,
),
),
),
GestureDetector(
onTap: () {
final stillOffline = ref.read(connectivityProvider) == false;
if (stillOffline) {
AppToast.showWarning('网络仍未连接');
}
},
child: Padding(
padding: const EdgeInsets.only(left: AppSpacing.sm),
child: Icon(
CupertinoIcons.refresh,
size: 16,
color: CupertinoColors.systemOrange.darkColor
.withValues(alpha: 0.7),
),
),
),
GestureDetector(
onTap: () {
if (mounted) {
setState(() {
_dismissed = true;
});
}
},
child: Padding(
padding: const EdgeInsets.only(left: AppSpacing.sm),
child: Icon(
CupertinoIcons.xmark_circle_fill,
size: 16,
color: CupertinoColors.systemOrange.darkColor
.withValues(alpha: 0.6),
),
),
),
],
),
).animate().fadeIn(duration: 300.ms).slideY(begin: -0.5, end: 0);
}
}
/// 离线指示点 — 小圆点+文字
class OfflineIndicator extends ConsumerWidget {
const OfflineIndicator({super.key, this.showText = true});
final bool showText;
@override
Widget build(BuildContext context, WidgetRef ref) {
final isOffline = ref.isOffline;
if (!isOffline) return const SizedBox.shrink();
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: CupertinoColors.systemOrange,
shape: BoxShape.circle,
),
),
if (showText) ...[
const SizedBox(width: 4),
Text(
'离线',
style: AppTypography.caption2.copyWith(
color: CupertinoColors.systemOrange,
fontWeight: FontWeight.w600,
),
),
],
],
);
}
}