feat: 添加 Analytics 分析功能(行为追踪、错误码、协议更新)

This commit is contained in:
qzl
2026-04-02 11:52:23 +08:00
parent b101826de5
commit 7b6dbe72c3
24 changed files with 682 additions and 52 deletions
@@ -12,6 +12,7 @@ import '../../../../app/di/injection.dart';
import '../../../../app/router/app_route_observer.dart';
import '../../../../app/router/app_routes.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/analytics/tracker.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../core/inbox/inbox_sync_store.dart';
import '../../../chat/presentation/bloc/chat_bloc.dart';
@@ -98,6 +99,8 @@ class _HomeScreenState extends State<HomeScreen>
int _previousItemCount = 0;
bool _previousIsLoadingHistory = false;
bool _routeAwareSubscribed = false;
late final DateTime _pageEnteredAt;
int _pageClickCount = 0;
double? _historyViewportPixels;
double? _historyViewportMaxExtent;
final GlobalKey<HomeInputHostState> _inputHostKey =
@@ -121,6 +124,7 @@ class _HomeScreenState extends State<HomeScreen>
duration: const Duration(milliseconds: _rippleDurationMs),
);
_selectedImages.addAll(widget.initialSelectedImages);
_pageEnteredAt = DateTime.now();
final initialUserId = widget.initialUserId?.trim();
if (initialUserId != null && initialUserId.isNotEmpty) {
unawaited(_chatBloc.switchUser(initialUserId));
@@ -148,6 +152,14 @@ class _HomeScreenState extends State<HomeScreen>
@override
void dispose() {
final stayDurationMs = DateTime.now()
.difference(_pageEnteredAt)
.inMilliseconds;
AnalyticsTracker.instance.trackPageView(
pageName: 'home',
stayDurationMs: stayDurationMs,
clickCount: _pageClickCount,
);
_messageController.dispose();
_scrollController.removeListener(_handleScrollChanged);
_scrollController.dispose();
@@ -281,10 +293,18 @@ class _HomeScreenState extends State<HomeScreen>
Widget _buildHeader(BuildContext context) {
return HomeFloatingHeader(
unreadCount: _unreadCount,
onTapSettings: () => context.push(AppRoutes.settingsMain),
onTapCalendar: () =>
context.push('${AppRoutes.calendarDayWeek}?from=home'),
onTapMessages: () => context.push(AppRoutes.messageInviteList),
onTapSettings: () {
_trackClick('header_settings');
context.push(AppRoutes.settingsMain);
},
onTapCalendar: () {
_trackClick('header_calendar');
context.push('${AppRoutes.calendarDayWeek}?from=home');
},
onTapMessages: () {
_trackClick('header_messages');
context.push(AppRoutes.messageInviteList);
},
);
}
@@ -386,6 +406,7 @@ class _HomeScreenState extends State<HomeScreen>
child: HomeUnreadBadge(
count: _chatUnreadBadgeCount,
onTap: () {
_trackClick('unread_badge');
_scheduleAutoScroll(animated: true);
if (mounted) {
setState(() => _chatUnreadBadgeCount = 0);
@@ -438,6 +459,7 @@ class _HomeScreenState extends State<HomeScreen>
}
Future<void> _onLoadMore(BuildContext context) async {
_trackClick('history_load_more');
final chatBloc = context.read<ChatBloc>();
await _loadMoreHistoryPreservingViewport(chatBloc);
}
@@ -650,9 +672,18 @@ class _HomeScreenState extends State<HomeScreen>
isWaitingAgent: isWaitingAgent,
messageController: _messageController,
onTapPlus: _isRecording
? () => _stopRecording(autoSendAfterTranscribe: false)
: () => _showBottomSheet(context),
onStopGenerating: _onStopGenerating,
? () {
_trackClick('record_stop');
_stopRecording(autoSendAfterTranscribe: false);
}
: () {
_trackClick('input_plus');
_showBottomSheet(context);
},
onStopGenerating: () {
_trackClick('stop_generating');
_onStopGenerating();
},
onHoldToSpeakStart: _onHoldToSpeakStart,
onHoldToSpeakEnd: _onHoldToSpeakEnd,
onHoldToSpeakMoveUpdate: _onHoldToSpeakMoveUpdate,
@@ -662,6 +693,15 @@ class _HomeScreenState extends State<HomeScreen>
);
}
void _trackClick(String elementId) {
_pageClickCount += 1;
AnalyticsTracker.instance.trackClick(
pageName: 'home',
elementId: elementId,
elementType: 'button',
);
}
void _removeImage(int index) {
setState(() {
_selectedImages.removeAt(index);
@@ -53,6 +53,7 @@ extension _HomeScreenInteractions on _HomeScreenState {
});
try {
_trackClick('send_message');
await _chatBloc.sendMessage(content, images: images);
} finally {
if (mounted) {