mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-28 11:27:59 +00:00
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:
@@ -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(
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import com.android.build.gradle.BaseExtension
|
||||
|
||||
allprojects {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -5,12 +5,12 @@ SPDX-License-Identifier: MIT
|
||||
|
||||
class McpEndpoints {
|
||||
int? id;
|
||||
int? developerId; //修正namingstandard:snake_case 转 camelCase
|
||||
int? developerId; //namingstandard:snake_case camelCase
|
||||
String? name;
|
||||
String? description;
|
||||
int? enabled;
|
||||
String? createdAt; //修正namingstandard:snake_case 转 camelCase
|
||||
String? updatedAt; //修正namingstandard:snake_case 转 camelCase
|
||||
String? createdAt; //namingstandard:snake_case camelCase
|
||||
String? updatedAt; //namingstandard:snake_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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ class Pagination {
|
||||
bool? hasMore;
|
||||
int? page;
|
||||
int? limit;
|
||||
int? totaPages; //注意:这里field名可能is拼写error,应该is totalPages
|
||||
int? totaPages; //:fieldiserror,is 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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -28,11 +28,11 @@ enum MsgType {
|
||||
offAudio(0x19),
|
||||
aimedTakePhoto(0x1A);
|
||||
|
||||
final int value; //custom值,andiOSrawValuefullyfor齐
|
||||
final int value; //custom,andiOSrawValuefullyfor
|
||||
|
||||
const MsgType(this.value);
|
||||
|
||||
//修CurrentlySerializelogic:用valueAnd / WhileNon-index
|
||||
//CurrentlySerializelogic:valueAnd / WhileNon-index
|
||||
String toJson() => value.toString();
|
||||
|
||||
static MsgType fromJson(String json) {
|
||||
|
||||
@@ -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; //每次打印800字符(低于系统limit)
|
||||
//ifcontent较短,directPrint
|
||||
const int chunkSize = 800; //800(limit)
|
||||
//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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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!);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(); //提beforeinitlistener,avoid遗漏
|
||||
_setupPlayerListener(); //beforeinitlistener,avoid
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
///coreplaymethod(supportloop)
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
///createtime:2024/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,
|
||||
|
||||
@@ -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帧index(forhighlight)
|
||||
RxBool isRun = RxBool(false); //whether正inwholeplay
|
||||
RxBool isLoop = RxBool(false); //new增:loopplaymode开关
|
||||
RxInt dancePlayIndex = RxInt(-1); //inplayindex(forhighlight)
|
||||
RxBool isRun = RxBool(false); //whetherinwholeplay
|
||||
RxBool isLoop = RxBool(false); //new:loopplaymode
|
||||
|
||||
//playControl related
|
||||
Timer? playTimer; //wholeplaytimer(网络mode)
|
||||
Timer? playTimer; //wholeplaytimer(mode)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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), //fix:补充EdgeInsets
|
||||
padding: EdgeInsets.symmetric(vertical: 12), //fix:EdgeInsets
|
||||
child: Center(
|
||||
child: Text(
|
||||
"No more messages",
|
||||
@@ -175,7 +175,7 @@ class _ConversationMessagePageState extends State<ConversationMessagePage> {
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(), //hasmoredatawhen不show任何component(autoload)
|
||||
: const SizedBox.shrink(), //hasmoredatawhenshowcomponent(autoload)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).",
|
||||
//更精准(words→characters)
|
||||
//(words→characters)
|
||||
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 down,更符合UI习惯
|
||||
? "Short-term Memory" //standard
|
||||
: "Disabled", //Shut down,UI
|
||||
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, //new:selectedindex
|
||||
}) {
|
||||
//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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
);
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"; //平面节点name(for齐iOS)
|
||||
final double canvasWidth = 210; //canvas(correspondingiOS 42*5)
|
||||
final double canvasHeight = 160; //canvas(correspondingiOS 32*5)
|
||||
final String expressionPlaneName = "expressionPlane"; //name(foriOS)
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user