Commit 545418f38b7c00c465bf3a2fe89d453ab0eba7c6

Authored by 权海
1 parent c8044b54

feat(ui):修改一些代码

@@ -308,7 +308,10 @@ interface HealthKitHostApi { @@ -308,7 +308,10 @@ interface HealthKitHostApi {
308 fun cancelHealthAppAuthorization(): Boolean 308 fun cancelHealthAppAuthorization(): Boolean
309 /** Runs native health read and server upload pipeline. */ 309 /** Runs native health read and server upload pipeline. */
310 fun performHealthUpload(callback: (Result<HealthUploadResult>) -> Unit) 310 fun performHealthUpload(callback: (Result<HealthUploadResult>) -> Unit)
311 - /** Opens Huawei Health client authorization UI. Returns whether user granted. */ 311 + /**
  312 + * 获取当前已上传的健康数据点,主要用于调试
  313 + * Opens Huawei Health client authorization UI. Returns whether user granted.
  314 + */
312 fun checkHealthAppAuthorization(callback: (Result<HealthAuthorization>) -> Unit) 315 fun checkHealthAppAuthorization(callback: (Result<HealthAuthorization>) -> Unit)
313 fun requestHealthClientAuthorization(callback: (Result<Boolean>) -> Unit) 316 fun requestHealthClientAuthorization(callback: (Result<Boolean>) -> Unit)
314 fun fetchHrvData(startTime: Long, endTime: Long, callback: (Result<List<HealthUploadDataPoint>>) -> Unit) 317 fun fetchHrvData(startTime: Long, endTime: Long, callback: (Result<List<HealthUploadDataPoint>>) -> Unit)
@@ -118,6 +118,61 @@ data class WearDeviceInfo ( @@ -118,6 +118,61 @@ data class WearDeviceInfo (
118 118
119 override fun hashCode(): Int = toList().hashCode() 119 override fun hashCode(): Int = toList().hashCode()
120 } 120 }
  121 +
  122 +/** Generated class from Pigeon that represents data sent in messages. */
  123 +data class WatchTransformTheme (
  124 + val themeId: Long,
  125 + val themeName: String,
  126 + val energeticDescription: String,
  127 + val energeticImage: ByteArray? = null,
  128 + val normalDescription: String,
  129 + val normalImage: ByteArray? = null,
  130 + val slightStressfulDescription: String,
  131 + val slightStressfulImage: ByteArray? = null,
  132 + val stressfulDescription: String,
  133 + val stressfulImage: ByteArray? = null
  134 +)
  135 + {
  136 + companion object {
  137 + fun fromList(pigeonVar_list: List<Any?>): WatchTransformTheme {
  138 + val themeId = pigeonVar_list[0] as Long
  139 + val themeName = pigeonVar_list[1] as String
  140 + val energeticDescription = pigeonVar_list[2] as String
  141 + val energeticImage = pigeonVar_list[3] as ByteArray?
  142 + val normalDescription = pigeonVar_list[4] as String
  143 + val normalImage = pigeonVar_list[5] as ByteArray?
  144 + val slightStressfulDescription = pigeonVar_list[6] as String
  145 + val slightStressfulImage = pigeonVar_list[7] as ByteArray?
  146 + val stressfulDescription = pigeonVar_list[8] as String
  147 + val stressfulImage = pigeonVar_list[9] as ByteArray?
  148 + return WatchTransformTheme(themeId, themeName, energeticDescription, energeticImage, normalDescription, normalImage, slightStressfulDescription, slightStressfulImage, stressfulDescription, stressfulImage)
  149 + }
  150 + }
  151 + fun toList(): List<Any?> {
  152 + return listOf(
  153 + themeId,
  154 + themeName,
  155 + energeticDescription,
  156 + energeticImage,
  157 + normalDescription,
  158 + normalImage,
  159 + slightStressfulDescription,
  160 + slightStressfulImage,
  161 + stressfulDescription,
  162 + stressfulImage,
  163 + )
  164 + }
  165 + override fun equals(other: Any?): Boolean {
  166 + if (other !is WatchTransformTheme) {
  167 + return false
  168 + }
  169 + if (this === other) {
  170 + return true
  171 + }
  172 + return WearEngineApiPigeonUtils.deepEquals(toList(), other.toList()) }
  173 +
  174 + override fun hashCode(): Int = toList().hashCode()
  175 +}
121 private open class WearEngineApiPigeonCodec : StandardMessageCodec() { 176 private open class WearEngineApiPigeonCodec : StandardMessageCodec() {
122 override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { 177 override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
123 return when (type) { 178 return when (type) {
@@ -126,6 +181,11 @@ private open class WearEngineApiPigeonCodec : StandardMessageCodec() { @@ -126,6 +181,11 @@ private open class WearEngineApiPigeonCodec : StandardMessageCodec() {
126 WearDeviceInfo.fromList(it) 181 WearDeviceInfo.fromList(it)
127 } 182 }
128 } 183 }
  184 + 130.toByte() -> {
  185 + return (readValue(buffer) as? List<Any?>)?.let {
  186 + WatchTransformTheme.fromList(it)
  187 + }
  188 + }
129 else -> super.readValueOfType(type, buffer) 189 else -> super.readValueOfType(type, buffer)
130 } 190 }
131 } 191 }
@@ -135,6 +195,10 @@ private open class WearEngineApiPigeonCodec : StandardMessageCodec() { @@ -135,6 +195,10 @@ private open class WearEngineApiPigeonCodec : StandardMessageCodec() {
135 stream.write(129) 195 stream.write(129)
136 writeValue(stream, value.toList()) 196 writeValue(stream, value.toList())
137 } 197 }
  198 + is WatchTransformTheme -> {
  199 + stream.write(130)
  200 + writeValue(stream, value.toList())
  201 + }
138 else -> super.writeValue(stream, value) 202 else -> super.writeValue(stream, value)
139 } 203 }
140 } 204 }
@@ -149,7 +213,8 @@ interface WearEngineHostApi { @@ -149,7 +213,8 @@ interface WearEngineHostApi {
149 fun sendTextMessage(message: String): Boolean 213 fun sendTextMessage(message: String): Boolean
150 /** 添加表盘 */ 214 /** 添加表盘 */
151 fun addWatchSurface(callback: (Result<Boolean>) -> Unit) 215 fun addWatchSurface(callback: (Result<Boolean>) -> Unit)
152 - fun sendWatchSyncPayload(jsonPayload: String, callback: (Result<Boolean>) -> Unit) 216 + /** 添加或者删除主题 */
  217 + fun syncWatchTheme(theme: WatchTransformTheme?, callback: (Result<Boolean>) -> Unit)
153 /** 218 /**
154 * Opens the system photo picker and returns a local PNG file path after 219 * Opens the system photo picker and returns a local PNG file path after
155 * removing the image background on the host platform. 220 * removing the image background on the host platform.
@@ -251,12 +316,12 @@ interface WearEngineHostApi { @@ -251,12 +316,12 @@ interface WearEngineHostApi {
251 } 316 }
252 } 317 }
253 run { 318 run {
254 - val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendWatchSyncPayload$separatedMessageChannelSuffix", codec) 319 + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.syncWatchTheme$separatedMessageChannelSuffix", codec)
255 if (api != null) { 320 if (api != null) {
256 channel.setMessageHandler { message, reply -> 321 channel.setMessageHandler { message, reply ->
257 val args = message as List<Any?> 322 val args = message as List<Any?>
258 - val jsonPayloadArg = args[0] as String  
259 - api.sendWatchSyncPayload(jsonPayloadArg) { result: Result<Boolean> -> 323 + val themeArg = args[0] as WatchTransformTheme?
  324 + api.syncWatchTheme(themeArg) { result: Result<Boolean> ->
260 val error = result.exceptionOrNull() 325 val error = result.exceptionOrNull()
261 if (error != null) { 326 if (error != null) {
262 reply.reply(WearEngineApiPigeonUtils.wrapError(error)) 327 reply.reply(WearEngineApiPigeonUtils.wrapError(error))
@@ -333,6 +333,7 @@ protocol HealthKitHostApi { @@ -333,6 +333,7 @@ protocol HealthKitHostApi {
333 func cancelHealthAppAuthorization() throws -> Bool 333 func cancelHealthAppAuthorization() throws -> Bool
334 /// Runs native health read and server upload pipeline. 334 /// Runs native health read and server upload pipeline.
335 func performHealthUpload(completion: @escaping (Result<HealthUploadResult, Error>) -> Void) 335 func performHealthUpload(completion: @escaping (Result<HealthUploadResult, Error>) -> Void)
  336 + /// 获取当前已上传的健康数据点,主要用于调试
336 /// Opens Huawei Health client authorization UI. Returns whether user granted. 337 /// Opens Huawei Health client authorization UI. Returns whether user granted.
337 func checkHealthAppAuthorization(completion: @escaping (Result<HealthAuthorization, Error>) -> Void) 338 func checkHealthAppAuthorization(completion: @escaping (Result<HealthAuthorization, Error>) -> Void)
338 func requestHealthClientAuthorization(completion: @escaping (Result<Bool, Error>) -> Void) 339 func requestHealthClientAuthorization(completion: @escaping (Result<Bool, Error>) -> Void)
@@ -403,6 +404,7 @@ class HealthKitHostApiSetup { @@ -403,6 +404,7 @@ class HealthKitHostApiSetup {
403 } else { 404 } else {
404 performHealthUploadChannel.setMessageHandler(nil) 405 performHealthUploadChannel.setMessageHandler(nil)
405 } 406 }
  407 + /// 获取当前已上传的健康数据点,主要用于调试
406 /// Opens Huawei Health client authorization UI. Returns whether user granted. 408 /// Opens Huawei Health client authorization UI. Returns whether user granted.
407 let checkHealthAppAuthorizationChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) 409 let checkHealthAppAuthorizationChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
408 if let api = api { 410 if let api = api {
@@ -153,11 +153,74 @@ struct WearDeviceInfo: Hashable { @@ -153,11 +153,74 @@ struct WearDeviceInfo: Hashable {
153 } 153 }
154 } 154 }
155 155
  156 +/// Generated class from Pigeon that represents data sent in messages.
  157 +struct WatchTransformTheme: Hashable {
  158 + var themeId: Int64
  159 + var themeName: String
  160 + var energeticDescription: String
  161 + var energeticImage: FlutterStandardTypedData? = nil
  162 + var normalDescription: String
  163 + var normalImage: FlutterStandardTypedData? = nil
  164 + var slightStressfulDescription: String
  165 + var slightStressfulImage: FlutterStandardTypedData? = nil
  166 + var stressfulDescription: String
  167 + var stressfulImage: FlutterStandardTypedData? = nil
  168 +
  169 +
  170 + // swift-format-ignore: AlwaysUseLowerCamelCase
  171 + static func fromList(_ pigeonVar_list: [Any?]) -> WatchTransformTheme? {
  172 + let themeId = pigeonVar_list[0] as! Int64
  173 + let themeName = pigeonVar_list[1] as! String
  174 + let energeticDescription = pigeonVar_list[2] as! String
  175 + let energeticImage: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[3])
  176 + let normalDescription = pigeonVar_list[4] as! String
  177 + let normalImage: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[5])
  178 + let slightStressfulDescription = pigeonVar_list[6] as! String
  179 + let slightStressfulImage: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[7])
  180 + let stressfulDescription = pigeonVar_list[8] as! String
  181 + let stressfulImage: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[9])
  182 +
  183 + return WatchTransformTheme(
  184 + themeId: themeId,
  185 + themeName: themeName,
  186 + energeticDescription: energeticDescription,
  187 + energeticImage: energeticImage,
  188 + normalDescription: normalDescription,
  189 + normalImage: normalImage,
  190 + slightStressfulDescription: slightStressfulDescription,
  191 + slightStressfulImage: slightStressfulImage,
  192 + stressfulDescription: stressfulDescription,
  193 + stressfulImage: stressfulImage
  194 + )
  195 + }
  196 + func toList() -> [Any?] {
  197 + return [
  198 + themeId,
  199 + themeName,
  200 + energeticDescription,
  201 + energeticImage,
  202 + normalDescription,
  203 + normalImage,
  204 + slightStressfulDescription,
  205 + slightStressfulImage,
  206 + stressfulDescription,
  207 + stressfulImage,
  208 + ]
  209 + }
  210 + static func == (lhs: WatchTransformTheme, rhs: WatchTransformTheme) -> Bool {
  211 + return deepEqualsWearEngineApi(lhs.toList(), rhs.toList()) }
  212 + func hash(into hasher: inout Hasher) {
  213 + deepHashWearEngineApi(value: toList(), hasher: &hasher)
  214 + }
  215 +}
  216 +
156 private class WearEngineApiPigeonCodecReader: FlutterStandardReader { 217 private class WearEngineApiPigeonCodecReader: FlutterStandardReader {
157 override func readValue(ofType type: UInt8) -> Any? { 218 override func readValue(ofType type: UInt8) -> Any? {
158 switch type { 219 switch type {
159 case 129: 220 case 129:
160 return WearDeviceInfo.fromList(self.readValue() as! [Any?]) 221 return WearDeviceInfo.fromList(self.readValue() as! [Any?])
  222 + case 130:
  223 + return WatchTransformTheme.fromList(self.readValue() as! [Any?])
161 default: 224 default:
162 return super.readValue(ofType: type) 225 return super.readValue(ofType: type)
163 } 226 }
@@ -169,6 +232,9 @@ private class WearEngineApiPigeonCodecWriter: FlutterStandardWriter { @@ -169,6 +232,9 @@ private class WearEngineApiPigeonCodecWriter: FlutterStandardWriter {
169 if let value = value as? WearDeviceInfo { 232 if let value = value as? WearDeviceInfo {
170 super.writeByte(129) 233 super.writeByte(129)
171 super.writeValue(value.toList()) 234 super.writeValue(value.toList())
  235 + } else if let value = value as? WatchTransformTheme {
  236 + super.writeByte(130)
  237 + super.writeValue(value.toList())
172 } else { 238 } else {
173 super.writeValue(value) 239 super.writeValue(value)
174 } 240 }
@@ -198,7 +264,8 @@ protocol WearEngineHostApi { @@ -198,7 +264,8 @@ protocol WearEngineHostApi {
198 func sendTextMessage(message: String) throws -> Bool 264 func sendTextMessage(message: String) throws -> Bool
199 /// 添加表盘 265 /// 添加表盘
200 func addWatchSurface(completion: @escaping (Result<Bool, Error>) -> Void) 266 func addWatchSurface(completion: @escaping (Result<Bool, Error>) -> Void)
201 - func sendWatchSyncPayload(jsonPayload: String, completion: @escaping (Result<Bool, Error>) -> Void) 267 + /// 添加或者删除主题
  268 + func syncWatchTheme(theme: WatchTransformTheme?, completion: @escaping (Result<Bool, Error>) -> Void)
202 /// Opens the system photo picker and returns a local PNG file path after 269 /// Opens the system photo picker and returns a local PNG file path after
203 /// removing the image background on the host platform. 270 /// removing the image background on the host platform.
204 func removeBackground(originImagePath: String, completion: @escaping (Result<String?, Error>) -> Void) 271 func removeBackground(originImagePath: String, completion: @escaping (Result<String?, Error>) -> Void)
@@ -284,12 +351,13 @@ class WearEngineHostApiSetup { @@ -284,12 +351,13 @@ class WearEngineHostApiSetup {
284 } else { 351 } else {
285 addWatchSurfaceChannel.setMessageHandler(nil) 352 addWatchSurfaceChannel.setMessageHandler(nil)
286 } 353 }
287 - let sendWatchSyncPayloadChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendWatchSyncPayload\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) 354 + /// 添加或者删除主题
  355 + let syncWatchThemeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.syncWatchTheme\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
288 if let api = api { 356 if let api = api {
289 - sendWatchSyncPayloadChannel.setMessageHandler { message, reply in 357 + syncWatchThemeChannel.setMessageHandler { message, reply in
290 let args = message as! [Any?] 358 let args = message as! [Any?]
291 - let jsonPayloadArg = args[0] as! String  
292 - api.sendWatchSyncPayload(jsonPayload: jsonPayloadArg) { result in 359 + let themeArg: WatchTransformTheme? = nilOrValue(args[0])
  360 + api.syncWatchTheme(theme: themeArg) { result in
293 switch result { 361 switch result {
294 case .success(let res): 362 case .success(let res):
295 reply(wrapResult(res)) 363 reply(wrapResult(res))
@@ -299,7 +367,7 @@ class WearEngineHostApiSetup { @@ -299,7 +367,7 @@ class WearEngineHostApiSetup {
299 } 367 }
300 } 368 }
301 } else { 369 } else {
302 - sendWatchSyncPayloadChannel.setMessageHandler(nil) 370 + syncWatchThemeChannel.setMessageHandler(nil)
303 } 371 }
304 /// Opens the system photo picker and returns a local PNG file path after 372 /// Opens the system photo picker and returns a local PNG file path after
305 /// removing the image background on the host platform. 373 /// removing the image background on the host platform.
@@ -41,10 +41,20 @@ final class WearEngineHostApiImpl: WearEngineHostApi { @@ -41,10 +41,20 @@ final class WearEngineHostApiImpl: WearEngineHostApi {
41 watchService.sendTextMessage(message) 41 watchService.sendTextMessage(message)
42 } 42 }
43 43
44 - func sendWatchSyncPayload(jsonPayload: String, completion: @escaping (Result<Bool, any Error>) -> Void) {  
45 - let success = WatchConnectivityService.shared.sendWatchThemeChangedMessage(json: jsonPayload)  
46 - completion(.success(success))  
47 - } 44 + func syncWatchTheme(theme: WatchTransformTheme?, completion: @escaping (Result<Bool, any Error>) -> Void) {
  45 + guard let theme else{
  46 + //TODO: - 删除主题
  47 + completion(.success(true))
  48 + return
  49 + }
  50 + let success = WatchThemeStore.shared.saveTheme(theme)
  51 + guard success else {
  52 + completion(.success(false))
  53 + return
  54 + }
  55 + let notified = watchService.sendWatchThemeChangedMessage()
  56 + completion(.success(notified))
  57 + }
48 58
49 func removeBackground(originImagePath: String, completion: @escaping (Result<String?, any Error>) -> Void) { 59 func removeBackground(originImagePath: String, completion: @escaping (Result<String?, any Error>) -> Void) {
50 // 抠图 60 // 抠图
@@ -53,6 +53,7 @@ final class WatchConnectivityService: NSObject { @@ -53,6 +53,7 @@ final class WatchConnectivityService: NSObject {
53 return WearDeviceInfo( 53 return WearDeviceInfo(
54 deviceId: nil, 54 deviceId: nil,
55 deviceName: "Apple Watch", 55 deviceName: "Apple Watch",
  56 + isPaired: isPaired,
56 isReachable: isReachable, 57 isReachable: isReachable,
57 isWatchAppInstalled: isWatchAppInstalled 58 isWatchAppInstalled: isWatchAppInstalled
58 ) 59 )
@@ -111,7 +112,7 @@ final class WatchConnectivityService: NSObject { @@ -111,7 +112,7 @@ final class WatchConnectivityService: NSObject {
111 return true 112 return true
112 } 113 }
113 114
114 - func sendWatchThemeChangedMessage(json: String) -> Bool{ 115 + func sendWatchThemeChangedMessage(json: String? = nil) -> Bool{
115 sendCommandMessage(AppGroupMessageKey.reloadTheme, json: json) 116 sendCommandMessage(AppGroupMessageKey.reloadTheme, json: json)
116 } 117 }
117 } 118 }
1 import Foundation 1 import Foundation
2 2
3 /// Shared Watch theme persistence. 3 /// Shared Watch theme persistence.
4 -/// The Watch app and Widget extension read this exact JSON string from App Group. 4 +/// The Watch app and Widget extension read this exact JSON data from App Group.
5 final class WatchThemeStore { 5 final class WatchThemeStore {
6 static let shared = WatchThemeStore() 6 static let shared = WatchThemeStore()
7 7
@@ -9,13 +9,95 @@ final class WatchThemeStore { @@ -9,13 +9,95 @@ final class WatchThemeStore {
9 9
10 @discardableResult 10 @discardableResult
11 func saveThemeJSONString(_ json: String) -> Bool { 11 func saveThemeJSONString(_ json: String) -> Bool {
12 - guard json.data(using: .utf8) != nil else { return false }  
13 - AppGroupConstants.defaults?.set(json, forKey: AppGroupConstants.Key.myWatchTheme) 12 + guard let data = json.data(using: .utf8) else { return false }
  13 + AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.myWatchTheme)
14 return true 14 return true
15 } 15 }
16 16
  17 + @discardableResult
  18 + func saveTheme(_ theme: WatchTransformTheme) -> Bool {
  19 + guard let defaults = AppGroupConstants.defaults else { return false }
  20 + let imageStore = SyncedWatchThemeImageStore(themeId: theme.themeId)
  21 + let model = SyncedWatchThemeModel(
  22 + id: Int(theme.themeId),
  23 + themeName: theme.themeName,
  24 + isOfficial: 0,
  25 + energeticTitle: theme.energeticDescription,
  26 + energeticImageURL: imageStore.save(
  27 + theme.energeticImage?.data,
  28 + status: "energetic",
  29 + defaults: defaults
  30 + ),
  31 + normalTitle: theme.normalDescription,
  32 + normalImageURL: imageStore.save(
  33 + theme.normalImage?.data,
  34 + status: "normal",
  35 + defaults: defaults
  36 + ),
  37 + slightStressTitle: theme.slightStressfulDescription,
  38 + slightStressImageURL: imageStore.save(
  39 + theme.slightStressfulImage?.data,
  40 + status: "slight_stressful",
  41 + defaults: defaults
  42 + ),
  43 + stressfulTitle: theme.stressfulDescription,
  44 + stressfulImageURL: imageStore.save(
  45 + theme.stressfulImage?.data,
  46 + status: "stressful",
  47 + defaults: defaults
  48 + )
  49 + )
  50 +
  51 + do {
  52 + let data = try JSONEncoder().encode(model)
  53 + defaults.set(data, forKey: AppGroupConstants.Key.myWatchTheme)
  54 + defaults.synchronize()
  55 + return true
  56 + } catch {
  57 + return false
  58 + }
  59 + }
  60 +
17 func clearTheme() { 61 func clearTheme() {
18 AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.myWatchTheme) 62 AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.myWatchTheme)
19 } 63 }
20 } 64 }
21 65
  66 +private struct SyncedWatchThemeImageStore {
  67 + let themeId: Int64
  68 +
  69 + func save(_ data: Data?, status: String, defaults: UserDefaults) -> String? {
  70 + guard let data else { return nil }
  71 + let key = "watch_theme_\(themeId)_\(status)"
  72 + defaults.set(data, forKey: key)
  73 + return key
  74 + }
  75 +}
  76 +
  77 +private struct SyncedWatchThemeModel: Codable {
  78 + let id: Int?
  79 + let themeName: String?
  80 + let isOfficial: Int?
  81 + let energeticTitle: String?
  82 + let energeticImageURL: String?
  83 + let normalTitle: String?
  84 + let normalImageURL: String?
  85 + let slightStressTitle: String?
  86 + let slightStressImageURL: String?
  87 + let stressfulTitle: String?
  88 + let stressfulImageURL: String?
  89 +
  90 + enum CodingKeys: String, CodingKey {
  91 + case id
  92 + case themeName = "theme_name"
  93 + case isOfficial = "is_official"
  94 + case energeticTitle = "energetic_description"
  95 + case energeticImageURL = "energetic_image"
  96 + case normalTitle = "normal_description"
  97 + case normalImageURL = "normal_image"
  98 + case slightStressTitle = "slight_stressful_description"
  99 + case slightStressImageURL = "slight_stressful_image"
  100 + case stressfulTitle = "stressful_description"
  101 + case stressfulImageURL = "stressful_image"
  102 + }
  103 +}
1 import 'dart:async'; 1 import 'dart:async';
2 -import 'dart:convert';  
3 2
4 import 'package:doublefeel_flutter/app/modules/friends/data/friends_repository.dart'; 3 import 'package:doublefeel_flutter/app/modules/friends/data/friends_repository.dart';
5 import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_device_extension.dart'; 4 import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_device_extension.dart';
  5 +import 'package:doublefeel_flutter/app/modules/watch_theme/services/watch_theme_sync_payload_builder.dart';
6 import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; 6 import 'package:doublefeel_flutter/core/network/api/friend_api.dart';
7 import 'package:doublefeel_flutter/core/network/api/theme_api.dart'; 7 import 'package:doublefeel_flutter/core/network/api/theme_api.dart';
8 import 'package:doublefeel_flutter/core/result/app_result.dart'; 8 import 'package:doublefeel_flutter/core/result/app_result.dart';
@@ -12,6 +12,7 @@ import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; @@ -12,6 +12,7 @@ import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart';
12 import 'package:flutter/material.dart'; 12 import 'package:flutter/material.dart';
13 import 'package:get/get.dart'; 13 import 'package:get/get.dart';
14 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; 14 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
  15 +import 'package:logger/logger.dart';
15 16
16 import '../../friends/models/friend_health_data.dart'; 17 import '../../friends/models/friend_health_data.dart';
17 import '../../friends/views/select_friend_view.dart'; 18 import '../../friends/views/select_friend_view.dart';
@@ -24,6 +25,8 @@ class CustomWatchThemePreviewController extends GetxController { @@ -24,6 +25,8 @@ class CustomWatchThemePreviewController extends GetxController {
24 CustomWatchThemePreviewController(this._themeApi); 25 CustomWatchThemePreviewController(this._themeApi);
25 26
26 final ThemeApi _themeApi; 27 final ThemeApi _themeApi;
  28 + final WatchThemeSyncPayloadBuilder _syncPayloadBuilder =
  29 + const WatchThemeSyncPayloadBuilder();
27 final FriendsRepository _repository = 30 final FriendsRepository _repository =
28 FriendsRepositoryImpl(Get.find<FriendApi>()); 31 FriendsRepositoryImpl(Get.find<FriendApi>());
29 32
@@ -120,6 +123,7 @@ class CustomWatchThemePreviewController extends GetxController { @@ -120,6 +123,7 @@ class CustomWatchThemePreviewController extends GetxController {
120 AppToast.show(l10n.watchThemeDeleteFailed); 123 AppToast.show(l10n.watchThemeDeleteFailed);
121 return; 124 return;
122 } 125 }
  126 + await WearEngineHostApi().syncWatchTheme(null);
123 AppToast.show(l10n.watchThemeDeleted); 127 AppToast.show(l10n.watchThemeDeleted);
124 Get.back(); 128 Get.back();
125 } 129 }
@@ -160,10 +164,8 @@ class CustomWatchThemePreviewController extends GetxController { @@ -160,10 +164,8 @@ class CustomWatchThemePreviewController extends GetxController {
160 } 164 }
161 try { 165 try {
162 await WearEngineHostApi().addWatchSurface(); 166 await WearEngineHostApi().addWatchSurface();
163 - await PlatformHostApi().nativeHandleUrl("itms-watchs://");  
164 - } catch (e) {  
165 - AppToast.show(e.toString());  
166 - } 167 + } catch (e) {}
  168 + await PlatformHostApi().nativeHandleUrl("itms-watchs://");
167 } 169 }
168 170
169 Future<void> tryApplyAndSyncWatchFace() async { 171 Future<void> tryApplyAndSyncWatchFace() async {
@@ -201,20 +203,23 @@ class CustomWatchThemePreviewController extends GetxController { @@ -201,20 +203,23 @@ class CustomWatchThemePreviewController extends GetxController {
201 } 203 }
202 204
203 isApplying.value = true; 205 isApplying.value = true;
204 - final result = await _themeApi.applyTheme(themeId);  
205 - if (result is! AppSuccess<void>) {  
206 - isApplying.value = false;  
207 - AppToast.show(l10n.watchThemeApplyFailed);  
208 - return false;  
209 - }  
210 -  
211 - selectedThemeItem.value = themeItem;  
212 206
213 var syncSuccess = true; 207 var syncSuccess = true;
214 try { 208 try {
215 - syncSuccess = await WearEngineHostApi().sendWatchSyncPayload(  
216 - jsonEncode(themeItem.toJson()),  
217 - ); 209 + final payload = await _syncPayloadBuilder.build(themeItem);
  210 + final success = await WearEngineHostApi().syncWatchTheme(payload);
  211 + if (success) {
  212 + final result = await _themeApi.applyTheme(themeId);
  213 + if (result is! AppSuccess<void>) {
  214 + isApplying.value = false;
  215 + AppToast.show(l10n.watchThemeApplyFailed);
  216 + return false;
  217 + }
  218 + syncSuccess = success;
  219 + selectedThemeItem.value = themeItem;
  220 + return false;
  221 + }
  222 + syncSuccess = success;
218 } catch (_) { 223 } catch (_) {
219 // The active theme is already saved on the server. 224 // The active theme is already saved on the server.
220 syncSuccess = false; 225 syncSuccess = false;
1 import 'dart:async'; 1 import 'dart:async';
2 -import 'dart:convert';  
3 2
4 import 'package:doublefeel_flutter/app/modules/friends/data/friends_repository.dart'; 3 import 'package:doublefeel_flutter/app/modules/friends/data/friends_repository.dart';
5 import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_device_extension.dart'; 4 import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_device_extension.dart';
  5 +import 'package:doublefeel_flutter/app/modules/watch_theme/services/watch_theme_sync_payload_builder.dart';
6 import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; 6 import 'package:doublefeel_flutter/core/network/api/friend_api.dart';
7 import 'package:doublefeel_flutter/core/network/api/theme_api.dart'; 7 import 'package:doublefeel_flutter/core/network/api/theme_api.dart';
8 import 'package:doublefeel_flutter/core/result/app_result.dart'; 8 import 'package:doublefeel_flutter/core/result/app_result.dart';
@@ -23,6 +23,8 @@ class WatchThemePreviewController extends GetxController { @@ -23,6 +23,8 @@ class WatchThemePreviewController extends GetxController {
23 WatchThemePreviewController(this._themeApi); 23 WatchThemePreviewController(this._themeApi);
24 24
25 final ThemeApi _themeApi; 25 final ThemeApi _themeApi;
  26 + final WatchThemeSyncPayloadBuilder _syncPayloadBuilder =
  27 + const WatchThemeSyncPayloadBuilder();
26 final FriendsRepository _repository = 28 final FriendsRepository _repository =
27 FriendsRepositoryImpl(Get.find<FriendApi>()); 29 FriendsRepositoryImpl(Get.find<FriendApi>());
28 30
@@ -132,10 +134,10 @@ class WatchThemePreviewController extends GetxController { @@ -132,10 +134,10 @@ class WatchThemePreviewController extends GetxController {
132 134
133 try { 135 try {
134 await WearEngineHostApi().addWatchSurface(); 136 await WearEngineHostApi().addWatchSurface();
135 - await PlatformHostApi().nativeHandleUrl("itms-watchs://");  
136 } catch (e) { 137 } catch (e) {
137 - AppToast.show(e.toString()); 138 +
138 } 139 }
  140 + await PlatformHostApi().nativeHandleUrl("itms-watchs://");
139 } 141 }
140 142
141 Future<void> tryApplyAndSyncWatchFace() async { 143 Future<void> tryApplyAndSyncWatchFace() async {
@@ -173,20 +175,23 @@ class WatchThemePreviewController extends GetxController { @@ -173,20 +175,23 @@ class WatchThemePreviewController extends GetxController {
173 } 175 }
174 176
175 isApplying.value = true; 177 isApplying.value = true;
176 - final result = await _themeApi.applyTheme(themeId);  
177 - if (result is! AppSuccess<void>) {  
178 - isApplying.value = false;  
179 - AppToast.show(l10n.watchThemeApplyFailed);  
180 - return false;  
181 - }  
182 -  
183 - selectedThemeItem.value = themeItem;  
184 178
185 var syncSuccess = true; 179 var syncSuccess = true;
186 try { 180 try {
187 - syncSuccess = await WearEngineHostApi().sendWatchSyncPayload(  
188 - jsonEncode(themeItem.toJson()),  
189 - ); 181 + final payload = await _syncPayloadBuilder.build(themeItem);
  182 + final success = await WearEngineHostApi().syncWatchTheme(payload);
  183 + if (success) {
  184 + final result = await _themeApi.applyTheme(themeId);
  185 + if (result is! AppSuccess<void>) {
  186 + isApplying.value = false;
  187 + AppToast.show(l10n.watchThemeApplyFailed);
  188 + return false;
  189 + }
  190 + syncSuccess = success;
  191 + selectedThemeItem.value = themeItem;
  192 + return false;
  193 + }
  194 + syncSuccess = success;
190 } catch (_) { 195 } catch (_) {
191 // The active theme is already saved on the server. 196 // The active theme is already saved on the server.
192 syncSuccess = false; 197 syncSuccess = false;
  1 +import 'dart:io';
  2 +
  3 +import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_theme_models.dart';
  4 +import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart';
  5 +import 'package:flutter/foundation.dart';
  6 +import 'package:flutter/services.dart';
  7 +
  8 +class WatchThemeSyncPayloadBuilder {
  9 + const WatchThemeSyncPayloadBuilder();
  10 +
  11 + Future<WatchTransformTheme> build(WatchThemeItem item) async {
  12 + return WatchTransformTheme(
  13 + themeId: item.id ?? 0,
  14 + themeName: item.themeName,
  15 + energeticDescription: item.energeticDescription,
  16 + energeticImage: await _loadImageBytes(item.energeticImage),
  17 + normalDescription: item.normalDescription,
  18 + normalImage: await _loadImageBytes(item.normalImage),
  19 + slightStressfulDescription: item.slightStressfulDescription,
  20 + slightStressfulImage: await _loadImageBytes(item.slightStressfulImage),
  21 + stressfulDescription: item.stressfulDescription,
  22 + stressfulImage: await _loadImageBytes(item.stressfulImage),
  23 + );
  24 + }
  25 +
  26 + Future<Uint8List?> _loadImageBytes(String? imagePath) async {
  27 + final value = imagePath?.trim();
  28 + if (value == null || value.isEmpty) return null;
  29 +
  30 + final uri = Uri.tryParse(value);
  31 + if (uri != null && (uri.scheme == 'http' || uri.scheme == 'https')) {
  32 + return _downloadImage(uri);
  33 + }
  34 +
  35 + final file = File(value);
  36 + if (await file.exists()) {
  37 + return file.readAsBytes();
  38 + }
  39 +
  40 + try {
  41 + final data = await rootBundle.load(value);
  42 + return data.buffer.asUint8List();
  43 + } catch (_) {
  44 + return null;
  45 + }
  46 + }
  47 +
  48 + Future<Uint8List?> _downloadImage(Uri uri) async {
  49 + final client = HttpClient();
  50 + try {
  51 + final request = await client.getUrl(uri);
  52 + final response = await request.close();
  53 + if (response.statusCode < 200 || response.statusCode >= 300) {
  54 + return null;
  55 + }
  56 + return consolidateHttpClientResponseBytes(response);
  57 + } catch (_) {
  58 + return null;
  59 + } finally {
  60 + client.close(force: true);
  61 + }
  62 + }
  63 +}
@@ -55,6 +55,7 @@ class OfficialThemeGrid extends StatelessWidget { @@ -55,6 +55,7 @@ class OfficialThemeGrid extends StatelessWidget {
55 size: 88, 55 size: 88,
56 child: WatchThemeCharacterAvatar( 56 child: WatchThemeCharacterAvatar(
57 size: 88, 57 size: 88,
  58 + padding: 8,
58 assetPath: theme.infoList.firstOrNull?.assetPath, 59 assetPath: theme.infoList.firstOrNull?.assetPath,
59 imageUrl: theme.infoList.firstOrNull?.imgUrl, 60 imageUrl: theme.infoList.firstOrNull?.imgUrl,
60 ), 61 ),
@@ -4,12 +4,14 @@ class WatchThemeCharacterAvatar extends StatelessWidget { @@ -4,12 +4,14 @@ class WatchThemeCharacterAvatar extends StatelessWidget {
4 const WatchThemeCharacterAvatar({ 4 const WatchThemeCharacterAvatar({
5 super.key, 5 super.key,
6 required this.size, 6 required this.size,
  7 + this.padding = 0,
7 this.assetPath, 8 this.assetPath,
8 this.imageUrl, 9 this.imageUrl,
9 this.empty = false, 10 this.empty = false,
10 }); 11 });
11 12
12 final double size; 13 final double size;
  14 + final double padding;
13 final String? assetPath; 15 final String? assetPath;
14 final String? imageUrl; 16 final String? imageUrl;
15 final bool empty; 17 final bool empty;
@@ -18,24 +20,23 @@ class WatchThemeCharacterAvatar extends StatelessWidget { @@ -18,24 +20,23 @@ class WatchThemeCharacterAvatar extends StatelessWidget {
18 Widget build(BuildContext context) { 20 Widget build(BuildContext context) {
19 final avatarSize = size; 21 final avatarSize = size;
20 return SizedBox( 22 return SizedBox(
21 - width: avatarSize,  
22 - height: avatarSize, 23 + width: avatarSize,
  24 + height: avatarSize,
23 child: ClipRRect( 25 child: ClipRRect(
24 borderRadius: BorderRadius.circular(16), 26 borderRadius: BorderRadius.circular(16),
25 child: Container( 27 child: Container(
26 - width: avatarSize, 28 + width: avatarSize,
27 height: avatarSize, 29 height: avatarSize,
28 - alignment: Alignment.center,  
29 - child: empty || (assetPath == null && imageUrl == null)  
30 - ? const SizedBox.shrink()  
31 - : _buildImage(avatarSize),  
32 - ),  
33 - )  
34 - ); 30 + alignment: Alignment.center,
  31 + child: empty || (assetPath == null && imageUrl == null)
  32 + ? const SizedBox.shrink()
  33 + : _buildImage(avatarSize),
  34 + ),
  35 + ));
35 } 36 }
36 37
37 Widget _buildImage(double avatarSize) { 38 Widget _buildImage(double avatarSize) {
38 - final width = avatarSize; 39 + final width = avatarSize - padding * 2;
39 if (imageUrl != null && imageUrl!.isNotEmpty) { 40 if (imageUrl != null && imageUrl!.isNotEmpty) {
40 return Image.network( 41 return Image.network(
41 imageUrl!, 42 imageUrl!,
@@ -28,6 +28,9 @@ class _WatchThemeSyncDialogState extends State<WatchThemeSyncDialog> { @@ -28,6 +28,9 @@ class _WatchThemeSyncDialogState extends State<WatchThemeSyncDialog> {
28 if (_state == _WatchThemeSyncState.syncing) return; 28 if (_state == _WatchThemeSyncState.syncing) return;
29 setState(() => _state = _WatchThemeSyncState.syncing); 29 setState(() => _state = _WatchThemeSyncState.syncing);
30 final success = await widget.onSync(); 30 final success = await widget.onSync();
  31 + if (success) {
  32 + await Future<void>.delayed(const Duration(seconds: 1));
  33 + }
31 if (!mounted) return; 34 if (!mounted) return;
32 setState(() { 35 setState(() {
33 _success = success; 36 _success = success;
@@ -48,46 +48,4 @@ class WearEngineService { @@ -48,46 +48,4 @@ class WearEngineService {
48 return result is AppSuccess; 48 return result is AppSuccess;
49 } 49 }
50 50
51 - Future<bool> syncWatchUserPayload({required bool isOther}) async {  
52 - if (!isAndroid) {  
53 - return false;  
54 - }  
55 - final device = await _wearEngineHost.checkConnectedDevice();  
56 - if (device == null) {  
57 - return false;  
58 - }  
59 -  
60 - final today = await _healthApi.getTodayData(isOther: isOther);  
61 - final prefs = _userPrefs.preferences.value;  
62 - final me = prefs.meUserInfo;  
63 - final partner = prefs.partnerUserInfo;  
64 -  
65 - int hrvValue = 0;  
66 - int heartRate = 0;  
67 - int steps = 0;  
68 -  
69 - if (today case AppSuccess(data: final todayData)) {  
70 - if (todayData.hrvDataList?.isNotEmpty == true) {  
71 - hrvValue = todayData.hrvDataList!.first.value?.toInt() ?? 0;  
72 - }  
73 - heartRate = todayData.recentData?.heartRate ?? 0;  
74 - steps = todayData.recentData?.steps ?? 0;  
75 - }  
76 -  
77 - final payload = jsonEncode({  
78 - 'dataType': 1,  
79 - 'isLogin': _userPrefs.accessToken.isNotEmpty,  
80 - 'isBound': (partner?.pairId ?? 0) > 0,  
81 - 'isVip': prefs.vipInfo?.isVip ?? false,  
82 - 'meCharacter': me?.persona ?? UserCharacter.cat.id,  
83 - 'meHrv': hrvValue,  
84 - 'meHeartRate': heartRate,  
85 - 'meSteps': steps,  
86 - 'taCharacter': partner?.persona ?? UserCharacter.cat.id,  
87 - });  
88 -  
89 - final ok = await _wearEngineHost.sendWatchSyncPayload(payload);  
90 - AppLogger.i('WearEngineService.syncWatchUserPayload ok=$ok');  
91 - return ok;  
92 - }  
93 } 51 }
@@ -421,6 +421,7 @@ class HealthKitHostApi { @@ -421,6 +421,7 @@ class HealthKitHostApi {
421 } 421 }
422 } 422 }
423 423
  424 + /// 获取当前已上传的健康数据点,主要用于调试
424 /// Opens Huawei Health client authorization UI. Returns whether user granted. 425 /// Opens Huawei Health client authorization UI. Returns whether user granted.
425 Future<HealthAuthorization> checkHealthAppAuthorization() async { 426 Future<HealthAuthorization> checkHealthAppAuthorization() async {
426 final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization$pigeonVar_messageChannelSuffix'; 427 final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization$pigeonVar_messageChannelSuffix';
@@ -91,6 +91,92 @@ class WearDeviceInfo { @@ -91,6 +91,92 @@ class WearDeviceInfo {
91 ; 91 ;
92 } 92 }
93 93
  94 +class WatchTransformTheme {
  95 + WatchTransformTheme({
  96 + required this.themeId,
  97 + required this.themeName,
  98 + required this.energeticDescription,
  99 + this.energeticImage,
  100 + required this.normalDescription,
  101 + this.normalImage,
  102 + required this.slightStressfulDescription,
  103 + this.slightStressfulImage,
  104 + required this.stressfulDescription,
  105 + this.stressfulImage,
  106 + });
  107 +
  108 + int themeId;
  109 +
  110 + String themeName;
  111 +
  112 + String energeticDescription;
  113 +
  114 + Uint8List? energeticImage;
  115 +
  116 + String normalDescription;
  117 +
  118 + Uint8List? normalImage;
  119 +
  120 + String slightStressfulDescription;
  121 +
  122 + Uint8List? slightStressfulImage;
  123 +
  124 + String stressfulDescription;
  125 +
  126 + Uint8List? stressfulImage;
  127 +
  128 + List<Object?> _toList() {
  129 + return <Object?>[
  130 + themeId,
  131 + themeName,
  132 + energeticDescription,
  133 + energeticImage,
  134 + normalDescription,
  135 + normalImage,
  136 + slightStressfulDescription,
  137 + slightStressfulImage,
  138 + stressfulDescription,
  139 + stressfulImage,
  140 + ];
  141 + }
  142 +
  143 + Object encode() {
  144 + return _toList(); }
  145 +
  146 + static WatchTransformTheme decode(Object result) {
  147 + result as List<Object?>;
  148 + return WatchTransformTheme(
  149 + themeId: result[0]! as int,
  150 + themeName: result[1]! as String,
  151 + energeticDescription: result[2]! as String,
  152 + energeticImage: result[3] as Uint8List?,
  153 + normalDescription: result[4]! as String,
  154 + normalImage: result[5] as Uint8List?,
  155 + slightStressfulDescription: result[6]! as String,
  156 + slightStressfulImage: result[7] as Uint8List?,
  157 + stressfulDescription: result[8]! as String,
  158 + stressfulImage: result[9] as Uint8List?,
  159 + );
  160 + }
  161 +
  162 + @override
  163 + // ignore: avoid_equals_and_hash_code_on_mutable_classes
  164 + bool operator ==(Object other) {
  165 + if (other is! WatchTransformTheme || other.runtimeType != runtimeType) {
  166 + return false;
  167 + }
  168 + if (identical(this, other)) {
  169 + return true;
  170 + }
  171 + return _deepEquals(encode(), other.encode());
  172 + }
  173 +
  174 + @override
  175 + // ignore: avoid_equals_and_hash_code_on_mutable_classes
  176 + int get hashCode => Object.hashAll(_toList())
  177 +;
  178 +}
  179 +
94 180
95 class _PigeonCodec extends StandardMessageCodec { 181 class _PigeonCodec extends StandardMessageCodec {
96 const _PigeonCodec(); 182 const _PigeonCodec();
@@ -102,6 +188,9 @@ class _PigeonCodec extends StandardMessageCodec { @@ -102,6 +188,9 @@ class _PigeonCodec extends StandardMessageCodec {
102 } else if (value is WearDeviceInfo) { 188 } else if (value is WearDeviceInfo) {
103 buffer.putUint8(129); 189 buffer.putUint8(129);
104 writeValue(buffer, value.encode()); 190 writeValue(buffer, value.encode());
  191 + } else if (value is WatchTransformTheme) {
  192 + buffer.putUint8(130);
  193 + writeValue(buffer, value.encode());
105 } else { 194 } else {
106 super.writeValue(buffer, value); 195 super.writeValue(buffer, value);
107 } 196 }
@@ -112,6 +201,8 @@ class _PigeonCodec extends StandardMessageCodec { @@ -112,6 +201,8 @@ class _PigeonCodec extends StandardMessageCodec {
112 switch (type) { 201 switch (type) {
113 case 129: 202 case 129:
114 return WearDeviceInfo.decode(readValue(buffer)!); 203 return WearDeviceInfo.decode(readValue(buffer)!);
  204 + case 130:
  205 + return WatchTransformTheme.decode(readValue(buffer)!);
115 default: 206 default:
116 return super.readValueOfType(type, buffer); 207 return super.readValueOfType(type, buffer);
117 } 208 }
@@ -267,14 +358,15 @@ class WearEngineHostApi { @@ -267,14 +358,15 @@ class WearEngineHostApi {
267 } 358 }
268 } 359 }
269 360
270 - Future<bool> sendWatchSyncPayload(String jsonPayload) async {  
271 - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendWatchSyncPayload$pigeonVar_messageChannelSuffix'; 361 + /// 添加或者删除主题
  362 + Future<bool> syncWatchTheme(WatchTransformTheme? theme) async {
  363 + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.syncWatchTheme$pigeonVar_messageChannelSuffix';
272 final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( 364 final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
273 pigeonVar_channelName, 365 pigeonVar_channelName,
274 pigeonChannelCodec, 366 pigeonChannelCodec,
275 binaryMessenger: pigeonVar_binaryMessenger, 367 binaryMessenger: pigeonVar_binaryMessenger,
276 ); 368 );
277 - final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[jsonPayload]); 369 + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[theme]);
278 final List<Object?>? pigeonVar_replyList = 370 final List<Object?>? pigeonVar_replyList =
279 await pigeonVar_sendFuture as List<Object?>?; 371 await pigeonVar_sendFuture as List<Object?>?;
280 if (pigeonVar_replyList == null) { 372 if (pigeonVar_replyList == null) {
@@ -83,6 +83,9 @@ abstract class HealthKitHostApi { @@ -83,6 +83,9 @@ abstract class HealthKitHostApi {
83 @async 83 @async
84 HealthUploadResult performHealthUpload(); 84 HealthUploadResult performHealthUpload();
85 85
  86 + /// 获取当前已上传的健康数据点,主要用于调试
  87 + // List<HealthUploadDataPoint> getDebugCurrentUploadedData();
  88 +
86 // @required 89 // @required
87 90
88 /// Opens Huawei Health client authorization UI. Returns whether user granted. 91 /// Opens Huawei Health client authorization UI. Returns whether user granted.
@@ -16,6 +16,32 @@ class WearDeviceInfo { @@ -16,6 +16,32 @@ class WearDeviceInfo {
16 bool? isWatchAppInstalled; 16 bool? isWatchAppInstalled;
17 } 17 }
18 18
  19 +class WatchTransformTheme {
  20 + WatchTransformTheme({
  21 + required this.themeId,
  22 + required this.themeName,
  23 + required this.energeticDescription,
  24 + this.energeticImage,
  25 + required this.normalDescription,
  26 + this.normalImage,
  27 + required this.slightStressfulDescription,
  28 + this.slightStressfulImage,
  29 + required this.stressfulDescription,
  30 + this.stressfulImage,
  31 + });
  32 +
  33 + int themeId;
  34 + final String themeName;
  35 + final String energeticDescription;
  36 + final Uint8List? energeticImage;
  37 + final String normalDescription;
  38 + final Uint8List? normalImage;
  39 + final String slightStressfulDescription;
  40 + final Uint8List? slightStressfulImage;
  41 + final String stressfulDescription;
  42 + final Uint8List? stressfulImage;
  43 +}
  44 +
19 @ConfigurePigeon( 45 @ConfigurePigeon(
20 PigeonOptions( 46 PigeonOptions(
21 dartOut: 'lib/pigeon/wear_engine_api.g.dart', 47 dartOut: 'lib/pigeon/wear_engine_api.g.dart',
@@ -47,8 +73,9 @@ abstract class WearEngineHostApi { @@ -47,8 +73,9 @@ abstract class WearEngineHostApi {
47 @async 73 @async
48 bool addWatchSurface(); 74 bool addWatchSurface();
49 75
  76 +/// 添加或者删除主题
50 @async 77 @async
51 - bool sendWatchSyncPayload(String jsonPayload); 78 + bool syncWatchTheme(WatchTransformTheme? theme);
52 79
53 /// Opens the system photo picker and returns a local PNG file path after 80 /// Opens the system photo picker and returns a local PNG file path after
54 /// removing the image background on the host platform. 81 /// removing the image background on the host platform.