Commit 3921ac0cec8d49d020b818b152150239cf40044d
1 parent
51039469
feat(ui):增加flutter向原生注入的用户信息和url,增加iOS支付服务器校验
feat(ui):增加ios原生校验订单
Showing
18 changed files
with
1026 additions
and
106 deletions
| @@ -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)) |
| @@ -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) |
ios/Runner/AppShared.swift
0 → 100644
| 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 | } |
ios/Runner/FlutterStorage.swift
0 → 100644
| 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" |
-
Please register or login to post a comment