prepare v1.1.4 release with native bridge and stability cleanups

- align Android/iOS native bridge implementations and audio handling paths
- improve Bluetooth provisioning/verification flow and related error handling
- refactor WebSocket, music, and device utility logic for more stable behavior
- clean up noisy debug logs and normalize comments across Flutter and native code
- update AR view, dance/agent/device pages, and platform integration details
This commit is contained in:
袁智鸿
2026-04-28 10:57:01 +08:00
parent 7413e758ce
commit 6314188835
69 changed files with 537 additions and 648 deletions
+5
View File
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package com.m5stack.stackchan
import android.Manifest
@@ -40,7 +45,7 @@ class MainActivity : FlutterActivity() {
val messenger = flutterEngine.dartExecutor.binaryMessenger
// 注册通道
// translated comment
channel = MethodChannel(
messenger,
"com.m5stack.stackchan/native"
@@ -105,7 +110,7 @@ class MainActivity : FlutterActivity() {
audioTrack?.write(data, 0, data.size)
}
// ====================== 修复核心 1 ======================
// ====================== 1 ======================
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
private fun startRecording() {
if (isRecording.get()) return
@@ -124,7 +129,7 @@ class MainActivity : FlutterActivity() {
val readSize = audioRecord?.read(buffer, 0, buffer.size) ?: 0
if (readSize > 0) {
val data = buffer.copyOf(readSize)
// 切到主线程发送 → 修复崩溃
// translated comment
runOnUiThread {
eventSink?.success(data)
}
@@ -139,7 +144,7 @@ class MainActivity : FlutterActivity() {
audioRecord?.stop()
}
// ====================== 修复核心 2 ======================
// ====================== 2 ======================
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
private fun methodCallHandler(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
@@ -147,7 +152,7 @@ class MainActivity : FlutterActivity() {
if (audioTrack?.playState == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack?.pause()
}
result.success(null) // 必须回调
result.success(null) // translated comment
}
"startRecording" -> {
@@ -160,7 +165,7 @@ class MainActivity : FlutterActivity() {
result.success(null)
}
else -> result.notImplemented() // 缺失方法处理
else -> result.notImplemented() // translated comment
}
}
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package com.m5stack.stackchan.model
data class DanceData(
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package com.m5stack.stackchan.model
data class DanceList(
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package com.m5stack.stackchan.model
data class ExpressionData(
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package com.m5stack.stackchan.model
data class ExpressionItem(
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package com.m5stack.stackchan.model
data class MotionData(
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package com.m5stack.stackchan.model
data class MotionDataItem(
+5
View File
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
import com.android.build.gradle.BaseExtension
allprojects {
+5
View File
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
pluginManagement {
val flutterSdkPath =
run {
+5
View File
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
import Flutter
import CoreLocation
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//
// Generated file. Do not edit.
//
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//
// Generated file. Do not edit.
//
+8 -5
View File
@@ -1,8 +1,13 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//
// Dance.swift
// StackChan
//
// Created by 鸿 on 2026/1/16.
// Created by on 2026/1/16.
//
import Foundation
@@ -40,12 +45,10 @@ struct DanceData : Codable,Identifiable {
static func from(jsonString: String) -> DanceData? {
guard !jsonString.isEmpty else {
print("JSON string is empty")
return nil
return nil
}
guard let jsonData = jsonString.data(using: .utf8) else {
print("Failed to convert string to UTF-8 data")
return nil
return nil
}
do {
+5
View File
@@ -1 +1,6 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
#import "GeneratedPluginRegistrant.h"
+6 -2
View File
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
import Flutter
import NetworkExtension
import CoreLocation
@@ -70,8 +75,7 @@ class SceneDelegate: FlutterSceneDelegate,CLLocationManagerDelegate {
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print(manager.authorizationStatus)
if manager.authorizationStatus == .authorizedWhenInUse ||
if manager.authorizationStatus == .authorizedWhenInUse ||
manager.authorizationStatus == .authorizedAlways {
fetchWifiInfo()
}
+6 -13
View File
@@ -164,21 +164,16 @@ extension UIImage {
extension String {
func jsonPrint() {
guard let data = self.data(using: .utf8) else {
print(self)
return
return
}
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
let prettyData = try JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted, .sortedKeys])
if let prettyString = String(data: prettyData, encoding: .utf8) {
print("✅ JSON formatted output:\n\(prettyString)")
} else {
print(self)
}
} else {
}
} catch {
print("❌ Invalid JSON format: \(error.localizedDescription)")
print(self)
}
}
}
func leftPadding(toLength: Int, withPad character: Character) -> String {
@@ -253,8 +248,7 @@ extension Encodable {
let data = try encoder.encode(self)
return String(data: data, encoding: .utf8) ?? "{}"
} catch {
print("❌ JSON serialization failed: \(error)")
return "{}"
return "{}"
}
}
@@ -263,8 +257,7 @@ extension Encodable {
do {
return try encoder.encode(self)
} catch {
print("❌ Failed to convert JSON to Data: \(error.localizedDescription)")
return nil
return nil
}
}
}
+19 -24
View File
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
import Foundation
import Flutter
import AVFoundation
@@ -53,22 +58,18 @@ class NativeBridge {
private func playAudio(pcmData: Data) {
guard let audioFormat = audioFormat else {
print("❌ 音频格式初始化失败")
return
return
}
if !isAudioInitialized {
guard setupAudioSession() else {
print("❌ 会话初始化失败")
return
return
}
guard setupAudioEngine() else {
print("❌ 引擎初始化失败")
return
return
}
isAudioInitialized = true
print("✅ 音频初始化完成")
}
}
guard let engine = audioEngine, let playerNode = audioPlayerNode else {
resetAudio()
@@ -79,8 +80,7 @@ class NativeBridge {
do {
try engine.start()
} catch {
print("❌ 引擎启动失败: \(error)")
resetAudio()
resetAudio()
return
}
}
@@ -88,7 +88,7 @@ class NativeBridge {
var floatBuffer = pcmData.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [Float] in
let int16Buffer = bytes.bindMemory(to: Int16.self)
var floats = [Float](repeating: 0, count: int16Buffer.count)
//3
//3
for i in 0..<int16Buffer.count {
floats[i] = min(max(Float(int16Buffer[i]) / Float(Int16.max) * 3.0, -1.0), 1.0)
}
@@ -107,7 +107,7 @@ class NativeBridge {
}
}
// MARK: - -50
// MARK: - -50
private func setupAudioSession() -> Bool {
do {
let session = AVAudioSession.sharedInstance()
@@ -116,30 +116,27 @@ class NativeBridge {
return true
} catch {
let nsError = error as NSError
print("❌ 音频会话错误:\(nsError.code) - \(nsError.localizedDescription)")
return false
return false
}
}
// MARK: - -10868
// MARK: - -10868
private func setupAudioEngine() -> Bool {
guard let audioFormat = audioFormat else {
print("❌ 音频格式为空")
return false
return false
}
let engine = AVAudioEngine()
let playerNode = AVAudioPlayerNode()
engine.attach(playerNode)
//
// translated comment
engine.connect(playerNode, to: engine.mainMixerNode, format: audioFormat)
do {
try engine.start()
} catch {
print("❌ 引擎启动失败: \(error)")
return false
return false
}
self.audioEngine = engine
@@ -163,14 +160,12 @@ class NativeBridge {
func sendMessage(method: Method,_ arguments: Any? = nil,_ completion: ((Any?) -> Void)? = nil) {
guard method != .unknown else {
print("⚠️ 未知方法")
completion?(nil)
completion?(nil)
return
}
channel?.invokeMethod(method.rawValue, arguments: arguments) { result in
if let error = result as? FlutterError {
print("❌ 发送失败:\(error)")
}
}
completion?(result)
}
}
+16 -13
View File
@@ -1,8 +1,13 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//
// StackChanArView.swift
// Runner
//
// Created by 鸿 on 2026/2/5.
// Created by on 2026/2/5.
//
import RealityKit
@@ -52,13 +57,13 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
self?.handleMethodCall(call, result: result)
}
//
// translated comment
let expressionChannelName = "\(methodChannelName)_expression"
expressionStreamHandler = ExpressionStreamHandler()
expressionChannel = FlutterEventChannel(name: expressionChannelName, binaryMessenger: messenger)
expressionChannel?.setStreamHandler(expressionStreamHandler)
//
// translated comment
let frameChannelName = "\(methodChannelName)_frame"
frameStreamHandler = FrameStreamHandler()
frameChannel = FlutterEventChannel(name: frameChannelName, binaryMessenger: messenger)
@@ -67,8 +72,7 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
private func setupARSession() {
guard ARFaceTrackingConfiguration.isSupported else {
print("设备不支持面部追踪")
return
return
}
let configuration = ARFaceTrackingConfiguration()
configuration.isLightEstimationEnabled = true
@@ -105,7 +109,7 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
}
}
///
//translated comment
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
DispatchQueue.main.async {
self.emotionDetection(session: session, anchors: anchors)
@@ -288,7 +292,7 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
// 1. Slight or clear head tilt downward
let transform = faceAnchor.transform
let rotation = SCNMatrix4(transform)
let pitch = asin(-rotation.m32) //
let pitch = asin(-rotation.m32) // translated comment
let isHeadDown = pitch > emotionThresholds.shy.headPitch
// 2. Mouth closed with a slight smile
@@ -374,8 +378,7 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
private func createStackChanModel() -> SCNNode {
guard let scene = SCNScene(named: "StackChanModel.scn"),
let modelNode = scene.rootNode.childNodes.first else {
print("no model")
return SCNNode()
return SCNNode()
}
modelNode.name = "StackChanModel"
modelNode.scale = SCNVector3(0.004, 0.004, 0.004)
@@ -384,7 +387,7 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
modelNode.eulerAngles = SCNVector3Zero
modelNode.eulerAngles.x = -Float.pi / 2
//
//translated comment
if let foundation = modelNode.childNode(withName: "_00_stackchan450_3",recursively: false),let centralComponent = modelNode.childNode(withName: "_00_stackchan450_2", recursively: false) {
foundation.opacity = 0
centralComponent.opacity = 0
@@ -451,7 +454,7 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
)
}()
///
//translated comment
func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
if captureScreen {
if time - lastCaptureTime >= 0.5 {
@@ -467,7 +470,7 @@ class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNV
}
}
///
//translated comment
private func updateDecoration(expressionData: ExpressionData) {
DispatchQueue.main.async {
if self.decorate == 1 {
@@ -531,7 +534,7 @@ class FrameStreamHandler: NSObject, FlutterStreamHandler {
func sendFrameData(_ data: Data) {
guard let sink = eventSink else { return }
// 线
// translated comment
DispatchQueue.main.async {
sink(FlutterStandardTypedData(bytes: data))
}
+7 -3
View File
@@ -1,8 +1,13 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//
// StackChanRobot.swift
// Runner
//
// Created by 鸿 on 2026/1/30.
// Created by on 2026/1/30.
//
import SceneKit
@@ -104,8 +109,7 @@ class StackChanRobot: NSObject, FlutterPlatformView, FlutterStreamHandler {
private func setupInitialScene() {
guard let scene = SCNScene(named: "StackChanModel.scn") else {
print("Failed to load StackChanModel.scn")
return
return
}
scene.rootNode.eulerAngles = SCNVector3Zero
scene.rootNode.eulerAngles.x = -Float.pi / 2
@@ -1,8 +1,13 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//
// StackChanRotaryRobot.swift
// Runner
//
// Created by 鸿 on 2026/1/30.
// Created by on 2026/1/30.
//
import SceneKit
@@ -50,8 +55,7 @@ class StackChanRotaryRobot: NSObject, FlutterPlatformView, FlutterStreamHandler
private func setupInitialScene() {
guard let scene = SCNScene(named: "StackChanModel.scn") else {
print("Failed to load StackChanModel.scn")
return
return
}
scene.rootNode.eulerAngles = SCNVector3Zero
scene.rootNode.eulerAngles.x = -Float.pi / 2
@@ -98,7 +102,7 @@ class StackChanRotaryRobot: NSObject, FlutterPlatformView, FlutterStreamHandler
// Add expression plane to head
addExpressionPlane(to: head)
//
// translated comment
let rotateAction = SCNAction.rotateBy(x: 0, y: CGFloat(2 * Double.pi), z: 0, duration: 5)
let repeatAction = SCNAction.repeatForever(rotateAction)
scene.rootNode.runAction(repeatAction)
+6 -1
View File
@@ -1,8 +1,13 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//
// ViewFactory.swift
// Runner
//
// Created by 鸿 on 2026/1/30.
// Created by on 2026/1/30.
//
import Flutter
+5
View File
@@ -1,3 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
import Flutter
import UIKit
import XCTest
+4 -8
View File
@@ -222,8 +222,7 @@ class AppState extends GetxController {
}
}
} else if (message is String) {
debugPrint("Received a regular message: $message");
}
}
});
}
@@ -442,12 +441,10 @@ class AppState extends GetxController {
currentLocation.value = position;
isLocationAvailable.value = true;
debugPrint("GetPositionSuccess: 纬度${position.latitude}, 经度${position.longitude}");
} catch (e) {
} catch (e) {
showToast("Failed to obtain location: ${e.toString()}");
isLocationAvailable.value = false;
debugPrint("GetPositionError: $e");
}
}
}
///Continuously listen for location changes
@@ -463,8 +460,7 @@ class AppState extends GetxController {
isLocationAvailable.value = true;
},
onError: (e) {
debugPrint("PositionUpdateError: $e");
isLocationAvailable.value = false;
isLocationAvailable.value = false;
},
);
}
+1 -1
View File
@@ -119,7 +119,7 @@ class Agent {
if (jsonItem is Map<String, dynamic>) {
return Agent.fromJson(jsonItem);
} else {
//Non- Map typereturnNull Agent(oraccording to需求抛Throws/skip)
//Non- Map typereturnNull Agent(oraccording toThrows/skip)
return Agent();
}
}).toList();
+3 -3
View File
@@ -93,7 +93,7 @@ class LastDevice {
String? alias;
int? agent_id;
//Constructorfunction(allfieldasoptionalparameter,Nullsafe)
//Constructorfunction(allfieldasoptionalparameter,Nullsafe)
LastDevice({
this.id,
this.user_id,
@@ -106,7 +106,7 @@ class LastDevice {
this.agent_id,
});
//from JSON Deserialize(factory Factorymethod,)
//from JSON Deserialize(factory Factorymethod,)
factory LastDevice.fromJson(Map<String, dynamic> json) {
return LastDevice(
id: json['id'] as int?,
@@ -124,7 +124,7 @@ class LastDevice {
//Serializeas JSON(return Map<String, dynamic>,Candirectfor jsonEncode)
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
//one by onemapfield,nullWillautoSerializeas null( JSON standard)
//one by onemapfield,nullWillautoSerializeas null( JSON standard)
data['id'] = id;
data['user_id'] = user_id;
data['mac_address'] = mac_address;
@@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
///responsemodel
///responsemodel
class EndpointsResponse {
final List<Endpoint> endpoints;
+4 -4
View File
@@ -4,7 +4,7 @@ SPDX-License-Identifier: MIT
*/
class GenerateLicense {
//field采用Dart小驼峰namingstandard,parsewhenmapAPIreturnDown划线naming
//fieldDartnamingstandard,parsewhenmapAPIreturnDownnaming
String? productName;
String? boardName;
String? serialNumber;
@@ -27,18 +27,18 @@ class GenerateLicense {
//core:fromJsonFactorymethod,parseJSONdatatoobject
factory GenerateLicense.fromJson(Map<String, dynamic> json) {
return GenerateLicense(
//mapAPIreturnDown划线fieldtoDart小驼峰field
//mapAPIreturnDownfieldtoDartfield
productName: json['product_name'] as String?,
boardName: json['board_name'] as String?,
serialNumber: json['serial_number'] as String?,
licenseKey: json['license_key'] as String?,
licenseAlgorithm: json['license_algorithm'] as String?,
createdAt: json['created_at'] as String?,
firmware: json['firmware'], //dynamictypedirect赋值
firmware: json['firmware'], //dynamictypedirect
);
}
//optional:addtoJsonmethod(For easylaterSerialize,Local存储)
//optional:addtoJsonmethod(For easylaterSerialize,Local)
Map<String, dynamic> toJson() {
return {
'product_name': productName,
+8 -8
View File
@@ -5,12 +5,12 @@ SPDX-License-Identifier: MIT
class McpEndpoints {
int? id;
int? developerId; //namingstandardsnake_case camelCase
int? developerId; //namingstandardsnake_case camelCase
String? name;
String? description;
int? enabled;
String? createdAt; //namingstandardsnake_case camelCase
String? updatedAt; //namingstandardsnake_case camelCase
String? createdAt; //namingstandardsnake_case camelCase
String? updatedAt; //namingstandardsnake_case camelCase
//defaultConstructorfunction
McpEndpoints({
@@ -44,21 +44,21 @@ class McpEndpoints {
if (jsonList.isEmpty) return [];
return jsonList
.where((item) => item is Map<String, dynamic>) //filter非 Map type元素
.where((item) => item is Map<String, dynamic>) //filter Map type
.map((item) => McpEndpoints.fromJson(item as Map<String, dynamic>))
.toList();
}
///convertas JSON object(forNetworkrequest/)
///convertas JSON object(forNetworkrequest/)
Map<String, dynamic> toJson() {
return {
'id': id,
'developer_id': developerId, // snake_case adaptafter端
'developer_id': developerId, // snake_case adaptafter
'name': name,
'description': description,
'enabled': enabled,
'created_at': createdAt, // snake_case adaptafter端
'updated_at': updatedAt, // snake_case adaptafter端
'created_at': createdAt, // snake_case adaptafter
'updated_at': updatedAt, // snake_case adaptafter
};
}
}
+2 -2
View File
@@ -10,7 +10,7 @@ class Pagination {
bool? hasMore;
int? page;
int? limit;
int? totaPages; //field名可能is拼写erroris totalPages
int? totaPages; //fieldiserroris totalPages
//Constructorfunction
Pagination({
@@ -33,7 +33,7 @@ class Pagination {
page: json['page'] as int?,
limit: json['limit'] as int?,
totaPages:
json['totaPages'] as int? ?? json['totalPages'] as int?, //field名
json['totaPages'] as int? ?? json['totalPages'] as int?, //field
);
}
+2 -2
View File
@@ -7,7 +7,7 @@ class TTsList {
//languagelist
List<String> languages;
//coreModify:anylanguage -> corresponding语音list,supportalllanguagetype
//coreModify:anylanguage -> correspondinglist,supportalllanguagetype
Map<String, List<TTsVoice>>? ttsVoices;
//Constructorfunction(NullsafedefaultValue)
@@ -20,7 +20,7 @@ class TTsList {
? List<String>.from(json['languages'].map((x) => x.toString()))
: <String>[];
//parsedynamiclanguage语音 Map(core:autoadaptalllanguage key)
//parsedynamiclanguage Map(core:autoadaptalllanguage key)
Map<String, List<TTsVoice>>? voiceMap;
if (json['tts_voices'] != null) {
voiceMap = {};
+3 -3
View File
@@ -29,7 +29,7 @@ class BlueEncryptionDecryption {
//fromJson Constructorfunction:from JSON mapcreateinstance
BlueEncryptionDecryption.fromJson(Map<String, dynamic> json) {
//safe地from JSON in取Value,avoidtypeerror
//safefrom JSON inValue,avoidtypeerror
cmd = json['cmd'] as String?;
data = json['data'] as String?;
}
@@ -46,7 +46,7 @@ class BlueEncryptionDecryption {
return json;
}
//toString method:Stringoutput
//toString method:Stringoutput
@override
String toString() {
return '{\n'
@@ -55,7 +55,7 @@ class BlueEncryptionDecryption {
'}';
}
//optional:enhance版 toString(output,Debug)
//optional:enhance toString(output,Debug)
String toStringFormatted() {
return 'BlueEncryptionDecryption {\n'
' cmd: "${cmd ?? 'null'}"\n'
+2 -2
View File
@@ -102,7 +102,7 @@ class DanceData {
String? id,
}) : id = id ?? _uuid.v4();
/// Swift init(from decoder:)
/// Swift init(from decoder:)
factory DanceData.fromJson(Map<String, dynamic> json) {
return DanceData(
leftEye: ExpressionItem.fromJson(json['leftEye']),
@@ -129,7 +129,7 @@ class DanceData {
};
}
/// Swift copy()
/// Swift copy()
DanceData copy() {
return DanceData(
leftEye: leftEye.copy(),
+2 -2
View File
@@ -28,11 +28,11 @@ enum MsgType {
offAudio(0x19),
aimedTakePhoto(0x1A);
final int value; //custom值andiOSrawValuefullyfor齐
final int value; //customandiOSrawValuefullyfor
const MsgType(this.value);
//CurrentlySerializelogic:valueAnd / WhileNon-index
//CurrentlySerializelogic:valueAnd / WhileNon-index
String toJson() => value.toString();
static MsgType fromJson(String json) {
+6 -8
View File
@@ -15,20 +15,18 @@ import '../util/value_constant.dart';
void logPrint(Object? object) {
if (object == null) return;
String log = object.toString();
const int chunkSize = 800; //800limit
//ifcontent较短,directPrint
const int chunkSize = 800; //800limit
//ifcontent,directPrint
if (log.length <= chunkSize) {
debugPrint(log);
return;
return;
}
//content分段Print
//contentPrint
for (int i = 0; i < log.length; i += chunkSize) {
int end = i + chunkSize;
if (end > log.length) end = log.length;
//usedebugPrint(print更稳定,support更长content)
debugPrint(log.substring(i, end));
}
//usedebugPrint(print,supportcontent)
}
}
class Http {
+13 -32
View File
@@ -62,13 +62,11 @@ class WebSocketUtil {
_urlString = urlString;
if (AppState.shared.deviceMac.isEmpty) {
debugPrint(' WebSocket ConnectFailed:DeviceMACAddressIs null/empty');
return;
return;
}
//Printconnectstartlog
debugPrint('🔌 StartConnect WebSocket: $urlString');
try {
final encryptedToken = RsaUtil.encrypt(
getAuthorization(AppState.shared.deviceMac),
@@ -79,10 +77,7 @@ class WebSocketUtil {
//connectsuccesslog(ContainstimeandURL)
_isConnected = true;
final connectTime = DateTime.now().toString().split('.').first;
debugPrint(' WebSocket ConnectSuccess [$connectTime]');
debugPrint(' ConnectAddress: $urlString');
debugPrint(' ConnectState: ${_socket?.readyState} (OPEN)');
_subscription = _socket!.listen(
_handleMessage,
onError: _handleError,
@@ -97,10 +92,7 @@ class WebSocketUtil {
_isConnected = false;
//connectfaillog(ContainsSpecificerrorinfo)
final errorTime = DateTime.now().toString().split('.').first;
debugPrint(' WebSocket ConnectFailed [$errorTime]');
debugPrint(' ConnectAddress: $urlString');
debugPrint(' Error原Because: $e');
_scheduleReconnect();
_scheduleReconnect();
}
}
@@ -116,8 +108,7 @@ class WebSocketUtil {
void _handleError(Object error) {
//errorlog(DistinguishconnecterrorandRunning / Runtimewhenerror)
debugPrint(' WebSocket Running / RuntimeWhenError: $error');
_isConnected = false;
_isConnected = false;
_scheduleReconnect();
}
@@ -125,10 +116,7 @@ class WebSocketUtil {
//connectcloselog(ContainscloseoriginalBecause)
_isConnected = false;
final closeTime = DateTime.now().toString().split('.').first;
debugPrint(' WebSocket ConnectAlready关闭 [$closeTime]');
debugPrint(' 关闭Address: $_urlString');
debugPrint(' 关闭State: ${_socket?.closeCode} - ${_socket?.closeReason}');
_scheduleReconnect();
_scheduleReconnect();
}
bool replyPong(dynamic message) {
@@ -154,32 +142,27 @@ class WebSocketUtil {
* ======================= */
void sendString(String message) {
if (_socket == null) {
debugPrint(' 发送StringMessageFailed:WebSocket Disconnected');
return;
return;
}
debugPrint('📤 发送StringMessage: $message');
try {
try {
_socket!.add(message);
} catch (e) {
debugPrint(' 发送StringMessageFailed: $e');
_isConnected = false;
_isConnected = false;
_scheduleReconnect();
}
}
void send(Uint8List data) {
if (_socket == null) {
debugPrint(' 发送2BaseMessageFailed:WebSocket Disconnected');
return;
return;
}
//debugPrint('📤 send2Basemessage: length=${data.length} Byte');
try {
_socket!.add(data);
} catch (e) {
debugPrint(' 发送2BaseMessageFailed: $e');
_isConnected = false;
_isConnected = false;
_scheduleReconnect();
}
}
@@ -192,8 +175,7 @@ class WebSocketUtil {
//reconnectlog(avoidFrequentlyRepeatPrint)
if (!_isConnected) {
debugPrint('🔄 准备重连 WebSocket: $_urlString (1Second(s)BackRetry)');
}
}
await Future.delayed(const Duration(seconds: 1));
await connect(_urlString);
@@ -207,8 +189,7 @@ class WebSocketUtil {
_socket?.close(WebSocketStatus.goingAway, '主动断开连接');
_isConnected = false;
_socket = null;
debugPrint('🔌 WebSocket AlreadyProactiveDisconnectConnect');
}
}
/* =======================
* Observer
+7 -16
View File
@@ -115,13 +115,11 @@ class XiaoZhiUtil {
handler.resolve(newResponse);
return;
} catch (e) {
debugPrint('RetryRequestFailed: $e');
}
}
}
}
} catch (e) {
debugPrint('ParseResponseFailed: $e');
}
}
}
handler.next(response);
@@ -244,8 +242,7 @@ class XiaoZhiUtil {
}
return [];
} catch (e) {
debugPrint('查询DeviceException:$e');
return [];
return [];
}
}
@@ -306,8 +303,7 @@ class XiaoZhiUtil {
}
return [];
} catch (e) {
debugPrint('查询DeviceException:$e');
return [];
return [];
}
}
@@ -468,7 +464,7 @@ class XiaoZhiUtil {
///Returns: Authorization info (includes serial number), null on failure
Future<GenerateLicense?> generateLicense(String macAddress) async {
try {
// Get generateLicenseToken License
// Get generateLicenseToken License
final generateResponse = await Http.instance.get(
Urls.xiaozhiGenerateLicenseToken,
);
@@ -483,9 +479,6 @@ class XiaoZhiUtil {
return null;
}
print("拿到generateToken");
print(generateLicenseModel.data);
final Map<String, dynamic> queryParams = {
ValueConstant.token: generateLicenseModel.data,
ValueConstant.seed: macAddress,
@@ -505,8 +498,7 @@ class XiaoZhiUtil {
}
return null;
} catch (e) {
debugPrint('生成LicenseException:$e');
return null;
return null;
}
}
@@ -538,8 +530,7 @@ class XiaoZhiUtil {
XiaozhiResponse xiaozhiResponse = XiaozhiResponse.fromJsonT(
response.data,
);
debugPrint(xiaozhiResponse.message);
if (xiaozhiResponse.message == "该设备已经添加过,请不要重复添加") {
if (xiaozhiResponse.message == "该设备已经添加过,请不要重复添加") {
return true;
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ class _AppToastState extends State<AppToast> {
///Update Toast text and show
void _updateToast(String text) {
//First / Previouslycancel旧timer,avoidRepeat计when
//First / Previouslycanceltimer,avoidRepeatwhen
_hideTimer?.cancel();
setState(() {
_toastText = text;
+9 -17
View File
@@ -53,15 +53,13 @@ class AudioEngineManager {
);
_isInitialized = true;
debugPrint(" Audio引擎InitializeSuccess");
//listenoriginal生Recorddata
//listenoriginalRecorddata
// NativeBridge.shared.recordChannel.receiveBroadcastStream().listen((data) {
// if (data is Uint8List) _processPcm(data);
// });
} catch (e) {
debugPrint(" InitializeFailed: $e");
rethrow;
rethrow;
}
}
@@ -90,8 +88,7 @@ class AudioEngineManager {
}
}
} catch (e) {
debugPrint(" EncodeFailed: $e");
}
}
}
//decibelcalculate
@@ -116,8 +113,7 @@ class AudioEngineManager {
}
NativeBridge.shared.sendAudioStream(byteData);
} catch (e) {
debugPrint(" PlayFailed: $e");
}
}
}
Future<void> stopPlayOpus() async {
@@ -131,20 +127,17 @@ class AudioEngineManager {
//requestMicrophonepermission
final perm = await Permission.microphone.request();
if (!perm.isGranted) {
debugPrint("MicrophonePermissionNot授权");
return false;
return false;
}
debugPrint("🎙️ RecordStart (实WhenOpusEncode Int16)");
NativeBridge.shared.sendMessage(.startRecording);
NativeBridge.shared.sendMessage(.startRecording);
return true;
}
//====================== stopRecord ======================
Future<void> stopRecording() async {
NativeBridge.shared.sendMessage(.stopRecording);
debugPrint("🛑 RecordStop");
}
}
Future<void> dispose() async {
if (_isInitialized) {
@@ -152,6 +145,5 @@ class AudioEngineManager {
simpleOpusDecoder.destroy();
_isInitialized = false;
}
debugPrint("♻️ Asset / Resource源ReleaseComplete / Done");
}
}
}
+47 -107
View File
@@ -127,17 +127,14 @@ class BlueUtil {
//Execute enable Bluetooth command regardless of success to fix some plugin bugs
if (allGranted) {
debugPrint(" BluetoothPermissionAllGetSuccess");
_registerBluetoothStateListener();
_registerBluetoothStateListener();
await _tryTurnOnBluetooth();
} else {
debugPrint(" Part/AllBluetoothPermission被Reject,No法打开Bluetooth");
_registerBluetoothStateListener();
_registerBluetoothStateListener();
await _tryTurnOnBluetooth();
}
} catch (e) {
debugPrint(" Permission申请Exception:$e");
_registerBluetoothStateListener();
_registerBluetoothStateListener();
await _tryTurnOnBluetooth();
}
}
@@ -154,15 +151,12 @@ class BlueUtil {
Future<void> _tryTurnOnBluetooth() async {
try {
final currentState = FlutterBluePlus.adapterStateNow;
debugPrint("🔵 CurrentBluetoothState: $currentState");
if (currentState == BluetoothAdapterState.off) {
debugPrint("🔵 BluetoothAlready关闭,In progressAutoRequest打开...");
await FlutterBluePlus.turnOn();
await FlutterBluePlus.turnOn();
} else if (currentState == BluetoothAdapterState.on) {
//[fixkey]Androidfirstpermissionsuccess+BluetoothAlreadyenable proactivetriggerscan
debugPrint(" BluetoothAlready开启,ProactiveTrigger首Time(s)Scan");
blueSwitch = true;
blueSwitch = true;
if (automaticScanning) {
startScan();
}
@@ -171,28 +165,22 @@ class BlueUtil {
}
}
} catch (e) {
debugPrint(" Auto打开BluetoothFailed:$e");
}
}
}
//MARK: - Bluetooth status update (auto scan, auto reconnect)
void _centralManagerDidUpdateState(BluetoothAdapterState state) {
switch (state) {
case BluetoothAdapterState.unknown:
debugPrint("Bluetooth state unknown");
break;
break;
case BluetoothAdapterState.unavailable:
debugPrint("This device does not support Bluetooth");
break;
break;
case BluetoothAdapterState.unauthorized:
debugPrint("No permission to use Bluetooth, please check settings");
break;
break;
case BluetoothAdapterState.turningOn:
debugPrint("Bluetooth is resetting");
break;
break;
case BluetoothAdapterState.on:
debugPrint(" BluetoothAlready打开,AutoStartScan");
blueSwitch = true;
blueSwitch = true;
//Bluetoothenable autostartscan
if (automaticScanning) {
startScan();
@@ -203,11 +191,9 @@ class BlueUtil {
}
break;
case BluetoothAdapterState.turningOff:
debugPrint("Bluetooth is turning off");
break;
break;
case BluetoothAdapterState.off:
debugPrint("🔌 BluetoothAlready关闭");
blueSwitch = false;
blueSwitch = false;
//closeafterautoTryreOpen
_tryTurnOnBluetooth();
break;
@@ -217,15 +203,13 @@ class BlueUtil {
//MARK: - Scan related (auto execute)
void startScan() {
if (FlutterBluePlus.adapterStateNow != BluetoothAdapterState.on) {
debugPrint("Bluetooth is not ready when scanning");
//stateNotMeet / Satisfy,autoOpenBluetooth
//stateNotMeet / Satisfy,autoOpenBluetooth
_tryTurnOnBluetooth();
return;
}
discoveredDevices.clear();
debugPrint("🔍 StartAutoScan附近BLEDevice");
FlutterBluePlus.startScan(
withServices: [Guid(targetServiceUUID), Guid(danceTargetServiceUUID)],
continuousUpdates: true,
@@ -239,8 +223,7 @@ class BlueUtil {
}
},
onError: (e) {
debugPrint("Scan error: $e");
},
},
);
_startCleanupTimer();
@@ -349,10 +332,7 @@ class BlueUtil {
);
if (isBound && currentPeripheral == null) {
debugPrint(
"✅ 发现已绑定设备: ${deviceInfo.device.platformName}, MAC: $deviceMac, 自动连接",
);
currentPeripheral = deviceInfo.device;
currentPeripheral = deviceInfo.device;
//[Fix]First / Previouslymarkconnectin,connectsuccessafterAgainpopup
await connect(deviceInfo.device);
if (AppState.shared.popupState) {
@@ -398,10 +378,7 @@ class BlueUtil {
}
final String targetMac = AppState.shared.deviceMac.toUpperCase();
if (deviceMac.toUpperCase() == targetMac) {
debugPrint(
"✅ 匹配到目标设备: ${deviceInfo.device.platformName}, MAC: $deviceMac",
);
currentPeripheral = deviceInfo.device;
currentPeripheral = deviceInfo.device;
connect(deviceInfo.device);
break;
}
@@ -425,16 +402,14 @@ class BlueUtil {
}
void stopScan() {
debugPrint("⏹️ StopScan");
FlutterBluePlus.stopScan();
FlutterBluePlus.stopScan();
_scanSubscription?.cancel();
_cleanupTimer?.cancel();
}
//MARK: - Connection related
Future<void> connect(BluetoothDevice peripheral) async {
debugPrint("🔗 StartConnectDevice: ${peripheral.platformName}");
try {
try {
_connectionStateSubscription?.cancel();
_connectionStateSubscription = peripheral.connectionState.listen((state) {
_handleConnectionState(peripheral, state);
@@ -449,8 +424,7 @@ class BlueUtil {
String errorMsg = e is FlutterBluePlusException
? "${e.description} (code: ${e.code})"
: e.toString();
debugPrint(" ConnectFailed: $errorMsg");
connectionStateChanged?.call(peripheral, false);
connectionStateChanged?.call(peripheral, false);
}
}
@@ -460,17 +434,13 @@ class BlueUtil {
) {
switch (state) {
case BluetoothConnectionState.connected:
debugPrint(" ConnectSuccess: ${peripheral.platformName}");
currentPeripheral = peripheral;
currentPeripheral = peripheral;
_peripheralDidConnect(peripheral);
connectionStateChanged?.call(peripheral, true);
break;
case BluetoothConnectionState.disconnected:
final disconnectReason = peripheral.disconnectReason;
debugPrint(
"🔌 设备断开: ${peripheral.platformName}, ${disconnectReason?.description ?? "no error"}",
);
currentPeripheral = null;
currentPeripheral = null;
_resetCharacteristics();
connectionStateChanged?.call(peripheral, false);
@@ -480,8 +450,7 @@ class BlueUtil {
final mac = _getDeviceId(deviceInfo);
if (mac != null) {
cachedDeviceMacs.remove(mac.toUpperCase());
debugPrint("🔌 AlreadyWillDisconnectDevice从CacheInRemove: $mac");
}
}
break;
}
}
@@ -502,20 +471,14 @@ class BlueUtil {
Future<void> _discoverServices(BluetoothDevice peripheral) async {
try {
if (peripheral.isDisconnected) {
debugPrint(" DeviceDisconnected,No法DiscoverService");
return;
return;
}
debugPrint("=====================================");
debugPrint(" StartDiscoverService [Device: ${peripheral.platformName}]");
debugPrint("=====================================");
//CallSystemmethoddiscoverservice
final services = await peripheral.discoverServices(timeout: 35);
debugPrint(" DiscoverServiceSuccess,TotalCount / Number量: ${services.length}");
for (var s in services) {
debugPrint("🟢 ServiceUUID: ${s.uuid}");
}
for (var s in services) {
}
//iteratediscoverfeature
for (var service in services) {
@@ -523,13 +486,7 @@ class BlueUtil {
}
} catch (e, stack) {
//Add stack print trace
debugPrint("=====================================");
debugPrint(" DiscoverService[彻底Failed]");
debugPrint(" ErrorType: ${e.runtimeType}");
debugPrint(" ErrorInfo: $e");
debugPrint(" Error堆Stack: $stack");
debugPrint("=====================================");
}
}
}
Future<void> _discoverCharacteristics(
@@ -538,17 +495,14 @@ class BlueUtil {
) async {
try {
final characteristics = service.characteristics;
debugPrint("🔍 DiscoverService[${service.uuid}]DownCharacteristicCount / Number量: ${characteristics.length}");
for (var characteristic in characteristics) {
debugPrint("🔍 DiscoverCharacteristic: ${characteristic.uuid}");
characteristicCallback?.call(peripheral, characteristic);
characteristicCallback?.call(peripheral, characteristic);
await _setupCharacteristicListener(peripheral, characteristic);
_saveCharacteristicReference(characteristic);
}
} catch (e) {
debugPrint(" Discover特征Failed: $e");
}
}
}
Future<void> _setupCharacteristicListener(
@@ -560,8 +514,7 @@ class BlueUtil {
const List<String> needNotifyUuids = [wifiSetCharacteristicUUID];
if (!needNotifyUuids.map((e) => e.toLowerCase()).contains(uuid)) {
debugPrint("️ Characteristic[$uuid] Not in白名单,SkipListener");
return;
return;
}
//onlyhasinwhitelistInsidefeatureValue,Only thenexecuteDownSurface / Sidelistenlogic
@@ -570,19 +523,15 @@ class BlueUtil {
try {
bool notifySuccess = await characteristic.setNotifyValue(true);
if (notifySuccess) {
debugPrint(" Characteristic[$uuid] Listener开启Success");
} else {
debugPrint(" Characteristic[$uuid] Listener开启Failed");
}
} else {
}
} catch (e) {
debugPrint(" Characteristic[$uuid] ListenerSettingsException: $e");
}
}
}
//listendatareceive
characteristic.lastValueStream.listen((value) {
if (value.isEmpty) return;
debugPrint("📥 Characteristic[$uuid] 收到Data: ${utf8.decode(value)}");
if (uuid == wifiSetCharacteristicUUID.toLowerCase()) {
if (uuid == wifiSetCharacteristicUUID.toLowerCase()) {
wifiSetCharacteristicCall?.call(value);
}
});
@@ -618,16 +567,14 @@ class BlueUtil {
Future<void> disconnectCurrentPeripheral() async {
final peripheral = currentPeripheral;
if (peripheral == null) {
debugPrint(" NoConnectedDevice");
return;
return;
}
try {
await peripheral.disconnect(timeout: 35, queue: true, androidDelay: 2000);
_resetCharacteristics();
} catch (e) {
debugPrint("DisconnectFailed: $e");
}
}
}
void _resetCharacteristics() {
@@ -702,42 +649,35 @@ class BlueUtil {
String type,
) async {
if (characteristic == null) {
debugPrint(" 发送Failed:BluetoothDisconnected,No法发送: 发送Within容Is: $data");
return false;
return false;
}
final dataToSend = utf8.encode(data);
if (dataToSend.isEmpty) {
debugPrint(" 发送Failed:DataIs null/empty");
return false;
return false;
}
try {
debugPrint("📤 In progress发送Data:$data");
await characteristic.write(
dataToSend,
withoutResponse: false,
allowLongWrite: true,
);
debugPrint(" 发送Success!Type:$type");
return true;
return true;
} catch (e) {
debugPrint(" 发送Failed!Error:$e");
//Send failed = connection broken can reconnect here
if (e.toString().contains("Timed out")) {
debugPrint("🔌 Bluetooth发送Timeout,DeviceDisconnected");
//cantriggerreconnectlogic
//cantriggerreconnectlogic
}
return false;
}
}
Future<void> reconnect() async {
debugPrint("🔄 Try重连...");
if (currentPeripheral != null && currentPeripheral!.isDisconnected) {
if (currentPeripheral != null && currentPeripheral!.isDisconnected) {
_resetCharacteristics();
await connect(currentPeripheral!);
onReconnectSuccess?.call(currentPeripheral!);
+7 -8
View File
@@ -19,7 +19,7 @@ extension HexExtension on Uint8List {
}
}
// NeedReplaceKeyValueFor
// NeedReplaceKeyValueFor
final projectStringReplacement = {
"小智": "Xiaozhi",
"Qwen3 实时": "Qwen3 235B (Fast)",
@@ -32,20 +32,20 @@ final projectStringReplacement = {
};
extension StringTool on String? {
/// Regex批量ReplaceString
/// projectStringReplacement ThenReplaceAllMatchContent
/// RegexReplaceString
/// projectStringReplacement ThenReplaceAllMatchContent
String? regularExpressionSubstitution() {
// 1. NullValueDirectlyReturns null
if (this == null) {
return null;
}
// 2. Non-NullString
// 2. Non-NullString
String result = this!;
// 3. IterateReplaceDictionary,ReplaceAllMatchItem
// 3. IterateReplaceDictionary,ReplaceAllMatchItem
for (final entry in projectStringReplacement.entries) {
// Escape特殊字符,AvoidRegex报错(,)
// Escape,AvoidRegex(,)
final pattern = RegExp.escape(entry.key);
// GlobalReplaceAllMatchContent
result = result.replaceAll(RegExp(pattern), entry.value);
@@ -233,7 +233,6 @@ Future<Uint8List?> _compressImage(_CompressParams params) async {
return compressedData;
} catch (e) {
debugPrint('图片压缩Failed:$e');
return null;
return null;
}
}
+1 -2
View File
@@ -34,8 +34,7 @@ class MlKitUtil {
final faces = await _faceDetector.processImage(inputImage);
onFacesDetected(faces);
} catch (e) {
debugPrint("Face detection error: $e");
} finally {
} finally {
_isProcessing = false;
}
}
+49 -95
View File
@@ -17,7 +17,7 @@ import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
class MusicInfo {
int duration; //
int duration; //translated comment
String filePath;
String? title;
String? artist;
@@ -103,8 +103,7 @@ class MusicInfo {
}
} catch (e) {
//onlyPrintdeletefaillog,NotinterruptMainStreamProcess / Thread
debugPrint("DeleteWhenWhenPCMFileFailed: $e");
}
}
}
}
@@ -199,7 +198,7 @@ class MusicUtil {
//Singletonmode
MusicUtil._internal() {
_initAnalyzer();
_setupPlayerListener(); //beforeinitlisteneravoid遗漏
_setupPlayerListener(); //beforeinitlisteneravoid
}
static final MusicUtil shared = MusicUtil._internal();
@@ -223,10 +222,8 @@ class MusicUtil {
Future<void> _initAnalyzer() async {
try {
await MusicFeatureAnalyzer.initialize();
debugPrint("MusicFeatureAnalyzer InitializeSuccess");
} catch (e) {
debugPrint("MusicFeatureAnalyzer InitializeFailed: $e");
}
} catch (e) {
}
}
///configplayerlistener(System1Managerstate)
@@ -235,12 +232,10 @@ class MusicUtil {
//playerstatelisten(Containsplaystateandhandlestate)
_audioPlayer.playerStateStream.listen((PlayerState state) {
debugPrint("PlayerState变化: Play=${state.playing}, HandleState=${state.processingState}");
//Playback completion check (handle completed status)
if (state.processingState == ProcessingState.completed) {
debugPrint("MusicPlayComplete / Done,LoopState: ${_audioPlayer.loopMode}");
_currentPosition = 0.0; //resetprogress
_currentPosition = 0.0; //resetprogress
//SingleloopThenreplay,elseexecutecompletecallback
if (_audioPlayer.loopMode == LoopMode.one &&
@@ -263,8 +258,7 @@ class MusicUtil {
_audioPlayer.durationStream.listen((Duration? duration) {
if (duration != null) {
_musicDuration = duration.inMilliseconds / 1000.0;
debugPrint("MusicDurationUpdate: $_musicDuration Second(s)");
}
}
});
//playprogresslisten
@@ -279,8 +273,7 @@ class MusicUtil {
//errorlisten
_audioPlayer.errorStream.listen((PlayerException? e) {
if (e != null) {
debugPrint("PlayerError: 代码=${e.code}, Message=${e.message}");
}
}
});
}
@@ -300,27 +293,23 @@ class MusicUtil {
await _audioPlayer.setAudioSource(audioSource);
await _audioPlayer.play();
debugPrint("MusicByteDataPlaySuccess,VolumeAlreadySettingsIs1.0");
} on PlayerException catch (e) {
debugPrint("PlayMusicByteDataFailed: 代码=${e.code}, Message=${e.message}");
throw Exception("播放失败: ${e.message}");
} on PlayerException catch (e) {
throw Exception("播放失败: ${e.message}");
} catch (e) {
debugPrint("PlayMusicByteDataFailed: $e");
throw Exception("播放失败: $e");
throw Exception("播放失败: $e");
}
}
///playSinglemusic(playcompleteafterexecutecallback)
Future<void> playMusicOnce(MusicInfo musicInfo, Function() completion) async {
_playbackCompletion = completion;
await playMusic(musicInfo, isLoop: false); //play强制closeloop
await playMusic(musicInfo, isLoop: false); //playcloseloop
}
///PlayOnlineMusic1Time(s),RepeatCallThenStopFrontFrom beginningPlay
Future<void> playUrlMusicOnce(String? url, {Function()? completion}) async {
if (url == null) {
debugPrint("PlayFailed:URLIs null/empty");
return;
return;
}
try {
// First / PreviouslyStopFrontPlay
@@ -336,21 +325,17 @@ class MusicUtil {
await _audioPlayer.setUrl(url);
await _audioPlayer.play();
debugPrint("OnlineMusicPlaySuccess:$url");
} on PlayerException catch (e) {
debugPrint("PlayOnlineMusicFailed: 代码=${e.code}, Message=${e.message}");
throw Exception("播放失败: ${e.message}");
} on PlayerException catch (e) {
throw Exception("播放失败: ${e.message}");
} catch (e) {
debugPrint("PlayOnlineMusicFailed: $e");
throw Exception("播放失败: $e");
throw Exception("播放失败: $e");
}
}
///coreplaymethodsupportloop
Future<void> playMusic(MusicInfo? musicInfo, {bool isLoop = false}) async {
if (musicInfo == null) {
debugPrint("PlayFailed:MusicInfoIs null/empty");
return;
return;
}
//Recordcurrentplaymusicinfo
@@ -363,80 +348,67 @@ class MusicUtil {
final data = await musicInfo.loadData();
final contentType = musicInfo.mimeType;
await playMusicData(data, contentType: contentType);
debugPrint("PlayMusicSuccess:${musicInfo.title ?? musicInfo.filePath}");
} on PlayerException catch (e) {
debugPrint("PlayMusicFailed: 代码=${e.code}, Message=${e.message}");
throw Exception("播放失败: ${e.message}");
} on PlayerException catch (e) {
throw Exception("播放失败: ${e.message}");
} catch (e) {
debugPrint("PlayMusicFailed: $e");
throw Exception("播放失败: $e");
throw Exception("播放失败: $e");
}
}
///stopplay
Future<void> stopMusic() async {
await _audioPlayer.stop();
await _audioPlayer.seek(Duration.zero); //resetprogressto开头
await _audioPlayer.seek(Duration.zero); //resetprogressto
_currentPosition = 0.0;
_playbackCompletion = null;
_currentMusicInfo = null;
debugPrint("MusicAlreadyStopPlay");
}
}
///pauseplay
Future<void> pauseMusic() async {
if (_audioPlayer.playing) {
await _audioPlayer.pause();
debugPrint("MusicAlreadyPause");
}
}
}
///resumeplay
Future<void> resumeMusic() async {
if (!_audioPlayer.playing && _currentMusicInfo != null) {
await _audioPlayer.play();
debugPrint("MusicAlreadyResumePlay");
}
}
}
///setloopplaystate
void setMusicLoop(bool isLoop) {
final loopMode = isLoop ? LoopMode.one : LoopMode.off;
_audioPlayer.setLoopMode(loopMode);
debugPrint("LoopPlayStateAlreadySettingsIs: $isLoop (LoopMode: $loopMode)");
}
}
///jumpplayprogress
Future<void> seekTo(double seconds) async {
if (seconds < 0 || seconds > _musicDuration) {
debugPrint("ProgressJumpFailed:No效ProgressValue $seconds,TotalDuration $_musicDuration");
return;
return;
}
await _audioPlayer.seek(Duration(seconds: seconds.toInt()));
_currentPosition = seconds;
debugPrint("ProgressAlreadyJump到: $seconds Second(s)");
}
}
///Set volume (0.0 ~ 1.0)
Future<void> setVolume(double volume) async {
if (volume < 0.0 || volume > 1.0) {
debugPrint("VolumeSettingsFailed:No效Value $volume,Range需In 0.0 ~ 1.0 Between");
return;
return;
}
await _audioPlayer.setVolume(volume);
debugPrint("VolumeAlreadySettingsIs: $volume");
}
}
///setplayspeed
Future<void> setPlaybackSpeed(double speed) async {
if (speed <= 0) {
debugPrint("Play速度SettingsFailed:No效Value $speed,需Greater than 0");
return;
return;
}
await _audioPlayer.setSpeed(speed);
debugPrint("Play速度AlreadySettingsIs: $speed");
}
}
///Getcurrentplayprogress(Second(s))
double getCurrentPosition() => _currentPosition;
@@ -456,20 +428,17 @@ class MusicUtil {
await _audioPlayer.dispose();
_currentMusicInfo = null;
_playbackCompletion = null;
debugPrint("MusicUtil Asset / Resource源AlreadyRelease");
}
}
///improveaftermusicinfoparse(With / CarryVerboselog+cacheverify)
Future<MusicInfo?> getMusicInfoAsync(String urlString) async {
const tag = "MusicUtil/getMusicInfoAsync";
try {
debugPrint("$tag StartHandleMusicURL: $urlString");
//1. Parse URL
final uri = Uri.parse(urlString);
if (!uri.isAbsolute) {
debugPrint("$tag URLIs not绝ForPath,ParseFailed");
return null;
return null;
}
//2. Generate cache file info
@@ -481,8 +450,7 @@ class MusicUtil {
'.m4a',
'.flac',
].contains(extension.toLowerCase())) {
debugPrint("$tag Not支持File格式: $extension");
return null;
return null;
}
final fileName = '${uri.hashCode.toRadixString(16)}$extension';
//useDocumentDirectoryAnd / WhileNotisWhenwhenDirectory,avoidSystemautocleancachefile
@@ -499,11 +467,9 @@ class MusicUtil {
final stat = await file.stat();
final fileSizeKB = stat.size / 1024;
if (fileSizeKB < 10) {
debugPrint("$tag CacheFile过小($fileSizeKB KB),DeleteAndAgainDownload");
await file.delete();
await file.delete();
} else {
debugPrint("$tag UseCacheFile: $filePath");
return await _extractMetadataFromFile(filePath, uri);
return await _extractMetadataFromFile(filePath, uri);
}
}
@@ -512,15 +478,13 @@ class MusicUtil {
final stat = await file.stat();
final fileSizeKB = stat.size / 1024;
if (fileSizeKB < 10) {
debugPrint("$tag DownloadFile过小($fileSizeKB KB),No效File");
return null;
return null;
}
//5. Extract metadata
return await _extractMetadataFromFile(filePath, uri);
} catch (e, stackTrace) {
debugPrint("$tag ExecuteFailed:ExceptionType=${e.runtimeType},Message=$e,堆Stack=$stackTrace");
return null;
return null;
}
}
@@ -537,10 +501,8 @@ class MusicUtil {
}
await response.pipe(file.openWrite());
debugPrint("$tag FileDownloadComplete / Done:${file.path}");
} catch (e) {
debugPrint("$tag DownloadFailed:$e");
rethrow;
} catch (e) {
rethrow;
} finally {
httpClient.close();
}
@@ -552,15 +514,11 @@ class MusicUtil {
try {
final song = await MusicFeatureAnalyzer.metadata(filePath);
if (song == null) {
debugPrint("$tag No法ExtractMetadata:MusicFeatureAnalyzerReturnnull");
return null;
return null;
}
final durationSec = song.duration ~/ 1000; //convertas秒
debugPrint(
"$tag 提取元数据成功:时长=$durationSec秒,标题=${song.title},艺术家=${song.artist}",
);
final durationSec = song.duration ~/ 1000; //convertas
return MusicInfo(
durationSec,
filePath,
@@ -570,8 +528,7 @@ class MusicUtil {
artwork: song.albumArt,
);
} catch (e, stackTrace) {
debugPrint("$tag ExtractMetadataFailed:Exception=$e,堆Stack=$stackTrace");
return null;
return null;
}
}
@@ -595,14 +552,11 @@ class MusicUtil {
if (fileAge > maxAge) {
await file.delete();
deletedCount++;
debugPrint("$tag DeleteExpiredCache:${file.path},ExistsDuration=${fileAge.inDays}");
}
}
}
}
}
debugPrint("$tag CacheCleanupComplete / Done,共Delete$deletedCount个File");
} catch (e) {
debugPrint("$tag CacheCleanupFailed:$e");
}
} catch (e) {
}
}
}
+4 -8
View File
@@ -74,12 +74,10 @@ class NativeBridge {
if (method != Method.unknown && _handlers.containsKey(method)) {
return await _handlers[method]!(call);
} else {
debugPrint('Unregistered method: ${call.method}');
return null;
return null;
}
} catch (e) {
debugPrint('Error handling method call: $e');
return null;
return null;
}
}
@@ -88,8 +86,7 @@ class NativeBridge {
try {
return await _channel.invokeMethod(method.name, arguments);
} catch (e) {
debugPrint('Error sending message to native: $e');
return null;
return null;
}
}
@@ -98,8 +95,7 @@ class NativeBridge {
try {
await _audioPlayChannel.send(pcmData);
} catch (e) {
debugPrint("Audio stream send failed: $e");
}
}
}
}
+3 -3
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
import 'package:flutter/services.dart';
///createtime2024/1/8
///Author:鸿
///Author:
///Description:status barManagertoolClass
class StatusBarManagement {
@@ -16,7 +16,7 @@ class StatusBarManagement {
return _statusBarManagement ?? StatusBarManagement();
}
///set沉浸式status barfontandicon
///setstatus barfontandicon
void setStatusBarImmerse(Brightness statusBarBrightness) {
SystemUiOverlayStyle style = SystemUiOverlayStyle(
statusBarColor: const Color(0x00000000),
@@ -121,7 +121,7 @@ class StatusBarManagement {
);
}
///set沉浸式status barandnavigation barfontandicon
///setstatus barandnavigation barfontandicon
void setStatusBarAndNavigationBarImmerseDark(
Brightness statusBarBrightness,
Brightness navigationBrightness,
+11 -13
View File
@@ -33,16 +33,16 @@ class Dance extends StatefulWidget {
}
class DanceModel extends GetxController {
RxInt selectedDance = RxInt(0); //currentSelectedDance帧index
RxInt selectedDance = RxInt(0); //currentSelectedDanceindex
Rx<DanceList> danceInfo = Rx(DanceList()); //danceData
RxInt dancePlayIndex = RxInt(-1); //inplay帧indexforhighlight
RxBool isRun = RxBool(false); //whether正inwholeplay
RxBool isLoop = RxBool(false); //new增loopplaymode开关
RxInt dancePlayIndex = RxInt(-1); //inplayindexforhighlight
RxBool isRun = RxBool(false); //whetherinwholeplay
RxBool isLoop = RxBool(false); //newloopplaymode
//playControl related
Timer? playTimer; //wholeplaytimermode
Timer? playTimer; //wholeplaytimermode
List<Future<void>?> bluetoothPlayTasks = []; //Bluetoothplaytasklist
RxBool isPlayingSingle = RxBool(false); //whether正inplay单帧
RxBool isPlayingSingle = RxBool(false); //whetherinplay
}
class _DanceState extends State<Dance> {
@@ -60,7 +60,7 @@ class _DanceState extends State<Dance> {
model.isRun.value = false;
model.isLoop.value = false; //disposewhenresetloopstate
model.onClose();
stopDance(); //disposewhen强制stopplay
stopDance(); //disposewhenstopplay
super.dispose();
}
@@ -125,7 +125,7 @@ class _DanceState extends State<Dance> {
//updateplaystate
model.isPlayingSingle.value = true;
model.dancePlayIndex.value = selectedIndex; //highlightcurrentplay帧
model.dancePlayIndex.value = selectedIndex; //highlightcurrentplay
///Single frameplaycorelogic
Future<void> playSingleFrame() async {
@@ -148,8 +148,7 @@ class _DanceState extends State<Dance> {
);
}
} catch (e) {
debugPrint("Single framePlayFailed: $e");
Get.snackbar("错误", "播放失败: $e", snackPosition: SnackPosition.BOTTOM);
Get.snackbar("错误", "播放失败: $e", snackPosition: SnackPosition.BOTTOM);
} finally {
//playcompleteafterresetstate
model.isPlayingSingle.value = false;
@@ -184,7 +183,7 @@ class _DanceState extends State<Dance> {
//wrapwholeplaylogic(forloopCall)
void playFullDance() {
if (!model.isRun.value) return; //stop则exit
if (!model.isRun.value) return; //stopexit
if (AppState.shared.deviceControlMode == 0) {
//NetworkControlmode:1One-timesendalldancedata
@@ -266,8 +265,7 @@ class _DanceState extends State<Dance> {
Duration(milliseconds: currentData.durationMs + 70),
);
} catch (e) {
debugPrint("BluetoothPlayFrame $i Failed: $e");
break;
break;
}
}
+10 -10
View File
@@ -281,7 +281,7 @@ class _DanceListPageState extends State<DanceListPage> {
final textColor = CupertinoColors.label.resolveFrom(context);
final subTextColor = CupertinoColors.secondaryLabel.resolveFrom(context);
//after1个addbutton - optimizestyle
//after1addbutton - optimizestyle
if (index == model.list.length) {
return Container(
height: itemHeight,
@@ -307,7 +307,7 @@ class _DanceListPageState extends State<DanceListPage> {
builder: (context) {
return RecordDance(
onResult: (danceList, musicUrl, musicName) async {
//Wait 2 Second(s),afterexecute
//Wait 2 Second(s),afterexecute
showEditDanceName(danceList, musicUrl, musicName);
// if (musicName != null &&
@@ -393,14 +393,14 @@ class _DanceListPageState extends State<DanceListPage> {
);
} else {
DanceList dance = model.list[index];
//useSlidableImplementLeft滑menu
//useSlidableImplementLeftmenu
return Slidable(
key: Key('dance_${dance.id}'),
//Left滑config
//Leftconfig
endActionPane: ActionPane(
extentRatio: 0.6,
motion: const ScrollMotion(), //
motion: const ScrollMotion(), //translated comment
dismissible: DismissiblePane(
onDismissed: () => deleteDance(dance.id),
),
@@ -431,12 +431,12 @@ class _DanceListPageState extends State<DanceListPage> {
),
],
),
//listItemMain体 - optimizestyle
//listItemMain - optimizestyle
child: Obx(
() => Container(
height: itemHeight,
decoration: BoxDecoration(
//background + Shadow
//background + Shadow
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -623,7 +623,7 @@ class _DanceListPageState extends State<DanceListPage> {
isPlaying = true;
MusicUtil.shared.stopMusic();
//checkmusicinfoandfilewhetherhas效
//checkmusicinfoandfilewhetherhas
var musicInfo = currentDance.musicInfo;
if (musicInfo == null || !(await File(musicInfo.filePath).exists())) {
if (currentDance.musicUrl != null && currentDance.musicUrl!.isNotEmpty) {
@@ -662,7 +662,7 @@ class _DanceListPageState extends State<DanceListPage> {
if (isPlaying && model.runId.value == currentPlayId) {
if (currentLoopMode) {
isPlaying = false;
startPlay(); //async调用
startPlay(); //async
} else {
stopPlay();
}
@@ -707,7 +707,7 @@ class _DanceListPageState extends State<DanceListPage> {
if (isPlaying && model.runId.value == currentPlayId) {
if (currentLoopMode) {
isPlaying = false;
startPlay(); //async调用
startPlay(); //async
} else {
stopPlay();
}
+2 -2
View File
@@ -29,7 +29,7 @@ class _HomeState extends State<Home> {
}
Future<void> init() async {
//Main页initwhensetasWiFimode
//MaininitwhensetasWiFimode
BlueUtil.shared.blueMode = 1;
AppState.shared.webSocketMessageMonitoring();
@@ -37,7 +37,7 @@ class _HomeState extends State<Home> {
AppState.shared.connectWebSocket();
}
//setBluetoothdevicescancallback,updateMain页devicelist
//setBluetoothdevicescancallback,updateMaindevicelist
BlueUtil.shared.blufDevicesMonitoring = (List<BlueDeviceInfo> devices) {
AppState.shared.blueDeviceList.value = devices;
};
+2 -2
View File
@@ -76,7 +76,7 @@ class _MonitoringCameraState extends State<MonitoringCamera> {
}
});
//re打开Camera + audioStream(keyfix:exitAgain进must重发)
//reCamera + audioStream(keyfix:exitAgainmust)
if (AppState.shared.deviceMac.isNotEmpty) {
AppState.shared.sendWebSocketMessage(
.onCamera,
@@ -98,7 +98,7 @@ class _MonitoringCameraState extends State<MonitoringCamera> {
// };
}
//cleanAsset / Resource源
//cleanAsset / Resource
void _cleanResources() {
WebSocketUtil.shared.removeObserver(tag);
if (AppState.shared.deviceMac.isNotEmpty) {
+5 -7
View File
@@ -43,7 +43,7 @@ class _PanoPageState extends State<PanoPage> {
childAspectRatio: 1,
);
//data
//data
List<MotionData> motionList = [
//1
MotionData(
@@ -252,13 +252,12 @@ class _PanoPageState extends State<PanoPage> {
data: AppState.shared.deviceMac.toUint8List(),
);
AppState.shared.showToast("The shooting was unsuccessful.${e.toString()}");
debugPrint("拍照Exception:$e");
} finally {
} finally {
isTakingPhotos.value = false;
recordSwitch = false;
}
///deviceSide / EndCamera
///deviceSide / EndCamera
AppState.shared.sendWebSocketMessage(
.onCamera,
data: AppState.shared.deviceMac.toUint8List(),
@@ -271,7 +270,7 @@ class _PanoPageState extends State<PanoPage> {
data: jsonString.toUint8List(),
);
///Wait500ms,after recordSwitch = true, Again等 500ms, executeNext
///Wait500ms,after recordSwitch = true, Again 500ms, executeNext
}
///closedeviceSide / EndCamera
@@ -317,8 +316,7 @@ class _PanoPageState extends State<PanoPage> {
result.dispose();
} catch (e) {
AppState.shared.showToast("Error: ${e.toString()}");
debugPrint("Stitch error: $e");
} finally {
} finally {
for (var mat in mats) {
mat.dispose();
}
+1 -5
View File
@@ -104,11 +104,7 @@ class _RecordDanceState extends State<RecordDance> {
durationMs: 100,
);
recordedDanceFrames.add(danceFrame);
debugPrint(
"Recorded frame ${recordedDanceFrames.length}: "
"Yaw=${danceFrame.yawServo.angle}, Pitch=${danceFrame.pitchServo.angle}, Duration=100ms",
);
}
}
Widget buildBandFrequencyChart(List<double> frequencies, double progress) {
if (frequencies.isEmpty) {
+4 -4
View File
@@ -261,7 +261,7 @@ class _SettingsState extends State<Settings> {
),
),
//====================== userinfo区域 ======================
//====================== userinfo ======================
// CupertinoListSection.insetGrouped(
// children: [_buildUserProfileTile()],
// ),
@@ -272,12 +272,12 @@ class _SettingsState extends State<Settings> {
// ),
// ),
//====================== set ======================
//====================== set ======================
CupertinoListSection.insetGrouped(
children: [_buildChangeNameTile()],
),
//====================== AI代理set ======================
//====================== AIset ======================
CupertinoListSection.insetGrouped(
children: [
_buildAgentConfigTile(),
@@ -653,7 +653,7 @@ class _SettingsState extends State<Settings> {
maxLines: 1,
autofocus: true,
inputFormatters: [
//addinputlimit,
//addinputlimit,
LengthLimitingTextInputFormatter(15),
],
onChanged: (value) => newName = value,
+17 -19
View File
@@ -15,7 +15,7 @@ import '../../model/XiaoZhi/agent_template.dart';
import '../../model/XiaoZhi/device.dart';
import '../../util/XiaoZhi_util.dart';
//System1UIConstant - Can维护性
//System1UIConstant - Can
const double kCardRadius = 16.0;
const double kDefaultPadding = 16.0;
const double kDefaultSpacing = 16.0;
@@ -34,7 +34,7 @@ class AgentConfigurationModel extends GetxController {
RxList<AgentTemplate> agentTemplatesList = RxList([]);
Rxn<Agent> currentBindAgent = Rxn(null);
//search相关
//search
int agentListPage = 1;
RxBool isLoading = false.obs;
RxBool isDialogLoading = false.obs;
@@ -46,8 +46,7 @@ class AgentConfigurationModel extends GetxController {
try {
await Future.wait([loadDevice(), loadAgentTemplates()]);
} catch (e) {
debugPrint("Failed to initialize data: $e");
} finally {
} finally {
isLoading.value = false;
}
}
@@ -117,15 +116,14 @@ class AgentConfigurationModel extends GetxController {
AppState.shared.showToast("Successfully switched AI Agent");
return true;
} catch (e) {
debugPrint("Failed to switch bound AI Agent: $e");
AppState.shared.showToast(e.toString().replaceAll("Exception: ", ""));
AppState.shared.showToast(e.toString().replaceAll("Exception: ", ""));
return false;
} finally {
isLoading.value = false;
}
}
//loadagent模板
//loadagent
Future<void> loadAgentTemplates() async {
final templates = await XiaoZhiUtil.shared.agentTemplatesList(
agentListPage,
@@ -134,7 +132,7 @@ class AgentConfigurationModel extends GetxController {
agentTemplatesList.assignAll(templates);
}
//Replace字符Descriptionin占位符
//ReplaceDescriptionin
String replacePlaceholdersInCharacter(Agent agent) {
if (agent.character == null || agent.character!.isEmpty) {
return "";
@@ -199,7 +197,7 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
@override
Widget build(BuildContext context) {
//GetCupertinodynamicTheme色
//GetCupertinodynamicTheme
final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context);
final Color surfaceColor = CupertinoColors.systemGroupedBackground
.resolveFrom(context);
@@ -235,10 +233,10 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
color: primaryColor,
),
),
border: const Border(bottom: BorderSide.none), //remove底部边框更现代
border: const Border(bottom: BorderSide.none), //remove
),
child: Obx(() {
//Initialloadstate
//Initialloadstate
if (model.isLoading.value) {
return const Center(
child: CupertinoActivityIndicator(radius: 16), //increaseloadindicator
@@ -256,21 +254,21 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
vertical: 24,
),
children: [
//currentbindagent卡片
//currentbindagent
_buildCurrentBindCard(
cupertinoTheme,
primaryColor,
secondaryTextColor,
),
const SizedBox(height: 32),
//increase间距提升呼吸感
//increase
//listtitle
// const Text(
// "AI Agent Templates",
// style: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.w600,
//letterSpacing: -0.5, // Zoom out字间距更精致
//letterSpacing: -0.5, // Zoom out
// ),
// ),
// const SizedBox(height: 16),
@@ -289,7 +287,7 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
);
}
//currentbindagent卡片 - optimize视觉质感
//currentbindagent - optimize
Widget _buildCurrentBindCard(
CupertinoThemeData theme,
Color primaryColor,
@@ -308,7 +306,7 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
mainAxisSize: MainAxisSize.min,
children: [
Icon(
CupertinoIcons.person_badge_plus, //use填充icon更现代
CupertinoIcons.person_badge_plus, //useicon
size: 56,
color: secondaryTextColor.withValues(alpha: 0.7),
),
@@ -326,7 +324,7 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
"Select an Agent template below to bind, or create a custom AI Agent by tapping the edit button in the upper right corner.",
style: TextStyle(
fontSize: 15,
height: 1.5, //
height: 1.5, //translated comment
),
textAlign: TextAlign.center,
),
@@ -430,7 +428,7 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
);
}
//Nodevicehintcomponent - optimize视觉表现
//Nodevicehintcomponent - optimize
Widget _buildNoDeviceWidget(Color secondaryTextColor, Color primaryColor) {
return Center(
child: Padding(
@@ -479,7 +477,7 @@ class _AgentConfigurationState extends State<AgentConfiguration> {
);
}
//infoItemcomponent - optimize排版
//infoItemcomponent - optimize
Widget _buildInfoItem(String title, String value, Color secondaryTextColor) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
+2 -4
View File
@@ -262,9 +262,7 @@ class _ScanningEquipmentState extends State<ScanningEquipment> {
CupertinoSheetRoute.popSheet(context);
}
} on FormatException catch (e) {
debugPrint('JSONParseFailed: ${e.message}');
} on Exception catch (e) {
debugPrint('HandleMACAddressWhen出错: $e');
}
} on Exception catch (e) {
}
}
}
@@ -25,7 +25,7 @@ class _ConversationMessagePageState extends State<ConversationMessagePage> {
RxBool isLoading = RxBool(false); //loadstate
RxBool hasMore = RxBool(true); //whetherhasmoredata
final DateFormat timeFormat = DateFormat("yyyy-MM-dd HH:mm"); //time格式化
final DateFormat timeFormat = DateFormat("yyyy-MM-dd HH:mm"); //time
RxList<ConversationMessageData> messageList = RxList([]);
@@ -164,7 +164,7 @@ class _ConversationMessagePageState extends State<ConversationMessagePage> {
)
: !hasMore.value
? Padding(
padding: EdgeInsets.symmetric(vertical: 12), //fixEdgeInsets
padding: EdgeInsets.symmetric(vertical: 12), //fixEdgeInsets
child: Center(
child: Text(
"No more messages",
@@ -175,7 +175,7 @@ class _ConversationMessagePageState extends State<ConversationMessagePage> {
),
),
)
: const SizedBox.shrink(), //hasmoredatawhen不show任何componentautoload
: const SizedBox.shrink(), //hasmoredatawhenshowcomponentautoload
);
}
+1 -1
View File
@@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
//====================== devicename页(completed) ======================
//====================== devicename(completed) ======================
import 'package:flutter/cupertino.dart';
import 'package:stack_chan/view/popup/XiaoZhi_welcome_page.dart';
+8 -16
View File
@@ -68,10 +68,8 @@ class _DeviceWifiConfigState extends State<DeviceWifiConfig> {
try {
await AppState.asyncPrefs.setString(WifiCacheKeys.wifiName, name);
await AppState.asyncPrefs.setString(WifiCacheKeys.wifiPassword, password);
debugPrint("WiFi info cached: $name");
} catch (e) {
debugPrint("Failed to save WiFi cache: $e");
AppState.shared.showToast("Failed to save WiFi information");
} catch (e) {
AppState.shared.showToast("Failed to save WiFi information");
}
}
@@ -91,13 +89,11 @@ class _DeviceWifiConfigState extends State<DeviceWifiConfig> {
_nameTextEditingController.text = cachedName;
_passwordTextEditingController.text = cachedPassword;
});
debugPrint("Using cached WiFi info: $cachedName");
return true;
return true;
}
return false;
} catch (e) {
debugPrint("Failed to load WiFi cache: $e");
return false;
return false;
}
}
@@ -105,8 +101,7 @@ class _DeviceWifiConfigState extends State<DeviceWifiConfig> {
Future<void> _initializeWifiInfo() async {
final hasCache = await _loadCachedWifiInfo();
if (!hasCache) {
debugPrint("No WiFi cache found, fetching from system");
await _fetchSystemWifiName();
await _fetchSystemWifiName();
}
}
@@ -118,8 +113,7 @@ class _DeviceWifiConfigState extends State<DeviceWifiConfig> {
if (status.isGranted) {
await _fetchAndroidWifiName();
} else {
debugPrint("Location permission denied, cannot fetch WiFi name");
}
}
} else if (Platform.isIOS) {
// iOS: Request permission and immediately fetch via native bridge
// (no need to wait for permission result on iOS)
@@ -141,12 +135,10 @@ class _DeviceWifiConfigState extends State<DeviceWifiConfig> {
_wifiName = cleanWifiName;
_nameTextEditingController.text = cleanWifiName;
});
debugPrint("Fetched Android WiFi name: $cleanWifiName");
}
}
}
} on PlatformException catch (e) {
debugPrint("Failed to fetch Android WiFi name: $e");
}
}
}
@override
+28 -28
View File
@@ -16,7 +16,7 @@ import '../../model/XiaoZhi/common_mcp_tool.dart';
import '../../util/XiaoZhi_util.dart';
import 'agent_configuration.dart';
//Editor者Create agent
//EditorCreate agent
class EditAgent extends StatefulWidget {
const EditAgent({super.key, this.agent});
@@ -41,7 +41,7 @@ class EditAgentModel extends GetxController {
final Rxn<ModelData> selectedModel = Rxn();
final Rxn<TTsVoice> selectedTtsVoice = Rxn();
final RxString selectedLanguage = "".obs; //dynamic赋值
final RxString selectedLanguage = "".obs; //dynamic
final RxString ttsSpeed = "normal".obs;
final RxInt ttsPitch = 0.obs;
final RxString asrSpeed = "normal".obs;
@@ -70,7 +70,7 @@ class EditAgentModel extends GetxController {
ever(selectedLanguage, (lang) => _updateTtsVoiceList(lang));
await loadCommonMcpTools();
await loadTtsList(); //loadTTS autogeneratelanguagelist
await loadTtsList(); //loadTTS autogeneratelanguagelist
await loadModelList();
if (agent != null) {
@@ -128,7 +128,7 @@ class EditAgentModel extends GetxController {
characterController.text = agent.character ?? "";
memoryController.text = agent.memory ?? "";
//Editmode:languagemustexist于dynamiclistin
//Editmode:languagemustexistdynamiclistin
if (languageList.contains(agent.language)) {
selectedLanguage.value = agent.language!;
} else if (languageList.isNotEmpty) {
@@ -278,8 +278,8 @@ class _EditAgentState extends State<EditAgent> {
() => Text(
model.isEdit.value
? (agentConfigurationModel.currentBindAgent.value?.agent_name ??
"Edit Agent") //error
: "Create AI Agent", //
"Edit Agent") //error
: "Create AI Agent", //translated comment
),
),
leading: CupertinoNavigationBarBackButton(),
@@ -311,24 +311,24 @@ class _EditAgentState extends State<EditAgent> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//1. info
//1. info
_buildSectionTitle("Basic Information"),
// _buildInputItem(
// title: "Agent Name",
// controller: model.agentNameController,
// placeholder:
//"Please enter the name of the AI Agent.", // System1术语
//"Please enter the name of the AI Agent.", // System1
// ),
_buildInputItem(
title: "Assistant Name", //
title: "Assistant Name", //translated comment
controller: model.assistantNameController,
placeholder:
"Please enter the assistant's name (e.g. StackChan).",
),
//2. modelconfig
_buildSectionTitle("Model Configuration"), //standard
_buildSectionTitle("Model Configuration"), //standard
_buildSelectItem(
title: "LLM Model", //Large Language Model
title: "LLM Model", //Large Language Model
value: model.selectedModel.value?.name ?? "Please select",
onTap: showModelPicker,
),
@@ -337,55 +337,55 @@ class _EditAgentState extends State<EditAgent> {
value: getLanguagesTitle(model.selectedLanguage.value),
onTap: showLanguagePicker,
),
//3. config
_buildSectionTitle("Voice Configuration"), //standard
//3. config
_buildSectionTitle("Voice Configuration"), //standard
_buildSelectItem(
title: "Voice Tone", //Timbre
title: "Voice Tone", //Timbre
value:
model.selectedTtsVoice.value?.voiceName ??
"Please select",
onTap: showTtsPicker,
),
_buildSelectItem(
title: "TTS Speech Speed", //isTTS语速
title: "TTS Speech Speed", //isTTS
value: model.ttsSpeed.value,
onTap: showSpeedPicker,
),
_buildSelectItem(
title: "TTS Pitch", //isTTS音调
title: "TTS Pitch", //isTTS
value: model.ttsPitch.value.toString(),
onTap: showPitchPicker,
),
_buildSelectItem(
title: "ASR Speed", //Automatic Speech Recognition
title: "ASR Speed", //Automatic Speech Recognition
value: model.asrSpeed.value,
onTap: showAsrSpeedPicker,
),
//4. characterconfig
_buildSectionTitle("Character Configuration"), //standard
_buildSectionTitle("Character Configuration"), //standard
_buildInputItem(
title: "Character Description",
controller: model.characterController,
placeholder:
"Please provide the character description (max 2000 characters).",
//(wordscharacters)
//(wordscharacters)
maxLines: 4,
),
_buildInputItem(
title: "Short-term Memory Content", //
title: "Short-term Memory Content", //translated comment
controller: model.memoryController,
placeholder:
"Please enter the short-term memory content.",
maxLines: 3,
),
_buildSelectItem(
title: "Memory Type", //standard
title: "Memory Type", //standard
value: model.memoryType.value == "SHORT_TERM"
? "Short-term Memory" //standard
: "Disabled", //Shut downUI习惯
? "Short-term Memory" //standard
: "Disabled", //Shut downUI
onTap: showMemoryTypePicker,
),
//5. MCPtoolconfig(Part也optimize)
//5. MCPtoolconfig(Partoptimize)
// _buildSectionTitle("MCP Tools (Optional)"),
// model.commonMcpTools.isEmpty
// ? const Center(child: Text("No available MCP tools"))
@@ -510,7 +510,7 @@ class _EditAgentState extends State<EditAgent> {
required String title,
required List<String> items,
required Function(int) onSelected,
int initialIndex = 0, //new增selectedindex
int initialIndex = 0, //newselectedindex
}) {
//initselectedItemascurrentValue(fixdefaultselectederror)
selectedItem = initialIndex;
@@ -558,7 +558,7 @@ class _EditAgentState extends State<EditAgent> {
);
}
//title
//title
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -569,7 +569,7 @@ class _EditAgentState extends State<EditAgent> {
);
}
//input表单Item
//inputItem
Widget _buildInputItem({
required String title,
required TextEditingController controller,
@@ -603,7 +603,7 @@ class _EditAgentState extends State<EditAgent> {
);
}
//select表单Item
//selectItem
Widget _buildSelectItem({
required String title,
required String value,
+9 -9
View File
@@ -138,11 +138,11 @@ class _LoginPageState extends State<LoginPage> {
);
return;
} else {
//defaulthint
//defaulthint
String errorMessage = "Login failed";
if (text != null && text.isNotEmpty) {
//match [[error:xxx]] ,Extractin间errorcontent
//match [[error:xxx]] ,Extractinerrorcontent
final regExp = RegExp(r'\[\[error:(.*?)\]\]');
final match = regExp.firstMatch(text);
@@ -150,11 +150,11 @@ class _LoginPageState extends State<LoginPage> {
//Extracttocustomerrorcontent
errorMessage = match.group(1)!.trim();
} else {
//texterror,directuse
//texterror,directuse
errorMessage = text.trim();
}
}
// Toast
// Toast
AppState.shared.showToast(errorMessage);
}
}
@@ -455,7 +455,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
if (responseData.isSuccess()) {
AppState.shared.showToast("Registration successful!");
if (mounted) {
Navigator.pop(context); //returnlogin页
Navigator.pop(context); //returnlogin
}
} else {
showLoginErrMessage(responseData.message);
@@ -466,11 +466,11 @@ class _RegistrationPageState extends State<RegistrationPage> {
}
void showLoginErrMessage(String? text) {
//defaulthint
//defaulthint
String errorMessage = "Register failed";
if (text != null && text.isNotEmpty) {
//match [[error:xxx]] ,Extractin间errorcontent
//match [[error:xxx]] ,Extractinerrorcontent
final regExp = RegExp(r'\[\[error:(.*?)\]\]');
final match = regExp.firstMatch(text);
@@ -478,11 +478,11 @@ class _RegistrationPageState extends State<RegistrationPage> {
//Extracttocustomerrorcontent
errorMessage = match.group(1)!.trim();
} else {
//texterror,directuse
//texterror,directuse
errorMessage = text.trim();
}
}
// Toast
// Toast
AppState.shared.showToast(errorMessage);
}
+1 -1
View File
@@ -71,7 +71,7 @@ class _MotionState extends State<Motion> {
if (AppState.shared.deviceMac.isNotEmpty) {
String jsonString = AppState.shared.deviceMac + avatarData.toString();
AppState.shared.sendWebSocketMessage(
.controlAvatar, //as WebSocketMessageType
.controlAvatar, //as WebSocketMessageType
data: jsonString.toUint8List(),
);
}
+32 -53
View File
@@ -34,10 +34,10 @@ class SelectBlueDevice extends StatefulWidget {
}
class _SelectBlueDeviceState extends State<SelectBlueDevice> {
//connecttimeouttimer(preventNo限Wait)
//connecttimeouttimer(preventNoWait)
Timer? _connectTimer;
//verifytimeouttimer(preventdeviceNoresponsewhen1直to圈)
//verifytimeouttimer(preventdeviceNoresponsewhen1to)
Timer? _verifyTimer;
//timeouttime:30Second(s)
@@ -116,11 +116,9 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
await device
.requestMtu(512)
.then((mtu) {
debugPrint(" MTUSettingsSuccess: $mtu");
})
})
.catchError((e) {
debugPrint(" MTUSettingsFailed: $e");
if (mounted) {
if (mounted) {
AppState.shared.showToast(
"Failed to set MTU. Device may not work properly.",
);
@@ -130,11 +128,9 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
//enablefeatureValuenotify
try {
await characteristic.setNotifyValue(true);
debugPrint(" 开启DeviceNotificationSuccess");
startVerificationEquipment(characteristic);
startVerificationEquipment(characteristic);
} catch (e) {
debugPrint(" 开启DeviceNotificationFailed: $e");
_resetConnectState();
_resetConnectState();
if (mounted) {
AppState.shared.showToast(
"Failed to enable device notification.",
@@ -144,20 +140,17 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
return;
}
}
debugPrint(" Not找到WiFiConfigurationCharacteristic");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Device configuration feature not found.");
}
};
BlueUtil.shared.wifiSetCharacteristicCall = (data) async {
try {
String json = utf8.decode(data);
debugPrint("📥 收到DeviceNotification: $json");
final model = BlueNotifyStateModel.fromJson(json);
final model = BlueNotifyStateModel.fromJson(json);
if (model == null) {
debugPrint(" DeviceDataParseFailed");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Failed to parse device data.");
}
return;
@@ -166,11 +159,10 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
if (model.cmd != null &&
model.cmd == "notifyState" &&
model.data?.type == 4) {
//to身份Encryptinfo
//toEncryptinfo
String? data = model.data?.state;
if (data == null) {
debugPrint(" DeviceNotReturnEncryptInfo");
if (mounted) {
if (mounted) {
AppState.shared.showToast(
"Device did not return encryption data.",
);
@@ -180,10 +172,9 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
//RSADecrypt
final result = RsaUtil.decryptStackChanBlue(data);
//newIncrease / Add:Decryptfail/lengthNot足
//newIncrease / Add:Decryptfail/lengthNot
if (result.isEmpty || result.length < 12) {
debugPrint(" DeviceInfoDecryptFailed");
if (mounted) {
if (mounted) {
AppState.shared.showToast(
"Device verification decryption failed.",
);
@@ -207,8 +198,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
activateDevice(macAddress);
}
} catch (e) {
debugPrint(" HandleDeviceNotificationFailed: $e");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Failed to process device data.");
}
}
@@ -220,7 +210,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
return mac
.toUpperCase()
.replaceAllMapped(RegExp(r'(.{2})'), (match) => '${match.group(1)}:')
.substring(0, 17); //remove最after一个冒号
.substring(0, 17); //removeafter
}
bool isValidMac(String mac) {
@@ -235,7 +225,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
bool isConfiguration = await queryConfiguration(macAddress);
if (!isConfiguration) {
_resetConnectState();
//activatefail(Alreadyhashint,No需Repeat)
//activatefail(Alreadyhashint,NoRepeat)
return;
}
@@ -243,7 +233,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
AppState.shared.showToast("The AI Agent has been configured.");
}
//binddevice关系
//binddevice
bool result = await AppState.shared.bindDevice(macAddress);
if (result) {
_resetConnectState();
@@ -257,21 +247,19 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
} else {
_resetConnectState();
//newIncrease / Add:binddevicefail
debugPrint(" DeviceBindFailed");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Device binding failed. Please try again.");
}
}
} catch (e) {
_resetConnectState();
debugPrint(" DeviceActivateStreamProcess / ThreadException: $e");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Device activation exception.");
}
}
}
//querydeviceconfig(StreamProcess / Threaderrorhint)
//querydeviceconfig(StreamProcess / Threaderrorhint)
Future<bool> queryConfiguration(String macAddress) async {
try {
//1. querydevicewhetheractivated (laterNotAgainquery directactivate)
@@ -288,8 +276,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
macAddress,
);
if (generateLicense == null || generateLicense.serialNumber == null) {
debugPrint(" 生成DeviceLicenseFailed");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Failed to generate device license.");
}
return false;
@@ -307,14 +294,13 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
mac,
);
if (!activateResult) {
debugPrint(" Device云Side / EndActivateFailed");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Device cloud activation failed.");
}
return false;
}
///deviceMayexist Notactivate,Needverify
///deviceMayexist Notactivate,Needverify
final checkDevice = await XiaoZhiUtil.shared.serialNumberGetDevice(
serialNumber,
);
@@ -323,12 +309,10 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
return false;
} else {
AppState.shared.showToast("Device activation successful");
debugPrint("DeviceActivateSuccess,DeviceId: ${checkDevice.device_id}");
return true;
return true;
}
} catch (e) {
debugPrint(" 查询DeviceConfigurationException: $e");
if (mounted) {
if (mounted) {
AppState.shared.showToast("Failed to query device configuration.");
}
return false;
@@ -406,8 +390,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
Duration(seconds: _connectTimeout),
() {
_resetConnectState();
debugPrint(" DeviceConnectTimeout");
if (mounted) {
if (mounted) {
AppState.shared.showToast(
"Device connection timed out. Please try again.",
);
@@ -427,8 +410,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
);
} catch (e) {
_resetConnectState();
debugPrint(" BluetoothConnectFailed: $e");
if (mounted) {
if (mounted) {
AppState.shared.showToast(
"Bluetooth connection failed: ${e.toString()}",
);
@@ -470,7 +452,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
);
}
//startverifydevice(send握手data)
//startverifydevice(senddata)
void startVerificationEquipment(
BluetoothCharacteristic characteristic,
) async {
@@ -482,14 +464,13 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
_verifyTimer?.cancel();
_verifyTimer = Timer(Duration(seconds: _verifyTimeout), () {
_resetConnectState();
debugPrint(" DeviceVerifyTimeout");
if (mounted) {
if (mounted) {
AppState.shared.showToast(
"Device verification timed out. Please try again.",
);
}
});
//data
//data
final dateTimeString = DateTime.now().millisecondsSinceEpoch.toString();
final BlueEncryptionDecryption data = BlueEncryptionDecryption(
cmd: "handshake",
@@ -500,8 +481,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
//senddata
bool result = await BlueUtil.shared.sendWifiSetData(jsonString);
if (result) {
debugPrint(" 发送DeviceVerify指令Success");
} else {
} else {
_verifyTimer?.cancel();
AppState.shared.showToast(
"The equipment may have been disconnected. Please reconfigure it on the StackChan end.",
@@ -511,8 +491,7 @@ class _SelectBlueDeviceState extends State<SelectBlueDevice> {
} catch (e) {
_verifyTimer?.cancel();
_resetConnectState();
debugPrint(" 发送DeviceVerify指令Failed: $e");
if (mounted) {
if (mounted) {
AppState.shared.showToast(
"Failed to send device verification command.",
);
+3 -3
View File
@@ -23,7 +23,7 @@ class _UserInfoPageState extends State<UserInfoPage> {
}
}
//time戳to日期
//timeto
String _formatTimestamp(int? timestamp) {
if (timestamp == null) return "Unknown";
final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
@@ -69,7 +69,7 @@ class _UserInfoPageState extends State<UserInfoPage> {
final userInfo = AppState.shared.userInfo.value!;
return ListView(
children: [
//====================== center文字圆Avatar ======================
//====================== centerAvatar ======================
Center(
child: Container(
width: 100,
@@ -153,7 +153,7 @@ class _UserInfoPageState extends State<UserInfoPage> {
);
}
//info行component
//infocomponent
Widget _buildInfoItem(String title, String value) {
return CupertinoListTile(
title: Text(title, style: TextStyle(fontWeight: FontWeight.w500)),
+8 -10
View File
@@ -115,14 +115,13 @@ class XiaoZhiEditAgentModel extends GetxController {
fillEditData(agent!);
}
} else {
///hasactivateagent,Needactivateagent
///hasactivateagent,Needactivateagent
//2. generatelicense
final generateLicense = await XiaoZhiUtil.shared.generateLicense(
AppState.shared.deviceMac,
);
if (generateLicense == null || generateLicense.serialNumber == null) {
debugPrint("Failed to generate device license.");
AppState.shared.showToast("Failed to generate device license.");
AppState.shared.showToast("Failed to generate device license.");
return;
}
@@ -138,8 +137,7 @@ class XiaoZhiEditAgentModel extends GetxController {
mac,
);
if (!activateResult) {
debugPrint(" Device云Side / EndActivateFailed");
AppState.shared.showToast("Device cloud activation failed.");
AppState.shared.showToast("Device cloud activation failed.");
return;
}
@@ -454,7 +452,7 @@ class _XiaoZhiWelcomePageState extends State<XiaoZhiWelcomePage> {
),
SizedBox(height: 15),
//info
//info
CupertinoListSection.insetGrouped(
header: Row(
mainAxisSize: .min,
@@ -555,7 +553,7 @@ class _XiaoZhiWelcomePageState extends State<XiaoZhiWelcomePage> {
children: [
//voice tone
ttsVoiceWidget(),
//
//translated comment
Obx(
() => buildSelectItem(
title: "Speed :",
@@ -576,7 +574,7 @@ class _XiaoZhiWelcomePageState extends State<XiaoZhiWelcomePage> {
selectedValue: getSpeedText(model.ttsSpeed.value),
),
),
//
//translated comment
Obx(
() => buildSelectItem(
title: "Pitch :",
@@ -597,7 +595,7 @@ class _XiaoZhiWelcomePageState extends State<XiaoZhiWelcomePage> {
selectedValue: model.ttsPitch.value.toString(),
),
),
//ASR语速
//ASR
Obx(
() => buildSelectItem(
title: "ASR Speed :",
@@ -621,7 +619,7 @@ class _XiaoZhiWelcomePageState extends State<XiaoZhiWelcomePage> {
],
),
//with记忆
//with
CupertinoListSection.insetGrouped(
header: Text("Personality"),
children: [
+1 -1
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: MIT
import 'package:flutter/cupertino.dart';
///
//translated comment
class GlassEffectCircle extends StatelessWidget {
const GlassEffectCircle({super.key, this.padding, this.child});
+14 -14
View File
@@ -57,14 +57,14 @@ class _GridCoordinateJoystickState extends State<GridCoordinateJoystick> {
}
}
//in _GridCoordinateJoystickState ClassinModify以Downmethod
//in _GridCoordinateJoystickState ClassinModifyDownmethod
void _updatePoint(Offset localPosition, Size size) {
//Get padding Value便捷方式
//Get padding Value
final padding =
widget.padding?.resolve(Directionality.of(context)) ?? EdgeInsets.zero;
//calculate实际Can操控区域(Inside缩after矩形)
//calculateCan(Insideafter)
double clickableWidth = size.width - padding.left - padding.right;
double clickableHeight = size.height - padding.top - padding.bottom;
@@ -78,13 +78,13 @@ class _GridCoordinateJoystickState extends State<GridCoordinateJoystick> {
size.height - padding.bottom,
);
//convertlogic:in (clampedX - padding.left) rangeis 0 to clickableWidth
//convertlogic:in (clampedX - padding.left) rangeis 0 to clickableWidth
double normalizedX =
((clampedX - padding.left) / clickableWidth) *
(widget.maxX - widget.minX) +
widget.minX;
//Y axis同理
//Y axis
double normalizedY =
widget.maxY -
((clampedY - padding.top) / clickableHeight) *
@@ -108,7 +108,7 @@ class _GridCoordinateJoystickState extends State<GridCoordinateJoystick> {
widget.padding?.resolve(Directionality.of(context)) ??
EdgeInsets.zero;
//corecalculatelogic:willcurrent点mapBackscreen像素position
//corecalculatelogic:willcurrentmapBackscreenposition
double clickableWidth = size.width - padding.left - padding.right;
double clickableHeight = size.height - padding.top - padding.bottom;
@@ -117,7 +117,7 @@ class _GridCoordinateJoystickState extends State<GridCoordinateJoystick> {
double yPercent =
(_currentPoint.dy - widget.minY) / (widget.maxY - widget.minY);
//Up padding.left/top as起始offset
//Up padding.left/top asoffset
double xPos = padding.left + (xPercent * clickableWidth);
double yPos =
padding.top + (clickableHeight - (yPercent * clickableHeight));
@@ -140,7 +140,7 @@ class _GridCoordinateJoystickState extends State<GridCoordinateJoystick> {
onPointerCancel: (_) => setState(() => _isDragging = false),
behavior: HitTestBehavior.opaque,
child: Stack(
clipBehavior: Clip.none, //button阴影or元素超出父容器边界
clipBehavior: Clip.none, //buttonor
children: [
if (widget.showMarking)
Positioned.fill(
@@ -151,7 +151,7 @@ class _GridCoordinateJoystickState extends State<GridCoordinateJoystick> {
maxX: widget.maxX,
minY: widget.minY,
maxY: widget.maxY,
//canaccording toNeedPassed padding For / To Painter draw装饰线
//canaccording toNeedPassed padding For / To Painter draw
padding: padding,
gridCountX: (clickableWidth / widget.targetGridSize)
.floor(),
@@ -162,7 +162,7 @@ class _GridCoordinateJoystickState extends State<GridCoordinateJoystick> {
),
),
Positioned(
//buttoncanin padding Outsideshow,depends on xPos/yPos
//buttoncanin padding Outsideshow,depends on xPos/yPos
left: xPos - widget.buttonSize / 2,
top: yPos - widget.buttonSize / 2,
child: _buildJoystickButton(context),
@@ -241,7 +241,7 @@ class JoystickPainter extends CustomPainter {
..color = CupertinoColors.systemGrey
..style = PaintingStyle.stroke;
//1. drawbackground网格and刻度
//1. drawbackgroundand
for (int i = 0; i <= gridCountX; i++) {
double x = i * gridSpacingX;
double opacity = (i == 0 || i == gridCountX)
@@ -252,7 +252,7 @@ class JoystickPainter extends CustomPainter {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), gridPaint);
//draw X axis文字
//draw X axis
double xValue = i * (maxX - minX) / gridCountX + minX;
_drawText(
canvas,
@@ -272,12 +272,12 @@ class JoystickPainter extends CustomPainter {
canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
//draw Y axis文字
//draw Y axis
double yValue = maxY - i * (maxY - minY) / gridCountY;
_drawText(canvas, yValue.toInt().toString(), Offset(10, y - 5), size);
}
//2. drawactivateMainaxis线
//2. drawactivateMainaxis
final activePaint = Paint()
..color = accentColor.withValues(alpha: 0.8)
..strokeWidth = 2
+4 -4
View File
@@ -19,10 +19,10 @@ class StackChanArView extends StatefulWidget {
this.onCallback,
});
final int decorate; //ismodel
final bool captureScreen; //output画面
final Function(Uint8List)? onFrameCallback; //outputcallback
final Function(DanceData)? onCallback; //datacallback
final int decorate; //ismodel
final bool captureScreen; //output
final Function(Uint8List)? onFrameCallback; //outputcallback
final Function(DanceData)? onCallback; //datacallback
@override
State<StatefulWidget> createState() => _StackChanArViewState();
+7 -7
View File
@@ -22,9 +22,9 @@ class StackChanFaceView extends StatefulWidget {
this.onCallback,
});
final bool captureScreen; //output画面
final Function(Uint8List)? onFrameCallback; //outputcallback
final Function(DanceData)? onCallback; //datacallback
final bool captureScreen; //output
final Function(Uint8List)? onFrameCallback; //outputcallback
final Function(DanceData)? onCallback; //datacallback
@override
State<StatefulWidget> createState() => _StackChanFaceViewState();
@@ -88,16 +88,16 @@ class _StackChanFaceViewState extends State<StackChanFaceView> {
});
}
///faceand导出画Surface / Side
///faceandSurface / Side
void processCameraImage(CameraImage image, int sensorOrientation) {
///face
///face
MlKitUtil.shared.testing(image, sensorOrientation, (faces) {
if (faces.isNotEmpty) {
dataConversionTesting(faces.first);
}
});
if (widget.captureScreen) {
//willimagecompressConcurrencyBack去
//willimagecompressConcurrencyBack
final now = DateTime.now();
if (now.difference(lastProcessTime).inMilliseconds >= 100) {
if (isProcessing) return;
@@ -168,7 +168,7 @@ class _StackChanFaceViewState extends State<StackChanFaceView> {
}
}
///compressAndsend出去
///compressAndsend
Future<void> handleAsyncCompression(
CameraImage image,
int sensorOrientation,
@@ -90,7 +90,7 @@ class _StackChanRotaryRobotJsState extends State<StackChanRotaryRobotJs> {
Future<void> setup() async {
threeJs.scene = three.Scene();
//1. set (Maintain / KeepNot变)
//1. set (Maintain / KeepNot)
final hemiLight = three.HemisphereLight(0xffffff, 0x444444, 1);
hemiLight.position.setValues(0, 100, 0);
threeJs.scene.add(hemiLight);
@@ -99,7 +99,7 @@ class _StackChanRotaryRobotJsState extends State<StackChanRotaryRobotJs> {
dirLight.position.setValues(50, 50, 70);
threeJs.scene.add(dirLight);
//2. set
//2. set
threeJs.camera = three.PerspectiveCamera(
60,
widget.width / widget.height,
@@ -162,7 +162,7 @@ class _StackChanRotaryRobotJsState extends State<StackChanRotaryRobotJs> {
final canvas = ui.Canvas(recorder);
final paint = ui.Paint();
//background:With / Carry 70% transparency (0xB3 = 179/255)
//background:With / Carry 70% transparency (0xB3 = 179/255)
paint.color = const ui.Color(0xB3000000);
canvas.drawRect(ui.Rect.fromLTWH(0, 0, canvasWidth, canvasHeight), paint);
@@ -197,7 +197,7 @@ class _StackChanRotaryRobotJsState extends State<StackChanRotaryRobotJs> {
canvas.rotate(rotationDegrees * pi / 180);
canvas.translate(-centerX, -centerY);
//createcrop区域模拟eye睁开度
//createcropeye
final clipRect = ui.Rect.fromLTRB(
eyeRect.left,
eyeRect.bottom - visibleHeight,
@@ -206,14 +206,14 @@ class _StackChanRotaryRobotJsState extends State<StackChanRotaryRobotJs> {
);
canvas.clipRect(clipRect);
//draw白色椭圆eye
//draweye
paint.color = const ui.Color(0xFFFFFFFF);
canvas.drawOval(eyeRect, paint);
canvas.restore();
}
//calculateeye基础position
//calculateeyeposition
final eyeY = (canvasHeight * 0.4) - (eyeSize / 2);
final leftEyePoint = ui.Offset((canvasWidth / 4) - (eyeSize / 2), eyeY);
final rightEyePoint = ui.Offset(
@@ -270,7 +270,7 @@ class _StackChanRotaryRobotJsState extends State<StackChanRotaryRobotJs> {
final byteData = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
if (byteData != null) {
//convertas three_js Uint8Array
//convertas three_js Uint8Array
final uint8List = byteData.buffer.asUint8List();
final nativeArray = three.Uint8Array.fromList(uint8List);
@@ -281,10 +281,10 @@ class _StackChanRotaryRobotJsState extends State<StackChanRotaryRobotJs> {
height: canvasHeight.toInt(),
);
//marktexture及其源Needupdate
//marktextureNeedupdate
expressionTexture.needsUpdate = true;
//ifuse MeshBasicMaterial,Ensure它也Willrereadtexture
//ifuse MeshBasicMaterial,EnsureWillrereadtexture
if (expressionPlaneMesh.material is three.Material) {
(expressionPlaneMesh.material as three.Material).needsUpdate = true;
}
+19 -19
View File
@@ -113,17 +113,17 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
Future<void> setup() async {
threeJs.scene = three.Scene();
//
//translated comment
final hemiLight = three.HemisphereLight(0xffffff, 0x444444, 1);
hemiLight.position.setValues(0, 100, 0);
threeJs.scene.add(hemiLight);
//
//translated comment
final dirLight = three.DirectionalLight(0xffffff, 1);
dirLight.position.setValues(50, 50, 70);
threeJs.scene.add(dirLight);
//and光线set
//andset
threeJs.camera = three.PerspectiveCamera(
60,
widget.width / widget.height,
@@ -149,7 +149,7 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
applyDanceData();
}
//set视Angle
//setAngle
void setupCamera() {
if (widget.topLook) {
threeJs.camera.position.setValues(0, -100, 70);
@@ -162,14 +162,14 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
three.Object3D yawAxis = three.Object3D();
three.Object3D pitchAxis = three.Object3D();
three.Mesh? expressionPlaneMesh; //faceshow平面
three.Mesh? expressionPlaneMesh; //faceshow
three.CanvasTexture? expressionTexture; //facecanvastexture
final double canvasWidth = 210; //canvas宽correspondingiOS 42*5
final double canvasHeight = 160; //canvas高correspondingiOS 32*5
final String expressionPlaneName = "expressionPlane"; //namefor齐iOS
final double canvasWidth = 210; //canvascorrespondingiOS 42*5
final double canvasHeight = 160; //canvascorrespondingiOS 32*5
final String expressionPlaneName = "expressionPlane"; //nameforiOS
Function(double)? currentRotationEvent;
//
//translated comment
void setupRobotHierarchy() {
final model = threeJs.scene.children.firstWhere(
(element) => element.type == "Group",
@@ -180,7 +180,7 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
if (foundation == null || centralComponent == null || head == null) return;
//========== LeftRightto(yaw axis)logic(originalhaslogicCankeep,) ==========
//========== LeftRightto(yaw axis)logic(originalhaslogicCankeep,) ==========
final centralWorldPos = centralComponent.worldPosition();
centralWorldPos.y -= 20;
yawAxis.setWorldPosition(centralWorldPos);
@@ -191,7 +191,7 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
centralComponent.setWorldTransform(centralWorldTransform);
centralComponent.setWorldPosition(centralWorldPosition);
//========== UpDown点(pitch axis)logic(corefixPart) ==========
//========== UpDown(pitch axis)logic(corefixPart) ==========
final headWorldPosition = head.worldPosition();
final headWorldTransform = head.worldTransform();
final pitchAxisWorldPosition = pitchAxis.worldPosition();
@@ -296,14 +296,14 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
final canvas = ui.Canvas(recorder);
final paint = ui.Paint();
//fliphandle(Yaxisflip)
//fliphandle(Yaxisflip)
if (widget.mirrorFace) {
canvas.save();
canvas.translate(canvasWidth, 0);
canvas.scale(-1, 1);
}
//background:With / Carry 70% transparency (0xB3 = 179/255)
//background:With / Carry 70% transparency (0xB3 = 179/255)
paint.color = const ui.Color(0xB3000000);
canvas.drawRect(ui.Rect.fromLTWH(0, 0, canvasWidth, canvasHeight), paint);
@@ -338,7 +338,7 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
canvas.rotate(rotationDegrees * pi / 180);
canvas.translate(-centerX, -centerY);
//createcrop区域模拟eye睁开度
//createcropeye
final clipRect = ui.Rect.fromLTRB(
eyeRect.left,
eyeRect.bottom - visibleHeight,
@@ -347,14 +347,14 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
);
canvas.clipRect(clipRect);
//draw白色椭圆eye
//draweye
paint.color = const ui.Color(0xFFFFFFFF);
canvas.drawOval(eyeRect, paint);
canvas.restore();
}
//calculateeye基础position
//calculateeyeposition
final eyeY = (canvasHeight * 0.4) - (eyeSize / 2);
final leftEyePoint = ui.Offset((canvasWidth / 4) - (eyeSize / 2), eyeY);
final rightEyePoint = ui.Offset(
@@ -416,7 +416,7 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
final byteData = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
if (byteData != null) {
//convertas three_js Uint8Array
//convertas three_js Uint8Array
final uint8List = byteData.buffer.asUint8List();
final nativeArray = three.Uint8Array.fromList(uint8List);
@@ -427,10 +427,10 @@ class _StackchanRobotThreeState extends State<StackchanRobotJs> {
height: canvasHeight.toInt(),
);
//marktexture及其源Needupdate
//marktextureNeedupdate
expressionTexture!.needsUpdate = true;
//ifuse MeshBasicMaterial,Ensure它也Willrereadtexture
//ifuse MeshBasicMaterial,EnsureWillrereadtexture
if (expressionPlaneMesh!.material is three.Material) {
(expressionPlaneMesh!.material as three.Material).needsUpdate = true;
}