yx_tracking_flutter/example/lib/view/demo_page.dart

262 lines
8.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:yx_tracking_flutter/yx_tracking_flutter.dart';
import '../service/analytics_service.dart';
import '../view_model/demo_view_model.dart';
import 'widgets.dart';
class DemoPage extends StatefulWidget {
const DemoPage({super.key, required this.initialConfig});
final AnalyticsConfig initialConfig;
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
late DemoViewModel _viewModel;
final TextEditingController _mockBaseUrlController = TextEditingController();
final ScrollController _logScrollController = ScrollController();
int _lastLogCount = 0;
@override
void initState() {
super.initState();
_viewModel = DemoViewModel(
analytics: const AnalyticsService(),
initialConfig: widget.initialConfig,
)..init();
_mockBaseUrlController.text = _viewModel.mockBaseUrl;
_viewModel.addListener(_handleViewModelChanged);
}
@override
void dispose() {
_viewModel.removeListener(_handleViewModelChanged);
_viewModel.dispose();
_mockBaseUrlController.dispose();
_logScrollController.dispose();
super.dispose();
}
void _handleViewModelChanged() {
if (!mounted) {
return;
}
if (_viewModel.logs.length != _lastLogCount) {
_lastLogCount = _viewModel.logs.length;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_logScrollController.hasClients) {
_logScrollController.jumpTo(
_logScrollController.position.maxScrollExtent,
);
}
});
}
setState(() {});
}
Widget _buildConfigSummary() {
final config = _viewModel.activeConfig;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Endpoint: ${config.endpointBaseUrl}'),
Text('systemCode: ${config.systemCode}'),
Text('allowInsecureHttp: ${config.allowInsecureHttp}'),
Text('useIsolateStorage: ${config.useIsolateStorage}'),
Text('maxEventAge: ${config.maxEventAge.inSeconds}s'),
],
);
}
Widget _actionButton({
required String key,
required String label,
required Future<void> Function() action,
bool outlined = true,
bool suppressSdkLogs = false,
}) {
return ActionButton(
label: label,
outlined: outlined,
running: _viewModel.isRunning(key),
onPressed: _viewModel.isRunning(key)
? null
: () => _viewModel.runAction(
key,
label,
action,
suppressSdkLogs: suppressSdkLogs,
),
);
}
Widget _buildActionButtons(List<Widget> buttons) {
return Wrap(spacing: 12, runSpacing: 12, children: buttons);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('YX Tracking Demo')),
body: ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
Text(
'本地缓存事件数:${_viewModel.cacheCount}',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
SectionCard(title: '配置摘要', children: <Widget>[_buildConfigSummary()]),
SectionCard(
title: '基础操作',
children: <Widget>[
_buildActionButtons(<Widget>[
_actionButton(
key: 'track_demo',
label: 'Track 演示事件',
action: _viewModel.trackDemoEvent,
outlined: false,
),
_actionButton(
key: 'flush',
label: '立即上报',
action: _viewModel.flushNow,
outlined: false,
),
_actionButton(
key: 'refresh_config',
label: '刷新配置',
action: _viewModel.refreshConfig,
),
]),
const SizedBox(height: 8),
const Text(
'说明:已对接 SDK-TEST-FLUTTER 系统;若为 HTTP 联调,请保持 allowInsecureHttp=true。',
),
],
),
SectionCard(
title: '压力测试',
children: <Widget>[
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text('压力测试详细日志'),
subtitle: const Text('关闭后仅输出结果与异常'),
value: _viewModel.verboseStressLogs,
onChanged: (value) => _viewModel.setVerboseStressLogs(value),
),
_buildActionButtons(<Widget>[
_actionButton(
key: 'stress_seq',
label: '串行 Track 1000',
action: _viewModel.runStressSequential,
suppressSdkLogs: !_viewModel.verboseStressLogs,
),
_actionButton(
key: 'stress_con',
label: '并发 Track 1000',
action: _viewModel.runStressConcurrent,
suppressSdkLogs: !_viewModel.verboseStressLogs,
),
_actionButton(
key: 'continuous',
label: '持续 Track + Flush (30秒)',
action: _viewModel.runContinuousTrackFlush,
suppressSdkLogs: !_viewModel.verboseStressLogs,
),
]),
],
),
SectionCard(
title: '错误模拟',
children: <Widget>[
TextField(
controller: _mockBaseUrlController,
decoration: const InputDecoration(
labelText: '5xx/Mock BaseUrl可选',
hintText: 'http://localhost:8080',
),
onChanged: _viewModel.setMockBaseUrl,
),
const SizedBox(height: 8),
_buildActionButtons(<Widget>[
_actionButton(
key: 'invalid_event',
label: '非法事件 (缺少标签)',
action: _viewModel.trackInvalidEvent,
),
_actionButton(
key: 'timeout',
label: '模拟网络超时',
action: _viewModel.simulateTimeout,
),
_actionButton(
key: 'server_5xx',
label: '模拟服务器 5xx',
action: _viewModel.simulateServerError,
),
]),
],
),
SectionCard(
title: '边界用例',
children: <Widget>[
_buildActionButtons(<Widget>[
_actionButton(
key: 'empty_params',
label: '空参数',
action: _viewModel.trackEmptyParams,
),
_actionButton(
key: 'large_payload',
label: '大载荷 (100KB)',
action: _viewModel.trackLargePayload,
),
_actionButton(
key: 'special_chars',
label: '特殊字符',
action: _viewModel.trackSpecialChars,
),
_actionButton(
key: 'rapid_init',
label: '快速初始化/销毁 (x5)',
action: _viewModel.rapidInitDispose,
),
_actionButton(
key: 'expiration',
label: '测试过期 (1秒)',
action: _viewModel.testExpiration,
),
]),
],
),
SectionCard(
title: '状态与日志',
children: <Widget>[
const Text('最近事件(最多 20 条)'),
const SizedBox(height: 8),
SizedBox(
height: 220,
child: RecentEventList(items: _viewModel.recent),
),
const SizedBox(height: 12),
const Text('日志输出'),
const SizedBox(height: 8),
SizedBox(
height: 160,
child: LogList(
logs: _viewModel.logs,
controller: _logScrollController,
),
),
],
),
],
),
);
}
}