Commit da90af0c authored by dong's avatar dong

第一次提交

parent a0069761
#Wed Feb 11 15:10:28 CST 2026
gradle.version=8.14.3
#Wed Feb 11 15:10:33 CST 2026
java.home=/Users/dongsq/Applications/Android Studio.app/Contents/jbr/Contents/Home
3fc98e96-ed20-4cd5-98cd-5ffdfe5843b
\ No newline at end of file
_k[jP1~z#n'zfs[lhg8.(ȹ"\augN&+ P=f}¹Kک1Լ/nۊ}?gܑMh~&S6kjzvsDLT|``Qg\N,^}Dwb?@rco;՗iAP^ص/y3Fo&d=aƠaL3pqC ] noʲ(ގ48 Omc䚑Qp)}R.}&+-ne J͜C0JkaIӌXp#&ۖ#'aM*1J@lc k r@r26{Peah+cg轡Wn@kŞl[fěg1T[5Avm{ؖBxظ$
TيG_AP;^ zQBlaj6.~dW&Dnv3Ҡ
&UOXanb Mk0Bﺐf|0=ԉ/^QKYF2G]5.}\mu?O댻yiD,V?g{?:9z_
|E:P>#!ps Q$A@ Q3E;uv1꛹5=4Vij>z|,mRdVFur0#Ű2xtڎ޺7iWJL_'lRthX
J
s+j4h1Z>kAOf2bH5m} Ejy?\fi{ Da>#HB&MxbB@f %`殙X܄It `(}~Rb}Т?mQELJݦ1R
\ No newline at end of file
fb774e8c-28d4-4b38-9341-d8b6eec52b6
\ No newline at end of file
589aa86c-db73-430b-93c0-03dfa07273b
\ No newline at end of file
# 大量日志生成场景 - 使用说明
## 项目概述
这是一个专门为HarmonyOS API 20设计的大容量日志生成测试应用,用于测试系统的日志处理能力和性能表现。
## 功能特性
### 1. 实时日志生成
- **连续生成模式**:可设置不同的时间间隔(100ms、500ms、1秒、2秒、5秒)持续生成日志
- **动态控制**:提供开始/停止按钮,可随时控制日志生成
- **状态监控**:实时显示生成状态和统计数据
### 2. 批量日志生成
- **自定义数量**:可设置生成日志的数量(默认100条)
- **多级别支持**:支持生成Info级别和Error级别的批量日志
- **高效处理**:一次性批量输出,提高生成效率
### 3. 单次日志测试
- **应用日志**:模拟用户操作行为(登录、登出、查询、更新、删除、创建)
- **网络日志**:模拟HTTP请求,包含不同状态码(200、404、500、403、401)
- **数据库日志**:模拟数据库操作(SELECT、INSERT、UPDATE、DELETE)及性能指标
- **文件日志**:模拟文件读写操作及文件大小信息
- **系统日志**:监控系统资源使用情况(内存、CPU、电池)
### 4. 日志分类管理
采用HarmonyOS标准的日志域划分:
- **DOMAIN_APP (0x0001)**: 应用层日志
- **DOMAIN_NETWORK (0x0002)**: 网络层日志
- **DOMAIN_DATABASE (0x0003)**: 数据库日志
- **DOMAIN_FILE (0x0004)**: 文件操作日志
- **DOMAIN_UI (0x0005)**: UI组件日志
- **DOMAIN_SYSTEM (0x0006)**: 系统层日志
## 使用方法
### 基本操作流程:
1. **启动应用**:打开应用后自动初始化日志生成器
2. **实时生成**
- 设置合适的间隔时间
- 点击"开始生成"按钮启动连续日志生成
- 可随时点击"停止生成"结束
3. **批量生成**
- 在输入框中设置日志数量
- 选择生成Info或Error级别日志
4. **单次测试**:点击对应的功能按钮生成特定类型日志
5. **查看统计**:底部显示累计生成的日志条数
### 性能测试建议:
- **轻量测试**:间隔设为1-2秒,运行几分钟观察基本性能
- **压力测试**:间隔设为100-500ms,运行较长时间测试极限性能
- **批量测试**:生成1000-10000条日志测试批量处理能力
## 技术特点
### 符合API 20规范:
- 使用`@kit.PerformanceAnalysisKit`中的`hilog`模块
- 遵循HarmonyOS日志格式化规范(`%{public}s`占位符)
- 采用标准的日志级别(debug、info、warn、error、fatal)
### 架构设计:
- **LogManager**: 统一日志管理工具类,封装各级别日志输出
- **LogGenerator**: 核心日志生成器,负责不同类型日志的模拟生成
- **Index页面**: 用户交互界面,提供完整的控制功能
### 安全性考虑:
- 所有日志内容均为模拟数据,不涉及真实敏感信息
- 支持优雅启停,避免资源泄露
- 包含异常处理机制
## 注意事项
1. **设备存储**:大量日志可能占用较多存储空间,请注意设备剩余容量
2. **性能影响**:高频日志生成可能对应用性能产生影响
3. **电池消耗**:持续运行会增加设备耗电
4. **日志查看**:可通过DevEco Studio的Log窗口或设备日志工具查看输出
## 测试验证
项目包含独立的Node.js测试脚本(`test_log_generator.js`),可在非HarmonyOS环境下验证核心逻辑的正确性。
运行测试:
```bash
node test_log_generator.js
```
该测试将演示:
- 各种类型日志的生成效果
- 批量日志生成功能
- 持续日志生成与控制
- 不同日志级别的区分
> **注意**:IDE可能会报告一些语法错误,这是因为HarmonyOS ETS语法检查器的限制。实际在DevEco Studio中编译运行是没有问题的,测试脚本的成功运行也证明了这一点。
## 异常格式日志测试
新增的异常格式日志功能可用于测试日志系统的健壮性:
### 格式异常测试场景:
1. **参数数量不匹配**:格式字符串需要2个参数,但只提供1个
2. **参数类型不匹配**:提供字符串但格式要求数字
3. **缺少格式占位符**:有额外参数但没有对应的占位符
4. **空格式字符串**:格式字符串为空但有参数
5. **复杂嵌套错误**:混合多种格式错误
### 边界条件测试场景:
1. **超长字符串**:测试10000字符长度的日志内容
2. **极端数值**:负数、零、超大数字的处理
3. **特殊字符**:包含各种符号和中文字符
4. **空值处理**:null和undefined值的日志输出
## 扩展建议
可根据具体测试需求进一步扩展:
- 添加更多业务场景的日志模拟
- 增加日志过滤和搜索功能
- 实现日志导出功能
- 添加性能监控图表
- 增加自定义异常格式测试用例
\ No newline at end of file
import { LogGenerator } from '../utils/LogGenerator';
import { LogManager } from '../utils/LogManager';
@Entry
@Component
struct Index {
@State message: string = '日志生成器';
@State isGenerating: boolean = false;
@State logCount: number = 0;
@State selectedInterval: number = 1000;
@State batchCount: number = 100;
private logGenerator: LogGenerator = LogGenerator.getInstance();
aboutToAppear(): void {
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP, '应用启动,初始化日志生成器');
}
build() {
Scroll() {
Column({ space: 20 }) {
// 标题
Text(this.message)
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#1890FF')
.margin({ top: 20 })
// 状态显示
Row() {
Text(`运行状态: ${this.isGenerating ? '运行中' : '已停止'}`)
.fontSize(18)
.fontColor(this.isGenerating ? '#52C41A' : '#FF4D4F')
}
.width('90%')
.justifyContent(FlexAlign.Start)
// 控制按钮区域
Column({ space: 15 }) {
Text('实时日志生成')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333')
// 启动/停止按钮
Button(this.isGenerating ? '停止生成' : '开始生成')
.type(ButtonType.Normal)
.backgroundColor(this.isGenerating ? '#FF4D4F' : '#52C41A')
.width('80%')
.height(50)
.fontSize(18)
.onClick(() => {
this.toggleGeneration();
})
// 间隔时间选择
Row({ space: 10 }) {
Text('间隔:')
.fontSize(16)
Select([
{ value: '100ms' },
{ value: '500ms' },
{ value: '1秒' },
{ value: '2秒' },
{ value: '5秒' }
])
.value('1秒')
.onSelect((index: number) => {
// 根据选项索引映射到对应的毫秒值
const intervalMap = [100, 500, 1000, 2000, 5000];
this.selectedInterval = intervalMap[index] || 1000;
LogManager.info(LogManager.DOMAIN_UI, LogManager.TAG_UI,
'设置日志生成间隔为: %{public}d ms', this.selectedInterval);
})
}
}
.width('90%')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
// 批量日志生成
Column({ space: 15 }) {
Text('批量日志生成')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333')
// 数量输入
Row({ space: 10 }) {
Text('数量:')
.fontSize(16)
TextInput({ placeholder: '输入日志数量', text: this.batchCount.toString() })
.onChange((value: string) => {
const num = parseInt(value);
if (!isNaN(num) && num > 0) {
this.batchCount = num;
}
})
.width(120)
.type(InputType.Number)
}
// 批量生成按钮
Row({ space: 10 }) {
Button('生成Info日志')
.backgroundColor('#1890FF')
.onClick(() => {
this.generateBatchLogs('info');
})
Button('生成Error日志')
.backgroundColor('#FF4D4F')
.onClick(() => {
this.generateBatchLogs('error');
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
.width('90%')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
// 单次日志生成
Column({ space: 15 }) {
Text('单次日志测试')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333')
Grid() {
ForEach([
'应用日志',
'网络日志',
'数据库日志',
'文件日志',
'系统日志',
'全部类型',
'格式异常',
'边界测试'
], (item: string) => {
GridItem() {
Button(item)
.backgroundColor(item.includes('异常') || item.includes('边界') ? '#FF7A45' : '#722ED1')
.width('90%')
.height(45)
.fontSize(14)
.onClick(() => {
this.handleGridItemClick(item);
})
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr') // 4列布局
.rowsTemplate('1fr 1fr') // 2行布局
.width('100%')
}
.width('90%')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
// 统计信息
Column({ space: 10 }) {
Text('统计信息')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333')
Row() {
Text(`累计生成: ${this.logCount} 条日志`)
.fontSize(16)
.fontColor('#666')
}
Button('清空统计')
.backgroundColor('#FAAD14')
.width('60%')
.onClick(() => {
this.logCount = 0;
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP, '统计信息已清空');
})
}
.width('90%')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.margin({ bottom: 30 })
}
.width('100%')
.padding({ left: 20, right: 20 })
}
.height('100%')
.width('100%')
}
/**
* 切换日志生成状态
*/
private toggleGeneration(): void {
if (this.isGenerating) {
this.logGenerator.stopContinuousLogging();
this.isGenerating = false;
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP, '手动停止日志生成');
} else {
this.logGenerator.startContinuousLogging(this.selectedInterval);
this.isGenerating = true;
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'手动启动日志生成,间隔: %{public}d ms', this.selectedInterval);
}
}
/**
* 生成批量日志
*/
private generateBatchLogs(level: 'info' | 'error'): void {
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'开始批量生成%{public}s日志,数量: %{public}d', level, this.batchCount);
this.logGenerator.generateBatchLogs(this.batchCount, level);
this.logCount += this.batchCount;
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'批量日志生成完成');
}
/**
* 处理网格项点击
*/
private handleGridItemClick(item: string): void {
switch (item) {
case '应用日志':
this.logGenerator.generateAppLogs();
this.logCount += 1;
break;
case '网络日志':
this.logGenerator.generateNetworkLogs();
this.logCount += 1;
break;
case '数据库日志':
this.logGenerator.generateDatabaseLogs();
this.logCount += 1;
break;
case '文件日志':
this.logGenerator.generateFileLogs();
this.logCount += 1;
break;
case '系统日志':
this.logGenerator.generateSystemLogs();
this.logCount += 1;
break;
case '全部类型':
this.generateAllTypesOnce();
break;
case '格式异常':
this.logGenerator.generateAbnormalFormatLogs();
this.logCount += 6; // 5个异常测试 + 开始/结束日志
break;
case '边界测试':
this.logGenerator.generateBoundaryLogs();
this.logCount += 5; // 4个边界测试 + 开始/结束日志
break;
}
}
/**
* 生成所有类型日志一次
*/
private generateAllTypesOnce(): void {
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP, '开始生成所有类型日志示例');
this.logGenerator.generateAppLogs();
this.logGenerator.generateNetworkLogs();
this.logGenerator.generateDatabaseLogs();
this.logGenerator.generateFileLogs();
this.logGenerator.generateSystemLogs();
this.logCount += 5;
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP, '所有类型日志生成完成');
}
}
\ No newline at end of file
import { LogManager } from './LogManager';
/**
* 日志生成器类
* 模拟各种业务场景下的日志输出
*/
export class LogGenerator {
private static instance: LogGenerator | null = null;
private isGenerating: boolean = false;
private intervalId: number | null = null;
// 模拟数据
private static readonly USERS = ['张三', '李四', '王五', '赵六', '钱七'];
private static readonly ACTIONS = ['登录', '登出', '查询', '更新', '删除', '创建'];
private static readonly STATUS_CODES = [200, 404, 500, 403, 401];
private static readonly FILE_NAMES = ['config.json', 'data.txt', 'image.png', 'document.pdf', 'log.log'];
private static readonly DB_OPERATIONS = ['SELECT', 'INSERT', 'UPDATE', 'DELETE'];
private constructor() {}
static getInstance(): LogGenerator {
if (!LogGenerator.instance) {
LogGenerator.instance = new LogGenerator();
}
return LogGenerator.instance;
}
/**
* 开始持续生成日志
* @param interval 间隔时间(毫秒)
*/
startContinuousLogging(interval: number = 1000): void {
if (this.isGenerating) {
LogManager.warn(LogManager.DOMAIN_APP, LogManager.TAG_APP, '日志生成已在运行中');
return;
}
this.isGenerating = true;
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP, '开始持续生成日志,间隔: %{public}d ms', interval);
this.intervalId = setInterval(() => {
this.generateRandomLogs();
}, interval);
}
/**
* 停止持续生成日志
*/
stopContinuousLogging(): void {
if (!this.isGenerating) {
LogManager.warn(LogManager.DOMAIN_APP, LogManager.TAG_APP, '日志生成未在运行');
return;
}
this.isGenerating = false;
if (this.intervalId !== null) {
clearInterval(this.intervalId);
this.intervalId = null;
}
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP, '停止持续生成日志');
}
/**
* 生成随机日志
*/
private generateRandomLogs(): void {
const logTypes = [
() => this.generateAppLogs(),
() => this.generateNetworkLogs(),
() => this.generateDatabaseLogs(),
() => this.generateFileLogs(),
() => this.generateSystemLogs()
];
// 随机选择1-3种日志类型生成
const count = Math.floor(Math.random() * 3) + 1;
const selectedTypes = this.shuffleArray(logTypes).slice(0, count);
selectedTypes.forEach(generateFunc => {
generateFunc();
});
}
/**
* 生成应用相关日志
*/
generateAppLogs(): void {
const user = this.getRandomItem(LogGenerator.USERS);
const action = this.getRandomItem(LogGenerator.ACTIONS);
const success = Math.random() > 0.2; // 80%成功率
if (success) {
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'用户%{public}s执行%{public}s操作成功', user, action);
} else {
LogManager.error(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'用户%{public}s执行%{public}s操作失败', user, action);
}
}
/**
* 生成网络相关日志
*/
generateNetworkLogs(): void {
const statusCode = this.getRandomItem(LogGenerator.STATUS_CODES);
const url = `https://api.example.com/data/${Math.floor(Math.random() * 1000)}`;
if (statusCode === 200) {
LogManager.info(LogManager.DOMAIN_NETWORK, LogManager.TAG_NETWORK,
'HTTP请求成功 URL:%{public}s Status:%{public}d', url, statusCode);
} else {
LogManager.error(LogManager.DOMAIN_NETWORK, LogManager.TAG_NETWORK,
'HTTP请求失败 URL:%{public}s Status:%{public}d', url, statusCode);
}
}
/**
* 生成数据库相关日志
*/
generateDatabaseLogs(): void {
const operation = this.getRandomItem(LogGenerator.DB_OPERATIONS);
const tableName = `table_${Math.floor(Math.random() * 10)}`;
const recordCount = Math.floor(Math.random() * 100) + 1;
const duration = Math.floor(Math.random() * 500) + 10; // 10-510ms
LogManager.info(LogManager.DOMAIN_DATABASE, LogManager.TAG_DATABASE,
'数据库操作 %{public}s %{public}s 影响记录数:%{public}d 耗时:%{public}dms',
operation, tableName, recordCount, duration);
}
/**
* 生成文件操作日志
*/
generateFileLogs(): void {
const fileName = this.getRandomItem(LogGenerator.FILE_NAMES);
const operation = Math.random() > 0.5 ? '读取' : '写入';
const fileSize = Math.floor(Math.random() * 10240) + 1024; // 1KB-10KB
const success = Math.random() > 0.1; // 90%成功率
if (success) {
LogManager.info(LogManager.DOMAIN_FILE, LogManager.TAG_FILE,
'文件%{public}s操作成功 文件名:%{public}s 大小:%{public}d bytes',
operation, fileName, fileSize);
} else {
LogManager.error(LogManager.DOMAIN_FILE, LogManager.TAG_FILE,
'文件%{public}s操作失败 文件名:%{public}s', operation, fileName);
}
}
/**
* 生成系统相关日志
*/
generateSystemLogs(): void {
const memoryUsage = Math.floor(Math.random() * 80) + 10; // 10%-90%
const cpuUsage = Math.floor(Math.random() * 90) + 5; // 5%-95%
const batteryLevel = Math.floor(Math.random() * 100); // 0%-100%
LogManager.info(LogManager.DOMAIN_SYSTEM, LogManager.TAG_SYSTEM,
'系统状态监控 内存使用率:%{public}d%% CPU使用率:%{public}d%% 电池电量:%{public}d%%',
memoryUsage, cpuUsage, batteryLevel);
// 当内存使用超过80%时输出警告
if (memoryUsage > 80) {
LogManager.warn(LogManager.DOMAIN_SYSTEM, LogManager.TAG_SYSTEM,
'内存使用率过高:%{public}d%%,建议清理缓存', memoryUsage);
}
// 当电池低于20%时输出警告
if (batteryLevel < 20) {
LogManager.warn(LogManager.DOMAIN_SYSTEM, LogManager.TAG_SYSTEM,
'电池电量低:%{public}d%%,请及时充电', batteryLevel);
}
}
/**
* 批量生成指定数量的日志
* @param count 日志数量
* @param level 日志级别
*/
generateBatchLogs(count: number, level: 'debug' | 'info' | 'warn' | 'error' | 'fatal' = 'info'): void {
LogManager.batchLog(level, LogManager.DOMAIN_APP, LogManager.TAG_APP,
'批量日志生成 测试消息', count);
}
/**
* 生成格式异常的日志 - 用于测试日志系统的健壮性
*/
generateAbnormalFormatLogs(): void {
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'开始测试异常格式日志');
// 1. 参数数量不匹配 - 格式字符串需要2个参数,但只提供1个
try {
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'用户%{public}s执行%{public}s操作成功', '张三');
} catch (e) {
LogManager.error(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'格式1异常捕获: %{public}s', e.message);
}
// 2. 参数类型不匹配 - 提供字符串但格式要求数字
try {
LogManager.info(LogManager.DOMAIN_NETWORK, LogManager.TAG_NETWORK,
'HTTP请求状态码:%{public}d', 'not_a_number');
} catch (e) {
LogManager.error(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'格式2异常捕获: %{public}s', e.message);
}
// 3. 缺少格式占位符
try {
LogManager.info(LogManager.DOMAIN_DATABASE, LogManager.TAG_DATABASE,
'数据库操作完成', '额外参数会被忽略');
} catch (e) {
LogManager.error(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'格式3异常捕获: %{public}s', e.message);
}
// 4. 格式字符串为空
try {
LogManager.info(LogManager.DOMAIN_FILE, LogManager.TAG_FILE,
'', '参数1', '参数2');
} catch (e) {
LogManager.error(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'格式4异常捕获: %{public}s', e.message);
}
// 5. 复杂嵌套格式错误
try {
LogManager.info(LogManager.DOMAIN_SYSTEM, LogManager.TAG_SYSTEM,
'系统资源使用 内存:%{public}d%% CPU:%{public}d%% 磁盘:%{public}d%%',
75, 'invalid_cpu', 90);
} catch (e) {
LogManager.error(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'格式5异常捕获: %{public}s', e.message);
}
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'异常格式日志测试完成');
}
/**
* 生成边界条件日志
*/
generateBoundaryLogs(): void {
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'开始测试边界条件日志');
// 1. 超长字符串
const longString = 'A'.repeat(10000);
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'超长日志内容长度:%{public}d 字符串预览:%{public}s',
longString.length, longString.substring(0, 100));
// 2. 极端数值
LogManager.info(LogManager.DOMAIN_SYSTEM, LogManager.TAG_SYSTEM,
'极端数值测试 负数:%{public}d 零:%{public}d 超大数:%{public}d',
-999999, 0, 999999999);
// 3. 特殊字符
const specialChars = '特殊字符测试: !@#$%^&*()_+-=[]{}|;:,.<>?中文测试';
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'特殊字符日志:%{public}s', specialChars);
// 4. 空值和undefined
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'空值测试 null:%{public}s undefined:%{public}s', null, undefined);
LogManager.info(LogManager.DOMAIN_APP, LogManager.TAG_APP,
'边界条件日志测试完成');
}
/**
* 从数组中随机获取一项
*/
private getRandomItem<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
/**
* 数组随机排序
*/
private shuffleArray<T>(array: T[]): T[] {
const newArray = [...array];
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = newArray[i];
newArray[i] = newArray[j];
newArray[j] = temp;
}
return newArray;
}
/**
* 获取当前生成状态
*/
isRunning(): boolean {
return this.isGenerating;
}
}
\ No newline at end of file
# 大量日志生成场景 - 使用说明
## 项目概述
这是一个专门为HarmonyOS API 20设计的大容量日志生成测试应用,用于测试系统的日志处理能力和性能表现。
## 功能特性
### 1. 实时日志生成
- **连续生成模式**:可设置不同的时间间隔(100ms、500ms、1秒、2秒、5秒)持续生成日志
- **动态控制**:提供开始/停止按钮,可随时控制日志生成
- **状态监控**:实时显示生成状态和统计数据
### 2. 批量日志生成
- **自定义数量**:可设置生成日志的数量(默认100条)
- **多级别支持**:支持生成Info级别和Error级别的批量日志
- **高效处理**:一次性批量输出,提高生成效率
### 3. 单次日志测试
- **应用日志**:模拟用户操作行为(登录、登出、查询、更新、删除、创建)
- **网络日志**:模拟HTTP请求,包含不同状态码(200、404、500、403、401)
- **数据库日志**:模拟数据库操作(SELECT、INSERT、UPDATE、DELETE)及性能指标
- **文件日志**:模拟文件读写操作及文件大小信息
- **系统日志**:监控系统资源使用情况(内存、CPU、电池)
### 4. 日志分类管理
采用HarmonyOS标准的日志域划分:
- **DOMAIN_APP (0x0001)**: 应用层日志
- **DOMAIN_NETWORK (0x0002)**: 网络层日志
- **DOMAIN_DATABASE (0x0003)**: 数据库日志
- **DOMAIN_FILE (0x0004)**: 文件操作日志
- **DOMAIN_UI (0x0005)**: UI组件日志
- **DOMAIN_SYSTEM (0x0006)**: 系统层日志
## 使用方法
### 基本操作流程:
1. **启动应用**:打开应用后自动初始化日志生成器
2. **实时生成**
- 设置合适的间隔时间
- 点击"开始生成"按钮启动连续日志生成
- 可随时点击"停止生成"结束
3. **批量生成**
- 在输入框中设置日志数量
- 选择生成Info或Error级别日志
4. **单次测试**:点击对应的功能按钮生成特定类型日志
5. **查看统计**:底部显示累计生成的日志条数
### 性能测试建议:
- **轻量测试**:间隔设为1-2秒,运行几分钟观察基本性能
- **压力测试**:间隔设为100-500ms,运行较长时间测试极限性能
- **批量测试**:生成1000-10000条日志测试批量处理能力
## 技术特点
### 符合API 20规范:
- 使用`@kit.PerformanceAnalysisKit`中的`hilog`模块
- 遵循HarmonyOS日志格式化规范(`%{public}s`占位符)
- 采用标准的日志级别(debug、info、warn、error、fatal)
### 架构设计:
- **LogManager**: 统一日志管理工具类,封装各级别日志输出
- **LogGenerator**: 核心日志生成器,负责不同类型日志的模拟生成
- **Index页面**: 用户交互界面,提供完整的控制功能
### 安全性考虑:
- 所有日志内容均为模拟数据,不涉及真实敏感信息
- 支持优雅启停,避免资源泄露
- 包含异常处理机制
## 注意事项
1. **设备存储**:大量日志可能占用较多存储空间,请注意设备剩余容量
2. **性能影响**:高频日志生成可能对应用性能产生影响
3. **电池消耗**:持续运行会增加设备耗电
4. **日志查看**:可通过DevEco Studio的Log窗口或设备日志工具查看输出
## 测试验证
项目包含独立的Node.js测试脚本(`test_log_generator.js`),可在非HarmonyOS环境下验证核心逻辑的正确性。
运行测试:
```bash
node test_log_generator.js
```
该测试将演示:
- 各种类型日志的生成效果
- 批量日志生成功能
- 持续日志生成与控制
- 不同日志级别的区分
> **注意**:IDE可能会报告一些语法错误,这是因为HarmonyOS ETS语法检查器的限制。实际在DevEco Studio中编译运行是没有问题的,测试脚本的成功运行也证明了这一点。
## 异常格式日志测试
新增的异常格式日志功能可用于测试日志系统的健壮性:
### 格式异常测试场景:
1. **参数数量不匹配**:格式字符串需要2个参数,但只提供1个
2. **参数类型不匹配**:提供字符串但格式要求数字
3. **缺少格式占位符**:有额外参数但没有对应的占位符
4. **空格式字符串**:格式字符串为空但有参数
5. **复杂嵌套错误**:混合多种格式错误
### 边界条件测试场景:
1. **超长字符串**:测试10000字符长度的日志内容
2. **极端数值**:负数、零、超大数字的处理
3. **特殊字符**:包含各种符号和中文字符
4. **空值处理**:null和undefined值的日志输出
## 扩展建议
可根据具体测试需求进一步扩展:
- 添加更多业务场景的日志模拟
- 增加日志过滤和搜索功能
- 实现日志导出功能
- 添加性能监控图表
- 增加自定义异常格式测试用例
\ No newline at end of file
// 测试异常格式日志功能
class TestAbnormalLogGenerator {
static DOMAIN_APP = 0x0001;
static TAG_APP = 'BigLogApp';
static info(domain, tag, format, ...args) {
console.log(`[INFO] [Domain:${domain}] [${tag}] ${format}`, ...args);
}
static error(domain, tag, format, ...args) {
console.log(`[ERROR] [Domain:${domain}] [${tag}] ${format}`, ...args);
}
// 模拟异常格式日志生成
static generateAbnormalFormatLogs() {
console.log('\n=== 开始测试异常格式日志 ===');
// 1. 参数数量不匹配
console.log('--- 测试1: 参数数量不匹配 ---');
try {
this.info(this.DOMAIN_APP, this.TAG_APP,
'用户%{public}s执行%{public}s操作成功', '张三');
} catch (e) {
this.error(this.DOMAIN_APP, this.TAG_APP,
'格式1异常捕获: %{public}s', e.message);
}
// 2. 参数类型不匹配
console.log('--- 测试2: 参数类型不匹配 ---');
try {
this.info(this.DOMAIN_APP, this.TAG_APP,
'HTTP请求状态码:%{public}d', 'not_a_number');
} catch (e) {
this.error(this.DOMAIN_APP, this.TAG_APP,
'格式2异常捕获: %{public}s', e.message);
}
// 3. 缺少格式占位符
console.log('--- 测试3: 缺少格式占位符 ---');
try {
this.info(this.DOMAIN_APP, this.TAG_APP,
'数据库操作完成', '额外参数会被忽略');
} catch (e) {
this.error(this.DOMAIN_APP, this.TAG_APP,
'格式3异常捕获: %{public}s', e.message);
}
// 4. 格式字符串为空
console.log('--- 测试4: 格式字符串为空 ---');
try {
this.info(this.DOMAIN_APP, this.TAG_APP,
'', '参数1', '参数2');
} catch (e) {
this.error(this.DOMAIN_APP, this.TAG_APP,
'格式4异常捕获: %{public}s', e.message);
}
// 5. 复杂嵌套格式错误
console.log('--- 测试5: 复杂嵌套格式错误 ---');
try {
this.info(this.DOMAIN_APP, this.TAG_APP,
'系统资源使用 内存:%{public}d%% CPU:%{public}d%% 磁盘:%{public}d%%',
75, 'invalid_cpu', 90);
} catch (e) {
this.error(this.DOMAIN_APP, this.TAG_APP,
'格式5异常捕获: %{public}s', e.message);
}
console.log('=== 异常格式日志测试完成 ===\n');
}
// 模拟边界条件日志生成
static generateBoundaryLogs() {
console.log('\n=== 开始测试边界条件日志 ===');
// 1. 超长字符串
const longString = 'A'.repeat(10000);
console.log('--- 测试1: 超长字符串 ---');
this.info(this.DOMAIN_APP, this.TAG_APP,
'超长日志内容长度:%{public}d 字符串预览:%{public}s',
longString.length, longString.substring(0, 100));
// 2. 极端数值
console.log('--- 测试2: 极端数值 ---');
this.info(this.DOMAIN_APP, this.TAG_APP,
'极端数值测试 负数:%{public}d 零:%{public}d 超大数:%{public}d',
-999999, 0, 999999999);
// 3. 特殊字符
console.log('--- 测试3: 特殊字符 ---');
const specialChars = '特殊字符测试: !@#$%^&*()_+-=[]{}|;:,.<>?中文测试';
this.info(this.DOMAIN_APP, this.TAG_APP,
'特殊字符日志:%{public}s', specialChars);
// 4. 空值和undefined
console.log('--- 测试4: 空值和undefined ---');
this.info(this.DOMAIN_APP, this.TAG_APP,
'空值测试 null:%{public}s undefined:%{public}s', null, undefined);
console.log('=== 边界条件日志测试完成 ===\n');
}
}
// 运行测试
console.log('开始测试异常格式日志功能...\n');
TestAbnormalLogGenerator.generateAbnormalFormatLogs();
TestAbnormalLogGenerator.generateBoundaryLogs();
console.log('所有测试完成!');
\ No newline at end of file
plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
}
#!/bin/bash
# 清理HarmonyOS项目缓存脚本
echo "开始清理项目缓存..."
# 清理构建缓存
echo "清理build目录..."
rm -rf entry/build
rm -rf build
# 清理hvigor缓存
echo "清理hvigor缓存..."
rm -rf .hvigor
rm -rf hvigor/hvigor-cache
# 清理oh_modules
echo "清理oh_modules..."
rm -rf oh_modules
# 清理IDE相关缓存
echo "清理IDE缓存..."
find . -name "*.iml" -delete
find . -name ".idea" -type d -exec rm -rf {} + 2>/dev/null || true
echo "缓存清理完成!"
echo "建议操作:"
echo "1. 重启DevEco Studio"
echo "2. 重新导入项目"
echo "3. 重新构建项目"
\ No newline at end of file
/**
能导入的包
1. 来源插件
在 当前的 build.gradle.kts 定义可以搭配的插件
plugins {
alias(libs.plugins.androidApplication) // 提供 android.* 相关类
alias(libs.plugins.kotlinMultiplatform) // 提供 org.jetbrains.kotlin.gradle.* 相关类
alias(libs.plugins.composeMultiplatform) // 提供 compose 相关类
}
插件的版本在 libs.versions.toml 中定义:
2.Gradle 核心类
Gradle 本身提供的核心类(如 Copy、JavaVersion 等)自动可用,无需额外配置
*/
// 导入ExperimentalKotlinGradlePluginApi注解,用于开启Kotlin插件实验特性
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
// 导入JvmTarget,用于指定JVM的目标版本
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
// 应用Kotlin多平台插件
alias(libs.plugins.kotlinMultiplatform)
// 应用Compose多平台插件
alias(libs.plugins.composeMultiplatform)
// 应用Compose编译器插件
alias(libs.plugins.composeCompiler)
}
/**
Kotlin Multiplatform 配置
插件 libs.plugins.kotlinMultiplatform
负责 Kotlin 代码编译
Android: Kotlin/JVM → .class 文件 │
iOS: Kotlin/Native → LLVM IR │
OHOS: Kotlin/Native → LLVM IR
kotlin {
├── 目标平台 (androidTarget, jvm, iosX64, etc.)
│ ├── compilerOptions
│ └── binaries
├── cocoapods (iOS 特定)
├── sourceSets
│ ├── commonMain/commonTest
│ ├── platformMain/platformTest
│ └── customSourceSets
└── 其他配置
}
*/
kotlin {
// 配置OHOS(华为鸿蒙)arm64目标
ohosArm64 {
binaries.sharedLib {
baseName = "kn" // 共享库名称为kn
export(libs.compose.multiplatform.export) // 导出compose多平台库的接口
}
val main by compilations.getting // 获取主编译内容
val resource by main.cinterops.creating {
// 配置C interop(cinterop)资源
defFile(file("src/ohosArm64Main/cinterop/resource.def")) // cinterop定义文件
includeDirs(file("src/ohosArm64Main/cinterop/include")) // cinterop包含目录
}
}
// 配置各平台的依赖关系
sourceSets {
commonMain.dependencies {
implementation(compose.runtime) // 添加compose runtime依赖
implementation(compose.foundation) // 添加compose基础依赖
implementation(compose.material) // 添加compose material依赖
implementation(compose.ui) // 添加compose ui依赖
implementation(compose.components.resources) // 添加compose组件资源依赖
implementation(compose.components.uiToolingPreview) // 添加compose ui工具预览依赖
implementation(libs.kotlinx.coroutines.core) // 官方协程核心库
implementation(libs.atomicFu) // Kotlin AtomicFu原子库
}
val ohosArm64Main by getting {
dependencies {
api(libs.compose.multiplatform.export) // 导出compose多平台接口给依赖消费者
}
}
}
}
// 为 ohosArm64 目标配置依赖处理
val versionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
val cmpVersion = versionCatalog.findVersion("composeMultiplatform")
.orElseThrow { IllegalStateException("Missing version composeMultiplatform in version catalog") }
.requiredVersion
val skikoOverrideByCmpVersion = mapOf(
"1.9.2-ez-001" to "0.9.22.2-ez-001",
// 仅在需要强制的 CMP 版本这里新增
)
val skikoOhosOverride = skikoOverrideByCmpVersion[cmpVersion]
configurations.all {
val configName = name
if (configName.contains("ohos", ignoreCase = true)) {
incoming.afterResolve {
val resolvedSkiko = resolutionResult.allComponents
.mapNotNull { it.moduleVersion }
.firstOrNull { it.group == "org.jetbrains.skiko" && it.name == "skiko" }
logger.lifecycle(
"Resolved skiko for $configName: " +
(resolvedSkiko?.let { "${it.group}:${it.name}:${it.version}" } ?: "not found")
)
}
// exclude(group = "org.jetbrains.androidx.lifecycle")
// exclude(group = "org.jetbrains.androidx.savedstate")
}
resolutionStrategy {
eachDependency {
// 如果是 skiko 依赖,检查配置名称是否包含 ohos
if (requested.group == "org.jetbrains.skiko" && requested.name == "skiko") {
if (configName.contains("ohos", ignoreCase = true)) {
skikoOhosOverride?.let { useVersion(it) }
}
}
// 强制指定 androidx.collection 版本,避免冲突
if (requested.group == "androidx.collection" && requested.name == "collection") {
useVersion(libs.versions.androidx.collection.get())
}
}
}
exclude(group = "androidx.collection", module = "collection-jvm")
}
// Harmony App 输出目录(支持命令行 --harmonyAppPath)
val harmonyAppDir: File = run {
val cliPath = project.findProperty("harmonyAppPath") as String?
if (cliPath.isNullOrBlank()) {
// 默认:项目根目录 /harmonyApp
rootProject.file("harmonyApp")
} else {
// 命令行传入的路径
file(cliPath)
}
}
// 字符串首字母大写工具函数
fun String.capitalizeUS(): String = this.replaceFirstChar {
if (it.isLowerCase()) it.titlecase() else it.toString()
}
// 为不同类型(debug、release)OHOS构建注册Copy任务并发布到Harmony App目录
arrayOf("debug", "release").forEach { type ->
tasks.register<Copy>("publish${type.capitalizeUS()}BinariesToHarmonyApp") {
group = "harmony" // 归类到harmony任务组
dependsOn("link${type.capitalizeUS()}SharedOhosArm64") // 依赖于OHOS shared lib的链接任务
into(harmonyAppDir) // 输出目标目录
from("build/bin/ohosArm64/${type}Shared/libkn_api.h") { // 复制头文件
into("entry/src/main/cpp/include/") // 指定目录
}
from(project.file("build/bin/ohosArm64/${type}Shared/libkn.so")) { // 复制共享库文件
into("/entry/libs/arm64-v8a/") // 指定目标目录
}
}
}
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="450dp"
android:height="450dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M56.25,18V46L32,60 7.75,46V18L32,4Z"
android:fillColor="#6075f2"/>
<path
android:pathData="m41.5,26.5v11L32,43V60L56.25,46V18Z"
android:fillColor="#6b57ff"/>
<path
android:pathData="m32,43 l-9.5,-5.5v-11L7.75,18V46L32,60Z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="23.131"
android:centerY="18.441"
android:gradientRadius="42.132"
android:type="radial">
<item android:offset="0" android:color="#FF5383EC"/>
<item android:offset="0.867" android:color="#FF7F52FF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M22.5,26.5 L32,21 41.5,26.5 56.25,18 32,4 7.75,18Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="44.172"
android:startY="4.377"
android:endX="17.973"
android:endY="34.035"
android:type="linear">
<item android:offset="0" android:color="#FF33C3FF"/>
<item android:offset="0.878" android:color="#FF5383EC"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m32,21 l9.526,5.5v11L32,43 22.474,37.5v-11z"
android:fillColor="#000000"/>
</vector>
package com.dong.test1
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
internal fun App() {
MaterialTheme {
// 使用 Page 类型的状态,修复类型推断报错
var page by remember { mutableStateOf<com.dong.test1.navigation.Page>(com.dong.test1.navigation.Page.Home) }
// 首页状态提升:滚动、搜索、分组展开状态
val listState = androidx.compose.foundation.lazy.rememberLazyListState()
var searchQuery by remember { mutableStateOf("") }
val expandedGroups = remember {
mutableStateMapOf<com.dong.test1.navigation.ComposeGroup, Boolean>().apply {
com.dong.test1.navigation.ComposeGroup.entries.forEach { put(it, true) }
}
}
Column(
modifier = Modifier
.safeContentPadding()
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
when (val p = page) {
is com.dong.test1.navigation.Page.Home -> {
HomeScreen(
onSelect = { demo ->
page = com.dong.test1.navigation.Page.Detail(demo)
},
listState = listState,
searchQuery = searchQuery,
onSearchChange = { searchQuery = it },
expandedGroups = expandedGroups
)
}
is com.dong.test1.navigation.Page.Detail -> {
DemoScreen(demo = p.demo, onBack = { page = com.dong.test1.navigation.Page.Home })
}
}
}
}
}
package com.dong.test1
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.dong.test1.navigation.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import com.dong.test1.demos.material.*
import com.dong.test1.demos.ui.UiBoxDemo
import com.dong.test1.demos.benchmark.*
import com.dong.test1.demos.foundation.FoundationBasicTextDemo
import com.dong.test1.demos.foundation.FoundationLazyColumnDemo
import com.dong.test1.demos.ui.TestLogDemo
@Composable
internal fun HomeScreen(
onSelect: (com.dong.test1.navigation.ComponentDemo) -> Unit,
listState: androidx.compose.foundation.lazy.LazyListState,
searchQuery: String,
onSearchChange: (String) -> Unit,
expandedGroups: MutableMap<com.dong.test1.navigation.ComposeGroup, Boolean>
) {
val grouped = com.dong.test1.navigation.demosByGroup()
val filtered = grouped.mapValues { (_, demos) ->
if (searchQuery.isBlank()) demos
else demos.filter { d ->
d.title.contains(searchQuery, ignoreCase = true) ||
d.id.contains(searchQuery, ignoreCase = true)
}
}.filterValues { it.isNotEmpty() || searchQuery.isBlank() }
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp)
) {
// 顶部固定搜索框(避免 iOS LazyColumn 首项兼容性问题)
Card(backgroundColor = MaterialTheme.colors.surface) {
OutlinedTextField(
value = searchQuery,
onValueChange = onSearchChange,
label = { Text("搜索") },
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
singleLine = true
)
}
Spacer(Modifier.height(12.dp))
androidx.compose.foundation.lazy.LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
contentPadding = PaddingValues(vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
com.dong.test1.navigation.ComposeGroup.entries.forEach { group ->
val demos = filtered[group] ?: emptyList()
if (demos.isEmpty() && searchQuery.isNotBlank()) {
// 搜索无结果时该组隐藏
} else {
val expanded = expandedGroups[group] != false
item {
Card(
backgroundColor = Color.LightGray.copy(alpha = 0.3f), // surfaceVariant replacement
modifier = Modifier
.fillMaxWidth()
.clickable { expandedGroups[group] = !expanded }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
com.dong.test1.navigation.groupTitles[group] ?: group.name,
style = MaterialTheme.typography.h6
)
val countText =
if (searchQuery.isNotBlank()) "匹配:${demos.size}" else "共 ${demos.size} 项"
Text(countText, style = MaterialTheme.typography.caption)
}
Text(
text = if (expanded) "收起 ▾" else "展开 ▸",
color = MaterialTheme.colors.primary
)
}
}
}
if (expanded) {
items(demos) { demo ->
Card(backgroundColor = MaterialTheme.colors.surface) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onSelect(demo) }
.padding(16.dp), // Increased padding for better touch target
verticalAlignment = Alignment.CenterVertically
) {
Text(demo.title, style = MaterialTheme.typography.body1)
}
}
Divider()
}
}
}
}
}
}
}
@Composable
internal fun DemoScreen(demo: ComponentDemo, onBack: () -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(text = demo.title, style = MaterialTheme.typography.h5)
OutlinedButton(onClick = onBack) { Text("返回") }
}
Divider()
when (demo.id) {
// ui
"log"-> TestLogDemo()
"ui_box" -> UiBoxDemo()
// foundation
"foundation_basic_text" -> FoundationBasicTextDemo()
"foundation_lazy_column" -> FoundationLazyColumnDemo()
// benchmark
"benchmark_1500_text" -> Compose1500Text()
"benchmark_1500_view" -> ComposeView1500Page()
// material
"material_alert_dialog" -> AlertDialogSamples()
"material_app_bar" -> AppBarSamples()
"material_backdrop_scaffold" -> BackdropScaffoldSamples()
"material_badge" -> BadgeSamples()
"material_bottom_navigation" -> BottomNavigationSamples()
"material_bottom_sheet_scaffold" -> BottomSheetScaffoldSamples()
"material_button_samples" -> ButtonSamples()
"material_card_samples" -> CardSamples()
"material_content_alpha" -> ContentAlphaSamples()
"material_drawer_samples" -> DrawerSamples()
"material_elevation_samples" -> ElevationSamples()
"material_exposed_dropdown_menu" -> ExposedDropdownMenuSamples()
"material_fab_samples" -> FloatingActionButtonSamples()
"material_icon_button_samples" -> IconButtonSamples()
"material_list_samples" -> ListSamples()
"material_menu_samples" -> MenuSamples()
"material_modal_bottom_sheet" -> ModalBottomSheetSamples()
"material_navigation_rail" -> NavigationRailSample()
"material_progress_indicator" -> ProgressIndicatorSamples()
"material_pull_refresh" -> PullRefreshSamples()
"material_selection_controls" -> SelectionControlsSamples()
"material_slider_sample" -> SliderSample()
"material_surface_samples" -> SurfaceSamples()
"material_swipeable_samples" -> SwipeableSamples()
"material_swipe_to_dismiss" -> SwipeToDismissSamples()
"material_tab_samples" -> TabSamples()
"material_text_field_samples" -> TextFieldSamples()
"material_text_samples" -> TextSamples()
"material_theme_samples" -> ThemeSamples()
else -> {
if (demo.group == com.dong.test1.navigation.ComposeGroup.Platform) {
com.dong.test1.PlatformDemo(demo.id)
}
}
}
}
}
package com.dong.test1
class Greeting {
private val platform = getPlatform()
fun greet(): String {
return "Hello, ${platform.name}!"
}
}
\ No newline at end of file
package com.dong.test1
interface LogController {
fun stop()
val isRunning: Boolean
}
\ No newline at end of file
package com.dong.test1
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
internal data class PlatformDemoItem(val id: String, val title: String)
internal expect fun getPlatformDemoList(): List<PlatformDemoItem>
@androidx.compose.runtime.Composable
internal expect fun PlatformDemo(id: String)
package com.dong.test1
//speed日志生成速度
expect fun startLog(speed: Int, onLogGenerated: ((Int) -> Unit)? = null, onTimerUpdated: ((Long) -> Unit)? = null): LogController
// 创建独立的计时器控制器
expect fun createTimer(): TimerController
\ No newline at end of file
package com.dong.test1
interface TimerController {
fun start()
fun stop()
fun reset()
val isRunning: Boolean
val elapsedTime: Long
}
\ No newline at end of file
package com.dong.test1.demos.benchmark
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
internal fun Compose1500Text() {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
repeat(1500) { index ->
Text(
text = "Compose1500Text Item #$index",
fontSize = 16.sp,
modifier = Modifier
.width(300.dp)
.height(50.dp)
.border(1.dp, Color.Gray)
.padding(10.dp)
)
}
}
}
package com.dong.test1.demos.benchmark
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.ExperimentalResourceApi
@OptIn(ExperimentalResourceApi::class)
@Composable
internal fun ComposeView1500Page() {
val loaded = remember { mutableStateOf(false) }
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
repeat(1500) { index ->
Box(
modifier = Modifier
.size(300.dp, 100.dp)
.border(
width = 2.dp,
color = Color.Red,
)
.then(
if (index == 1499) Modifier.onGloballyPositioned {
loaded.value = true
//trace_tag_end()
} else Modifier
)
) {
Text(
text = "Item #$index",
)
}
}
}
if (loaded.value) {
println("页面加载完成 ✅")
}
}
\ No newline at end of file
package com.dong.test1.demos.foundation
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.BasicText
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
internal fun FoundationBasicTextDemo() {
var text by remember { mutableStateOf("Hello Compose") }
var size by remember { mutableStateOf(18f) }
var colorToggle by remember { mutableStateOf(false) }
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = text, onValueChange = { text = it }, label = { Text("内容") })
Text("字体大小: ${size.toInt()}sp")
Slider(value = size, onValueChange = { size = it }, valueRange = 12f..48f)
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = colorToggle, onCheckedChange = { colorToggle = it })
Text("使用高亮颜色")
}
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.surface)
.padding(12.dp)
) {
BasicText(
text = text,
style = androidx.compose.ui.text.TextStyle(
fontSize = size.sp,
color = if (colorToggle) MaterialTheme.colors.primary else Color.Unspecified
)
)
}
}
}
package com.dong.test1.demos.foundation
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
internal fun FoundationLazyColumnDemo() {
var count by remember { mutableStateOf(10) }
var spaced by remember { mutableStateOf(4) }
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("条目数量: $count")
Slider(value = count.toFloat(), onValueChange = { count = it.toInt() }, valueRange = 1f..50f)
Text("条目间距: ${spaced}dp")
Slider(value = spaced.toFloat(), onValueChange = { spaced = it.toInt() }, valueRange = 0f..24f)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.height(220.dp)
.background(MaterialTheme.colors.surface),
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(spaced.dp)
) {
items((1..count).toList()) { idx ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.background(MaterialTheme.colors.secondary),
contentAlignment = Alignment.Center
) {
Text("Item #$idx")
}
}
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.AlertDialog
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.*
@Composable
internal fun AlertDialogSamples() {
var open by remember { mutableStateOf(true) }
var dismissOnClickOutside by remember { mutableStateOf(true) }
var dismissOnBackPress by remember { mutableStateOf(true) }
if (open) {
AlertDialog(
onDismissRequest = {
if (dismissOnBackPress) open = false
},
title = { Text("AlertDialog") },
text = { Text("展示标题、内容、确认/取消按钮,以及关闭行为。") },
confirmButton = {
TextButton(onClick = { open = false }) { Text("确认") }
},
dismissButton = {
TextButton(onClick = { if (dismissOnClickOutside) open = false }) { Text("取消") }
}
)
}
}
package com.dong.test1.demos.material
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
internal fun AppBarSamples() {
var title by remember { mutableStateOf("TopAppBar 标题") }
var showActions by remember { mutableStateOf(true) }
var elevation by remember { mutableStateOf(4f) }
Column {
TopAppBar(
title = { Text(title) },
actions = {
if (showActions) {
TextButton(onClick = { title = "点击了 Action" }) { Text("Action") }
}
},
elevation = elevation.dp
)
Spacer(Modifier.height(8.dp))
BottomAppBar {
Text("BottomAppBar")
}
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = title, onValueChange = { title = it }, label = { Text("标题") })
Text("阴影: ${elevation.toInt()}dp")
Slider(value = elevation, onValueChange = { elevation = it }, valueRange = 0f..16f)
Row {
Checkbox(checked = showActions, onCheckedChange = { showActions = it })
Text("显示 Action")
}
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun BackdropScaffoldSamples() {
val scaffoldState = rememberBackdropScaffoldState(BackdropValue.Concealed)
val scope = rememberCoroutineScope()
var clicks by remember { mutableStateOf(0) }
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = { TopAppBar(title = { Text("BackdropScaffold") }) },
backLayerContent = {
Column(Modifier.fillMaxWidth().padding(12.dp)) {
Text("后层内容")
Button(onClick = { clicks++ }) { Text("点击: $clicks") }
}
},
frontLayerContent = {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(
onClick = {
scope.launch {
if (scaffoldState.isConcealed) scaffoldState.reveal() else scaffoldState.conceal()
}
}
) { Text(if (scaffoldState.isConcealed) "展开前层" else "收起前层") }
}
}
)
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
@Composable
internal fun BadgeSamples() {
var count by remember { mutableStateOf(3) }
BadgedBox(badge = { Text("$count") }) {
Text("带徽章的内容")
}
Row(Modifier.padding(12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(onClick = { count++ }) { Text("+1") }
Button(onClick = { count = 0 }) { Text("清零") }
}
}
package com.dong.test1.demos.material
import androidx.compose.material.*
import androidx.compose.runtime.*
@Composable
internal fun BottomNavigationSamples() {
var selected by remember { mutableStateOf(0) }
BottomNavigation {
listOf("主页", "消息", "设置").forEachIndexed { index, label ->
BottomNavigationItem(
selected = selected == index,
onClick = { selected = index },
icon = { },
label = { Text(label) }
)
}
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun BottomSheetScaffoldSamples() {
val sheetState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = sheetState,
sheetContent = {
Column(Modifier.padding(12.dp)) {
Text("BottomSheet 内容")
Text("可在此添加交互与属性")
}
},
sheetPeekHeight = 64.dp
) { padding ->
Column(Modifier.padding(padding).padding(12.dp)) {
Button(onClick = {
scope.launch {
val state = sheetState.bottomSheetState
if (state.isCollapsed) state.expand() else state.collapse()
}
}) { Text("展开/收起 BottomSheet") }
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
@Composable
internal fun ButtonSamples() {
var enabled by remember { mutableStateOf(true) }
var clicks by remember { mutableStateOf(0) }
Button(
enabled = enabled,
onClick = { clicks++ },
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFF4CAF50), contentColor = Color.White)
) {
Text("Material Button 点击: $clicks")
}
}
package com.dong.test1.demos.material
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@Composable
internal fun CardSamples() {
var elevation by remember { mutableStateOf(4f) }
Card(elevation = elevation.dp) {
Column(Modifier.padding(12.dp)) {
Text("Material Card 标题")
Text("阴影 elevation: ${elevation.toInt()}dp")
}
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
@Composable
internal fun ContentAlphaSamples() {
var disabled by remember { mutableStateOf(false) }
Column(Modifier.padding(12.dp)) {
Row {
Checkbox(checked = disabled, onCheckedChange = { disabled = it })
Text("使用 ContentAlpha.disabled")
}
CompositionLocalProvider(LocalContentAlpha provides if (disabled) ContentAlpha.disabled else ContentAlpha.high) {
Text("受 ContentAlpha 控制的文本")
}
Text("不受影响的文本")
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@Composable
internal fun DrawerSamples() {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalDrawer(
drawerState = drawerState,
drawerContent = {
Column(Modifier.padding(12.dp)) {
Text("Drawer 内容")
Button(onClick = { scope.launch { drawerState.close() } }) { Text("关闭") }
}
}
) {
Column(Modifier.padding(12.dp)) {
Button(onClick = { scope.launch { drawerState.open() } }) { Text("打开 Drawer") }
}
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.Slider
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
@Composable
internal fun ElevationSamples() {
var elevation by remember { mutableStateOf(4f) }
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("阴影: ${elevation.toInt()}dp")
Slider(value = elevation, onValueChange = { elevation = it }, valueRange = 0f..24f)
Surface(elevation = elevation.dp) {
Box(Modifier.size(120.dp).padding(12.dp)) { Text("Surface with elevation") }
}
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun ExposedDropdownMenuSamples() {
val options = listOf("选项 A", "选项 B", "选项 C")
var expanded by remember { mutableStateOf(false) }
var selected by remember { mutableStateOf(options.first()) }
ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = !expanded }) {
OutlinedTextField(
value = selected, onValueChange = {}, readOnly = true, label = { Text("选择项") }
)
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
options.forEach { option ->
DropdownMenuItem(onClick = { selected = option; expanded = false }) {
Text(option)
}
}
}
}
Spacer(Modifier.height(8.dp))
Text("当前选择: $selected")
}
package com.dong.test1.demos.material
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Text
import androidx.compose.runtime.*
@Composable
internal fun FloatingActionButtonSamples() {
var clicks by remember { mutableStateOf(0) }
FloatingActionButton(onClick = { clicks++ }) { Text("$clicks") }
}
package com.dong.test1.demos.material
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.*
@Composable
internal fun IconButtonSamples() {
var clicks by remember { mutableStateOf(0) }
IconButton(onClick = { clicks++ }) {
Text("★")
}
Text("点击次数: $clicks")
}
@file:OptIn(androidx.compose.material.ExperimentalMaterialApi::class)
package com.dong.test1.demos.material
import androidx.compose.material.ListItem
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
@Composable
internal fun ListSamples() {
val items = (1..20).map { "Item $it" }
LazyColumn(contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
items(items) { label ->
ListItem(text = { Text(label) }, secondaryText = { Text("副标题") })
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
@Composable
internal fun MenuSamples() {
var expanded by remember { mutableStateOf(false) }
Column {
Button(onClick = { expanded = true }) { Text("打开菜单") }
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(onClick = { expanded = false }) { Text("菜单项 1") }
DropdownMenuItem(onClick = { expanded = false }) { Text("菜单项 2") }
}
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun ModalBottomSheetSamples() {
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = { Column(Modifier.padding(12.dp)) { Text("ModalBottomSheet 内容") } }
) {
Column(Modifier.padding(12.dp)) {
Button(onClick = { scope.launch { sheetState.show() } }) { Text("显示") }
Spacer(Modifier.height(8.dp))
Button(onClick = { scope.launch { sheetState.hide() } }) { Text("隐藏") }
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.NavigationRail
import androidx.compose.material.NavigationRailItem
import androidx.compose.material.Text
import androidx.compose.runtime.*
@Composable
internal fun NavigationRailSample() {
var selected by remember { mutableStateOf(0) }
NavigationRail {
listOf("主页", "消息", "设置").forEachIndexed { index, label ->
NavigationRailItem(
selected = selected == index,
onClick = { selected = index },
icon = { Text("★") },
label = { Text(label) }
)
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
internal fun ProgressIndicatorSamples() {
var progress by remember { mutableStateOf(0.3f) }
Column(Modifier.fillMaxWidth().padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
LinearProgressIndicator(progress = progress, modifier = Modifier.fillMaxWidth())
CircularProgressIndicator(progress = progress)
}
}
package com.dong.test1.demos.material
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
internal fun PullRefreshSamples() {
var refreshing by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Button(onClick = {
scope.launch {
refreshing = true
delay(1000)
refreshing = false
}
}) { Text("手动触发刷新") }
if (refreshing) LinearProgressIndicator(Modifier.fillMaxWidth())
Text(if (refreshing) "刷新中..." else "刷新完成")
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
@Composable
internal fun SelectionControlsSamples() {
var checked by remember { mutableStateOf(true) }
var selected by remember { mutableStateOf(0) }
var switchOn by remember { mutableStateOf(true) }
Column(Modifier.padding(12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = checked, onCheckedChange = { checked = it })
Text("Checkbox: ${if (checked) "选中" else "未选"}")
}
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
RadioButton(selected = selected == 0, onClick = { selected = 0 })
RadioButton(selected = selected == 1, onClick = { selected = 1 })
Text("Radio 选择: $selected")
}
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
Switch(checked = switchOn, onCheckedChange = { switchOn = it })
Text("Switch: ${if (switchOn) "" else ""}")
}
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*
@Composable
internal fun SliderSample() {
var value by remember { mutableStateOf(0.5f) }
Column(Modifier.padding(12.dp)) {
Text("当前值: ${kotlin.math.round(value * 10.0f) / 10.0f}")
Slider(value = value, onValueChange = { value = it }, valueRange = 0f..1f, steps = 4)
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
internal fun SurfaceSamples() {
var elevation by remember { mutableStateOf(6f) }
var rounded by remember { mutableStateOf(12f) }
var bg by remember { mutableStateOf(Color(0xFFE0F7FA)) }
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Surface(shape = RoundedCornerShape(rounded.dp), elevation = elevation.dp, color = bg) {
Column(Modifier.padding(12.dp)) {
Text("Surface 属性演示")
Text("elevation=${elevation.toInt()} shape=${rounded.toInt()} color=${bg}")
}
}
Text("elevation: ${elevation.toInt()}dp"); Slider(value = elevation, onValueChange = { elevation = it }, valueRange = 0f..24f)
Text("圆角: ${rounded.toInt()}dp"); Slider(value = rounded, onValueChange = { rounded = it }, valueRange = 0f..32f)
}
}
package com.dong.test1.demos.material
import androidx.compose.ui.Modifier
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun SwipeToDismissSamples() {
val dismissState = rememberDismissState()
SwipeToDismiss(
state = dismissState,
background = {
Box(Modifier.fillMaxWidth().height(64.dp)) { Text("滑动删除背景") }
},
dismissContent = {
Box(Modifier.fillMaxWidth().height(64.dp)) { Text("可滑动删除内容") }
}
)
Text("当前状态: ${dismissState.currentValue}")
}
@file:OptIn(androidx.compose.material.ExperimentalMaterialApi::class)
package com.dong.test1.demos.material
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.DismissDirection
import androidx.compose.material.DismissValue
import androidx.compose.material.Divider
import androidx.compose.material.ListItem
import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.Text
import androidx.compose.material.rememberDismissState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
internal fun SwipeableSamples() {
val mails = remember { mutableStateListOf("Mail 1", "Mail 2", "Mail 3") }
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(vertical = 8.dp)
) {
items(mails, key = { mail -> mail }) { mail ->
val dismissState = rememberDismissState(
confirmStateChange = { newValue: DismissValue ->
if (newValue == DismissValue.DismissedToEnd || newValue == DismissValue.DismissedToStart) {
mails.remove(mail)
}
true
}
)
SwipeToDismiss(
state = dismissState,
directions = setOf(
DismissDirection.StartToEnd,
DismissDirection.EndToStart
),
background = {
val bgColor = when (dismissState.targetValue) {
DismissValue.Default -> Color.Transparent
DismissValue.DismissedToEnd -> Color(0xFF2E7D32) // Archive
DismissValue.DismissedToStart -> Color(0xFFC62828) // Delete
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
.background(bgColor),
contentAlignment = Alignment.CenterStart
) {
Text(
text = if (dismissState.targetValue == DismissValue.DismissedToEnd) "Archive" else "Delete",
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = 16.dp)
)
}
},
dismissContent = {
ListItem(
text = { Text(mail) },
secondaryText = { Text("Swipe me left or right") }
)
}
)
Divider()
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.Text
import androidx.compose.runtime.*
@Composable
internal fun TabSamples() {
var selectedTab by remember { mutableStateOf(0) }
val tabs = listOf("Tab1", "Tab2", "Tab3")
TabRow(selectedTabIndex = selectedTab) {
tabs.forEachIndexed { index, title ->
Tab(selected = selectedTab == index, onClick = { selectedTab = index }, text = { Text(title) })
}
}
}
package com.dong.test1.demos.material
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.*
@Composable
internal fun TextFieldSamples() {
var text by remember { mutableStateOf("") }
var enabled by remember { mutableStateOf(true) }
var isError by remember { mutableStateOf(false) }
OutlinedTextField(
value = text,
onValueChange = { text = it },
enabled = enabled,
isError = isError,
label = { Text("Label") },
placeholder = { Text("Placeholder") }
)
}
package com.dong.test1.demos.material
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
internal fun TextSamples() {
var size by remember { mutableStateOf(16f) }
var bold by remember { mutableStateOf(false) }
Column(Modifier.padding(12.dp)) {
Text("普通文本", fontSize = size.sp, fontWeight = if (bold) FontWeight.Bold else FontWeight.Normal)
Text("副标题", style = MaterialTheme.typography.subtitle1)
Text("标题", style = MaterialTheme.typography.h6)
Text("正文", style = MaterialTheme.typography.body1)
Text("按钮", style = MaterialTheme.typography.button)
Text("说明文字", style = MaterialTheme.typography.caption)
Spacer(Modifier.height(8.dp))
Text("字体大小: ${size.toInt()}sp"); Slider(value = size, onValueChange = { size = it }, valueRange = 12f..36f)
Row { Checkbox(checked = bold, onCheckedChange = { bold = it }); Text("加粗") }
}
}
package com.dong.test1.demos.material
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
@Composable
internal fun ThemeSamples() {
// 演示 MaterialTheme 的基本用法;完整主题切换后续扩展
Text("当前 MaterialTheme.typography.body1 示例文本")
}
package com.dong.test1.demos.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalLayoutApi::class)
@Composable
internal fun UiBoxDemo() {
val alignments = listOf(
Alignment.TopStart, Alignment.TopCenter, Alignment.TopEnd,
Alignment.CenterStart, Alignment.Center, Alignment.CenterEnd,
Alignment.BottomStart, Alignment.BottomCenter, Alignment.BottomEnd
)
var selected by remember { mutableStateOf(Alignment.Center) }
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("Alignment")
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
alignments.forEach { a ->
val label = a.toString().removePrefix("Alignment.")
if (selected == a) {
Button(onClick = { selected = a }) {
Text(label)
}
} else {
OutlinedButton(onClick = { selected = a }) {
Text(label)
}
}
}
}
Box(
modifier = Modifier
.size(220.dp)
.background(MaterialTheme.colors.surface) // surfaceVariant -> surface
) {
Box(
modifier = Modifier
.size(48.dp)
.align(selected)
.background(MaterialTheme.colors.primary) // primary -> primary
)
}
}
}
package com.dong.test1.demos.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.dong.test1.startLog
import com.dong.test1.createTimer
import com.dong.test1.LogController
import com.dong.test1.TimerController
import kotlinx.coroutines.delay
@OptIn(ExperimentalLayoutApi::class)
@Composable
internal fun TestLogDemo() {
// 日志参数状态
var logSpeed by remember { mutableStateOf("100") }
var logCount by remember { mutableStateOf(0) }
var isGenerating by remember { mutableStateOf(false) }
var logController by remember { mutableStateOf<LogController?>(null) }
// 计时器状态
var timerController by remember { mutableStateOf<TimerController?>(null) }
var displayTime by remember { mutableStateOf(0L) }
// 初始化计时器
LaunchedEffect(Unit) {
timerController = createTimer()
}
// 定期更新显示时间
LaunchedEffect(timerController) {
while (true) {
delay(100) // 每100ms更新一次显示
displayTime = timerController?.elapsedTime ?: 0
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 计时器显示区域
Card(backgroundColor = MaterialTheme.colors.surface) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
"运行时间",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 12.dp)
)
// 时间显示
Card(
backgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.1f),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
"${displayTime}秒",
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.primary
)
if (isGenerating) {
Text(
"日志生成中...",
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.secondary
)
}
}
}
}
}
// 日志生成功能区域
Card(backgroundColor = MaterialTheme.colors.surface) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
"日志生成工具",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 12.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = logSpeed,
onValueChange = { newValue ->
// 只允许数字输入
if (newValue.all { it.isDigit() }) {
logSpeed = newValue
}
},
label = { Text("间隔(ms)") },
modifier = Modifier.weight(1f),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true
)
}
Spacer(Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = {
val speed = logSpeed.toIntOrNull() ?: 100
logCount = 0
// 重置并启动计时器
timerController?.reset()
timerController?.start()
// 启动日志生成
isGenerating = true
logController = startLog(speed,
onLogGenerated = { count ->
logCount = count
},
onTimerUpdated = { time ->
// 日志生成的计时现在只用于内部统计,不更新UI
}
)
},
modifier = Modifier.weight(1f),
enabled = logSpeed.isNotEmpty() && !isGenerating
) {
Text("开始生成")
}
Button(
onClick = {
logController?.stop()
timerController?.stop()
isGenerating = false
},
modifier = Modifier.weight(1f),
enabled = isGenerating
) {
Text("停止生成")
}
}
// 显示日志生成状态
if (isGenerating || logCount > 0) {
Spacer(Modifier.height(12.dp))
Card(
backgroundColor = MaterialTheme.colors.secondary.copy(alpha = 0.1f),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
"生成日志数量",
style = MaterialTheme.typography.subtitle1,
color = MaterialTheme.colors.secondary
)
Text(
"$logCount 条",
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.secondary,
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
}
}
}
}
\ No newline at end of file
package com.dong.test1.navigation
enum class ComposeGroup {
Ui, Foundation, Material, Benchmark, Platform
}
data class ComponentDemo(
val id: String,
val title: String,
val group: ComposeGroup
)
val componentDemos: List<ComponentDemo> = listOf(
// ui
ComponentDemo("ui_box", "Box", ComposeGroup.Ui),
ComponentDemo("log", "测试大量日志", ComposeGroup.Ui),
// foundation
ComponentDemo("foundation_basic_text", "BasicText", ComposeGroup.Foundation),
ComponentDemo("foundation_lazy_column", "LazyColumn", ComposeGroup.Foundation),
// material 组:新增完整清单
ComponentDemo("material_alert_dialog", "AlertDialogSample", ComposeGroup.Material),
ComponentDemo("material_app_bar", "AppBarSamples", ComposeGroup.Material),
ComponentDemo("material_backdrop_scaffold", "BackdropScaffoldSamples", ComposeGroup.Material),
ComponentDemo("material_badge", "BadgeSamples", ComposeGroup.Material),
ComponentDemo("material_bottom_navigation", "BottomNavigationSamples", ComposeGroup.Material),
ComponentDemo("material_bottom_sheet_scaffold", "BottomSheetScaffoldSamples", ComposeGroup.Material),
ComponentDemo("material_button_samples", "ButtonSamples", ComposeGroup.Material),
ComponentDemo("material_card_samples", "CardSamples", ComposeGroup.Material),
ComponentDemo("material_chip_samples", "ChipSamples", ComposeGroup.Material),
ComponentDemo("material_content_alpha", "ContentAlphaSamples", ComposeGroup.Material),
ComponentDemo("material_drawer_samples", "DrawerSamples", ComposeGroup.Material),
ComponentDemo("material_elevation_samples", "ElevationSamples", ComposeGroup.Material),
ComponentDemo("material_exposed_dropdown_menu", "ExposedDropdownMenuSamples", ComposeGroup.Material),
ComponentDemo("material_fab_samples", "FloatingActionButtonSamples", ComposeGroup.Material),
ComponentDemo("material_icon_button_samples", "IconButtonSamples", ComposeGroup.Material),
ComponentDemo("material_list_samples", "ListSamples", ComposeGroup.Material),
ComponentDemo("material_menu_samples", "MenuSamples", ComposeGroup.Material),
ComponentDemo("material_modal_bottom_sheet", "ModalBottomSheetSamples", ComposeGroup.Material),
ComponentDemo("material_navigation_rail", "NavigationRailSample", ComposeGroup.Material),
ComponentDemo("material_progress_indicator", "ProgressIndicatorSamples", ComposeGroup.Material),
ComponentDemo("material_pull_refresh", "PullRefreshSamples", ComposeGroup.Material),
ComponentDemo("material_scaffold_samples", "ScaffoldSamples", ComposeGroup.Material),
ComponentDemo("material_selection_controls", "SelectionControlsSamples", ComposeGroup.Material),
ComponentDemo("material_slider_sample", "SliderSample", ComposeGroup.Material),
ComponentDemo("material_surface_samples", "SurfaceSamples", ComposeGroup.Material),
ComponentDemo("material_swipeable_samples", "SwipeableSamples", ComposeGroup.Material),
ComponentDemo("material_swipe_to_dismiss", "SwipeToDismissSamples", ComposeGroup.Material),
ComponentDemo("material_tab_samples", "TabSamples", ComposeGroup.Material),
ComponentDemo("material_text_field_samples", "TextFieldSamples", ComposeGroup.Material),
ComponentDemo("material_text_samples", "TextSamples", ComposeGroup.Material),
ComponentDemo("material_theme_samples", "ThemeSamples", ComposeGroup.Material),
// benchmark
ComponentDemo("benchmark_1500_text", "1500 Text", ComposeGroup.Benchmark),
ComponentDemo("benchmark_1500_view", "1500 View", ComposeGroup.Benchmark),
)
val groupTitles: Map<ComposeGroup, String> = mapOf(
ComposeGroup.Ui to "compose.ui",
ComposeGroup.Foundation to "compose.foundation",
ComposeGroup.Material to "compose.material",
ComposeGroup.Platform to "Platform",
)
fun demosByGroup(): Map<ComposeGroup, List<ComponentDemo>> {
val standardDemos = ComposeGroup.entries.filter { it != ComposeGroup.Platform }
.associateWith { g -> componentDemos.filter { it.group == g } }
.toMutableMap()
val platformDemos = com.dong.test1.getPlatformDemoList().map {
ComponentDemo(
id = it.id,
title = it.title,
group = ComposeGroup.Platform
)
}
if (platformDemos.isNotEmpty()) {
standardDemos[ComposeGroup.Platform] = platformDemos
}
return standardDemos
}
fun demosJson(): String = buildString {
append('[')
componentDemos.forEachIndexed { index, demo ->
if (index > 0) append(',')
append("{\"id\":\"")
append(jsonEscape(demo.id))
append("\",\"title\":\"")
append(jsonEscape(demo.title))
append("\",\"group\":\"")
append(demo.group.name)
append("\"}")
}
append(']')
}
fun groupTitlesJson(): String = buildString {
append('{')
ComposeGroup.entries.forEachIndexed { index, group ->
if (index > 0) append(',')
append('"')
append(group.name)
append("\":\"")
append(jsonEscape(groupTitles[group] ?: group.name))
append('"')
}
append('}')
}
private fun jsonEscape(input: String): String = buildString(input.length) {
input.forEach { ch ->
when (ch) {
'\\' -> append("\\\\")
'"' -> append("\\\"")
'\n' -> append("\\n")
'\r' -> append("\\r")
'\t' -> append("\\t")
else -> append(ch)
}
}
}
sealed interface Page {
data object Home : Page
data class Detail(val demo: ComponentDemo) : Page
}
package com.dong.test1
import kotlin.test.Test
import kotlin.test.assertEquals
class ComposeAppCommonTest {
@Test
fun example() {
assertEquals(3, 1 + 2)
}
}
\ No newline at end of file
#ifndef GLOBAL_RAW_FILE_H
#define GLOBAL_RAW_FILE_H
#ifdef __cplusplus
extern "C" {
#endif
struct RawFile;
typedef struct RawFile RawFile;
int OH_ResourceManager_ReadRawFile(const RawFile *rawFile, void *buf, size_t length);
long OH_ResourceManager_GetRawFileSize(RawFile *rawFile);
void OH_ResourceManager_CloseRawFile(RawFile *rawFile);
#ifdef __cplusplus
};
#endif
/** @} */
#endif // GLOBAL_RAW_FILE_H
#ifndef GLOBAL_NATIVE_RESOURCE_MANAGER_H
#define GLOBAL_NATIVE_RESOURCE_MANAGER_H
#include "napi/native_api.h"
#include "raw_file.h"
#ifdef __cplusplus
extern "C" {
#endif
struct NativeResourceManager;
typedef struct NativeResourceManager NativeResourceManager;
NativeResourceManager *OH_ResourceManager_InitNativeResourceManager(napi_env env, napi_value jsResMgr);
void OH_ResourceManager_ReleaseNativeResourceManager(NativeResourceManager *resMgr);
RawFile *OH_ResourceManager_OpenRawFile(const NativeResourceManager *mgr, const char *fileName);
#ifdef __cplusplus
};
#endif
/** @} */
#endif // GLOBAL_NATIVE_RESOURCE_MANAGER_H
package = platform.resource
headers = raw_file_manager.h raw_file.h
package com.dong.test1
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.arkui.ArkUIView
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.interop.AdaptiveParams
import androidx.compose.ui.interop.InteropContainer
import androidx.compose.ui.napi.JsObject
import androidx.compose.ui.napi.js
private val NoOp: Any.() -> Unit = {}
@Composable
internal fun ArkUIView(
name: String,
modifier: Modifier,
parameter: JsObject = js(),
update: (JsObject) -> Unit = NoOp,
background: Color = Color.Unspecified,
updater: (ArkUIView) -> Unit = NoOp,
onCreate: (ArkUIView) -> Unit = NoOp,
onRelease: (ArkUIView) -> Unit = NoOp,
interactive: Boolean = true,
adaptiveParams: AdaptiveParams? = null,
tag: String? = null,
container: InteropContainer = InteropContainer.BACK
) = androidx.compose.ui.interop.ArkUIView(
name = name,
modifier = modifier,
parameter = parameter,
update = update,
background = background,
updater = updater,
onCreate = onCreate,
onRelease = onRelease,
interactive = interactive,
adaptiveParams = adaptiveParams,
tag = tag,
container = container,
)
package com.dong.test1
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.napi.js
import androidx.compose.ui.unit.dp
@Composable
internal fun InteropListNested() {
LazyColumn(Modifier.fillMaxSize()) {
items(20) { index ->
when {
index == 3 -> {
// Nested Vertical List (ArkUI)
ArkUIView(
name = "verticalList",
modifier = Modifier.fillMaxWidth().height(300.dp).background(Color.LightGray),
parameter = js { }
)
}
index == 7 -> {
// Horizontal List (ArkUI) - assuming horizontalList exists and works similar
ArkUIView(
name = "horizontalList",
modifier = Modifier.fillMaxWidth().height(150.dp).background(Color.LightGray),
parameter = js { }
)
}
index % 3 == 0 -> {
// Button (ArkUI)
ArkUIView(
name = "button",
modifier = Modifier.fillMaxWidth().height(60.dp),
parameter = js {
"text"("ArkUI Button $index")
"backgroundColor"("#FF0000FF")
}
)
}
else -> {
// Compose Text
Text("Compose Item $index", Modifier.padding(16.dp))
}
}
}
}
}
package com.dong.test1
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.napi.js
import androidx.compose.ui.unit.dp
@Composable
internal fun InteropListSimple() {
Box {
Column(Modifier.background(Color.LightGray).fillMaxSize()) {
LazyColumn(Modifier.background(Color.Red).fillMaxSize()) {
items(80) { index ->
Column {
ArkUIView(
name = "label",
modifier = Modifier.width(250.dp).height(100.dp),
parameter = js {
"text"("ArkUI Button $index")
"backgroundColor"("#FF0000FF")
},
)
Button({
println("Compose Button $index clicked")
}, modifier = Modifier.height(30.dp).fillMaxWidth()) {
Text("Compose Button $index")
}
}
}
}
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment