diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 78672e9..5973e2d 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: docker run --rm \ -v "$PWD/client:/app" -w /app \ ghcr.io/cirruslabs/flutter:stable \ - bash -c "flutter pub get && flutter analyze && flutter test test/unit test/widget test/contract" + bash -c "flutter pub get && flutter analyze --no-fatal-infos && flutter test test/unit test/widget test/contract" # ── Job 5: Portable SQL Scan (支柱 3:双库可移植)───────────────────────── # server Go 运行时查询不得含 MySQL 专属构造(同一份 SQL 要跑 mysql 与 sqlite)。 diff --git a/client/analysis_options.yaml b/client/analysis_options.yaml index 24520d0..42005a3 100644 --- a/client/analysis_options.yaml +++ b/client/analysis_options.yaml @@ -1,5 +1,11 @@ include: package:flutter_lints/flutter.yaml +analyzer: + # 第三方 vendored path 依赖不该被项目 lint 评判(否则 ~1191 条 unnecessary_const + # 噪音淹没自身 issue,让 analyze 闸形同虚设)。 + exclude: + - packages/lucide_icons_patched/** + linter: rules: prefer_const_constructors: false diff --git a/client/lib/bridge/vpn_bridge.dart b/client/lib/bridge/vpn_bridge.dart index ce3366d..480c588 100644 --- a/client/lib/bridge/vpn_bridge.dart +++ b/client/lib/bridge/vpn_bridge.dart @@ -110,9 +110,9 @@ class VpnStatsEvent { uploadSpeed: (m['uploadSpeed'] as num?)?.toDouble() ?? 0.0, downloadSpeed: (m['downloadSpeed'] as num?)?.toDouble() ?? 0.0, urltestResults: ((m['urltestResults'] as List?) - ?.where((e) => e is Map) + ?.whereType() .map((e) => UrltestResult.fromMap( - Map.from(e as Map), + Map.from(e), )) .toList()) ?? const [], diff --git a/client/lib/screens/account_page.dart b/client/lib/screens/account_page.dart index 37358a5..fc365d2 100644 --- a/client/lib/screens/account_page.dart +++ b/client/lib/screens/account_page.dart @@ -179,7 +179,7 @@ class _PlanBanner extends StatelessWidget { child: Row(children: [ Container( width: 44, height: 44, - decoration: BoxDecoration(color: PangolinColors.white.withOpacity(0.18), shape: BoxShape.circle), + decoration: BoxDecoration(color: PangolinColors.white.withValues(alpha: 0.18), shape: BoxShape.circle), child: const Icon(PangolinIcons.crown, size: 22, color: PangolinColors.white), ), const SizedBox(width: 12), @@ -187,7 +187,7 @@ class _PlanBanner extends StatelessWidget { Text(email, style: const TextStyle(color: PangolinColors.white, fontWeight: FontWeight.w700, fontSize: 16)), const SizedBox(height: 4), Text(expiresLabel.isEmpty ? t.proMember : '${t.proMember} · $expiresLabel', - style: TextStyle(color: PangolinColors.white.withOpacity(0.85), fontSize: 12)), + style: TextStyle(color: PangolinColors.white.withValues(alpha: 0.85), fontSize: 12)), ])), FilledButton( onPressed: onUpgrade, diff --git a/client/lib/services/auth_api.dart b/client/lib/services/auth_api.dart index 8201f86..77a63d1 100644 --- a/client/lib/services/auth_api.dart +++ b/client/lib/services/auth_api.dart @@ -99,8 +99,6 @@ class AuthApi { Future _post(String path, Map body) async { final uri = Uri.parse('$baseUrl$path'); - print('>>>AUTHLOG POST $uri (baseUrl=$baseUrl) 发起...'); - final sw = Stopwatch()..start(); try { final resp = await _client .post( @@ -109,10 +107,8 @@ class AuthApi { body: jsonEncode(body), ) .timeout(const Duration(seconds: 15)); - print('>>>AUTHLOG POST $uri -> HTTP ${resp.statusCode} (${sw.elapsedMilliseconds}ms)'); return resp; } on Exception catch (e) { - print('>>>AUTHLOG POST $uri 失败 (${sw.elapsedMilliseconds}ms): ${e.runtimeType}: $e'); throw AuthApiException( statusCode: -1, messageZh: '网络请求失败,请检查连接后重试', diff --git a/client/lib/widgets/plan_card.dart b/client/lib/widgets/plan_card.dart index e987fb5..016164a 100644 --- a/client/lib/widgets/plan_card.dart +++ b/client/lib/widgets/plan_card.dart @@ -27,7 +27,7 @@ class PlanCard extends StatelessWidget { Widget build(BuildContext context) { final c = context.pangolin; final onCard = featured ? PangolinColors.white : c.fg1; - final subColor = featured ? PangolinColors.white.withOpacity(0.92) : c.fg2; + final subColor = featured ? PangolinColors.white.withValues(alpha: 0.92) : c.fg2; final card = Container( padding: const EdgeInsets.fromLTRB(20, 22, 20, 20), @@ -44,7 +44,7 @@ class PlanCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(name, style: PangolinText.sm.copyWith(color: featured ? PangolinColors.white.withOpacity(0.85) : c.fg2, fontWeight: FontWeight.w600)), + Text(name, style: PangolinText.sm.copyWith(color: featured ? PangolinColors.white.withValues(alpha: 0.85) : c.fg2, fontWeight: FontWeight.w600)), const SizedBox(height: 6), Row( crossAxisAlignment: CrossAxisAlignment.baseline, diff --git a/client/test/bridge/vpn_bridge_mock_test.dart b/client/test/bridge/vpn_bridge_mock_test.dart index 41bc8f6..d980dc5 100644 --- a/client/test/bridge/vpn_bridge_mock_test.dart +++ b/client/test/bridge/vpn_bridge_mock_test.dart @@ -7,7 +7,6 @@ // 4. selectOutbound 切换时产生 connecting → on 重连序列 // 5. getStatus / getActiveOutbound / setKillSwitch 返回正确值 -import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:pangolin_vpn/bridge/vpn_bridge.dart'; import 'package:pangolin_vpn/bridge/vpn_bridge_mock.dart'; @@ -225,7 +224,7 @@ void main() { // ── 5. VpnStatsEvent schema round-trip ─────────────────────── group('VpnStatsEvent schema', () { test('fromMap / toMap round-trip', () { - final original = const VpnStatsEvent( + const original = VpnStatsEvent( uploadBytes: 1024, downloadBytes: 4096, uploadSpeed: 512.0, diff --git a/client/test/flutter_test_config.dart b/client/test/flutter_test_config.dart index e33d336..04070c7 100644 --- a/client/test/flutter_test_config.dart +++ b/client/test/flutter_test_config.dart @@ -4,7 +4,6 @@ // 让 widget/golden 测试用真字体渲染、且不触发 google_fonts 运行时拉取(避免抛异常)。 import 'dart:async'; import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart';