docs(webrtc_controller_flutter): 更新项目文档以反映重构后的架构

AGENTS.md 与 README.md 同步更新:根据实际代码结构重写目录树、技术栈、架构分层及编码规范,移除旧版内联示例并补充新的开发约定与代码生成命令。
This commit is contained in:
2026-08-03 16:12:44 +08:00
parent 1918e5738e
commit d5e66a1777
51 changed files with 4711 additions and 1458 deletions

View File

@@ -0,0 +1,56 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../data/auth_providers.dart';
import '../domain/auth_repository.dart';
import '../domain/auth_state.dart';
part 'auth_controller.g.dart';
/// 登录状态控制器。
@Riverpod(keepAlive: true)
class AuthController extends _$AuthController {
AuthRepository get _repository => ref.read(authRepositoryProvider);
@override
FutureOr<AuthState> build() async {
final repository = ref.watch(authRepositoryProvider);
await repository.restore();
final loggedIn =
repository.accessToken != null && await repository.hasRefreshToken();
return AuthState(
loggedIn: loggedIn,
restoring: false,
username: await repository.getUsername(),
);
}
/// 登录:成功返回 true失败返回 false 并写入错误信息。
Future<bool> login(String username, String password) async {
try {
await _repository.login(username, password);
state = AsyncData(
AuthState(loggedIn: true, username: username),
);
return true;
} catch (e) {
state = AsyncData(
AuthState(loggedIn: false, error: e.toString()),
);
return false;
}
}
/// 退出登录:清空令牌并复位状态。
Future<void> logout() async {
await _repository.clear();
state = const AsyncData(AuthState());
}
/// 清空错误信息。
void clearError() {
final current = state.valueOrNull;
if (current != null && current.error != null) {
state = AsyncData(current.copyWith(error: null));
}
}
}

View File

@@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_controller.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authControllerHash() => r'35d9a474535207949af85ca44019d70f50afd7c2';
/// 登录状态控制器。
///
/// Copied from [AuthController].
@ProviderFor(AuthController)
final authControllerProvider =
AsyncNotifierProvider<AuthController, AuthState>.internal(
AuthController.new,
name: r'authControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$authControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AuthController = AsyncNotifier<AuthState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -0,0 +1,97 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:webrtc_controller_flutter/l10n/app_localizations.dart';
import '../auth_controller.dart';
/// 登录对话框:输入用户名/密码,调用登录保存令牌。
class LoginDialog extends ConsumerStatefulWidget {
const LoginDialog({super.key});
@override
ConsumerState<LoginDialog> createState() => _LoginDialogState();
}
class _LoginDialogState extends ConsumerState<LoginDialog> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return CupertinoAlertDialog(
title: Text(l10n.login),
content: Padding(
padding: const EdgeInsets.only(top: 12),
child: Column(
children: [
CupertinoTextField(
controller: _usernameController,
placeholder: l10n.username,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
),
const SizedBox(height: 10),
CupertinoTextField(
controller: _passwordController,
placeholder: l10n.password,
obscureText: true,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
),
],
),
),
actions: [
CupertinoDialogAction(
child: Text(l10n.cancel),
onPressed: () => Navigator.of(context).pop(false),
),
CupertinoDialogAction(
child: Text(l10n.login),
onPressed: () async {
final user = _usernameController.text.trim();
final pass = _passwordController.text.trim();
if (user.isEmpty || pass.isEmpty) {
_showAlert(context, l10n.usernamePasswordRequired);
return;
}
final ok = await ref
.read(authControllerProvider.notifier)
.login(user, pass);
if (!context.mounted) return;
if (ok) {
Navigator.of(context).pop(true);
} else {
final error = ref.read(authControllerProvider).valueOrNull?.error;
_showAlert(
context,
l10n.loginFailed(error ?? ''),
);
}
},
),
],
);
}
static void _showAlert(BuildContext context, String message) {
showCupertinoDialog<void>(
context: context,
builder: (ctx) => CupertinoAlertDialog(
content: Text(message),
actions: [
CupertinoDialogAction(
child: const Text('确定'),
onPressed: () => Navigator.of(ctx).pop(),
),
],
),
);
}
}