Commit 3921ac0cec8d49d020b818b152150239cf40044d

Authored by 权海
1 parent 51039469

feat(ui):增加flutter向原生注入的用户信息和url,增加iOS支付服务器校验

feat(ui):增加ios原生校验订单
@@ -132,11 +132,21 @@ data class AppleSignInModel ( @@ -132,11 +132,21 @@ data class AppleSignInModel (
132 132
133 /** Generated class from Pigeon that represents data sent in messages. */ 133 /** Generated class from Pigeon that represents data sent in messages. */
134 data class AppleProductInfo ( 134 data class AppleProductInfo (
  135 + /** 苹果商品id */
135 val productId: String, 136 val productId: String,
  137 + /** 原价描述 */
136 val originPriceDescription: String, 138 val originPriceDescription: String,
137 - val priceDescription: String? = null, 139 + /** 真实价格描述 */
  140 + val priceDescription: String,
  141 + /** 原价格 */
138 val originPrice: Double, 142 val originPrice: Double,
139 - val price: Double? = null, 143 + /** 真实价格, = 0 代表试用商品 */
  144 + val price: Double,
  145 + /** price/unit = 单位价格 */
  146 + val unitPrice: String,
  147 + /** 当前货币符号 */
  148 + val currencyCode: String,
  149 + /** 当前用户是否可以试用 */
140 val isTrialPeriod: Boolean 150 val isTrialPeriod: Boolean
141 ) 151 )
142 { 152 {
@@ -144,11 +154,13 @@ data class AppleProductInfo ( @@ -144,11 +154,13 @@ data class AppleProductInfo (
144 fun fromList(pigeonVar_list: List<Any?>): AppleProductInfo { 154 fun fromList(pigeonVar_list: List<Any?>): AppleProductInfo {
145 val productId = pigeonVar_list[0] as String 155 val productId = pigeonVar_list[0] as String
146 val originPriceDescription = pigeonVar_list[1] as String 156 val originPriceDescription = pigeonVar_list[1] as String
147 - val priceDescription = pigeonVar_list[2] as String? 157 + val priceDescription = pigeonVar_list[2] as String
148 val originPrice = pigeonVar_list[3] as Double 158 val originPrice = pigeonVar_list[3] as Double
149 - val price = pigeonVar_list[4] as Double?  
150 - val isTrialPeriod = pigeonVar_list[5] as Boolean  
151 - return AppleProductInfo(productId, originPriceDescription, priceDescription, originPrice, price, isTrialPeriod) 159 + val price = pigeonVar_list[4] as Double
  160 + val unitPrice = pigeonVar_list[5] as String
  161 + val currencyCode = pigeonVar_list[6] as String
  162 + val isTrialPeriod = pigeonVar_list[7] as Boolean
  163 + return AppleProductInfo(productId, originPriceDescription, priceDescription, originPrice, price, unitPrice, currencyCode, isTrialPeriod)
152 } 164 }
153 } 165 }
154 fun toList(): List<Any?> { 166 fun toList(): List<Any?> {
@@ -158,6 +170,8 @@ data class AppleProductInfo ( @@ -158,6 +170,8 @@ data class AppleProductInfo (
158 priceDescription, 170 priceDescription,
159 originPrice, 171 originPrice,
160 price, 172 price,
  173 + unitPrice,
  174 + currencyCode,
161 isTrialPeriod, 175 isTrialPeriod,
162 ) 176 )
163 } 177 }
@@ -277,10 +291,20 @@ interface PlatformHostApi { @@ -277,10 +291,20 @@ interface PlatformHostApi {
277 * `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)` 291 * `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
278 */ 292 */
279 fun getFullUserAgent(): String 293 fun getFullUserAgent(): String
  294 + /**
  295 + * 更新用户信息, 有登录态后调用
  296 + * jsonString: UserPreferences的序列化string
  297 + * baseUrl: 请求地址, https://api.doublefeel.cn
  298 + */
  299 + fun updateLoginInfo(jsonString: String, baseUrl: String)
280 /** 请求苹果登录 */ 300 /** 请求苹果登录 */
281 fun requestAppleSignIn(callback: (Result<AppleSignInModel?>) -> Unit) 301 fun requestAppleSignIn(callback: (Result<AppleSignInModel?>) -> Unit)
282 - /** 查询指定id的苹果商品 */  
283 - fun requestAppleProductInfo(productId: String, callback: (Result<AppleProductInfo?>) -> Unit) 302 + /**
  303 + * 查询指定id的苹果商品
  304 + * productId: 苹果商品id
  305 + * baseUnit: 除数基础单位(多少个天、周、月) ->
  306 + */
  307 + fun requestAppleProductInfo(productId: String, baseUnit: Long, callback: (Result<AppleProductInfo?>) -> Unit)
284 /** 从服务器下单后请求苹果支付 */ 308 /** 从服务器下单后请求苹果支付 */
285 fun performApplePayment(productId: String, uuid: String, callback: (Result<AppleProductPaymentResult?>) -> Unit) 309 fun performApplePayment(productId: String, uuid: String, callback: (Result<AppleProductPaymentResult?>) -> Unit)
286 310
@@ -309,6 +333,25 @@ interface PlatformHostApi { @@ -309,6 +333,25 @@ interface PlatformHostApi {
309 } 333 }
310 } 334 }
311 run { 335 run {
  336 + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.updateLoginInfo$separatedMessageChannelSuffix", codec)
  337 + if (api != null) {
  338 + channel.setMessageHandler { message, reply ->
  339 + val args = message as List<Any?>
  340 + val jsonStringArg = args[0] as String
  341 + val baseUrlArg = args[1] as String
  342 + val wrapped: List<Any?> = try {
  343 + api.updateLoginInfo(jsonStringArg, baseUrlArg)
  344 + listOf(null)
  345 + } catch (exception: Throwable) {
  346 + PlatformApiPigeonUtils.wrapError(exception)
  347 + }
  348 + reply.reply(wrapped)
  349 + }
  350 + } else {
  351 + channel.setMessageHandler(null)
  352 + }
  353 + }
  354 + run {
312 val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn$separatedMessageChannelSuffix", codec) 355 val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn$separatedMessageChannelSuffix", codec)
313 if (api != null) { 356 if (api != null) {
314 channel.setMessageHandler { _, reply -> 357 channel.setMessageHandler { _, reply ->
@@ -332,7 +375,8 @@ interface PlatformHostApi { @@ -332,7 +375,8 @@ interface PlatformHostApi {
332 channel.setMessageHandler { message, reply -> 375 channel.setMessageHandler { message, reply ->
333 val args = message as List<Any?> 376 val args = message as List<Any?>
334 val productIdArg = args[0] as String 377 val productIdArg = args[0] as String
335 - api.requestAppleProductInfo(productIdArg) { result: Result<AppleProductInfo?> -> 378 + val baseUnitArg = args[1] as Long
  379 + api.requestAppleProductInfo(productIdArg, baseUnitArg) { result: Result<AppleProductInfo?> ->
336 val error = result.exceptionOrNull() 380 val error = result.exceptionOrNull()
337 if (error != null) { 381 if (error != null) {
338 reply.reply(PlatformApiPigeonUtils.wrapError(error)) 382 reply.reply(PlatformApiPigeonUtils.wrapError(error))
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 archiveVersion = 1; 3 archiveVersion = 1;
4 classes = { 4 classes = {
5 }; 5 };
6 - objectVersion = 54; 6 + objectVersion = 77;
7 objects = { 7 objects = {
8 8
9 /* Begin PBXBuildFile section */ 9 /* Begin PBXBuildFile section */
@@ -30,8 +30,8 @@ class AppDelegate: NSObject, UIApplicationDelegate { @@ -30,8 +30,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
30 _ application: UIApplication, 30 _ application: UIApplication,
31 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 31 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
32 ) -> Bool { 32 ) -> Bool {
33 - UserAgent.share.genUA()  
34 - ApplePayment.shared.delegate = self 33 + AppShared.shared.agent.genUA()
  34 + AppShared.shared.payment.delegate = self
35 35
36 UIApplication.shared.registerForRemoteNotifications() 36 UIApplication.shared.registerForRemoteNotifications()
37 UNUserNotificationCenter.current().setBadgeCount(0) 37 UNUserNotificationCenter.current().setBadgeCount(0)
  1 +//
  2 +// AppShared.swift
  3 +// Runner
  4 +//
  5 +// Created by 权海 on 2026/6/17.
  6 +//
  7 +
  8 +import Foundation
  9 +
  10 +
  11 +class AppShared{
  12 + static let shared = AppShared()
  13 +
  14 + var isLogin: Bool{
  15 + baseUrl.hasPrefix("http") && (userSummary?.accessToken?.count ?? 0 > 0)
  16 + }
  17 +
  18 + private(set) var agent = UserAgent()
  19 + private(set) var payment = ApplePayment()
  20 +
  21 + var baseUrl: String = ""
  22 + var userSummary: FlutterUserSummary?
  23 +
  24 +
  25 +}
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
8 import Foundation 8 import Foundation
9 import StoreKit 9 import StoreKit
10 10
  11 +
11 enum StoreKitError: Error, LocalizedError { 12 enum StoreKitError: Error, LocalizedError {
12 case productNotFound 13 case productNotFound
13 case failedVerification 14 case failedVerification
@@ -26,9 +27,10 @@ enum StoreKitError: Error, LocalizedError { @@ -26,9 +27,10 @@ enum StoreKitError: Error, LocalizedError {
26 } 27 }
27 28
28 class ApplePayment { 29 class ApplePayment {
29 - static let shared = ApplePayment()  
30 var delegate: AppDelegate? 30 var delegate: AppDelegate?
31 31
  32 + private var waitingVerifyTransitions: [Transaction] = []
  33 +
32 func purchase(_ productId: String, uuidString: String) async -> AppleProductPaymentResult { 34 func purchase(_ productId: String, uuidString: String) async -> AppleProductPaymentResult {
33 do { 35 do {
34 guard let uuid = UUID(uuidString: uuidString) else { 36 guard let uuid = UUID(uuidString: uuidString) else {
@@ -168,21 +170,50 @@ class ApplePayment { @@ -168,21 +170,50 @@ class ApplePayment {
168 } 170 }
169 171
170 private func verifyWithServer(appAccountToken: String, originalTransactionId: String?, transactionId: String?, productId: String) async -> Bool{ 172 private func verifyWithServer(appAccountToken: String, originalTransactionId: String?, transactionId: String?, productId: String) async -> Bool{
171 - guard let delegate else{ 173 + guard AppShared.shared.isLogin else {
  174 + print("Apple payment verify skipped: missing login info")
  175 + return false
  176 + }
  177 +
  178 + guard let baseURL = URL(string: AppShared.shared.baseUrl),
  179 + let url = URL(string: "/client/doublefeel/payment/order_verify/apple/", relativeTo: baseURL)?.absoluteURL else {
  180 + print("Apple payment verify failed: invalid baseUrl \(AppShared.shared.baseUrl)")
172 return false 181 return false
173 } 182 }
  183 +
174 var params: [String: String] = [ 184 var params: [String: String] = [
175 "productId": productId, 185 "productId": productId,
176 "appAccountToken": appAccountToken 186 "appAccountToken": appAccountToken
177 ] 187 ]
178 params["originalTransactionId"] = originalTransactionId 188 params["originalTransactionId"] = originalTransactionId
179 params["transactionId"] = transactionId 189 params["transactionId"] = transactionId
180 -  
181 - return await withCheckedContinuation { continuous in  
182 - delegate.invoke(method: .verifyPayment, arguments: params) { result in  
183 - print("invoke verifyPayment result: \(String(describing: result))")  
184 - continuous.resume(returning: true) 190 +
  191 + var request = URLRequest(url: url)
  192 + request.httpMethod = "POST"
  193 + request.timeoutInterval = 30
  194 + request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  195 + request.setValue("application/json", forHTTPHeaderField: "Accept")
  196 + request.setValue(AppShared.shared.userSummary?.accessToken, forHTTPHeaderField: "access_token")
  197 + request.setValue(AppShared.shared.agent.finalUA, forHTTPHeaderField: "User-Agent")
  198 +
  199 + do {
  200 + request.httpBody = try JSONSerialization.data(withJSONObject: params)
  201 + let (data, response) = try await URLSession.shared.data(for: request)
  202 + guard let httpResponse = response as? HTTPURLResponse else {
  203 + print("Apple payment verify failed: invalid response")
  204 + return false
  205 + }
  206 +
  207 + let bodyText = String(data: data, encoding: .utf8) ?? ""
  208 + print("Apple payment verify response: status=\(httpResponse.statusCode), body=\(bodyText)")
  209 +
  210 + guard (200..<300).contains(httpResponse.statusCode) else {
  211 + return false
185 } 212 }
  213 + return true
  214 + } catch {
  215 + print("Apple payment verify failed: \(error.localizedDescription)")
  216 + return false
186 } 217 }
187 } 218 }
188 219
@@ -219,11 +250,15 @@ extension ApplePayment { @@ -219,11 +250,15 @@ extension ApplePayment {
219 for await result in Transaction.updates { 250 for await result in Transaction.updates {
220 do { 251 do {
221 let transaction = try await self.checkVerified(result) 252 let transaction = try await self.checkVerified(result)
222 - let _ = await self.verifyWithServer(appAccountToken: transaction.appAccountToken?.uuidString ?? "", originalTransactionId: String(transaction.originalID), transactionId: String(transaction.id), productId: transaction.productID)  
223 - // 更新客户产品状态  
224 - await self.updateCustomerProductStatus()  
225 - await transaction.finish()  
226 - print("交易更新处理完成: \(transaction.debugDescription)") 253 + if await AppShared.shared.isLogin{
  254 + let success = await self.verifyWithServer(appAccountToken: transaction.appAccountToken?.uuidString ?? "", originalTransactionId: String(transaction.originalID), transactionId: String(transaction.id), productId: transaction.productID)
  255 + // 更新客户产品状态
  256 + if success{
  257 + await self.updateCustomerProductStatus()
  258 + await transaction.finish()
  259 + print("交易更新处理完成: \(transaction.debugDescription)")
  260 + }
  261 + }
227 } catch { 262 } catch {
228 print("交易更新处理失败: \(error)") 263 print("交易更新处理失败: \(error)")
229 } 264 }
  1 +//
  2 +// FlutterStorage.swift
  3 +// Runner
  4 +//
  5 +// Created by 权海 on 2026/6/17.
  6 +//
  7 +
  8 +import Foundation
  9 +
  10 +private let kFlutterStorageKey = "flutter.double_feel_user_preferences_json"
  11 +
  12 +
  13 +
  14 +class FlutterStorage{
  15 + static func userSummary() -> FlutterUserSummary?{
  16 + guard let json = UserDefaults.standard.object(forKey: kFlutterStorageKey) else{
  17 + return nil
  18 + }
  19 + do{
  20 + let data = try JSONSerialization.data(withJSONObject: json)
  21 + let model = try JSONDecoder().decode(FlutterUserSummary.self, from: data)
  22 + return model
  23 + }catch{
  24 + return nil
  25 + }
  26 + }
  27 +}
  28 +
  29 +
  30 +struct FlutterUserSummary: Codable{
  31 + var meUserInfo: FlutterUserInfo?
  32 + var partnerUserInfo: FlutterUserInfo?
  33 + var accessToken: String?
  34 + var rongcloudToken: String?
  35 + var vipInfo: FlutterVipInfo?
  36 +
  37 + enum CodingKeys: String, CodingKey {
  38 + case meUserInfo = "me_user_info"
  39 + case partnerUserInfo = "partner_user_info"
  40 + case accessToken = "access_token"
  41 + case rongcloudToken = "rongcloud_token"
  42 + case vipInfo = "vip_info"
  43 + }
  44 +}
  45 +
  46 +struct FlutterVipInfo: Codable{
  47 + var isVip: Bool?
  48 + var isForeverVip: Bool?
  49 + var isShare: Bool?
  50 + var vipStartDate: Int?
  51 + var vipEndDate: Int?
  52 +
  53 + enum CodingKeys: String, CodingKey {
  54 + case isVip = "is_vip"
  55 + case isForeverVip = "is_forever_vip"
  56 + case isShare = "is_share"
  57 + case vipStartDate = "vip_start_date"
  58 + case vipEndDate = "vip_end_date"
  59 + }
  60 +}
  61 +
  62 +struct FlutterUserInfo: Codable{
  63 + var id: Int?
  64 + var pairId: Int?
  65 + var pairCode: String?
  66 + var nickname: String?
  67 + var avatar: String?
  68 + var telephone: String?
  69 + var persona: Int?
  70 + var isBot: Int?
  71 +
  72 + enum CodingKeys: String, CodingKey {
  73 + case id
  74 + case pairId = "pair_id"
  75 + case pairCode = "pair_code"
  76 + case nickname
  77 + case avatar
  78 + case telephone
  79 + case persona
  80 + case isBot = "is_bot"
  81 + }
  82 +}
@@ -159,11 +159,21 @@ struct AppleSignInModel: Hashable { @@ -159,11 +159,21 @@ struct AppleSignInModel: Hashable {
159 159
160 /// Generated class from Pigeon that represents data sent in messages. 160 /// Generated class from Pigeon that represents data sent in messages.
161 struct AppleProductInfo: Hashable { 161 struct AppleProductInfo: Hashable {
  162 + /// 苹果商品id
162 var productId: String 163 var productId: String
  164 + /// 原价描述
163 var originPriceDescription: String 165 var originPriceDescription: String
164 - var priceDescription: String? = nil 166 + /// 真实价格描述
  167 + var priceDescription: String
  168 + /// 原价格
165 var originPrice: Double 169 var originPrice: Double
166 - var price: Double? = nil 170 + /// 真实价格, = 0 代表试用商品
  171 + var price: Double
  172 + /// price/unit = 单位价格
  173 + var unitPrice: String
  174 + /// 当前货币符号
  175 + var currencyCode: String
  176 + /// 当前用户是否可以试用
167 var isTrialPeriod: Bool 177 var isTrialPeriod: Bool
168 178
169 179
@@ -171,10 +181,12 @@ struct AppleProductInfo: Hashable { @@ -171,10 +181,12 @@ struct AppleProductInfo: Hashable {
171 static func fromList(_ pigeonVar_list: [Any?]) -> AppleProductInfo? { 181 static func fromList(_ pigeonVar_list: [Any?]) -> AppleProductInfo? {
172 let productId = pigeonVar_list[0] as! String 182 let productId = pigeonVar_list[0] as! String
173 let originPriceDescription = pigeonVar_list[1] as! String 183 let originPriceDescription = pigeonVar_list[1] as! String
174 - let priceDescription: String? = nilOrValue(pigeonVar_list[2]) 184 + let priceDescription = pigeonVar_list[2] as! String
175 let originPrice = pigeonVar_list[3] as! Double 185 let originPrice = pigeonVar_list[3] as! Double
176 - let price: Double? = nilOrValue(pigeonVar_list[4])  
177 - let isTrialPeriod = pigeonVar_list[5] as! Bool 186 + let price = pigeonVar_list[4] as! Double
  187 + let unitPrice = pigeonVar_list[5] as! String
  188 + let currencyCode = pigeonVar_list[6] as! String
  189 + let isTrialPeriod = pigeonVar_list[7] as! Bool
178 190
179 return AppleProductInfo( 191 return AppleProductInfo(
180 productId: productId, 192 productId: productId,
@@ -182,6 +194,8 @@ struct AppleProductInfo: Hashable { @@ -182,6 +194,8 @@ struct AppleProductInfo: Hashable {
182 priceDescription: priceDescription, 194 priceDescription: priceDescription,
183 originPrice: originPrice, 195 originPrice: originPrice,
184 price: price, 196 price: price,
  197 + unitPrice: unitPrice,
  198 + currencyCode: currencyCode,
185 isTrialPeriod: isTrialPeriod 199 isTrialPeriod: isTrialPeriod
186 ) 200 )
187 } 201 }
@@ -192,6 +206,8 @@ struct AppleProductInfo: Hashable { @@ -192,6 +206,8 @@ struct AppleProductInfo: Hashable {
192 priceDescription, 206 priceDescription,
193 originPrice, 207 originPrice,
194 price, 208 price,
  209 + unitPrice,
  210 + currencyCode,
195 isTrialPeriod, 211 isTrialPeriod,
196 ] 212 ]
197 } 213 }
@@ -311,10 +327,16 @@ protocol PlatformHostApi { @@ -311,10 +327,16 @@ protocol PlatformHostApi {
311 /// 返回完整的 User-Agent 字符串,由 native 侧组装: 327 /// 返回完整的 User-Agent 字符串,由 native 侧组装:
312 /// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)` 328 /// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
313 func getFullUserAgent() throws -> String 329 func getFullUserAgent() throws -> String
  330 + /// 更新用户信息, 有登录态后调用
  331 + /// jsonString: UserPreferences的序列化string
  332 + /// baseUrl: 请求地址, https://api.doublefeel.cn
  333 + func updateLoginInfo(jsonString: String, baseUrl: String) throws
314 /// 请求苹果登录 334 /// 请求苹果登录
315 func requestAppleSignIn(completion: @escaping (Result<AppleSignInModel?, Error>) -> Void) 335 func requestAppleSignIn(completion: @escaping (Result<AppleSignInModel?, Error>) -> Void)
316 /// 查询指定id的苹果商品 336 /// 查询指定id的苹果商品
317 - func requestAppleProductInfo(productId: String, completion: @escaping (Result<AppleProductInfo?, Error>) -> Void) 337 + /// productId: 苹果商品id
  338 + /// baseUnit: 除数基础单位(多少个天、周、月) ->
  339 + func requestAppleProductInfo(productId: String, baseUnit: Int64, completion: @escaping (Result<AppleProductInfo?, Error>) -> Void)
318 /// 从服务器下单后请求苹果支付 340 /// 从服务器下单后请求苹果支付
319 func performApplePayment(productId: String, uuid: String, completion: @escaping (Result<AppleProductPaymentResult?, Error>) -> Void) 341 func performApplePayment(productId: String, uuid: String, completion: @escaping (Result<AppleProductPaymentResult?, Error>) -> Void)
320 } 342 }
@@ -340,6 +362,25 @@ class PlatformHostApiSetup { @@ -340,6 +362,25 @@ class PlatformHostApiSetup {
340 } else { 362 } else {
341 getFullUserAgentChannel.setMessageHandler(nil) 363 getFullUserAgentChannel.setMessageHandler(nil)
342 } 364 }
  365 + /// 更新用户信息, 有登录态后调用
  366 + /// jsonString: UserPreferences的序列化string
  367 + /// baseUrl: 请求地址, https://api.doublefeel.cn
  368 + let updateLoginInfoChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.updateLoginInfo\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
  369 + if let api = api {
  370 + updateLoginInfoChannel.setMessageHandler { message, reply in
  371 + let args = message as! [Any?]
  372 + let jsonStringArg = args[0] as! String
  373 + let baseUrlArg = args[1] as! String
  374 + do {
  375 + try api.updateLoginInfo(jsonString: jsonStringArg, baseUrl: baseUrlArg)
  376 + reply(wrapResult(nil))
  377 + } catch {
  378 + reply(wrapError(error))
  379 + }
  380 + }
  381 + } else {
  382 + updateLoginInfoChannel.setMessageHandler(nil)
  383 + }
343 /// 请求苹果登录 384 /// 请求苹果登录
344 let requestAppleSignInChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) 385 let requestAppleSignInChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
345 if let api = api { 386 if let api = api {
@@ -357,12 +398,15 @@ class PlatformHostApiSetup { @@ -357,12 +398,15 @@ class PlatformHostApiSetup {
357 requestAppleSignInChannel.setMessageHandler(nil) 398 requestAppleSignInChannel.setMessageHandler(nil)
358 } 399 }
359 /// 查询指定id的苹果商品 400 /// 查询指定id的苹果商品
  401 + /// productId: 苹果商品id
  402 + /// baseUnit: 除数基础单位(多少个天、周、月) ->
360 let requestAppleProductInfoChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) 403 let requestAppleProductInfoChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
361 if let api = api { 404 if let api = api {
362 requestAppleProductInfoChannel.setMessageHandler { message, reply in 405 requestAppleProductInfoChannel.setMessageHandler { message, reply in
363 let args = message as! [Any?] 406 let args = message as! [Any?]
364 let productIdArg = args[0] as! String 407 let productIdArg = args[0] as! String
365 - api.requestAppleProductInfo(productId: productIdArg) { result in 408 + let baseUnitArg = args[1] as! Int64
  409 + api.requestAppleProductInfo(productId: productIdArg, baseUnit: baseUnitArg) { result in
366 switch result { 410 switch result {
367 case .success(let res): 411 case .success(let res):
368 reply(wrapResult(res)) 412 reply(wrapResult(res))
@@ -4,6 +4,31 @@ import StoreKit @@ -4,6 +4,31 @@ import StoreKit
4 import UIKit 4 import UIKit
5 import WebKit 5 import WebKit
6 6
  7 +class PriceFormatter{
  8 + static func formatPrice(_ price: Decimal, currencyCode: String) -> String {
  9 + let formatter = NumberFormatter()
  10 + formatter.numberStyle = .currency
  11 + formatter.currencyCode = currencyCode
  12 + formatter.minimumFractionDigits = 2
  13 + formatter.maximumFractionDigits = 2
  14 +
  15 + return formatter.string(from: price as NSDecimalNumber) ?? "\(price)"
  16 + }
  17 +
  18 + static func currencySymbol(for currencyCode: String) -> String {
  19 +
  20 + let formatter = NumberFormatter()
  21 +
  22 + formatter.numberStyle = .currency
  23 +
  24 + formatter.currencyCode = currencyCode
  25 +
  26 +// formatter.locale = Locale(identifier: "zh_CN")
  27 +
  28 + return formatter.currencySymbol
  29 +
  30 + }
  31 +}
7 32
8 /** 33 /**
9 * PlatformApi iOS implementation. 34 * PlatformApi iOS implementation.
@@ -12,15 +37,27 @@ import WebKit @@ -12,15 +37,27 @@ import WebKit
12 * `{systemWebViewUA} doublefeel/{versionCode}({versionName})(Apple##Apple##{model}; iOS{osVersion}; {height}x{width})(huawei)` 37 * `{systemWebViewUA} doublefeel/{versionCode}({versionName})(Apple##Apple##{model}; iOS{osVersion}; {height}x{width})(huawei)`
13 */ 38 */
14 final class PlatformHostApiImpl: PlatformHostApi { 39 final class PlatformHostApiImpl: PlatformHostApi {
15 - func requestAppleProductInfo(productId: String, completion: @escaping (Result<AppleProductInfo?, any Error>) -> Void) { 40 + func updateLoginInfo(jsonString: String, baseUrl: String) throws {
  41 + AppShared.shared.baseUrl = baseUrl
  42 + if let data = jsonString.data(using: .utf8){
  43 + do{
  44 + let model = try JSONDecoder().decode(FlutterUserSummary.self, from: data)
  45 + AppShared.shared.userSummary = model
  46 + }catch{
  47 + DebugLogger.log(desc: error.localizedDescription)
  48 + }
  49 + }
  50 + }
  51 +
  52 + func requestAppleProductInfo(productId: String, baseUnit: Int64, completion: @escaping (Result<AppleProductInfo?, any Error>) -> Void) {
16 Task { 53 Task {
17 do { 54 do {
18 - let product = try await ApplePayment.shared.requestProducts(productId)  
19 - let isTrialPeriod = await ApplePayment.shared.isFreeTrail(product: product) 55 + let product = try await AppShared.shared.payment.requestProducts(productId)
  56 + let isTrialPeriod = await AppShared.shared.payment.isFreeTrail(product: product)
20 let originPrice = product.price 57 let originPrice = product.price
21 var displayPrice = product.displayPrice 58 var displayPrice = product.displayPrice
22 var price = originPrice 59 var price = originPrice
23 - if let introductoryOffer = product.subscription?.introductoryOffer, introductoryOffer.price > 0{ 60 + if let introductoryOffer = product.subscription?.introductoryOffer{
24 price = introductoryOffer.price 61 price = introductoryOffer.price
25 displayPrice = introductoryOffer.displayPrice 62 displayPrice = introductoryOffer.displayPrice
26 }else if let offer = product.subscription?.promotionalOffers.first(where: { $0.id == product.id }){ 63 }else if let offer = product.subscription?.promotionalOffers.first(where: { $0.id == product.id }){
@@ -28,12 +65,17 @@ final class PlatformHostApiImpl: PlatformHostApi { @@ -28,12 +65,17 @@ final class PlatformHostApiImpl: PlatformHostApi {
28 displayPrice = offer.displayPrice 65 displayPrice = offer.displayPrice
29 } 66 }
30 67
  68 + let unitPrice = price/Decimal(baseUnit)
  69 + let currencyCode = product.priceFormatStyle.currencyCode
  70 +
31 let productInfo = AppleProductInfo( 71 let productInfo = AppleProductInfo(
32 productId: product.id, 72 productId: product.id,
33 originPriceDescription: product.displayPrice, 73 originPriceDescription: product.displayPrice,
34 priceDescription: displayPrice, 74 priceDescription: displayPrice,
35 - originPrice: NSDecimalNumber(decimal: originPrice).doubleValue,  
36 - price: NSDecimalNumber(decimal: price).doubleValue, 75 + originPrice: NSDecimalNumber(decimal: originPrice * 100).doubleValue,
  76 + price: NSDecimalNumber(decimal: price * 100).doubleValue,
  77 + unitPrice: PriceFormatter.formatPrice(unitPrice, currencyCode: currencyCode),
  78 + currencyCode: PriceFormatter.currencySymbol(for: currencyCode),
37 isTrialPeriod: isTrialPeriod 79 isTrialPeriod: isTrialPeriod
38 ) 80 )
39 print("[PlatformHostApiImpl.requestAppleProductInfo] return: \(productInfo)") 81 print("[PlatformHostApiImpl.requestAppleProductInfo] return: \(productInfo)")
@@ -50,7 +92,7 @@ final class PlatformHostApiImpl: PlatformHostApi { @@ -50,7 +92,7 @@ final class PlatformHostApiImpl: PlatformHostApi {
50 92
51 func performApplePayment(productId: String, uuid: String, completion: @escaping (Result<AppleProductPaymentResult?, any Error>) -> Void) { 93 func performApplePayment(productId: String, uuid: String, completion: @escaping (Result<AppleProductPaymentResult?, any Error>) -> Void) {
52 Task { 94 Task {
53 - let result = await ApplePayment.shared.purchase(productId, uuidString: uuid) 95 + let result = await AppShared.shared.payment.purchase(productId, uuidString: uuid)
54 print("[PlatformHostApiImpl.performApplePayment] return: \(result)") 96 print("[PlatformHostApiImpl.performApplePayment] return: \(result)")
55 completion(.success(result)) 97 completion(.success(result))
56 } 98 }
@@ -96,7 +138,7 @@ final class PlatformHostApiImpl: PlatformHostApi { @@ -96,7 +138,7 @@ final class PlatformHostApiImpl: PlatformHostApi {
96 } 138 }
97 139
98 func getFullUserAgent() throws -> String { 140 func getFullUserAgent() throws -> String {
99 - let userAgent = UserAgent.share.finalUA 141 + let userAgent = AppShared.shared.agent.finalUA
100 print("[PlatformHostApiImpl.getFullUserAgent] return: \(userAgent)") 142 print("[PlatformHostApiImpl.getFullUserAgent] return: \(userAgent)")
101 return userAgent 143 return userAgent
102 } 144 }
@@ -16,7 +16,7 @@ struct RunnerApp: App { @@ -16,7 +16,7 @@ struct RunnerApp: App {
16 FlutterRootView(engine:appDelegate.flutterEngine ) 16 FlutterRootView(engine:appDelegate.flutterEngine )
17 .ignoresSafeArea() 17 .ignoresSafeArea()
18 .onAppear { 18 .onAppear {
19 - _ = ApplePayment.shared.listenForTransactions() 19 + _ = AppShared.shared.payment.listenForTransactions()
20 } 20 }
21 } 21 }
22 } 22 }
@@ -22,7 +22,6 @@ extension UIApplication { @@ -22,7 +22,6 @@ extension UIApplication {
22 } 22 }
23 23
24 class UserAgent { 24 class UserAgent {
25 - static let share = UserAgent()  
26 var unifiedUA: String = "" 25 var unifiedUA: String = ""
27 var finalUA: String = "" 26 var finalUA: String = ""
28 27
@@ -4,10 +4,14 @@ import 'package:doublefeel_flutter/core/network/api/theme_api.dart'; @@ -4,10 +4,14 @@ import 'package:doublefeel_flutter/core/network/api/theme_api.dart';
4 import 'package:doublefeel_flutter/core/result/app_result.dart'; 4 import 'package:doublefeel_flutter/core/result/app_result.dart';
5 import 'package:doublefeel_flutter/core/util/app_toast.dart'; 5 import 'package:doublefeel_flutter/core/util/app_toast.dart';
6 import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; 6 import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart';
  7 +import 'package:doublefeel_flutter/r.dart';
  8 +import 'package:flutter/material.dart';
7 import 'package:get/get.dart'; 9 import 'package:get/get.dart';
8 10
9 import '../models/watch_theme_models.dart'; 11 import '../models/watch_theme_models.dart';
10 import '../widgets/watch_theme_dialogs.dart'; 12 import '../widgets/watch_theme_dialogs.dart';
  13 +import '../widgets/watch_theme_no_watch_dialog.dart';
  14 +import '../widgets/watch_theme_sync_dialog.dart';
11 15
12 class CustomWatchThemePreviewController extends GetxController { 16 class CustomWatchThemePreviewController extends GetxController {
13 CustomWatchThemePreviewController(this._themeApi); 17 CustomWatchThemePreviewController(this._themeApi);
@@ -70,10 +74,36 @@ class CustomWatchThemePreviewController extends GetxController { @@ -70,10 +74,36 @@ class CustomWatchThemePreviewController extends GetxController {
70 } 74 }
71 75
72 Future<void> addWatchFace() async { 76 Future<void> addWatchFace() async {
  77 + WearDeviceInfo? device;
  78 + try {
  79 + device = await WearEngineHostApi().checkConnectedDevice();
  80 + } catch (_) {
  81 + device = null;
  82 + }
  83 + if (device == null) {
  84 + await Get.dialog<void>(
  85 + const WatchThemeNoWatchDialog(),
  86 + barrierDismissible: true,
  87 + barrierColor: const Color(0xB3000000),
  88 + );
  89 + return;
  90 + }
  91 +
  92 + await Get.dialog<void>(
  93 + WatchThemeSyncDialog(
  94 + faceAsset: R.assetsImagesWatchThemeCustomFacePreview,
  95 + onSync: _applyAndSyncWatchFace,
  96 + ),
  97 + barrierDismissible: false,
  98 + barrierColor: const Color(0xB3000000),
  99 + );
  100 + }
  101 +
  102 + Future<bool> _applyAndSyncWatchFace() async {
73 final themeId = themeItem.id; 103 final themeId = themeItem.id;
74 if (themeId == null) { 104 if (themeId == null) {
75 AppToast.show('主题信息不完整'); 105 AppToast.show('主题信息不完整');
76 - return; 106 + return false;
77 } 107 }
78 108
79 isApplying.value = true; 109 isApplying.value = true;
@@ -81,17 +111,22 @@ class CustomWatchThemePreviewController extends GetxController { @@ -81,17 +111,22 @@ class CustomWatchThemePreviewController extends GetxController {
81 if (result is! AppSuccess<void>) { 111 if (result is! AppSuccess<void>) {
82 isApplying.value = false; 112 isApplying.value = false;
83 AppToast.show('应用主题失败'); 113 AppToast.show('应用主题失败');
84 - return; 114 + return false;
85 } 115 }
86 116
  117 + var syncSuccess = true;
87 try { 118 try {
88 - await WearEngineHostApi().sendWatchSyncPayload( 119 + syncSuccess = await WearEngineHostApi().sendWatchSyncPayload(
89 jsonEncode(themeItem.toJson()), 120 jsonEncode(themeItem.toJson()),
90 ); 121 );
91 } catch (_) { 122 } catch (_) {
92 // The active theme is already saved on the server. 123 // The active theme is already saved on the server.
  124 + syncSuccess = false;
93 } 125 }
94 isApplying.value = false; 126 isApplying.value = false;
95 - AppToast.show('已应用主题'); 127 + if (!syncSuccess) {
  128 + AppToast.show('表盘同步失败');
  129 + }
  130 + return syncSuccess;
96 } 131 }
97 } 132 }
@@ -4,9 +4,13 @@ import 'package:doublefeel_flutter/core/network/api/theme_api.dart'; @@ -4,9 +4,13 @@ import 'package:doublefeel_flutter/core/network/api/theme_api.dart';
4 import 'package:doublefeel_flutter/core/result/app_result.dart'; 4 import 'package:doublefeel_flutter/core/result/app_result.dart';
5 import 'package:doublefeel_flutter/core/util/app_toast.dart'; 5 import 'package:doublefeel_flutter/core/util/app_toast.dart';
6 import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; 6 import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart';
  7 +import 'package:doublefeel_flutter/r.dart';
  8 +import 'package:flutter/material.dart';
7 import 'package:get/get.dart'; 9 import 'package:get/get.dart';
8 10
9 import '../models/watch_theme_models.dart'; 11 import '../models/watch_theme_models.dart';
  12 +import '../widgets/watch_theme_no_watch_dialog.dart';
  13 +import '../widgets/watch_theme_sync_dialog.dart';
10 14
11 class WatchThemePreviewController extends GetxController { 15 class WatchThemePreviewController extends GetxController {
12 WatchThemePreviewController(this._themeApi); 16 WatchThemePreviewController(this._themeApi);
@@ -38,10 +42,36 @@ class WatchThemePreviewController extends GetxController { @@ -38,10 +42,36 @@ class WatchThemePreviewController extends GetxController {
38 } 42 }
39 43
40 Future<void> addWatchFace() async { 44 Future<void> addWatchFace() async {
  45 + WearDeviceInfo? device;
  46 + try {
  47 + device = await WearEngineHostApi().checkConnectedDevice();
  48 + } catch (_) {
  49 + device = null;
  50 + }
  51 + if (device == null) {
  52 + await Get.dialog<void>(
  53 + const WatchThemeNoWatchDialog(),
  54 + barrierDismissible: true,
  55 + barrierColor: const Color(0xB3000000),
  56 + );
  57 + return;
  58 + }
  59 +
  60 + await Get.dialog<void>(
  61 + WatchThemeSyncDialog(
  62 + faceAsset: R.assetsImagesWatchThemeFaceDefault,
  63 + onSync: _applyAndSyncWatchFace,
  64 + ),
  65 + barrierDismissible: false,
  66 + barrierColor: const Color(0xB3000000),
  67 + );
  68 + }
  69 +
  70 + Future<bool> _applyAndSyncWatchFace() async {
41 final themeId = themeItem.id; 71 final themeId = themeItem.id;
42 if (themeId == null) { 72 if (themeId == null) {
43 AppToast.show('主题信息不完整'); 73 AppToast.show('主题信息不完整');
44 - return; 74 + return false;
45 } 75 }
46 76
47 isApplying.value = true; 77 isApplying.value = true;
@@ -49,17 +79,22 @@ class WatchThemePreviewController extends GetxController { @@ -49,17 +79,22 @@ class WatchThemePreviewController extends GetxController {
49 if (result is! AppSuccess<void>) { 79 if (result is! AppSuccess<void>) {
50 isApplying.value = false; 80 isApplying.value = false;
51 AppToast.show('应用主题失败'); 81 AppToast.show('应用主题失败');
52 - return; 82 + return false;
53 } 83 }
54 84
  85 + var syncSuccess = true;
55 try { 86 try {
56 - await WearEngineHostApi().sendWatchSyncPayload( 87 + syncSuccess = await WearEngineHostApi().sendWatchSyncPayload(
57 jsonEncode(themeItem.toJson()), 88 jsonEncode(themeItem.toJson()),
58 ); 89 );
59 } catch (_) { 90 } catch (_) {
60 // The server state has been updated; watch sync can be retried later. 91 // The server state has been updated; watch sync can be retried later.
  92 + syncSuccess = false;
61 } 93 }
62 isApplying.value = false; 94 isApplying.value = false;
63 - AppToast.show('已应用主题'); 95 + if (!syncSuccess) {
  96 + AppToast.show('表盘同步失败');
  97 + }
  98 + return syncSuccess;
64 } 99 }
65 } 100 }
  1 +import 'package:doublefeel_flutter/core/util/size_extensions.dart';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +import 'watch_theme_colors.dart';
  6 +
  7 +class WatchThemeFriendPickerDialog extends StatelessWidget {
  8 + const WatchThemeFriendPickerDialog({
  9 + super.key,
  10 + this.onConfirm,
  11 + });
  12 +
  13 + final VoidCallback? onConfirm;
  14 +
  15 + @override
  16 + Widget build(BuildContext context) {
  17 + return Material(
  18 + color: Colors.transparent,
  19 + child: Align(
  20 + alignment: Alignment.bottomCenter,
  21 + child: Container(
  22 + height: 452.dp,
  23 + width: double.infinity,
  24 + decoration: BoxDecoration(
  25 + color: WatchThemeColors.background,
  26 + borderRadius: BorderRadius.vertical(top: Radius.circular(14.dp)),
  27 + ),
  28 + child: SafeArea(
  29 + top: false,
  30 + child: Stack(
  31 + children: [
  32 + Column(
  33 + children: [
  34 + SizedBox(
  35 + height: 56.dp,
  36 + child: Stack(
  37 + alignment: Alignment.center,
  38 + children: [
  39 + Text(
  40 + '选择好友',
  41 + style: TextStyle(
  42 + color: WatchThemeColors.textPrimary,
  43 + fontSize: 16.dp,
  44 + fontWeight: FontWeight.w600,
  45 + ),
  46 + ),
  47 + Positioned(
  48 + left: 26.dp,
  49 + child: GestureDetector(
  50 + behavior: HitTestBehavior.opaque,
  51 + onTap: Get.back,
  52 + child: SizedBox(
  53 + width: 32.dp,
  54 + height: 32.dp,
  55 + child: Icon(
  56 + Icons.close,
  57 + size: 22.dp,
  58 + color: const Color(0xFFA084EF),
  59 + ),
  60 + ),
  61 + ),
  62 + ),
  63 + ],
  64 + ),
  65 + ),
  66 + Expanded(
  67 + child: ListView.separated(
  68 + padding: EdgeInsets.fromLTRB(16.dp, 0, 16.dp, 84.dp),
  69 + itemBuilder: (_, index) {
  70 + return _FriendPlaceholderCard(selected: index == 0);
  71 + },
  72 + separatorBuilder: (_, __) => SizedBox(height: 12.dp),
  73 + itemCount: 3,
  74 + ),
  75 + ),
  76 + ],
  77 + ),
  78 + Positioned(
  79 + left: 49.dp,
  80 + right: 47.dp,
  81 + bottom: 35.dp,
  82 + child: GestureDetector(
  83 + behavior: HitTestBehavior.opaque,
  84 + onTap: onConfirm ?? () => Get.back(result: true),
  85 + child: Container(
  86 + height: 48.dp,
  87 + alignment: Alignment.center,
  88 + decoration: BoxDecoration(
  89 + color: WatchThemeColors.brand,
  90 + borderRadius: BorderRadius.circular(24.dp),
  91 + ),
  92 + child: Text(
  93 + '选择并同步至表盘',
  94 + style: TextStyle(
  95 + color: Colors.white,
  96 + fontSize: 16.dp,
  97 + fontWeight: FontWeight.w600,
  98 + ),
  99 + ),
  100 + ),
  101 + ),
  102 + ),
  103 + ],
  104 + ),
  105 + ),
  106 + ),
  107 + ),
  108 + );
  109 + }
  110 +}
  111 +
  112 +class _FriendPlaceholderCard extends StatelessWidget {
  113 + const _FriendPlaceholderCard({required this.selected});
  114 +
  115 + final bool selected;
  116 +
  117 + @override
  118 + Widget build(BuildContext context) {
  119 + return Container(
  120 + height: 220.dp,
  121 + decoration: BoxDecoration(
  122 + color: selected ? Colors.white : const Color(0xFFFDFDFF),
  123 + borderRadius: BorderRadius.circular(16.dp),
  124 + border: selected
  125 + ? Border.all(color: WatchThemeColors.brand, width: 2.dp)
  126 + : null,
  127 + ),
  128 + padding: EdgeInsets.all(20.dp),
  129 + child: Column(
  130 + crossAxisAlignment: CrossAxisAlignment.start,
  131 + children: [
  132 + Row(
  133 + children: [
  134 + _Block(width: 36.dp, height: 36.dp, radius: 18.dp),
  135 + SizedBox(width: 12.dp),
  136 + Column(
  137 + crossAxisAlignment: CrossAxisAlignment.start,
  138 + children: [
  139 + _Block(width: 132.dp, height: 16.dp, radius: 8.dp),
  140 + SizedBox(height: 6.dp),
  141 + _Block(width: 72.dp, height: 10.dp, radius: 5.dp),
  142 + ],
  143 + ),
  144 + ],
  145 + ),
  146 + SizedBox(height: 29.dp),
  147 + Row(
  148 + children: [
  149 + Expanded(
  150 + child: _Block(height: 106.dp, radius: 18.dp),
  151 + ),
  152 + SizedBox(width: 32.dp),
  153 + Expanded(
  154 + child: Column(
  155 + children: [
  156 + _Block(height: 60.dp, radius: 16.dp),
  157 + SizedBox(height: 4.dp),
  158 + _Block(height: 60.dp, radius: 16.dp),
  159 + ],
  160 + ),
  161 + ),
  162 + ],
  163 + ),
  164 + ],
  165 + ),
  166 + );
  167 + }
  168 +}
  169 +
  170 +class _Block extends StatelessWidget {
  171 + const _Block({
  172 + this.width,
  173 + required this.height,
  174 + required this.radius,
  175 + });
  176 +
  177 + final double? width;
  178 + final double height;
  179 + final double radius;
  180 +
  181 + @override
  182 + Widget build(BuildContext context) {
  183 + return Container(
  184 + width: width,
  185 + height: height,
  186 + decoration: BoxDecoration(
  187 + color: const Color(0xFFE8E0FF),
  188 + borderRadius: BorderRadius.circular(radius),
  189 + ),
  190 + );
  191 + }
  192 +}
  1 +import 'package:doublefeel_flutter/core/util/size_extensions.dart';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +import 'watch_theme_colors.dart';
  6 +
  7 +class WatchThemeNoWatchDialog extends StatelessWidget {
  8 + const WatchThemeNoWatchDialog({super.key});
  9 +
  10 + @override
  11 + Widget build(BuildContext context) {
  12 + return Dialog(
  13 + backgroundColor: Colors.transparent,
  14 + insetPadding: EdgeInsets.zero,
  15 + child: Container(
  16 + width: 300.dp,
  17 + padding: EdgeInsets.fromLTRB(24.dp, 24.dp, 24.dp, 20.dp),
  18 + decoration: BoxDecoration(
  19 + color: Colors.white,
  20 + borderRadius: BorderRadius.circular(24.dp),
  21 + ),
  22 + child: Column(
  23 + mainAxisSize: MainAxisSize.min,
  24 + children: [
  25 + Text(
  26 + '未找到苹果手表',
  27 + textAlign: TextAlign.center,
  28 + style: TextStyle(
  29 + color: const Color(0xFF141414),
  30 + fontSize: 18.dp,
  31 + fontWeight: FontWeight.w600,
  32 + ),
  33 + ),
  34 + SizedBox(height: 16.dp),
  35 + Text(
  36 + '请配对苹果手表后再尝试',
  37 + textAlign: TextAlign.center,
  38 + style: TextStyle(
  39 + color: const Color(0xFF6E6D80),
  40 + fontSize: 15.dp,
  41 + ),
  42 + ),
  43 + SizedBox(height: 20.dp),
  44 + GestureDetector(
  45 + behavior: HitTestBehavior.opaque,
  46 + onTap: Get.back,
  47 + child: Container(
  48 + width: 220.dp,
  49 + height: 48.dp,
  50 + alignment: Alignment.center,
  51 + decoration: BoxDecoration(
  52 + color: WatchThemeColors.brand,
  53 + borderRadius: BorderRadius.circular(24.dp),
  54 + ),
  55 + child: Text(
  56 + '好的',
  57 + style: TextStyle(
  58 + color: Colors.white,
  59 + fontSize: 14.dp,
  60 + fontWeight: FontWeight.w500,
  61 + ),
  62 + ),
  63 + ),
  64 + ),
  65 + ],
  66 + ),
  67 + ),
  68 + );
  69 + }
  70 +}
  1 +import 'package:doublefeel_flutter/core/util/size_extensions.dart';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +import 'watch_face_preview.dart';
  6 +import 'watch_theme_colors.dart';
  7 +
  8 +class WatchThemeSyncDialog extends StatefulWidget {
  9 + const WatchThemeSyncDialog({
  10 + super.key,
  11 + required this.onSync,
  12 + this.faceAsset,
  13 + this.faceFilePath,
  14 + });
  15 +
  16 + final Future<bool> Function() onSync;
  17 + final String? faceAsset;
  18 + final String? faceFilePath;
  19 +
  20 + @override
  21 + State<WatchThemeSyncDialog> createState() => _WatchThemeSyncDialogState();
  22 +}
  23 +
  24 +class _WatchThemeSyncDialogState extends State<WatchThemeSyncDialog> {
  25 + _WatchThemeSyncState _state = _WatchThemeSyncState.intro;
  26 + bool _success = true;
  27 +
  28 + Future<void> _startSync() async {
  29 + if (_state == _WatchThemeSyncState.syncing) return;
  30 + setState(() => _state = _WatchThemeSyncState.syncing);
  31 + final success = await widget.onSync();
  32 + if (!mounted) return;
  33 + setState(() {
  34 + _success = success;
  35 + _state = _WatchThemeSyncState.done;
  36 + });
  37 + }
  38 +
  39 + @override
  40 + Widget build(BuildContext context) {
  41 + return Material(
  42 + color: Colors.transparent,
  43 + child: Align(
  44 + alignment: Alignment.bottomCenter,
  45 + child: Container(
  46 + height: 452.dp,
  47 + width: double.infinity,
  48 + decoration: BoxDecoration(
  49 + color: WatchThemeColors.background,
  50 + borderRadius: BorderRadius.vertical(top: Radius.circular(14.dp)),
  51 + ),
  52 + child: SafeArea(
  53 + top: false,
  54 + child: Stack(
  55 + children: [
  56 + Positioned(
  57 + left: 26.dp,
  58 + top: 12.dp,
  59 + child: GestureDetector(
  60 + behavior: HitTestBehavior.opaque,
  61 + onTap: Get.back,
  62 + child: SizedBox(
  63 + width: 32.dp,
  64 + height: 32.dp,
  65 + child: Icon(
  66 + Icons.close,
  67 + size: 22.dp,
  68 + color: const Color(0xFFA084EF),
  69 + ),
  70 + ),
  71 + ),
  72 + ),
  73 + Positioned(
  74 + top: 66.dp,
  75 + left: 0,
  76 + right: 0,
  77 + child: Center(
  78 + child: WatchFacePreview(
  79 + width: 134,
  80 + height: 160,
  81 + faceAsset: widget.faceAsset,
  82 + faceFilePath: widget.faceFilePath,
  83 + ),
  84 + ),
  85 + ),
  86 + Positioned(
  87 + top: 246.dp,
  88 + left: 0,
  89 + right: 0,
  90 + child: _buildMessage(),
  91 + ),
  92 + Positioned(
  93 + left: 49.dp,
  94 + right: 47.dp,
  95 + bottom: 35.dp,
  96 + child: _buildAction(),
  97 + ),
  98 + ],
  99 + ),
  100 + ),
  101 + ),
  102 + ),
  103 + );
  104 + }
  105 +
  106 + Widget _buildMessage() {
  107 + return switch (_state) {
  108 + _WatchThemeSyncState.intro => Padding(
  109 + padding: EdgeInsets.symmetric(horizontal: 46.dp),
  110 + child: Text(
  111 + '请先打开DoubleFeel手表端App,然后点击下方的“下一步”按钮',
  112 + textAlign: TextAlign.center,
  113 + style: TextStyle(
  114 + color: WatchThemeColors.textPrimary,
  115 + fontSize: 14.dp,
  116 + height: 1.35,
  117 + ),
  118 + ),
  119 + ),
  120 + _WatchThemeSyncState.syncing => Text(
  121 + '请保持打开手表App,等待同步',
  122 + textAlign: TextAlign.center,
  123 + style: TextStyle(
  124 + color: WatchThemeColors.textPrimary,
  125 + fontSize: 14.dp,
  126 + ),
  127 + ),
  128 + _WatchThemeSyncState.done => Row(
  129 + mainAxisAlignment: MainAxisAlignment.center,
  130 + children: [
  131 + Container(
  132 + width: 14.dp,
  133 + height: 14.dp,
  134 + decoration: BoxDecoration(
  135 + color: _success
  136 + ? WatchThemeColors.excellent
  137 + : const Color(0xFFFC4447),
  138 + shape: BoxShape.circle,
  139 + ),
  140 + child: Icon(
  141 + _success ? Icons.check : Icons.close,
  142 + size: 10.dp,
  143 + color: Colors.white,
  144 + ),
  145 + ),
  146 + SizedBox(width: 4.dp),
  147 + Text(
  148 + _success ? '同步完成' : '同步失败',
  149 + style: TextStyle(
  150 + color: WatchThemeColors.textPrimary,
  151 + fontSize: 14.dp,
  152 + ),
  153 + ),
  154 + ],
  155 + ),
  156 + };
  157 + }
  158 +
  159 + Widget _buildAction() {
  160 + return switch (_state) {
  161 + _WatchThemeSyncState.intro => _PrimaryButton(
  162 + label: '下一步',
  163 + onTap: _startSync,
  164 + ),
  165 + _WatchThemeSyncState.syncing => const _ProgressButton(progress: 0.85),
  166 + _WatchThemeSyncState.done => _PrimaryButton(
  167 + label: '好的',
  168 + onTap: Get.back,
  169 + ),
  170 + };
  171 + }
  172 +}
  173 +
  174 +class _PrimaryButton extends StatelessWidget {
  175 + const _PrimaryButton({
  176 + required this.label,
  177 + required this.onTap,
  178 + });
  179 +
  180 + final String label;
  181 + final VoidCallback onTap;
  182 +
  183 + @override
  184 + Widget build(BuildContext context) {
  185 + return GestureDetector(
  186 + behavior: HitTestBehavior.opaque,
  187 + onTap: onTap,
  188 + child: Container(
  189 + height: 48.dp,
  190 + alignment: Alignment.center,
  191 + decoration: BoxDecoration(
  192 + color: WatchThemeColors.brand,
  193 + borderRadius: BorderRadius.circular(24.dp),
  194 + ),
  195 + child: Text(
  196 + label,
  197 + style: TextStyle(
  198 + color: Colors.white,
  199 + fontSize: 16.dp,
  200 + fontWeight: FontWeight.w600,
  201 + ),
  202 + ),
  203 + ),
  204 + );
  205 + }
  206 +}
  207 +
  208 +class _ProgressButton extends StatelessWidget {
  209 + const _ProgressButton({required this.progress});
  210 +
  211 + final double progress;
  212 +
  213 + @override
  214 + Widget build(BuildContext context) {
  215 + return ClipRRect(
  216 + borderRadius: BorderRadius.circular(24.dp),
  217 + child: SizedBox(
  218 + height: 48.dp,
  219 + child: Stack(
  220 + fit: StackFit.expand,
  221 + children: [
  222 + const ColoredBox(color: Color(0xFFCBB9F6)),
  223 + FractionallySizedBox(
  224 + alignment: Alignment.centerLeft,
  225 + widthFactor: progress.clamp(0, 1),
  226 + child: const ColoredBox(color: WatchThemeColors.brand),
  227 + ),
  228 + Center(
  229 + child: Text(
  230 + '同步中 ${(progress * 100).round()}%',
  231 + style: TextStyle(
  232 + color: Colors.white,
  233 + fontSize: 16.dp,
  234 + fontWeight: FontWeight.w600,
  235 + ),
  236 + ),
  237 + ),
  238 + ],
  239 + ),
  240 + ),
  241 + );
  242 + }
  243 +}
  244 +
  245 +enum _WatchThemeSyncState { intro, syncing, done }
@@ -98,22 +98,36 @@ class AppleProductInfo { @@ -98,22 +98,36 @@ class AppleProductInfo {
98 AppleProductInfo({ 98 AppleProductInfo({
99 required this.productId, 99 required this.productId,
100 required this.originPriceDescription, 100 required this.originPriceDescription,
101 - this.priceDescription, 101 + required this.priceDescription,
102 required this.originPrice, 102 required this.originPrice,
103 - this.price, 103 + required this.price,
  104 + required this.unitPrice,
  105 + required this.currencyCode,
104 required this.isTrialPeriod, 106 required this.isTrialPeriod,
105 }); 107 });
106 108
  109 + /// 苹果商品id
107 String productId; 110 String productId;
108 111
  112 + /// 原价描述
109 String originPriceDescription; 113 String originPriceDescription;
110 114
111 - String? priceDescription; 115 + /// 真实价格描述
  116 + String priceDescription;
112 117
  118 + /// 原价格
113 double originPrice; 119 double originPrice;
114 120
115 - double? price; 121 + /// 真实价格, = 0 代表试用商品
  122 + double price;
116 123
  124 + /// price/unit = 单位价格
  125 + String unitPrice;
  126 +
  127 + /// 当前货币符号
  128 + String currencyCode;
  129 +
  130 + /// 当前用户是否可以试用
117 bool isTrialPeriod; 131 bool isTrialPeriod;
118 132
119 List<Object?> _toList() { 133 List<Object?> _toList() {
@@ -123,6 +137,8 @@ class AppleProductInfo { @@ -123,6 +137,8 @@ class AppleProductInfo {
123 priceDescription, 137 priceDescription,
124 originPrice, 138 originPrice,
125 price, 139 price,
  140 + unitPrice,
  141 + currencyCode,
126 isTrialPeriod, 142 isTrialPeriod,
127 ]; 143 ];
128 } 144 }
@@ -135,10 +151,12 @@ class AppleProductInfo { @@ -135,10 +151,12 @@ class AppleProductInfo {
135 return AppleProductInfo( 151 return AppleProductInfo(
136 productId: result[0]! as String, 152 productId: result[0]! as String,
137 originPriceDescription: result[1]! as String, 153 originPriceDescription: result[1]! as String,
138 - priceDescription: result[2] as String?, 154 + priceDescription: result[2]! as String,
139 originPrice: result[3]! as double, 155 originPrice: result[3]! as double,
140 - price: result[4] as double?,  
141 - isTrialPeriod: result[5]! as bool, 156 + price: result[4]! as double,
  157 + unitPrice: result[5]! as String,
  158 + currencyCode: result[6]! as String,
  159 + isTrialPeriod: result[7]! as bool,
142 ); 160 );
143 } 161 }
144 162
@@ -315,6 +333,32 @@ class PlatformHostApi { @@ -315,6 +333,32 @@ class PlatformHostApi {
315 } 333 }
316 } 334 }
317 335
  336 + /// 更新用户信息, 有登录态后调用
  337 + /// jsonString: UserPreferences的序列化string
  338 + /// baseUrl: 请求地址, https://api.doublefeel.cn
  339 + Future<void> updateLoginInfo(String jsonString, String baseUrl) async {
  340 + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.updateLoginInfo$pigeonVar_messageChannelSuffix';
  341 + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
  342 + pigeonVar_channelName,
  343 + pigeonChannelCodec,
  344 + binaryMessenger: pigeonVar_binaryMessenger,
  345 + );
  346 + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[jsonString, baseUrl]);
  347 + final List<Object?>? pigeonVar_replyList =
  348 + await pigeonVar_sendFuture as List<Object?>?;
  349 + if (pigeonVar_replyList == null) {
  350 + throw _createConnectionError(pigeonVar_channelName);
  351 + } else if (pigeonVar_replyList.length > 1) {
  352 + throw PlatformException(
  353 + code: pigeonVar_replyList[0]! as String,
  354 + message: pigeonVar_replyList[1] as String?,
  355 + details: pigeonVar_replyList[2],
  356 + );
  357 + } else {
  358 + return;
  359 + }
  360 + }
  361 +
318 /// 请求苹果登录 362 /// 请求苹果登录
319 Future<AppleSignInModel?> requestAppleSignIn() async { 363 Future<AppleSignInModel?> requestAppleSignIn() async {
320 final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn$pigeonVar_messageChannelSuffix'; 364 final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn$pigeonVar_messageChannelSuffix';
@@ -340,14 +384,16 @@ class PlatformHostApi { @@ -340,14 +384,16 @@ class PlatformHostApi {
340 } 384 }
341 385
342 /// 查询指定id的苹果商品 386 /// 查询指定id的苹果商品
343 - Future<AppleProductInfo?> requestAppleProductInfo(String productId) async { 387 + /// productId: 苹果商品id
  388 + /// baseUnit: 除数基础单位(多少个天、周、月) ->
  389 + Future<AppleProductInfo?> requestAppleProductInfo(String productId, int baseUnit) async {
344 final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo$pigeonVar_messageChannelSuffix'; 390 final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo$pigeonVar_messageChannelSuffix';
345 final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( 391 final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
346 pigeonVar_channelName, 392 pigeonVar_channelName,
347 pigeonChannelCodec, 393 pigeonChannelCodec,
348 binaryMessenger: pigeonVar_binaryMessenger, 394 binaryMessenger: pigeonVar_binaryMessenger,
349 ); 395 );
350 - final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[productId]); 396 + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[productId, baseUnit]);
351 final List<Object?>? pigeonVar_replyList = 397 final List<Object?>? pigeonVar_replyList =
352 await pigeonVar_sendFuture as List<Object?>?; 398 await pigeonVar_sendFuture as List<Object?>?;
353 if (pigeonVar_replyList == null) { 399 if (pigeonVar_replyList == null) {
@@ -15,18 +15,37 @@ class AppleSignInModel { @@ -15,18 +15,37 @@ class AppleSignInModel {
15 } 15 }
16 16
17 class AppleProductInfo { 17 class AppleProductInfo {
  18 + /// 苹果商品id
18 final String productId; 19 final String productId;
  20 +
  21 + /// 原价描述
19 final String originPriceDescription; 22 final String originPriceDescription;
20 - final String? priceDescription; 23 +
  24 + /// 真实价格描述
  25 + final String priceDescription;
  26 +
  27 + /// 原价格
21 final double originPrice; 28 final double originPrice;
22 - final double? price; 29 +
  30 + /// 真实价格, = 0 代表试用商品
  31 + final double price;
  32 +
  33 + /// price/unit = 单位价格
  34 + final String unitPrice;
  35 +
  36 + /// 当前货币符号
  37 + final String currencyCode;
  38 +
  39 + /// 当前用户是否可以试用
23 final bool isTrialPeriod; 40 final bool isTrialPeriod;
24 AppleProductInfo({ 41 AppleProductInfo({
25 required this.productId, 42 required this.productId,
26 required this.originPriceDescription, 43 required this.originPriceDescription,
27 - this.priceDescription, 44 + required this.priceDescription,
28 required this.originPrice, 45 required this.originPrice,
29 - this.price, 46 + required this.price,
  47 + required this.unitPrice,
  48 + required this.currencyCode,
30 required this.isTrialPeriod, 49 required this.isTrialPeriod,
31 }); 50 });
32 } 51 }
@@ -83,13 +102,20 @@ abstract class PlatformHostApi { @@ -83,13 +102,20 @@ abstract class PlatformHostApi {
83 /// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)` 102 /// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
84 String getFullUserAgent(); 103 String getFullUserAgent();
85 104
  105 + /// 更新用户信息, 有登录态后调用
  106 + /// jsonString: UserPreferences的序列化string
  107 + /// baseUrl: 请求地址, https://api.doublefeel.cn
  108 + void updateLoginInfo(String jsonString, String baseUrl);
  109 +
86 /// 请求苹果登录 110 /// 请求苹果登录
87 @async 111 @async
88 AppleSignInModel? requestAppleSignIn(); 112 AppleSignInModel? requestAppleSignIn();
89 113
90 /// 查询指定id的苹果商品 114 /// 查询指定id的苹果商品
  115 + /// productId: 苹果商品id
  116 + /// baseUnit: 除数基础单位(多少个天、周、月) ->
91 @async 117 @async
92 - AppleProductInfo? requestAppleProductInfo(String productId); 118 + AppleProductInfo? requestAppleProductInfo(String productId, int baseUnit);
93 119
94 /// 从服务器下单后请求苹果支付 120 /// 从服务器下单后请求苹果支付
95 @async 121 @async
@@ -133,10 +133,10 @@ packages: @@ -133,10 +133,10 @@ packages:
133 dependency: transitive 133 dependency: transitive
134 description: 134 description:
135 name: characters 135 name: characters
136 - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 136 + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
137 url: "https://pub.dev" 137 url: "https://pub.dev"
138 source: hosted 138 source: hosted
139 - version: "1.3.0" 139 + version: "1.4.0"
140 checked_yaml: 140 checked_yaml:
141 dependency: transitive 141 dependency: transitive
142 description: 142 description:
@@ -149,10 +149,10 @@ packages: @@ -149,10 +149,10 @@ packages:
149 dependency: transitive 149 dependency: transitive
150 description: 150 description:
151 name: clock 151 name: clock
152 - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 152 + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
153 url: "https://pub.dev" 153 url: "https://pub.dev"
154 source: hosted 154 source: hosted
155 - version: "1.1.1" 155 + version: "1.1.2"
156 code_builder: 156 code_builder:
157 dependency: transitive 157 dependency: transitive
158 description: 158 description:
@@ -165,10 +165,10 @@ packages: @@ -165,10 +165,10 @@ packages:
165 dependency: transitive 165 dependency: transitive
166 description: 166 description:
167 name: collection 167 name: collection
168 - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf 168 + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
169 url: "https://pub.dev" 169 url: "https://pub.dev"
170 source: hosted 170 source: hosted
171 - version: "1.19.0" 171 + version: "1.19.1"
172 convert: 172 convert:
173 dependency: transitive 173 dependency: transitive
174 description: 174 description:
@@ -237,10 +237,10 @@ packages: @@ -237,10 +237,10 @@ packages:
237 dependency: transitive 237 dependency: transitive
238 description: 238 description:
239 name: fake_async 239 name: fake_async
240 - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 240 + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
241 url: "https://pub.dev" 241 url: "https://pub.dev"
242 source: hosted 242 source: hosted
243 - version: "1.3.1" 243 + version: "1.3.3"
244 ffi: 244 ffi:
245 dependency: transitive 245 dependency: transitive
246 description: 246 description:
@@ -426,7 +426,7 @@ packages: @@ -426,7 +426,7 @@ packages:
426 dependency: transitive 426 dependency: transitive
427 description: 427 description:
428 path: image_cropper_for_web 428 path: image_cropper_for_web
429 - ref: "br_v9.1.0_ohos" 429 + ref: "65c2c99891882ea59732959a672f3d5993a837bb"
430 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb" 430 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb"
431 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git" 431 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git"
432 source: git 432 source: git
@@ -435,7 +435,7 @@ packages: @@ -435,7 +435,7 @@ packages:
435 dependency: transitive 435 dependency: transitive
436 description: 436 description:
437 path: image_cropper_platform_interface 437 path: image_cropper_platform_interface
438 - ref: "br_v9.1.0_ohos" 438 + ref: "65c2c99891882ea59732959a672f3d5993a837bb"
439 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb" 439 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb"
440 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git" 440 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git"
441 source: git 441 source: git
@@ -517,10 +517,10 @@ packages: @@ -517,10 +517,10 @@ packages:
517 dependency: "direct main" 517 dependency: "direct main"
518 description: 518 description:
519 name: intl 519 name: intl
520 - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf 520 + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
521 url: "https://pub.dev" 521 url: "https://pub.dev"
522 source: hosted 522 source: hosted
523 - version: "0.19.0" 523 + version: "0.20.2"
524 io: 524 io:
525 dependency: transitive 525 dependency: transitive
526 description: 526 description:
@@ -549,26 +549,26 @@ packages: @@ -549,26 +549,26 @@ packages:
549 dependency: transitive 549 dependency: transitive
550 description: 550 description:
551 name: leak_tracker 551 name: leak_tracker
552 - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" 552 + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
553 url: "https://pub.dev" 553 url: "https://pub.dev"
554 source: hosted 554 source: hosted
555 - version: "10.0.7" 555 + version: "11.0.2"
556 leak_tracker_flutter_testing: 556 leak_tracker_flutter_testing:
557 dependency: transitive 557 dependency: transitive
558 description: 558 description:
559 name: leak_tracker_flutter_testing 559 name: leak_tracker_flutter_testing
560 - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" 560 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
561 url: "https://pub.dev" 561 url: "https://pub.dev"
562 source: hosted 562 source: hosted
563 - version: "3.0.8" 563 + version: "3.0.10"
564 leak_tracker_testing: 564 leak_tracker_testing:
565 dependency: transitive 565 dependency: transitive
566 description: 566 description:
567 name: leak_tracker_testing 567 name: leak_tracker_testing
568 - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 568 + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
569 url: "https://pub.dev" 569 url: "https://pub.dev"
570 source: hosted 570 source: hosted
571 - version: "3.0.1" 571 + version: "3.0.2"
572 lints: 572 lints:
573 dependency: transitive 573 dependency: transitive
574 description: 574 description:
@@ -597,10 +597,10 @@ packages: @@ -597,10 +597,10 @@ packages:
597 dependency: transitive 597 dependency: transitive
598 description: 598 description:
599 name: matcher 599 name: matcher
600 - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 600 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
601 url: "https://pub.dev" 601 url: "https://pub.dev"
602 source: hosted 602 source: hosted
603 - version: "0.12.16+1" 603 + version: "0.12.17"
604 material_color_utilities: 604 material_color_utilities:
605 dependency: transitive 605 dependency: transitive
606 description: 606 description:
@@ -613,10 +613,10 @@ packages: @@ -613,10 +613,10 @@ packages:
613 dependency: transitive 613 dependency: transitive
614 description: 614 description:
615 name: meta 615 name: meta
616 - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 616 + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
617 url: "https://pub.dev" 617 url: "https://pub.dev"
618 source: hosted 618 source: hosted
619 - version: "1.15.0" 619 + version: "1.17.0"
620 mime: 620 mime:
621 dependency: transitive 621 dependency: transitive
622 description: 622 description:
@@ -645,10 +645,10 @@ packages: @@ -645,10 +645,10 @@ packages:
645 dependency: transitive 645 dependency: transitive
646 description: 646 description:
647 name: path 647 name: path
648 - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 648 + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
649 url: "https://pub.dev" 649 url: "https://pub.dev"
650 source: hosted 650 source: hosted
651 - version: "1.9.0" 651 + version: "1.9.1"
652 path_provider: 652 path_provider:
653 dependency: transitive 653 dependency: transitive
654 description: 654 description:
@@ -966,18 +966,18 @@ packages: @@ -966,18 +966,18 @@ packages:
966 dependency: transitive 966 dependency: transitive
967 description: 967 description:
968 name: stack_trace 968 name: stack_trace
969 - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" 969 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
970 url: "https://pub.dev" 970 url: "https://pub.dev"
971 source: hosted 971 source: hosted
972 - version: "1.12.0" 972 + version: "1.12.1"
973 stream_channel: 973 stream_channel:
974 dependency: transitive 974 dependency: transitive
975 description: 975 description:
976 name: stream_channel 976 name: stream_channel
977 - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 977 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
978 url: "https://pub.dev" 978 url: "https://pub.dev"
979 source: hosted 979 source: hosted
980 - version: "2.1.2" 980 + version: "2.1.4"
981 stream_transform: 981 stream_transform:
982 dependency: transitive 982 dependency: transitive
983 description: 983 description:
@@ -1006,10 +1006,10 @@ packages: @@ -1006,10 +1006,10 @@ packages:
1006 dependency: "direct main" 1006 dependency: "direct main"
1007 description: 1007 description:
1008 name: table_calendar 1008 name: table_calendar
1009 - sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63 1009 + sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
1010 url: "https://pub.dev" 1010 url: "https://pub.dev"
1011 source: hosted 1011 source: hosted
1012 - version: "3.1.3" 1012 + version: "3.2.0"
1013 term_glyph: 1013 term_glyph:
1014 dependency: transitive 1014 dependency: transitive
1015 description: 1015 description:
@@ -1022,10 +1022,10 @@ packages: @@ -1022,10 +1022,10 @@ packages:
1022 dependency: transitive 1022 dependency: transitive
1023 description: 1023 description:
1024 name: test_api 1024 name: test_api
1025 - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" 1025 + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
1026 url: "https://pub.dev" 1026 url: "https://pub.dev"
1027 source: hosted 1027 source: hosted
1028 - version: "0.7.3" 1028 + version: "0.7.7"
1029 timing: 1029 timing:
1030 dependency: transitive 1030 dependency: transitive
1031 description: 1031 description:
@@ -1054,10 +1054,10 @@ packages: @@ -1054,10 +1054,10 @@ packages:
1054 dependency: transitive 1054 dependency: transitive
1055 description: 1055 description:
1056 name: vector_math 1056 name: vector_math
1057 - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 1057 + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
1058 url: "https://pub.dev" 1058 url: "https://pub.dev"
1059 source: hosted 1059 source: hosted
1060 - version: "2.1.4" 1060 + version: "2.2.0"
1061 vm_service: 1061 vm_service:
1062 dependency: transitive 1062 dependency: transitive
1063 description: 1063 description:
@@ -1111,8 +1111,8 @@ packages: @@ -1111,8 +1111,8 @@ packages:
1111 dependency: transitive 1111 dependency: transitive
1112 description: 1112 description:
1113 path: "packages/webview_flutter/webview_flutter_android" 1113 path: "packages/webview_flutter/webview_flutter_android"
1114 - ref: "br_webview_flutter-v4.13.0_ohos"  
1115 - resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3 1114 + ref: de942e79c9057b32ad31106508bd87c0d60aef83
  1115 + resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
1116 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1116 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1117 source: git 1117 source: git
1118 version: "4.7.0" 1118 version: "4.7.0"
@@ -1120,8 +1120,8 @@ packages: @@ -1120,8 +1120,8 @@ packages:
1120 dependency: transitive 1120 dependency: transitive
1121 description: 1121 description:
1122 path: "packages/webview_flutter/webview_flutter_ohos" 1122 path: "packages/webview_flutter/webview_flutter_ohos"
1123 - ref: "br_webview_flutter-v4.13.0_ohos"  
1124 - resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3 1123 + ref: de942e79c9057b32ad31106508bd87c0d60aef83
  1124 + resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
1125 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1125 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1126 source: git 1126 source: git
1127 version: "4.7.0" 1127 version: "4.7.0"
@@ -1129,8 +1129,8 @@ packages: @@ -1129,8 +1129,8 @@ packages:
1129 dependency: transitive 1129 dependency: transitive
1130 description: 1130 description:
1131 path: "packages/webview_flutter/webview_flutter_platform_interface" 1131 path: "packages/webview_flutter/webview_flutter_platform_interface"
1132 - ref: "br_webview_flutter-v4.13.0_ohos"  
1133 - resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3 1132 + ref: de942e79c9057b32ad31106508bd87c0d60aef83
  1133 + resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
1134 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1134 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1135 source: git 1135 source: git
1136 version: "2.13.1" 1136 version: "2.13.1"
@@ -1138,8 +1138,8 @@ packages: @@ -1138,8 +1138,8 @@ packages:
1138 dependency: transitive 1138 dependency: transitive
1139 description: 1139 description:
1140 path: "packages/webview_flutter/webview_flutter_wkwebview" 1140 path: "packages/webview_flutter/webview_flutter_wkwebview"
1141 - ref: "br_webview_flutter-v4.13.0_ohos"  
1142 - resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3 1141 + ref: de942e79c9057b32ad31106508bd87c0d60aef83
  1142 + resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
1143 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1143 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1144 source: git 1144 source: git
1145 version: "3.22.0" 1145 version: "3.22.0"
@@ -1160,5 +1160,5 @@ packages: @@ -1160,5 +1160,5 @@ packages:
1160 source: hosted 1160 source: hosted
1161 version: "3.1.3" 1161 version: "3.1.3"
1162 sdks: 1162 sdks:
1163 - dart: ">=3.6.2 <4.0.0" 1163 + dart: ">=3.8.0-0 <4.0.0"
1164 flutter: ">=3.27.0" 1164 flutter: ">=3.27.0"