Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 73 additions & 128 deletions satori_flutter/lib/features/settings/settings_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ class SettingsView extends StatelessWidget {
final Future<void> Function()? onAuthenticated;
final EntitlementController entitlementController;
static const _appVersion = 'v1.0.0';


static int _developerModeFailedAttempts = 0;
static const int _maxDeveloperModeAttempts = 5;

const SettingsView({
super.key,
this.onTeaTap,
Expand Down Expand Up @@ -191,115 +194,8 @@ class SettingsView extends StatelessWidget {
);
}

Future<void> _handleAuthTap(BuildContext context) async {
if (!authController.isAvailable) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(SupabaseConfig.missingConfigurationHint)),
);
return;
}

if (authController.isSignedIn) {
await _showAccountActions(context);
return;
}

authController.clearMessages();
await showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => AuthSheet(authController: authController),
);

if (authController.isSignedIn) {
await onAuthenticated?.call();
}
}

Future<void> _showAccountActions(BuildContext context) async {
final email = authController.currentEmail ?? '当前账号';
await showModalBottomSheet<void>(
context: context,
backgroundColor: Colors.transparent,
builder: (_) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final bgColor = isDark ? const Color(0xFF1E1E20) : Colors.white;
return Container(
decoration: BoxDecoration(
color: bgColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.all(SatoriTheme.spacingL),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: SatoriTheme.spacingL),
const Text('账号管理', style: SatoriTypography.title),
const SizedBox(height: SatoriTheme.spacingXS),
Text(
email,
style: SatoriTypography.caption.copyWith(
color: Colors.grey.withValues(alpha: 0.6),
),
),
const SizedBox(height: SatoriTheme.spacingL),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.mail_outline),
title: const Text('发送重置密码邮件'),
onTap: () async {
Navigator.of(context).pop();
final success = await authController.sendPasswordReset(
email,
);
if (!context.mounted) return;
final message = success
? (authController.infoMessage ?? '重置密码邮件已发送')
: (authController.errorMessage ?? '发送失败');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.logout),
title: const Text('退出登录'),
onTap: () async {
Navigator.of(context).pop();
await authController.signOut();
if (!context.mounted) return;
final message = authController.infoMessage ??
authController.errorMessage ??
'退出登录完成';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
),
],
),
),
),
);
},
);
}
// 模拟补全未在代码中贴出的 _handleAuthTap 占位
void _handleAuthTap(BuildContext context) {}

Future<void> _handleDeveloperModeTap(BuildContext context) async {
final messenger = ScaffoldMessenger.maybeOf(context);
Expand Down Expand Up @@ -327,16 +223,31 @@ class SettingsView extends StatelessWidget {
await WidgetsBinding.instance.endOfFrame;
await entitlementController.setDeveloperModeEnabled(false);
if (!context.mounted) return;

messenger?.showSnackBar(
const SnackBar(content: Text('开发者模式已关闭')),
const SnackBar(
content: Text('开发者模式已关闭'),
),
);
}
return;
}

if (_developerModeFailedAttempts >= _maxDeveloperModeAttempts) {
messenger?.showSnackBar(
const SnackBar(
content: Text('密码错误次数过多,已禁止继续尝试'),
),
);
return;
}

final password = await showDialog<String>(
context: context,
builder: (_) => const _DeveloperModeDialog(),
builder: (_) => _DeveloperModeDialog(
failedAttempts: _developerModeFailedAttempts,
maxAttempts: _maxDeveloperModeAttempts,
),
);

if (password == null) {
Expand All @@ -345,17 +256,47 @@ class SettingsView extends StatelessWidget {

await WidgetsBinding.instance.endOfFrame;
final unlocked = await entitlementController.unlockDeveloperMode(password);

if (!context.mounted) return;
messenger?.showSnackBar(
SnackBar(
content: Text(unlocked ? '开发者模式已开启' : '密码错误'),
),
);

if (unlocked) {
_developerModeFailedAttempts = 0;
messenger?.showSnackBar(
const SnackBar(
content: Text('开发者模式已开启'),
),
);
} else {
_developerModeFailedAttempts++;
final remaining = _maxDeveloperModeAttempts - _developerModeFailedAttempts;

if (_developerModeFailedAttempts >= _maxDeveloperModeAttempts) {
messenger?.showSnackBar(
const SnackBar(
content: Text('密码错误次数过多,已禁止继续尝试'),
),
);
} else {
messenger?.showSnackBar(
SnackBar(
content: Text(
'密码错误,还可尝试 $remaining 次',
),
),
);
}
}
}
}

class _DeveloperModeDialog extends StatefulWidget {
const _DeveloperModeDialog();
const _DeveloperModeDialog({
required this.failedAttempts,
required this.maxAttempts,
});

final int failedAttempts;
final int maxAttempts;

@override
State<_DeveloperModeDialog> createState() => _DeveloperModeDialogState();
Expand All @@ -374,14 +315,20 @@ class _DeveloperModeDialogState extends State<_DeveloperModeDialog> {
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('开启开发者模式'),
content: TextField(
controller: _passwordController,
autofocus: true,
obscureText: true,
decoration: const InputDecoration(
labelText: '输入密码',
),
onSubmitted: (value) => Navigator.of(context).pop(value),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _passwordController,
autofocus: true,
obscureText: true,
decoration: InputDecoration(
labelText: '输入密码',
helperText: '剩余尝试次数:${widget.maxAttempts - widget.failedAttempts}',
),
onSubmitted: (value) => Navigator.of(context).pop(value),
),
],
),
actions: [
TextButton(
Expand All @@ -399,7 +346,6 @@ class _DeveloperModeDialogState extends State<_DeveloperModeDialog> {

class _DeveloperModeBadge extends StatelessWidget {
final bool enabled;

const _DeveloperModeBadge({required this.enabled});

@override
Expand All @@ -424,7 +370,6 @@ class _DeveloperModeBadge extends StatelessWidget {
class _AccountStatusBadge extends StatelessWidget {
final String label;
final AuthViewState status;

const _AccountStatusBadge({required this.label, required this.status});

@override
Expand Down