feat(app): P0 batch 3 — offline detection with engine pause/resume + UI banner

P0-4 离线感知:
  - connectivity_plus → NetworkAvailability(online/offline/unknown)
  - engine_scope 三重绑定:有账号+有网 → start;任一失去 → stop(在途不中断)
  - 网络恢复 → start + onNetworkRestored(waitingNetwork 即时重试)
  - HomePage 顶部离线黄条(wifi_off + i18n 双语)

P0-5 更正:EXIF 方向已由服务端 sharp .rotate() 自动转正——v2 分析误报

397/397 全绿、analyze 0

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-28 11:23:51 +08:00
parent 638dd9b45d
commit 33f347cdea
11 changed files with 643 additions and 6 deletions
+43
View File
@@ -0,0 +1,43 @@
/// 网络连接状态 Provider(P0-4:离线感知)。
///
/// connectivity_plus 监听网络变化 → 全局 Stream → UI/引擎消费:
/// - **UI**:顶部黄条「当前无网络连接」(HomePage 或全局 Scaffold 叠加)
/// - **上传引擎**:离线 → 暂停再出队;恢复 → 自动重试
///
/// 三档:online / offline / unknown(初始/权限不足)。
/// offline 只暂停再出队,**不中断在途传输**——避免假离线(VPN 等)打断上传。
library;
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// 网络可用性(三档)。
enum NetworkAvailability { online, offline, unknown }
/// 网络状态 Provider(StreamProvider——connectivity_plus Stream 直通)。
final networkStatusProvider = StreamProvider<NetworkAvailability>((ref) async* {
final connectivity = Connectivity();
try {
// 初始值(checkConnectivity 是 Future<List<ConnectivityResult>>)。
final initial = await connectivity.checkConnectivity();
yield _map(initial);
// 后续变化(onConnectivityChanged 是 Stream<List<ConnectivityResult>>)。
yield* connectivity.onConnectivityChanged.map(_map);
} on Object {
// 权限不足/平台不支持——报 unknown(不阻塞 App)。
yield NetworkAvailability.unknown;
}
});
NetworkAvailability _map(List<ConnectivityResult> results) {
if (results.contains(ConnectivityResult.none)) {
return NetworkAvailability.offline;
}
if (results.isEmpty) return NetworkAvailability.unknown;
return NetworkAvailability.online;
}
/// 便捷读取:当前是否有网(unknown 视为有网——假阴性代价低于假阳性)。
bool isNetworkOnline(NetworkAvailability? status) {
return status != NetworkAvailability.offline;
}