feat(client): 通用自适应弹出菜单(按屏幕位置上/下弹,不挡触发器)

新增 AdaptiveMenuButton:GlobalKey 量触发器矩形,据上下剩余空间决定向下/向上
弹出、右对齐裁剪到屏内,Overlay 全屏 barrier + 淡入缩放动画。「我的设备」⋯
菜单由 PopupMenuButton 换成它(重命名 + 强制退出/清除),修正小屏被遮挡问题。
devices_screen_test 同步改用 AdaptiveMenuButton 触发器断言。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-29 10:11:20 +08:00
parent 880fa32793
commit 6788501e4a
3 changed files with 244 additions and 45 deletions
+6 -5
View File
@@ -14,6 +14,7 @@ import 'package:pangolin_vpn/services/token_store.dart';
import 'package:pangolin_vpn/state/account_providers.dart';
import 'package:pangolin_vpn/state/auth_provider.dart';
import 'package:pangolin_vpn/widgets/account_screens.dart';
import 'package:pangolin_vpn/widgets/adaptive_menu.dart';
import '../helpers/harness.dart';
@@ -95,7 +96,7 @@ void main() {
testWidgets('本机 ⋯ 菜单只有「重命名」,无强制退出/清除', (tester) async {
await pump(tester);
// 第一个菜单 = 本机(MacBook Pro)。
await tester.tap(find.byType(PopupMenuButton<String>).first);
await tester.tap(find.byType(AdaptiveMenuButton).first);
await tester.pumpAndSettle();
expect(find.text(t.devRename), findsOneWidget);
expect(find.text(t.devForceLogout), findsNothing);
@@ -105,8 +106,8 @@ void main() {
testWidgets('非本机行 ⋯ 菜单 → 强制退出 → 确认 → 调 logout 接口', (tester) async {
await pump(tester);
// 本机+手机各一个菜单;手机(最后一个)才有强制退出/清除。
expect(find.byType(PopupMenuButton<String>), findsNWidgets(2));
await tester.tap(find.byType(PopupMenuButton<String>).last);
expect(find.byType(AdaptiveMenuButton), findsNWidgets(2));
await tester.tap(find.byType(AdaptiveMenuButton).last);
await tester.pumpAndSettle();
expect(find.text(t.devForceLogout), findsOneWidget);
expect(find.text(t.devClearLogin), findsOneWidget);
@@ -124,7 +125,7 @@ void main() {
testWidgets('清除登录信息 → 确认 → 调 DELETE 接口', (tester) async {
await pump(tester);
await tester.tap(find.byType(PopupMenuButton<String>).last);
await tester.tap(find.byType(AdaptiveMenuButton).last);
await tester.pumpAndSettle();
await tester.tap(find.text(t.devClearLogin));
await tester.pumpAndSettle();
@@ -136,7 +137,7 @@ void main() {
testWidgets('重命名 → 输入新名 → 保存 → 调 rename 接口', (tester) async {
await pump(tester);
await tester.tap(find.byType(PopupMenuButton<String>).last);
await tester.tap(find.byType(AdaptiveMenuButton).last);
await tester.pumpAndSettle();
await tester.tap(find.text(t.devRename));
await tester.pumpAndSettle();