feat(device): 添加USB设备通信支持和程序参数优化
- 在AndroidManifest.xml中添加USB Host权限和设备过滤器配置 - 新增设备控制国际化词条包括速度档位、吹气时间等 - 重构数据库结构将速度相关字段统一为档位数值存储 - 添加通用KV存储方法用于settings表数据读写 - 优化首页导航实现tab间跳转和状态保持功能 - 更新程序详情页面布局和参数表单界面 - 移除模拟运行器相关测试代码 - 添加USB串口通信依赖包usb_serial
This commit is contained in:
126
lib/features/device/models/serial_config.dart
Normal file
126
lib/features/device/models/serial_config.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
import 'dart:convert';
|
||||
|
||||
/// 串口奇偶校验
|
||||
enum SerialParity { none, odd, even, mark, space }
|
||||
|
||||
/// 串口流控
|
||||
enum SerialFlowControl { none, rtsCts, xonXoff, dtrDsr }
|
||||
|
||||
/// 串口配置
|
||||
///
|
||||
/// 持久化到 settings 表的 `serial_config` key 中;
|
||||
/// 打开串口时根据此配置构造底层 `UsbConfig`。
|
||||
class SerialConfig {
|
||||
/// 设备 Vendor ID(十六进制)
|
||||
final int vendorId;
|
||||
|
||||
/// 设备 Product ID(十六进制),0 表示不指定
|
||||
final int productId;
|
||||
|
||||
/// 波特率
|
||||
final int baudRate;
|
||||
|
||||
/// 数据位 (5/6/7/8)
|
||||
final int dataBits;
|
||||
|
||||
/// 停止位 (1/2)
|
||||
final int stopBits;
|
||||
|
||||
/// 校验位
|
||||
final SerialParity parity;
|
||||
|
||||
/// 流控
|
||||
final SerialFlowControl flowControl;
|
||||
|
||||
/// 读超时(毫秒)
|
||||
final int readTimeoutMs;
|
||||
|
||||
/// 写超时(毫秒)
|
||||
final int writeTimeoutMs;
|
||||
|
||||
const SerialConfig({
|
||||
this.vendorId = 0x1A86,
|
||||
this.productId = 0x7523,
|
||||
this.baudRate = 9600,
|
||||
this.dataBits = 8,
|
||||
this.stopBits = 1,
|
||||
this.parity = SerialParity.none,
|
||||
this.flowControl = SerialFlowControl.none,
|
||||
this.readTimeoutMs = 2000,
|
||||
this.writeTimeoutMs = 2000,
|
||||
});
|
||||
|
||||
/// 默认配置
|
||||
static const SerialConfig defaults = SerialConfig();
|
||||
|
||||
/// 常用波特率
|
||||
static const List<int> commonBaudRates = [
|
||||
1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800,
|
||||
];
|
||||
|
||||
SerialConfig copyWith({
|
||||
int? vendorId,
|
||||
int? productId,
|
||||
int? baudRate,
|
||||
int? dataBits,
|
||||
int? stopBits,
|
||||
SerialParity? parity,
|
||||
SerialFlowControl? flowControl,
|
||||
int? readTimeoutMs,
|
||||
int? writeTimeoutMs,
|
||||
}) {
|
||||
return SerialConfig(
|
||||
vendorId: vendorId ?? this.vendorId,
|
||||
productId: productId ?? this.productId,
|
||||
baudRate: baudRate ?? this.baudRate,
|
||||
dataBits: dataBits ?? this.dataBits,
|
||||
stopBits: stopBits ?? this.stopBits,
|
||||
parity: parity ?? this.parity,
|
||||
flowControl: flowControl ?? this.flowControl,
|
||||
readTimeoutMs: readTimeoutMs ?? this.readTimeoutMs,
|
||||
writeTimeoutMs: writeTimeoutMs ?? this.writeTimeoutMs,
|
||||
);
|
||||
}
|
||||
|
||||
/// 编码为 JSON 字符串(用于持久化)
|
||||
String toJsonString() => jsonEncode({
|
||||
'vendorId': vendorId,
|
||||
'productId': productId,
|
||||
'baudRate': baudRate,
|
||||
'dataBits': dataBits,
|
||||
'stopBits': stopBits,
|
||||
'parity': parity.name,
|
||||
'flowControl': flowControl.name,
|
||||
'readTimeoutMs': readTimeoutMs,
|
||||
'writeTimeoutMs': writeTimeoutMs,
|
||||
});
|
||||
|
||||
/// 从 JSON 字符串解码;解析失败时返回默认值
|
||||
factory SerialConfig.fromJsonString(String? raw) {
|
||||
if (raw == null || raw.isEmpty) return defaults;
|
||||
try {
|
||||
final map = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return SerialConfig(
|
||||
vendorId: (map['vendorId'] as num?)?.toInt() ?? defaults.vendorId,
|
||||
productId: (map['productId'] as num?)?.toInt() ?? defaults.productId,
|
||||
baudRate: (map['baudRate'] as num?)?.toInt() ?? defaults.baudRate,
|
||||
dataBits: (map['dataBits'] as num?)?.toInt() ?? defaults.dataBits,
|
||||
stopBits: (map['stopBits'] as num?)?.toInt() ?? defaults.stopBits,
|
||||
parity: SerialParity.values.firstWhere(
|
||||
(e) => e.name == map['parity'],
|
||||
orElse: () => defaults.parity,
|
||||
),
|
||||
flowControl: SerialFlowControl.values.firstWhere(
|
||||
(e) => e.name == map['flowControl'],
|
||||
orElse: () => defaults.flowControl,
|
||||
),
|
||||
readTimeoutMs:
|
||||
(map['readTimeoutMs'] as num?)?.toInt() ?? defaults.readTimeoutMs,
|
||||
writeTimeoutMs:
|
||||
(map['writeTimeoutMs'] as num?)?.toInt() ?? defaults.writeTimeoutMs,
|
||||
);
|
||||
} catch (_) {
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../programs/models/program.dart';
|
||||
import '../../programs/models/step.dart';
|
||||
import '../../programs/services/program_service.dart';
|
||||
import '../services/mock_runner.dart';
|
||||
import '../services/runner_interface.dart';
|
||||
import 'serial_provider.dart';
|
||||
|
||||
/// 运行状态枚举
|
||||
enum RunStatus {
|
||||
idle, // 待机
|
||||
running, // 运行中
|
||||
paused, // 已暂停
|
||||
completed,// 已完成
|
||||
error, // 错误
|
||||
idle, // 待机
|
||||
running, // 运行中
|
||||
paused, // 已暂停
|
||||
completed, // 已完成
|
||||
error, // 错误
|
||||
}
|
||||
|
||||
/// 运行状态
|
||||
@@ -23,6 +24,7 @@ class RunState {
|
||||
final int remainingSeconds;
|
||||
final double progress;
|
||||
final String? currentWell;
|
||||
final String? errorMessage;
|
||||
|
||||
const RunState({
|
||||
this.status = RunStatus.idle,
|
||||
@@ -32,6 +34,7 @@ class RunState {
|
||||
this.remainingSeconds = 0,
|
||||
this.progress = 0,
|
||||
this.currentWell,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
RunState copyWith({
|
||||
@@ -42,17 +45,21 @@ class RunState {
|
||||
int? remainingSeconds,
|
||||
double? progress,
|
||||
String? currentWell,
|
||||
String? errorMessage,
|
||||
bool clearProgram = false,
|
||||
bool clearWell = false,
|
||||
bool clearError = false,
|
||||
}) {
|
||||
return RunState(
|
||||
status: status ?? this.status,
|
||||
currentProgram: clearProgram ? null : (currentProgram ?? this.currentProgram),
|
||||
currentProgram:
|
||||
clearProgram ? null : (currentProgram ?? this.currentProgram),
|
||||
steps: steps ?? this.steps,
|
||||
currentStepIndex: currentStepIndex ?? this.currentStepIndex,
|
||||
remainingSeconds: remainingSeconds ?? this.remainingSeconds,
|
||||
progress: progress ?? this.progress,
|
||||
currentWell: clearWell ? null : (currentWell ?? this.currentWell),
|
||||
errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,8 +75,8 @@ class RunState {
|
||||
final minutes = (remainingSeconds % 3600) ~/ 60;
|
||||
final seconds = remainingSeconds % 60;
|
||||
return '${hours.toString().padLeft(2, '0')}:'
|
||||
'${minutes.toString().padLeft(2, '0')}:'
|
||||
'${seconds.toString().padLeft(2, '0')}';
|
||||
'${minutes.toString().padLeft(2, '0')}:'
|
||||
'${seconds.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
/// 格式化进度百分比
|
||||
@@ -80,18 +87,24 @@ class RunState {
|
||||
|
||||
/// 运行状态 Notifier
|
||||
class RunStateNotifier extends StateNotifier<RunState> {
|
||||
final MockRunner _runner;
|
||||
final Runner _runner;
|
||||
final ProgramService _programService;
|
||||
|
||||
RunStateNotifier(this._runner, this._programService) : super(const RunState());
|
||||
|
||||
/// 开始运行程序
|
||||
Future<void> start(Program program) async {
|
||||
// 获取程序步骤(这里使用模拟数据,实际应从数据库读取)
|
||||
final steps = await _loadSteps(program.id!);
|
||||
if (state.status == RunStatus.running ||
|
||||
state.status == RunStatus.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
final steps = await _programService.getStepsByProgramId(program.id!);
|
||||
if (steps.isEmpty) {
|
||||
state = state.copyWith(status: RunStatus.error);
|
||||
state = state.copyWith(
|
||||
status: RunStatus.error,
|
||||
errorMessage: '程序步骤为空',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,26 +114,36 @@ class RunStateNotifier extends StateNotifier<RunState> {
|
||||
steps: steps,
|
||||
currentStepIndex: 0,
|
||||
progress: 0,
|
||||
currentWell: steps.first.position,
|
||||
clearError: true,
|
||||
);
|
||||
|
||||
_runner.start(
|
||||
program,
|
||||
steps,
|
||||
(stepIndex, remaining, progress, well) {
|
||||
state = state.copyWith(
|
||||
currentStepIndex: stepIndex,
|
||||
remainingSeconds: remaining,
|
||||
progress: progress,
|
||||
currentWell: well,
|
||||
);
|
||||
},
|
||||
() {
|
||||
state = state.copyWith(
|
||||
status: RunStatus.completed,
|
||||
progress: 1,
|
||||
clearWell: true,
|
||||
);
|
||||
},
|
||||
RunnerCallbacks(
|
||||
onProgress: (stepIndex, remaining, progress, well) {
|
||||
state = state.copyWith(
|
||||
currentStepIndex: stepIndex,
|
||||
remainingSeconds: remaining,
|
||||
progress: progress,
|
||||
currentWell: well,
|
||||
);
|
||||
},
|
||||
onComplete: () {
|
||||
state = state.copyWith(
|
||||
status: RunStatus.completed,
|
||||
progress: 1,
|
||||
currentWell: steps.last.position,
|
||||
);
|
||||
},
|
||||
onError: (msg) {
|
||||
state = state.copyWith(
|
||||
status: RunStatus.error,
|
||||
errorMessage: msg,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -142,26 +165,17 @@ class RunStateNotifier extends StateNotifier<RunState> {
|
||||
|
||||
/// 停止运行
|
||||
void stop() {
|
||||
_runner.stop();
|
||||
if (state.status == RunStatus.running ||
|
||||
state.status == RunStatus.paused) {
|
||||
_runner.stop();
|
||||
}
|
||||
state = const RunState(status: RunStatus.idle);
|
||||
}
|
||||
|
||||
/// 重置状态
|
||||
void reset() {
|
||||
stop();
|
||||
}
|
||||
|
||||
/// 加载程序步骤(从数据库读取)
|
||||
Future<List<Step>> _loadSteps(int programId) async {
|
||||
return await _programService.getStepsByProgramId(programId);
|
||||
}
|
||||
void reset() => stop();
|
||||
}
|
||||
|
||||
/// MockRunner Provider
|
||||
final mockRunnerProvider = Provider<MockRunner>((ref) {
|
||||
return MockRunner();
|
||||
});
|
||||
|
||||
/// ProgramService Provider
|
||||
final programServiceProvider = Provider<ProgramService>((ref) {
|
||||
return ProgramService.instance;
|
||||
@@ -170,7 +184,7 @@ final programServiceProvider = Provider<ProgramService>((ref) {
|
||||
/// 运行状态 Provider
|
||||
final runStateProvider =
|
||||
StateNotifierProvider<RunStateNotifier, RunState>((ref) {
|
||||
final runner = ref.watch(mockRunnerProvider);
|
||||
final runner = ref.watch(runnerProvider);
|
||||
final programService = ref.watch(programServiceProvider);
|
||||
return RunStateNotifier(runner, programService);
|
||||
});
|
||||
@@ -185,4 +199,4 @@ final isRunningProvider = Provider<bool>((ref) {
|
||||
final isPausedProvider = Provider<bool>((ref) {
|
||||
final status = ref.watch(runStateProvider).status;
|
||||
return status == RunStatus.paused;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import 'dart:async';
|
||||
import '../../programs/models/step.dart';
|
||||
import '../../programs/models/program.dart';
|
||||
|
||||
/// 模拟运行器回调
|
||||
typedef RunProgressCallback = void Function(
|
||||
int currentStepIndex,
|
||||
int remainingSeconds,
|
||||
double progress,
|
||||
String currentWell,
|
||||
);
|
||||
|
||||
typedef RunCompleteCallback = void Function();
|
||||
|
||||
/// 模拟运行器
|
||||
/// 用于在没有实际硬件连接时模拟程序执行过程
|
||||
class MockRunner {
|
||||
Timer? _timer;
|
||||
Program? _currentProgram;
|
||||
List<Step> _steps = [];
|
||||
int _currentStepIndex = 0;
|
||||
int _remainingSeconds = 0;
|
||||
bool _isPaused = false;
|
||||
RunProgressCallback? _onProgress;
|
||||
RunCompleteCallback? _onComplete;
|
||||
|
||||
/// 是否正在运行
|
||||
bool get isRunning => _timer != null && !_isPaused;
|
||||
|
||||
/// 是否已暂停
|
||||
bool get isPaused => _isPaused;
|
||||
|
||||
/// 当前程序
|
||||
Program? get currentProgram => _currentProgram;
|
||||
|
||||
/// 开始运行程序
|
||||
void start(
|
||||
Program program,
|
||||
List<Step> steps,
|
||||
RunProgressCallback onProgress,
|
||||
RunCompleteCallback onComplete,
|
||||
) {
|
||||
_currentProgram = program;
|
||||
_steps = steps;
|
||||
_onProgress = onProgress;
|
||||
_onComplete = onComplete;
|
||||
_currentStepIndex = 0;
|
||||
_isPaused = false;
|
||||
|
||||
if (steps.isEmpty) {
|
||||
onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
// 开始执行第一个步骤
|
||||
_startStep(steps[0]);
|
||||
}
|
||||
|
||||
/// 暂停运行
|
||||
void pause() {
|
||||
if (_timer != null && !_isPaused) {
|
||||
_isPaused = true;
|
||||
_timer!.cancel();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 继续运行
|
||||
void resume() {
|
||||
if (_isPaused && _currentProgram != null) {
|
||||
_isPaused = false;
|
||||
_resumeStep();
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止运行
|
||||
void stop() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
_currentProgram = null;
|
||||
_steps = [];
|
||||
_currentStepIndex = 0;
|
||||
_remainingSeconds = 0;
|
||||
_isPaused = false;
|
||||
}
|
||||
|
||||
/// 开始执行步骤
|
||||
void _startStep(Step step) {
|
||||
// 计算步骤总时间(混合时间 + 吸磁时间 + 吹气时间)
|
||||
_remainingSeconds = step.mixTime + step.magnetTime + step.blowTime;
|
||||
|
||||
// 如果步骤时间为0,设置最小演示时间(5秒)
|
||||
if (_remainingSeconds == 0) {
|
||||
_remainingSeconds = 5;
|
||||
}
|
||||
|
||||
// 启动定时器,每秒更新
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_remainingSeconds--;
|
||||
|
||||
// 计算总进度
|
||||
final totalSeconds = _calculateTotalSeconds();
|
||||
final elapsedSeconds = _calculateElapsedSeconds();
|
||||
final progress = totalSeconds > 0 ? elapsedSeconds / totalSeconds : 0.0;
|
||||
|
||||
// 回调进度更新
|
||||
_onProgress?.call(
|
||||
_currentStepIndex,
|
||||
_remainingSeconds,
|
||||
progress,
|
||||
step.position,
|
||||
);
|
||||
|
||||
// 步骤完成
|
||||
if (_remainingSeconds <= 0) {
|
||||
timer.cancel();
|
||||
_timer = null;
|
||||
_nextStep();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 继续执行步骤(从暂停恢复)
|
||||
void _resumeStep() {
|
||||
if (_currentStepIndex >= _steps.length) return;
|
||||
|
||||
final step = _steps[_currentStepIndex];
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_remainingSeconds--;
|
||||
|
||||
final totalSeconds = _calculateTotalSeconds();
|
||||
final elapsedSeconds = _calculateElapsedSeconds();
|
||||
final progress = totalSeconds > 0 ? elapsedSeconds / totalSeconds : 0.0;
|
||||
|
||||
_onProgress?.call(
|
||||
_currentStepIndex,
|
||||
_remainingSeconds,
|
||||
progress,
|
||||
step.position,
|
||||
);
|
||||
|
||||
if (_remainingSeconds <= 0) {
|
||||
timer.cancel();
|
||||
_timer = null;
|
||||
_nextStep();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 执行下一个步骤
|
||||
void _nextStep() {
|
||||
_currentStepIndex++;
|
||||
|
||||
if (_currentStepIndex >= _steps.length) {
|
||||
// 所有步骤完成
|
||||
_onComplete?.call();
|
||||
stop();
|
||||
} else {
|
||||
// 执行下一个步骤
|
||||
_startStep(_steps[_currentStepIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
/// 计算总执行时间
|
||||
int _calculateTotalSeconds() {
|
||||
int total = 0;
|
||||
for (final step in _steps) {
|
||||
int stepTime = step.mixTime + step.magnetTime + step.blowTime;
|
||||
if (stepTime == 0) stepTime = 5;
|
||||
total += stepTime;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/// 计算已执行时间
|
||||
int _calculateElapsedSeconds() {
|
||||
int elapsed = 0;
|
||||
for (int i = 0; i < _currentStepIndex; i++) {
|
||||
int stepTime = _steps[i].mixTime + _steps[i].magnetTime + _steps[i].blowTime;
|
||||
if (stepTime == 0) stepTime = 5;
|
||||
elapsed += stepTime;
|
||||
}
|
||||
// 加上当前步骤已执行的时间
|
||||
final currentStep = _steps[_currentStepIndex];
|
||||
int currentStepTime = currentStep.mixTime + currentStep.magnetTime + currentStep.blowTime;
|
||||
if (currentStepTime == 0) currentStepTime = 5;
|
||||
elapsed += currentStepTime - _remainingSeconds;
|
||||
return elapsed;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import '../../programs/models/program.dart';
|
||||
import '../../programs/models/step.dart';
|
||||
import 'runner_interface.dart';
|
||||
|
||||
/// 模拟运行器(用于开发测试)
|
||||
/// 模拟硬件运行过程
|
||||
class MockRunner implements Runner {
|
||||
@override
|
||||
RunnerStatus status = RunnerStatus.idle;
|
||||
|
||||
bool _isRunning = false;
|
||||
int _currentStep = 0;
|
||||
int _remainingSeconds = 0;
|
||||
RunnerCallbacks? _callbacks;
|
||||
List<Step> _steps = [];
|
||||
|
||||
@override
|
||||
void start(Program program, List<Step> steps, RunnerCallbacks callbacks) {
|
||||
if (steps.isEmpty) {
|
||||
callbacks.onError?.call('No steps to run');
|
||||
status = RunnerStatus.error;
|
||||
return;
|
||||
}
|
||||
|
||||
_steps = steps;
|
||||
_callbacks = callbacks;
|
||||
_currentStep = 0;
|
||||
_isRunning = true;
|
||||
status = RunnerStatus.running;
|
||||
|
||||
// 开始模拟运行
|
||||
_runSimulation();
|
||||
}
|
||||
|
||||
void _runSimulation() {
|
||||
if (!_isRunning || _currentStep >= _steps.length) {
|
||||
_completeRun();
|
||||
return;
|
||||
}
|
||||
|
||||
final step = _steps[_currentStep];
|
||||
// 计算步骤时间(混合时间 + 吸磁时间 + 吹气时间 + 5秒最小)
|
||||
final stepTime = (step.mixTime ?? 0) + (step.magnetTime ?? 0) + (step.blowTime ?? 0) + 5;
|
||||
_remainingSeconds = stepTime.clamp(5, 300);
|
||||
|
||||
// 模拟倒计时
|
||||
_simulateStepProgress(stepTime);
|
||||
}
|
||||
|
||||
void _simulateStepProgress(int totalSeconds) {
|
||||
// 简化模拟:每秒更新进度
|
||||
int elapsed = 0;
|
||||
while (_isRunning && elapsed < totalSeconds) {
|
||||
elapsed++;
|
||||
final remaining = totalSeconds - elapsed;
|
||||
final progress = elapsed / totalSeconds;
|
||||
|
||||
_callbacks?.onProgress?.call(
|
||||
_currentStep,
|
||||
remaining,
|
||||
(_currentStep + progress) / _steps.length,
|
||||
_steps[_currentStep].position,
|
||||
);
|
||||
|
||||
// 实际实现需要使用 Timer
|
||||
// await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
|
||||
if (_isRunning) {
|
||||
_currentStep++;
|
||||
_runSimulation();
|
||||
}
|
||||
}
|
||||
|
||||
void _completeRun() {
|
||||
status = RunnerStatus.completed;
|
||||
_isRunning = false;
|
||||
_callbacks?.onComplete?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void pause() {
|
||||
if (status == RunnerStatus.running) {
|
||||
_isRunning = false;
|
||||
status = RunnerStatus.paused;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void resume() {
|
||||
if (status == RunnerStatus.paused) {
|
||||
_isRunning = true;
|
||||
status = RunnerStatus.running;
|
||||
// 继续运行
|
||||
_runSimulation();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void stop() {
|
||||
_isRunning = false;
|
||||
status = RunnerStatus.idle;
|
||||
_currentStep = 0;
|
||||
_remainingSeconds = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
RunnerStatus getStatus() => status;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user