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:
@@ -9,6 +9,7 @@ import '../l10n/app_text.dart';
|
||||
import '../models/device.dart';
|
||||
import '../pangolin_theme.dart';
|
||||
import '../services/device_identity.dart';
|
||||
import 'adaptive_menu.dart';
|
||||
import '../services/auth_api.dart';
|
||||
import '../state/account_providers.dart';
|
||||
import 'pangolin_button.dart';
|
||||
@@ -215,50 +216,45 @@ class DevicesScreen extends ConsumerWidget {
|
||||
}
|
||||
|
||||
// 本机:只给「重命名」(自我踢下线/清除没意义);其他设备:重命名+强制退出+清除。
|
||||
Widget _menu(BuildContext context, WidgetRef ref, PangolinScheme c, Device d, bool isLocal) => PopupMenuButton<String>(
|
||||
icon: Icon(PangolinIcons.moreVertical, size: 18, color: c.fg3),
|
||||
color: c.surface,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(PangolinRadius.md), side: BorderSide(color: c.border)),
|
||||
itemBuilder: (_) => [
|
||||
PopupMenuItem(value: 'rename', child: Row(children: [
|
||||
Icon(PangolinIcons.edit, size: 16, color: c.fg2), const SizedBox(width: 10),
|
||||
Text(t.devRename, style: PangolinText.sm.copyWith(color: c.fg1)),
|
||||
])),
|
||||
if (!isLocal) ...[
|
||||
PopupMenuItem(value: 'logout', child: Row(children: [
|
||||
Icon(PangolinIcons.logOut, size: 16, color: c.fg2), const SizedBox(width: 10),
|
||||
Text(t.devForceLogout, style: PangolinText.sm.copyWith(color: c.fg1)),
|
||||
])),
|
||||
PopupMenuItem(value: 'clear', child: Row(children: [
|
||||
Icon(PangolinIcons.trash, size: 16, color: c.danger), const SizedBox(width: 10),
|
||||
Text(t.devClearLogin, style: PangolinText.sm.copyWith(color: c.danger)),
|
||||
])),
|
||||
],
|
||||
],
|
||||
onSelected: (v) async {
|
||||
if (v == 'rename') {
|
||||
// 用 AdaptiveMenuButton:菜单按屏幕位置自动上/下弹、不遮挡 ⋯ 按钮。
|
||||
Widget _menu(BuildContext context, WidgetRef ref, PangolinScheme c, Device d, bool isLocal) {
|
||||
Future<void> confirmAction({required bool isClear}) async {
|
||||
final label = isClear ? t.devClearLogin : t.devForceLogout;
|
||||
final shown = d.name.isNotEmpty ? d.name : d.platformKind.label;
|
||||
final ok = await _confirm(context, c,
|
||||
title: '$label「$shown」',
|
||||
body: isClear ? t.devClearLoginConfirm : t.devForceLogoutConfirm,
|
||||
action: label);
|
||||
if (!ok) return;
|
||||
final n = ref.read(devicesProvider.notifier);
|
||||
if (isClear) {
|
||||
await n.remove(d.uuid);
|
||||
} else {
|
||||
await n.forceLogout(d.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
return AdaptiveMenuButton(
|
||||
icon: PangolinIcons.moreVertical,
|
||||
iconColor: c.fg3,
|
||||
items: [
|
||||
AdaptiveMenuItem(
|
||||
icon: PangolinIcons.edit,
|
||||
label: t.devRename,
|
||||
onSelected: () async {
|
||||
final newName = await _renameDialog(context, c, d);
|
||||
if (newName != null && newName.trim().isNotEmpty) {
|
||||
await ref.read(devicesProvider.notifier).rename(d.uuid, newName.trim());
|
||||
}
|
||||
return;
|
||||
}
|
||||
final isClear = v == 'clear';
|
||||
final label = isClear ? t.devClearLogin : t.devForceLogout;
|
||||
final shown = d.name.isNotEmpty ? d.name : d.platformKind.label;
|
||||
final ok = await _confirm(context, c,
|
||||
title: '$label「$shown」',
|
||||
body: isClear ? t.devClearLoginConfirm : t.devForceLogoutConfirm,
|
||||
action: label);
|
||||
if (!ok) return;
|
||||
final n = ref.read(devicesProvider.notifier);
|
||||
if (isClear) {
|
||||
await n.remove(d.uuid);
|
||||
} else {
|
||||
await n.forceLogout(d.uuid);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!isLocal) ...[
|
||||
AdaptiveMenuItem(icon: PangolinIcons.logOut, label: t.devForceLogout, onSelected: () => confirmAction(isClear: false)),
|
||||
AdaptiveMenuItem(icon: PangolinIcons.trash, label: t.devClearLogin, danger: true, onSelected: () => confirmAction(isClear: true)),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 危险操作二次确认弹窗(走 token 配色)。
|
||||
Future<bool> _confirm(BuildContext context, PangolinScheme c,
|
||||
|
||||
Reference in New Issue
Block a user