Refine signed-in account status layout
This commit is contained in:
parent
af1a4e5661
commit
f4ccb959dc
@ -113,6 +113,7 @@ class SettingsAccountPanel extends StatelessWidget {
|
||||
accountState: accountState,
|
||||
accountBusy: accountBusy,
|
||||
accountStatus: accountStatus,
|
||||
onSaveAccountProfile: onSaveAccountProfile,
|
||||
onSync: onSync,
|
||||
onLogout: onLogout,
|
||||
);
|
||||
@ -417,6 +418,7 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
required this.accountState,
|
||||
required this.accountBusy,
|
||||
required this.accountStatus,
|
||||
required this.onSaveAccountProfile,
|
||||
required this.onSync,
|
||||
required this.onLogout,
|
||||
});
|
||||
@ -426,11 +428,17 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
final AccountSyncState? accountState;
|
||||
final bool accountBusy;
|
||||
final String accountStatus;
|
||||
final Future<void> Function() onSaveAccountProfile;
|
||||
final Future<void> Function() onSync;
|
||||
final Future<void> Function() onLogout;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mode = _signedInAccountModeFromSettings(
|
||||
settings: settings,
|
||||
accountState: accountState,
|
||||
);
|
||||
final isAccountSyncMode = mode == _SignedInAccountMode.accountSync;
|
||||
final cloudSync = settings.acpBridgeServerModeConfig.cloudSynced;
|
||||
final serviceUrl = cloudSync.accountBaseUrl.trim().isNotEmpty
|
||||
? cloudSync.accountBaseUrl.trim()
|
||||
@ -450,11 +458,25 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
final syncMessage = accountState?.syncMessage.trim().isNotEmpty == true
|
||||
? accountState!.syncMessage.trim()
|
||||
: appText('尚未同步远端配置', 'Remote config not synced yet');
|
||||
final effectiveSyncState = accountBusy ? 'syncing' : syncState;
|
||||
final effectiveSyncMessage =
|
||||
accountBusy && accountStatus.trim().isNotEmpty
|
||||
final modeStateLabel = accountBusy
|
||||
? (isAccountSyncMode ? appText('同步中', 'Syncing') : appText('保存中', 'Saving'))
|
||||
: (isAccountSyncMode
|
||||
? _describeAccountSyncState(syncState)
|
||||
: _describeBridgeSaveState(settings));
|
||||
final modeStatusLabel = accountBusy && accountStatus.trim().isNotEmpty
|
||||
? accountStatus.trim()
|
||||
: syncMessage;
|
||||
final modeIcon = isAccountSyncMode ? Icons.cloud_outlined : Icons.link_outlined;
|
||||
final modeTitle = isAccountSyncMode
|
||||
? appText('账号同步', 'Account Sync')
|
||||
: appText('手动 Bridge', 'Manual Bridge');
|
||||
final primaryActionLabel = isAccountSyncMode
|
||||
? appText('重新同步', 'Resync')
|
||||
: appText('重新设置', 'Reset');
|
||||
final primaryActionKey = isAccountSyncMode
|
||||
? 'settings-account-sync-button'
|
||||
: 'settings-account-manual-reset-button';
|
||||
final primaryAction = isAccountSyncMode ? onSync : onSaveAccountProfile;
|
||||
final mfaEnabled =
|
||||
accountSession?.totpEnabled == true ||
|
||||
accountSession?.mfaEnabled == true;
|
||||
@ -468,24 +490,11 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
appText(
|
||||
'登录身份和 svc.plus 托管连接同步已合并到这里,直接查看状态并执行重新同步或断开。',
|
||||
'Identity and managed svc.plus connection sync now live together here.',
|
||||
'登录后只保留状态条和主动作,详细信息默认折叠。',
|
||||
'After sign-in, keep only the status bar and primary actions; details stay collapsed by default.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
accountSession?.email.trim().isNotEmpty == true
|
||||
? accountSession!.email.trim()
|
||||
: appText('当前账号', 'Current account'),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${appText('同步状态', 'Sync Status')}: $effectiveSyncState · $effectiveSyncMessage',
|
||||
key: const ValueKey('settings-account-sync-status'),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
@ -494,96 +503,244 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appText('登录与同步状态', 'Login and Sync Status'),
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${appText('服务地址', 'Service URL')}: ${serviceUrl.isEmpty ? appText('待配置', 'Pending') : serviceUrl}',
|
||||
key: const ValueKey('settings-account-summary-service-url'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('账户标识', 'Account Identifier')}: ${accountIdentifier.isEmpty ? appText('待登录', 'Not signed in') : accountIdentifier}',
|
||||
key: const ValueKey(
|
||||
'settings-account-summary-account-identifier',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('连接来源', 'Connection Source')}: ${appText('svc.plus 托管配置', 'svc.plus managed profile')}',
|
||||
key: const ValueKey(
|
||||
'settings-account-summary-connection-source',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('远端摘要', 'Remote Summary')}: ${remoteSummary.isEmpty ? appText('待同步', 'Pending sync') : remoteSummary}',
|
||||
key: const ValueKey('settings-account-summary-remote-summary'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('最近同步', 'Last Sync')}: ${_formatSyncTime(cloudSync.lastSyncAt)}',
|
||||
key: const ValueKey('settings-account-summary-last-sync'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('MFA 状态', 'MFA Status')}: ${mfaEnabled ? appText('已启用', 'Enabled') : appText('未启用', 'Disabled')}',
|
||||
key: const ValueKey('settings-account-summary-mfa-status'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('同步范围', 'Sync Scope')}: $syncScope',
|
||||
key: const ValueKey('settings-account-summary-sync-scope'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
_TokenConfiguredSummary(accountState: accountState),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
FilledButton.tonal(
|
||||
key: const ValueKey('settings-account-sync-button'),
|
||||
onPressed: accountBusy ? null : () => onSync(),
|
||||
child: accountBusy
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(modeIcon, color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
key: const ValueKey(
|
||||
'settings-account-sync-progress',
|
||||
),
|
||||
strokeWidth: 2,
|
||||
Text(
|
||||
modeTitle,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
isAccountSyncMode
|
||||
? '${appText('账号同步状态', 'Account Sync Status')}: $modeStateLabel'
|
||||
: '${appText('保存状态', 'Save Status')}: $modeStateLabel',
|
||||
key: const ValueKey('settings-account-sync-status'),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
accountSession?.email.trim().isNotEmpty == true
|
||||
? accountSession!.email.trim()
|
||||
: appText('当前账号', 'Current account'),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.78),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(appText('同步中', 'Syncing')),
|
||||
],
|
||||
)
|
||||
: Text(appText('重新同步', 'Sync Again')),
|
||||
),
|
||||
TextButton(
|
||||
key: const ValueKey('settings-account-logout-button'),
|
||||
onPressed: accountBusy ? null : () => onLogout(),
|
||||
child: Text(appText('退出登录', 'Log Out')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
alignment: WrapAlignment.end,
|
||||
children: [
|
||||
FilledButton.tonal(
|
||||
key: ValueKey(primaryActionKey),
|
||||
onPressed: accountBusy ? null : () => primaryAction(),
|
||||
child: accountBusy
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
key: const ValueKey(
|
||||
'settings-account-sync-progress',
|
||||
),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
isAccountSyncMode
|
||||
? appText('同步中', 'Syncing')
|
||||
: appText('保存中', 'Saving'),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(primaryActionLabel),
|
||||
),
|
||||
TextButton(
|
||||
key: const ValueKey('settings-account-logout-button'),
|
||||
onPressed: accountBusy ? null : () => onLogout(),
|
||||
child: Text(appText('退出', 'Exit')),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
isAccountSyncMode
|
||||
? '${appText('同步说明', 'Sync Summary')}: $modeStatusLabel'
|
||||
: '${appText('保存说明', 'Save Summary')}: $modeStatusLabel',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.78),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ExpansionTile(
|
||||
key: const ValueKey('settings-account-summary-expansion'),
|
||||
initiallyExpanded: false,
|
||||
tilePadding: EdgeInsets.zero,
|
||||
childrenPadding: const EdgeInsets.only(top: 8),
|
||||
title: Text(
|
||||
appText('详细信息', 'Details'),
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
subtitle: Text(
|
||||
appText('查看服务地址、令牌与远端摘要', 'View service URL, tokens, and remote summary'),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
children: [
|
||||
_SignedInAccountDetails(
|
||||
settings: settings,
|
||||
accountSession: accountSession,
|
||||
accountState: accountState,
|
||||
serviceUrl: serviceUrl,
|
||||
accountIdentifier: accountIdentifier,
|
||||
remoteSummary: remoteSummary,
|
||||
syncScope: syncScope,
|
||||
mfaEnabled: mfaEnabled,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SignedInAccountDetails extends StatelessWidget {
|
||||
const _SignedInAccountDetails({
|
||||
required this.settings,
|
||||
required this.accountSession,
|
||||
required this.accountState,
|
||||
required this.serviceUrl,
|
||||
required this.accountIdentifier,
|
||||
required this.remoteSummary,
|
||||
required this.syncScope,
|
||||
required this.mfaEnabled,
|
||||
});
|
||||
|
||||
final SettingsSnapshot settings;
|
||||
final AccountSessionSummary? accountSession;
|
||||
final AccountSyncState? accountState;
|
||||
final String serviceUrl;
|
||||
final String accountIdentifier;
|
||||
final String remoteSummary;
|
||||
final String syncScope;
|
||||
final bool mfaEnabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cloudSync = settings.acpBridgeServerModeConfig.cloudSynced;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${appText('服务地址', 'Service URL')}: ${serviceUrl.isEmpty ? appText('待配置', 'Pending') : serviceUrl}',
|
||||
key: const ValueKey('settings-account-summary-service-url'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('账户标识', 'Account Identifier')}: ${accountIdentifier.isEmpty ? appText('待登录', 'Not signed in') : accountIdentifier}',
|
||||
key: const ValueKey('settings-account-summary-account-identifier'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('连接来源', 'Connection Source')}: ${_connectionSourceLabel(settings, accountState)}',
|
||||
key: const ValueKey('settings-account-summary-connection-source'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('远端摘要', 'Remote Summary')}: ${remoteSummary.isEmpty ? appText('待同步', 'Pending sync') : remoteSummary}',
|
||||
key: const ValueKey('settings-account-summary-remote-summary'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('最近同步', 'Last Sync')}: ${_formatSyncTime(cloudSync.lastSyncAt)}',
|
||||
key: const ValueKey('settings-account-summary-last-sync'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('MFA 状态', 'MFA Status')}: ${mfaEnabled ? appText('已启用', 'Enabled') : appText('未启用', 'Disabled')}',
|
||||
key: const ValueKey('settings-account-summary-mfa-status'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${appText('同步范围', 'Sync Scope')}: $syncScope',
|
||||
key: const ValueKey('settings-account-summary-sync-scope'),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
_TokenConfiguredSummary(accountState: accountState),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum _SignedInAccountMode { accountSync, manualBridge }
|
||||
|
||||
_SignedInAccountMode _signedInAccountModeFromSettings({
|
||||
required SettingsSnapshot settings,
|
||||
required AccountSyncState? accountState,
|
||||
}) {
|
||||
if (accountState?.profileScope.trim().toLowerCase() == 'bridge') {
|
||||
return _SignedInAccountMode.accountSync;
|
||||
}
|
||||
return _SignedInAccountMode.manualBridge;
|
||||
}
|
||||
|
||||
String _describeAccountSyncState(String syncState) {
|
||||
final normalized = syncState.trim().toLowerCase();
|
||||
switch (normalized) {
|
||||
case 'ready':
|
||||
return appText('已同步', 'Synced');
|
||||
case 'syncing':
|
||||
return appText('同步中', 'Syncing');
|
||||
case 'blocked':
|
||||
case 'error':
|
||||
return appText('失败', 'Failed');
|
||||
default:
|
||||
return appText('待同步', 'Pending sync');
|
||||
}
|
||||
}
|
||||
|
||||
String _describeBridgeSaveState(SettingsSnapshot settings) {
|
||||
final configured = settings.acpBridgeServerModeConfig.selfHosted.isConfigured;
|
||||
return configured ? appText('已保存', 'Saved') : appText('未保存', 'Not saved');
|
||||
}
|
||||
|
||||
String _connectionSourceLabel(
|
||||
SettingsSnapshot settings,
|
||||
AccountSyncState? accountState,
|
||||
) {
|
||||
final mode = _signedInAccountModeFromSettings(
|
||||
settings: settings,
|
||||
accountState: accountState,
|
||||
);
|
||||
return mode == _SignedInAccountMode.accountSync
|
||||
? appText('svc.plus 托管配置', 'svc.plus managed profile')
|
||||
: appText('手动 Bridge 配置', 'Manual Bridge configuration');
|
||||
}
|
||||
|
||||
class _TokenConfiguredSummary extends StatelessWidget {
|
||||
const _TokenConfiguredSummary({required this.accountState});
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ void main() {
|
||||
expect(loginCount, 1);
|
||||
});
|
||||
|
||||
testWidgets('shows sync and logout actions on the same row', (
|
||||
testWidgets('shows account sync status, resync, and exit in signed-in mode', (
|
||||
tester,
|
||||
) async {
|
||||
final controllers = _TestControllers();
|
||||
@ -141,22 +141,18 @@ void main() {
|
||||
);
|
||||
|
||||
expect(find.text('账号登录与同步'), findsOneWidget);
|
||||
expect(find.textContaining('svc.plus 托管配置'), findsOneWidget);
|
||||
expect(find.text('账号同步'), findsOneWidget);
|
||||
expect(find.textContaining('账号同步状态'), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-sync-button')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-manual-reset-button')), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('settings-account-disconnect-button')),
|
||||
findsNothing,
|
||||
);
|
||||
expect(find.textContaining('本地配置'), findsNothing);
|
||||
expect(find.textContaining('已断开'), findsNothing);
|
||||
expect(find.textContaining('当前使用本地连接配置'), findsNothing);
|
||||
|
||||
final syncTop = tester.getTopLeft(
|
||||
find.byKey(const ValueKey('settings-account-sync-button')),
|
||||
);
|
||||
final logoutTop = tester.getTopLeft(
|
||||
find.byKey(const ValueKey('settings-account-logout-button')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(syncTop.dy, logoutTop.dy);
|
||||
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-service-url')), findsNothing);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-account-identifier')), findsNothing);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-remote-summary')), findsNothing);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const ValueKey('settings-account-sync-button')),
|
||||
@ -171,7 +167,7 @@ void main() {
|
||||
expect(logoutCount, 1);
|
||||
});
|
||||
|
||||
testWidgets('keeps managed connection copy when account is signed in', (
|
||||
testWidgets('keeps details collapsed by default and expands diagnostics', (
|
||||
tester,
|
||||
) async {
|
||||
final controllers = _TestControllers();
|
||||
@ -193,6 +189,12 @@ void main() {
|
||||
accountState: AccountSyncState.defaults().copyWith(
|
||||
syncState: 'ready',
|
||||
syncMessage: 'Bridge access synced',
|
||||
profileScope: 'bridge',
|
||||
tokenConfigured: const AccountTokenConfigured(
|
||||
bridge: true,
|
||||
vault: false,
|
||||
apisix: false,
|
||||
),
|
||||
),
|
||||
accountBusy: false,
|
||||
accountSignedIn: true,
|
||||
@ -213,14 +215,88 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.textContaining('svc.plus 托管配置'), findsOneWidget);
|
||||
expect(find.textContaining('本地配置'), findsNothing);
|
||||
expect(find.textContaining('已断开'), findsNothing);
|
||||
expect(find.textContaining('当前使用本地连接配置'), findsNothing);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-service-url')), findsNothing);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-account-identifier')), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('settings-account-disconnect-button')),
|
||||
findsNothing,
|
||||
find.byKey(const ValueKey('settings-account-summary-expansion')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(find.byType(ExpansionTile));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-service-url')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-account-identifier')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-remote-summary')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-last-sync')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-mfa-status')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-sync-scope')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-summary-token-configured')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows manual bridge save status and reset action when not account sync', (
|
||||
tester,
|
||||
) async {
|
||||
final controllers = _TestControllers();
|
||||
addTearDown(controllers.dispose);
|
||||
|
||||
var saveCount = 0;
|
||||
var logoutCount = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
child: SettingsAccountPanel(
|
||||
settings: SettingsSnapshot.defaults().copyWith(
|
||||
acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults().copyWith(
|
||||
selfHosted: AcpBridgeServerModeConfig.defaults().selfHosted.copyWith(
|
||||
serverUrl: 'https://xworkmate-bridge.svc.plus',
|
||||
passwordRef: 'bridge-token-ref',
|
||||
),
|
||||
),
|
||||
),
|
||||
accountSession: const AccountSessionSummary(
|
||||
userId: 'u-1',
|
||||
email: 'review@svc.plus',
|
||||
name: 'Review User',
|
||||
role: 'operator',
|
||||
mfaEnabled: false,
|
||||
),
|
||||
accountState: null,
|
||||
accountBusy: false,
|
||||
accountSignedIn: true,
|
||||
accountMfaRequired: false,
|
||||
accountBaseUrlController: controllers.baseUrl,
|
||||
accountIdentifierController: controllers.identifier,
|
||||
accountPasswordController: controllers.password,
|
||||
accountMfaCodeController: controllers.mfaCode,
|
||||
bridgeUrlController: controllers.bridgeUrl,
|
||||
bridgeTokenController: controllers.bridgeToken,
|
||||
onSaveAccountProfile: () async {
|
||||
saveCount += 1;
|
||||
},
|
||||
onLogin: () async {},
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onLogout: () async {
|
||||
logoutCount += 1;
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('手动 Bridge'), findsOneWidget);
|
||||
expect(find.textContaining('保存状态'), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-manual-reset-button')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('settings-account-sync-button')), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('settings-account-manual-reset-button')));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byKey(const ValueKey('settings-account-logout-button')));
|
||||
await tester.pump();
|
||||
|
||||
expect(saveCount, 1);
|
||||
expect(logoutCount, 1);
|
||||
});
|
||||
|
||||
testWidgets('shows live syncing feedback while resync is running', (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user