Commit 3921ac0cec8d49d020b818b152150239cf40044d

Authored by 权海
1 parent 51039469

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

feat(ui):增加ios原生校验订单
... ... @@ -132,11 +132,21 @@ data class AppleSignInModel (
/** Generated class from Pigeon that represents data sent in messages. */
data class AppleProductInfo (
/** 苹果商品id */
val productId: String,
/** 原价描述 */
val originPriceDescription: String,
val priceDescription: String? = null,
/** 真实价格描述 */
val priceDescription: String,
/** 原价格 */
val originPrice: Double,
val price: Double? = null,
/** 真实价格, = 0 代表试用商品 */
val price: Double,
/** price/unit = 单位价格 */
val unitPrice: String,
/** 当前货币符号 */
val currencyCode: String,
/** 当前用户是否可以试用 */
val isTrialPeriod: Boolean
)
{
... ... @@ -144,11 +154,13 @@ data class AppleProductInfo (
fun fromList(pigeonVar_list: List<Any?>): AppleProductInfo {
val productId = pigeonVar_list[0] as String
val originPriceDescription = pigeonVar_list[1] as String
val priceDescription = pigeonVar_list[2] as String?
val priceDescription = pigeonVar_list[2] as String
val originPrice = pigeonVar_list[3] as Double
val price = pigeonVar_list[4] as Double?
val isTrialPeriod = pigeonVar_list[5] as Boolean
return AppleProductInfo(productId, originPriceDescription, priceDescription, originPrice, price, isTrialPeriod)
val price = pigeonVar_list[4] as Double
val unitPrice = pigeonVar_list[5] as String
val currencyCode = pigeonVar_list[6] as String
val isTrialPeriod = pigeonVar_list[7] as Boolean
return AppleProductInfo(productId, originPriceDescription, priceDescription, originPrice, price, unitPrice, currencyCode, isTrialPeriod)
}
}
fun toList(): List<Any?> {
... ... @@ -158,6 +170,8 @@ data class AppleProductInfo (
priceDescription,
originPrice,
price,
unitPrice,
currencyCode,
isTrialPeriod,
)
}
... ... @@ -277,10 +291,20 @@ interface PlatformHostApi {
* `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
*/
fun getFullUserAgent(): String
/**
* 更新用户信息, 有登录态后调用
* jsonString: UserPreferences的序列化string
* baseUrl: 请求地址, https://api.doublefeel.cn
*/
fun updateLoginInfo(jsonString: String, baseUrl: String)
/** 请求苹果登录 */
fun requestAppleSignIn(callback: (Result<AppleSignInModel?>) -> Unit)
/** 查询指定id的苹果商品 */
fun requestAppleProductInfo(productId: String, callback: (Result<AppleProductInfo?>) -> Unit)
/**
* 查询指定id的苹果商品
* productId: 苹果商品id
* baseUnit: 除数基础单位(多少个天、周、月) ->
*/
fun requestAppleProductInfo(productId: String, baseUnit: Long, callback: (Result<AppleProductInfo?>) -> Unit)
/** 从服务器下单后请求苹果支付 */
fun performApplePayment(productId: String, uuid: String, callback: (Result<AppleProductPaymentResult?>) -> Unit)
... ... @@ -309,6 +333,25 @@ interface PlatformHostApi {
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.updateLoginInfo$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val jsonStringArg = args[0] as String
val baseUrlArg = args[1] as String
val wrapped: List<Any?> = try {
api.updateLoginInfo(jsonStringArg, baseUrlArg)
listOf(null)
} catch (exception: Throwable) {
PlatformApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
... ... @@ -332,7 +375,8 @@ interface PlatformHostApi {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val productIdArg = args[0] as String
api.requestAppleProductInfo(productIdArg) { result: Result<AppleProductInfo?> ->
val baseUnitArg = args[1] as Long
api.requestAppleProductInfo(productIdArg, baseUnitArg) { result: Result<AppleProductInfo?> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PlatformApiPigeonUtils.wrapError(error))
... ...
... ... @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
... ...
... ... @@ -30,8 +30,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UserAgent.share.genUA()
ApplePayment.shared.delegate = self
AppShared.shared.agent.genUA()
AppShared.shared.payment.delegate = self
UIApplication.shared.registerForRemoteNotifications()
UNUserNotificationCenter.current().setBadgeCount(0)
... ...
//
// AppShared.swift
// Runner
//
// Created by 权海 on 2026/6/17.
//
import Foundation
class AppShared{
static let shared = AppShared()
var isLogin: Bool{
baseUrl.hasPrefix("http") && (userSummary?.accessToken?.count ?? 0 > 0)
}
private(set) var agent = UserAgent()
private(set) var payment = ApplePayment()
var baseUrl: String = ""
var userSummary: FlutterUserSummary?
}
... ...
... ... @@ -8,6 +8,7 @@
import Foundation
import StoreKit
enum StoreKitError: Error, LocalizedError {
case productNotFound
case failedVerification
... ... @@ -26,9 +27,10 @@ enum StoreKitError: Error, LocalizedError {
}
class ApplePayment {
static let shared = ApplePayment()
var delegate: AppDelegate?
private var waitingVerifyTransitions: [Transaction] = []
func purchase(_ productId: String, uuidString: String) async -> AppleProductPaymentResult {
do {
guard let uuid = UUID(uuidString: uuidString) else {
... ... @@ -168,21 +170,50 @@ class ApplePayment {
}
private func verifyWithServer(appAccountToken: String, originalTransactionId: String?, transactionId: String?, productId: String) async -> Bool{
guard let delegate else{
guard AppShared.shared.isLogin else {
print("Apple payment verify skipped: missing login info")
return false
}
guard let baseURL = URL(string: AppShared.shared.baseUrl),
let url = URL(string: "/client/doublefeel/payment/order_verify/apple/", relativeTo: baseURL)?.absoluteURL else {
print("Apple payment verify failed: invalid baseUrl \(AppShared.shared.baseUrl)")
return false
}
var params: [String: String] = [
"productId": productId,
"appAccountToken": appAccountToken
]
params["originalTransactionId"] = originalTransactionId
params["transactionId"] = transactionId
return await withCheckedContinuation { continuous in
delegate.invoke(method: .verifyPayment, arguments: params) { result in
print("invoke verifyPayment result: \(String(describing: result))")
continuous.resume(returning: true)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.timeoutInterval = 30
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue(AppShared.shared.userSummary?.accessToken, forHTTPHeaderField: "access_token")
request.setValue(AppShared.shared.agent.finalUA, forHTTPHeaderField: "User-Agent")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: params)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
print("Apple payment verify failed: invalid response")
return false
}
let bodyText = String(data: data, encoding: .utf8) ?? ""
print("Apple payment verify response: status=\(httpResponse.statusCode), body=\(bodyText)")
guard (200..<300).contains(httpResponse.statusCode) else {
return false
}
return true
} catch {
print("Apple payment verify failed: \(error.localizedDescription)")
return false
}
}
... ... @@ -219,11 +250,15 @@ extension ApplePayment {
for await result in Transaction.updates {
do {
let transaction = try await self.checkVerified(result)
let _ = await self.verifyWithServer(appAccountToken: transaction.appAccountToken?.uuidString ?? "", originalTransactionId: String(transaction.originalID), transactionId: String(transaction.id), productId: transaction.productID)
// 更新客户产品状态
await self.updateCustomerProductStatus()
await transaction.finish()
print("交易更新处理完成: \(transaction.debugDescription)")
if await AppShared.shared.isLogin{
let success = await self.verifyWithServer(appAccountToken: transaction.appAccountToken?.uuidString ?? "", originalTransactionId: String(transaction.originalID), transactionId: String(transaction.id), productId: transaction.productID)
// 更新客户产品状态
if success{
await self.updateCustomerProductStatus()
await transaction.finish()
print("交易更新处理完成: \(transaction.debugDescription)")
}
}
} catch {
print("交易更新处理失败: \(error)")
}
... ...
//
// FlutterStorage.swift
// Runner
//
// Created by 权海 on 2026/6/17.
//
import Foundation
private let kFlutterStorageKey = "flutter.double_feel_user_preferences_json"
class FlutterStorage{
static func userSummary() -> FlutterUserSummary?{
guard let json = UserDefaults.standard.object(forKey: kFlutterStorageKey) else{
return nil
}
do{
let data = try JSONSerialization.data(withJSONObject: json)
let model = try JSONDecoder().decode(FlutterUserSummary.self, from: data)
return model
}catch{
return nil
}
}
}
struct FlutterUserSummary: Codable{
var meUserInfo: FlutterUserInfo?
var partnerUserInfo: FlutterUserInfo?
var accessToken: String?
var rongcloudToken: String?
var vipInfo: FlutterVipInfo?
enum CodingKeys: String, CodingKey {
case meUserInfo = "me_user_info"
case partnerUserInfo = "partner_user_info"
case accessToken = "access_token"
case rongcloudToken = "rongcloud_token"
case vipInfo = "vip_info"
}
}
struct FlutterVipInfo: Codable{
var isVip: Bool?
var isForeverVip: Bool?
var isShare: Bool?
var vipStartDate: Int?
var vipEndDate: Int?
enum CodingKeys: String, CodingKey {
case isVip = "is_vip"
case isForeverVip = "is_forever_vip"
case isShare = "is_share"
case vipStartDate = "vip_start_date"
case vipEndDate = "vip_end_date"
}
}
struct FlutterUserInfo: Codable{
var id: Int?
var pairId: Int?
var pairCode: String?
var nickname: String?
var avatar: String?
var telephone: String?
var persona: Int?
var isBot: Int?
enum CodingKeys: String, CodingKey {
case id
case pairId = "pair_id"
case pairCode = "pair_code"
case nickname
case avatar
case telephone
case persona
case isBot = "is_bot"
}
}
... ...
... ... @@ -159,11 +159,21 @@ struct AppleSignInModel: Hashable {
/// Generated class from Pigeon that represents data sent in messages.
struct AppleProductInfo: Hashable {
/// 苹果商品id
var productId: String
/// 原价描述
var originPriceDescription: String
var priceDescription: String? = nil
/// 真实价格描述
var priceDescription: String
/// 原价格
var originPrice: Double
var price: Double? = nil
/// 真实价格, = 0 代表试用商品
var price: Double
/// price/unit = 单位价格
var unitPrice: String
/// 当前货币符号
var currencyCode: String
/// 当前用户是否可以试用
var isTrialPeriod: Bool
... ... @@ -171,10 +181,12 @@ struct AppleProductInfo: Hashable {
static func fromList(_ pigeonVar_list: [Any?]) -> AppleProductInfo? {
let productId = pigeonVar_list[0] as! String
let originPriceDescription = pigeonVar_list[1] as! String
let priceDescription: String? = nilOrValue(pigeonVar_list[2])
let priceDescription = pigeonVar_list[2] as! String
let originPrice = pigeonVar_list[3] as! Double
let price: Double? = nilOrValue(pigeonVar_list[4])
let isTrialPeriod = pigeonVar_list[5] as! Bool
let price = pigeonVar_list[4] as! Double
let unitPrice = pigeonVar_list[5] as! String
let currencyCode = pigeonVar_list[6] as! String
let isTrialPeriod = pigeonVar_list[7] as! Bool
return AppleProductInfo(
productId: productId,
... ... @@ -182,6 +194,8 @@ struct AppleProductInfo: Hashable {
priceDescription: priceDescription,
originPrice: originPrice,
price: price,
unitPrice: unitPrice,
currencyCode: currencyCode,
isTrialPeriod: isTrialPeriod
)
}
... ... @@ -192,6 +206,8 @@ struct AppleProductInfo: Hashable {
priceDescription,
originPrice,
price,
unitPrice,
currencyCode,
isTrialPeriod,
]
}
... ... @@ -311,10 +327,16 @@ protocol PlatformHostApi {
/// 返回完整的 User-Agent 字符串,由 native 侧组装:
/// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
func getFullUserAgent() throws -> String
/// 更新用户信息, 有登录态后调用
/// jsonString: UserPreferences的序列化string
/// baseUrl: 请求地址, https://api.doublefeel.cn
func updateLoginInfo(jsonString: String, baseUrl: String) throws
/// 请求苹果登录
func requestAppleSignIn(completion: @escaping (Result<AppleSignInModel?, Error>) -> Void)
/// 查询指定id的苹果商品
func requestAppleProductInfo(productId: String, completion: @escaping (Result<AppleProductInfo?, Error>) -> Void)
/// productId: 苹果商品id
/// baseUnit: 除数基础单位(多少个天、周、月) ->
func requestAppleProductInfo(productId: String, baseUnit: Int64, completion: @escaping (Result<AppleProductInfo?, Error>) -> Void)
/// 从服务器下单后请求苹果支付
func performApplePayment(productId: String, uuid: String, completion: @escaping (Result<AppleProductPaymentResult?, Error>) -> Void)
}
... ... @@ -340,6 +362,25 @@ class PlatformHostApiSetup {
} else {
getFullUserAgentChannel.setMessageHandler(nil)
}
/// 更新用户信息, 有登录态后调用
/// jsonString: UserPreferences的序列化string
/// baseUrl: 请求地址, https://api.doublefeel.cn
let updateLoginInfoChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.updateLoginInfo\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
updateLoginInfoChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let jsonStringArg = args[0] as! String
let baseUrlArg = args[1] as! String
do {
try api.updateLoginInfo(jsonString: jsonStringArg, baseUrl: baseUrlArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
updateLoginInfoChannel.setMessageHandler(nil)
}
/// 请求苹果登录
let requestAppleSignInChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
... ... @@ -357,12 +398,15 @@ class PlatformHostApiSetup {
requestAppleSignInChannel.setMessageHandler(nil)
}
/// 查询指定id的苹果商品
/// productId: 苹果商品id
/// baseUnit: 除数基础单位(多少个天、周、月) ->
let requestAppleProductInfoChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestAppleProductInfoChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let productIdArg = args[0] as! String
api.requestAppleProductInfo(productId: productIdArg) { result in
let baseUnitArg = args[1] as! Int64
api.requestAppleProductInfo(productId: productIdArg, baseUnit: baseUnitArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
... ...
... ... @@ -4,6 +4,31 @@ import StoreKit
import UIKit
import WebKit
class PriceFormatter{
static func formatPrice(_ price: Decimal, currencyCode: String) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = currencyCode
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter.string(from: price as NSDecimalNumber) ?? "\(price)"
}
static func currencySymbol(for currencyCode: String) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = currencyCode
// formatter.locale = Locale(identifier: "zh_CN")
return formatter.currencySymbol
}
}
/**
* PlatformApi iOS implementation.
... ... @@ -12,15 +37,27 @@ import WebKit
* `{systemWebViewUA} doublefeel/{versionCode}({versionName})(Apple##Apple##{model}; iOS{osVersion}; {height}x{width})(huawei)`
*/
final class PlatformHostApiImpl: PlatformHostApi {
func requestAppleProductInfo(productId: String, completion: @escaping (Result<AppleProductInfo?, any Error>) -> Void) {
func updateLoginInfo(jsonString: String, baseUrl: String) throws {
AppShared.shared.baseUrl = baseUrl
if let data = jsonString.data(using: .utf8){
do{
let model = try JSONDecoder().decode(FlutterUserSummary.self, from: data)
AppShared.shared.userSummary = model
}catch{
DebugLogger.log(desc: error.localizedDescription)
}
}
}
func requestAppleProductInfo(productId: String, baseUnit: Int64, completion: @escaping (Result<AppleProductInfo?, any Error>) -> Void) {
Task {
do {
let product = try await ApplePayment.shared.requestProducts(productId)
let isTrialPeriod = await ApplePayment.shared.isFreeTrail(product: product)
let product = try await AppShared.shared.payment.requestProducts(productId)
let isTrialPeriod = await AppShared.shared.payment.isFreeTrail(product: product)
let originPrice = product.price
var displayPrice = product.displayPrice
var price = originPrice
if let introductoryOffer = product.subscription?.introductoryOffer, introductoryOffer.price > 0{
if let introductoryOffer = product.subscription?.introductoryOffer{
price = introductoryOffer.price
displayPrice = introductoryOffer.displayPrice
}else if let offer = product.subscription?.promotionalOffers.first(where: { $0.id == product.id }){
... ... @@ -28,12 +65,17 @@ final class PlatformHostApiImpl: PlatformHostApi {
displayPrice = offer.displayPrice
}
let unitPrice = price/Decimal(baseUnit)
let currencyCode = product.priceFormatStyle.currencyCode
let productInfo = AppleProductInfo(
productId: product.id,
originPriceDescription: product.displayPrice,
priceDescription: displayPrice,
originPrice: NSDecimalNumber(decimal: originPrice).doubleValue,
price: NSDecimalNumber(decimal: price).doubleValue,
originPrice: NSDecimalNumber(decimal: originPrice * 100).doubleValue,
price: NSDecimalNumber(decimal: price * 100).doubleValue,
unitPrice: PriceFormatter.formatPrice(unitPrice, currencyCode: currencyCode),
currencyCode: PriceFormatter.currencySymbol(for: currencyCode),
isTrialPeriod: isTrialPeriod
)
print("[PlatformHostApiImpl.requestAppleProductInfo] return: \(productInfo)")
... ... @@ -50,7 +92,7 @@ final class PlatformHostApiImpl: PlatformHostApi {
func performApplePayment(productId: String, uuid: String, completion: @escaping (Result<AppleProductPaymentResult?, any Error>) -> Void) {
Task {
let result = await ApplePayment.shared.purchase(productId, uuidString: uuid)
let result = await AppShared.shared.payment.purchase(productId, uuidString: uuid)
print("[PlatformHostApiImpl.performApplePayment] return: \(result)")
completion(.success(result))
}
... ... @@ -96,7 +138,7 @@ final class PlatformHostApiImpl: PlatformHostApi {
}
func getFullUserAgent() throws -> String {
let userAgent = UserAgent.share.finalUA
let userAgent = AppShared.shared.agent.finalUA
print("[PlatformHostApiImpl.getFullUserAgent] return: \(userAgent)")
return userAgent
}
... ...
... ... @@ -16,7 +16,7 @@ struct RunnerApp: App {
FlutterRootView(engine:appDelegate.flutterEngine )
.ignoresSafeArea()
.onAppear {
_ = ApplePayment.shared.listenForTransactions()
_ = AppShared.shared.payment.listenForTransactions()
}
}
}
... ...
... ... @@ -22,7 +22,6 @@ extension UIApplication {
}
class UserAgent {
static let share = UserAgent()
var unifiedUA: String = ""
var finalUA: String = ""
... ...
... ... @@ -4,10 +4,14 @@ import 'package:doublefeel_flutter/core/network/api/theme_api.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart';
import 'package:doublefeel_flutter/r.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../models/watch_theme_models.dart';
import '../widgets/watch_theme_dialogs.dart';
import '../widgets/watch_theme_no_watch_dialog.dart';
import '../widgets/watch_theme_sync_dialog.dart';
class CustomWatchThemePreviewController extends GetxController {
CustomWatchThemePreviewController(this._themeApi);
... ... @@ -70,10 +74,36 @@ class CustomWatchThemePreviewController extends GetxController {
}
Future<void> addWatchFace() async {
WearDeviceInfo? device;
try {
device = await WearEngineHostApi().checkConnectedDevice();
} catch (_) {
device = null;
}
if (device == null) {
await Get.dialog<void>(
const WatchThemeNoWatchDialog(),
barrierDismissible: true,
barrierColor: const Color(0xB3000000),
);
return;
}
await Get.dialog<void>(
WatchThemeSyncDialog(
faceAsset: R.assetsImagesWatchThemeCustomFacePreview,
onSync: _applyAndSyncWatchFace,
),
barrierDismissible: false,
barrierColor: const Color(0xB3000000),
);
}
Future<bool> _applyAndSyncWatchFace() async {
final themeId = themeItem.id;
if (themeId == null) {
AppToast.show('主题信息不完整');
return;
return false;
}
isApplying.value = true;
... ... @@ -81,17 +111,22 @@ class CustomWatchThemePreviewController extends GetxController {
if (result is! AppSuccess<void>) {
isApplying.value = false;
AppToast.show('应用主题失败');
return;
return false;
}
var syncSuccess = true;
try {
await WearEngineHostApi().sendWatchSyncPayload(
syncSuccess = await WearEngineHostApi().sendWatchSyncPayload(
jsonEncode(themeItem.toJson()),
);
} catch (_) {
// The active theme is already saved on the server.
syncSuccess = false;
}
isApplying.value = false;
AppToast.show('已应用主题');
if (!syncSuccess) {
AppToast.show('表盘同步失败');
}
return syncSuccess;
}
}
... ...
... ... @@ -4,9 +4,13 @@ import 'package:doublefeel_flutter/core/network/api/theme_api.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart';
import 'package:doublefeel_flutter/r.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../models/watch_theme_models.dart';
import '../widgets/watch_theme_no_watch_dialog.dart';
import '../widgets/watch_theme_sync_dialog.dart';
class WatchThemePreviewController extends GetxController {
WatchThemePreviewController(this._themeApi);
... ... @@ -38,10 +42,36 @@ class WatchThemePreviewController extends GetxController {
}
Future<void> addWatchFace() async {
WearDeviceInfo? device;
try {
device = await WearEngineHostApi().checkConnectedDevice();
} catch (_) {
device = null;
}
if (device == null) {
await Get.dialog<void>(
const WatchThemeNoWatchDialog(),
barrierDismissible: true,
barrierColor: const Color(0xB3000000),
);
return;
}
await Get.dialog<void>(
WatchThemeSyncDialog(
faceAsset: R.assetsImagesWatchThemeFaceDefault,
onSync: _applyAndSyncWatchFace,
),
barrierDismissible: false,
barrierColor: const Color(0xB3000000),
);
}
Future<bool> _applyAndSyncWatchFace() async {
final themeId = themeItem.id;
if (themeId == null) {
AppToast.show('主题信息不完整');
return;
return false;
}
isApplying.value = true;
... ... @@ -49,17 +79,22 @@ class WatchThemePreviewController extends GetxController {
if (result is! AppSuccess<void>) {
isApplying.value = false;
AppToast.show('应用主题失败');
return;
return false;
}
var syncSuccess = true;
try {
await WearEngineHostApi().sendWatchSyncPayload(
syncSuccess = await WearEngineHostApi().sendWatchSyncPayload(
jsonEncode(themeItem.toJson()),
);
} catch (_) {
// The server state has been updated; watch sync can be retried later.
syncSuccess = false;
}
isApplying.value = false;
AppToast.show('已应用主题');
if (!syncSuccess) {
AppToast.show('表盘同步失败');
}
return syncSuccess;
}
}
... ...
import 'package:doublefeel_flutter/core/util/size_extensions.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'watch_theme_colors.dart';
class WatchThemeFriendPickerDialog extends StatelessWidget {
const WatchThemeFriendPickerDialog({
super.key,
this.onConfirm,
});
final VoidCallback? onConfirm;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 452.dp,
width: double.infinity,
decoration: BoxDecoration(
color: WatchThemeColors.background,
borderRadius: BorderRadius.vertical(top: Radius.circular(14.dp)),
),
child: SafeArea(
top: false,
child: Stack(
children: [
Column(
children: [
SizedBox(
height: 56.dp,
child: Stack(
alignment: Alignment.center,
children: [
Text(
'选择好友',
style: TextStyle(
color: WatchThemeColors.textPrimary,
fontSize: 16.dp,
fontWeight: FontWeight.w600,
),
),
Positioned(
left: 26.dp,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Get.back,
child: SizedBox(
width: 32.dp,
height: 32.dp,
child: Icon(
Icons.close,
size: 22.dp,
color: const Color(0xFFA084EF),
),
),
),
),
],
),
),
Expanded(
child: ListView.separated(
padding: EdgeInsets.fromLTRB(16.dp, 0, 16.dp, 84.dp),
itemBuilder: (_, index) {
return _FriendPlaceholderCard(selected: index == 0);
},
separatorBuilder: (_, __) => SizedBox(height: 12.dp),
itemCount: 3,
),
),
],
),
Positioned(
left: 49.dp,
right: 47.dp,
bottom: 35.dp,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onConfirm ?? () => Get.back(result: true),
child: Container(
height: 48.dp,
alignment: Alignment.center,
decoration: BoxDecoration(
color: WatchThemeColors.brand,
borderRadius: BorderRadius.circular(24.dp),
),
child: Text(
'选择并同步至表盘',
style: TextStyle(
color: Colors.white,
fontSize: 16.dp,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
),
),
),
);
}
}
class _FriendPlaceholderCard extends StatelessWidget {
const _FriendPlaceholderCard({required this.selected});
final bool selected;
@override
Widget build(BuildContext context) {
return Container(
height: 220.dp,
decoration: BoxDecoration(
color: selected ? Colors.white : const Color(0xFFFDFDFF),
borderRadius: BorderRadius.circular(16.dp),
border: selected
? Border.all(color: WatchThemeColors.brand, width: 2.dp)
: null,
),
padding: EdgeInsets.all(20.dp),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
_Block(width: 36.dp, height: 36.dp, radius: 18.dp),
SizedBox(width: 12.dp),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Block(width: 132.dp, height: 16.dp, radius: 8.dp),
SizedBox(height: 6.dp),
_Block(width: 72.dp, height: 10.dp, radius: 5.dp),
],
),
],
),
SizedBox(height: 29.dp),
Row(
children: [
Expanded(
child: _Block(height: 106.dp, radius: 18.dp),
),
SizedBox(width: 32.dp),
Expanded(
child: Column(
children: [
_Block(height: 60.dp, radius: 16.dp),
SizedBox(height: 4.dp),
_Block(height: 60.dp, radius: 16.dp),
],
),
),
],
),
],
),
);
}
}
class _Block extends StatelessWidget {
const _Block({
this.width,
required this.height,
required this.radius,
});
final double? width;
final double height;
final double radius;
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: const Color(0xFFE8E0FF),
borderRadius: BorderRadius.circular(radius),
),
);
}
}
... ...
import 'package:doublefeel_flutter/core/util/size_extensions.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'watch_theme_colors.dart';
class WatchThemeNoWatchDialog extends StatelessWidget {
const WatchThemeNoWatchDialog({super.key});
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.zero,
child: Container(
width: 300.dp,
padding: EdgeInsets.fromLTRB(24.dp, 24.dp, 24.dp, 20.dp),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.dp),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'未找到苹果手表',
textAlign: TextAlign.center,
style: TextStyle(
color: const Color(0xFF141414),
fontSize: 18.dp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 16.dp),
Text(
'请配对苹果手表后再尝试',
textAlign: TextAlign.center,
style: TextStyle(
color: const Color(0xFF6E6D80),
fontSize: 15.dp,
),
),
SizedBox(height: 20.dp),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Get.back,
child: Container(
width: 220.dp,
height: 48.dp,
alignment: Alignment.center,
decoration: BoxDecoration(
color: WatchThemeColors.brand,
borderRadius: BorderRadius.circular(24.dp),
),
child: Text(
'好的',
style: TextStyle(
color: Colors.white,
fontSize: 14.dp,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
),
);
}
}
... ...
import 'package:doublefeel_flutter/core/util/size_extensions.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'watch_face_preview.dart';
import 'watch_theme_colors.dart';
class WatchThemeSyncDialog extends StatefulWidget {
const WatchThemeSyncDialog({
super.key,
required this.onSync,
this.faceAsset,
this.faceFilePath,
});
final Future<bool> Function() onSync;
final String? faceAsset;
final String? faceFilePath;
@override
State<WatchThemeSyncDialog> createState() => _WatchThemeSyncDialogState();
}
class _WatchThemeSyncDialogState extends State<WatchThemeSyncDialog> {
_WatchThemeSyncState _state = _WatchThemeSyncState.intro;
bool _success = true;
Future<void> _startSync() async {
if (_state == _WatchThemeSyncState.syncing) return;
setState(() => _state = _WatchThemeSyncState.syncing);
final success = await widget.onSync();
if (!mounted) return;
setState(() {
_success = success;
_state = _WatchThemeSyncState.done;
});
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 452.dp,
width: double.infinity,
decoration: BoxDecoration(
color: WatchThemeColors.background,
borderRadius: BorderRadius.vertical(top: Radius.circular(14.dp)),
),
child: SafeArea(
top: false,
child: Stack(
children: [
Positioned(
left: 26.dp,
top: 12.dp,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Get.back,
child: SizedBox(
width: 32.dp,
height: 32.dp,
child: Icon(
Icons.close,
size: 22.dp,
color: const Color(0xFFA084EF),
),
),
),
),
Positioned(
top: 66.dp,
left: 0,
right: 0,
child: Center(
child: WatchFacePreview(
width: 134,
height: 160,
faceAsset: widget.faceAsset,
faceFilePath: widget.faceFilePath,
),
),
),
Positioned(
top: 246.dp,
left: 0,
right: 0,
child: _buildMessage(),
),
Positioned(
left: 49.dp,
right: 47.dp,
bottom: 35.dp,
child: _buildAction(),
),
],
),
),
),
),
);
}
Widget _buildMessage() {
return switch (_state) {
_WatchThemeSyncState.intro => Padding(
padding: EdgeInsets.symmetric(horizontal: 46.dp),
child: Text(
'请先打开DoubleFeel手表端App,然后点击下方的“下一步”按钮',
textAlign: TextAlign.center,
style: TextStyle(
color: WatchThemeColors.textPrimary,
fontSize: 14.dp,
height: 1.35,
),
),
),
_WatchThemeSyncState.syncing => Text(
'请保持打开手表App,等待同步',
textAlign: TextAlign.center,
style: TextStyle(
color: WatchThemeColors.textPrimary,
fontSize: 14.dp,
),
),
_WatchThemeSyncState.done => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 14.dp,
height: 14.dp,
decoration: BoxDecoration(
color: _success
? WatchThemeColors.excellent
: const Color(0xFFFC4447),
shape: BoxShape.circle,
),
child: Icon(
_success ? Icons.check : Icons.close,
size: 10.dp,
color: Colors.white,
),
),
SizedBox(width: 4.dp),
Text(
_success ? '同步完成' : '同步失败',
style: TextStyle(
color: WatchThemeColors.textPrimary,
fontSize: 14.dp,
),
),
],
),
};
}
Widget _buildAction() {
return switch (_state) {
_WatchThemeSyncState.intro => _PrimaryButton(
label: '下一步',
onTap: _startSync,
),
_WatchThemeSyncState.syncing => const _ProgressButton(progress: 0.85),
_WatchThemeSyncState.done => _PrimaryButton(
label: '好的',
onTap: Get.back,
),
};
}
}
class _PrimaryButton extends StatelessWidget {
const _PrimaryButton({
required this.label,
required this.onTap,
});
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 48.dp,
alignment: Alignment.center,
decoration: BoxDecoration(
color: WatchThemeColors.brand,
borderRadius: BorderRadius.circular(24.dp),
),
child: Text(
label,
style: TextStyle(
color: Colors.white,
fontSize: 16.dp,
fontWeight: FontWeight.w600,
),
),
),
);
}
}
class _ProgressButton extends StatelessWidget {
const _ProgressButton({required this.progress});
final double progress;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(24.dp),
child: SizedBox(
height: 48.dp,
child: Stack(
fit: StackFit.expand,
children: [
const ColoredBox(color: Color(0xFFCBB9F6)),
FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: progress.clamp(0, 1),
child: const ColoredBox(color: WatchThemeColors.brand),
),
Center(
child: Text(
'同步中 ${(progress * 100).round()}%',
style: TextStyle(
color: Colors.white,
fontSize: 16.dp,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
);
}
}
enum _WatchThemeSyncState { intro, syncing, done }
... ...
... ... @@ -98,22 +98,36 @@ class AppleProductInfo {
AppleProductInfo({
required this.productId,
required this.originPriceDescription,
this.priceDescription,
required this.priceDescription,
required this.originPrice,
this.price,
required this.price,
required this.unitPrice,
required this.currencyCode,
required this.isTrialPeriod,
});
/// 苹果商品id
String productId;
/// 原价描述
String originPriceDescription;
String? priceDescription;
/// 真实价格描述
String priceDescription;
/// 原价格
double originPrice;
double? price;
/// 真实价格, = 0 代表试用商品
double price;
/// price/unit = 单位价格
String unitPrice;
/// 当前货币符号
String currencyCode;
/// 当前用户是否可以试用
bool isTrialPeriod;
List<Object?> _toList() {
... ... @@ -123,6 +137,8 @@ class AppleProductInfo {
priceDescription,
originPrice,
price,
unitPrice,
currencyCode,
isTrialPeriod,
];
}
... ... @@ -135,10 +151,12 @@ class AppleProductInfo {
return AppleProductInfo(
productId: result[0]! as String,
originPriceDescription: result[1]! as String,
priceDescription: result[2] as String?,
priceDescription: result[2]! as String,
originPrice: result[3]! as double,
price: result[4] as double?,
isTrialPeriod: result[5]! as bool,
price: result[4]! as double,
unitPrice: result[5]! as String,
currencyCode: result[6]! as String,
isTrialPeriod: result[7]! as bool,
);
}
... ... @@ -315,6 +333,32 @@ class PlatformHostApi {
}
}
/// 更新用户信息, 有登录态后调用
/// jsonString: UserPreferences的序列化string
/// baseUrl: 请求地址, https://api.doublefeel.cn
Future<void> updateLoginInfo(String jsonString, String baseUrl) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.updateLoginInfo$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[jsonString, baseUrl]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
/// 请求苹果登录
Future<AppleSignInModel?> requestAppleSignIn() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleSignIn$pigeonVar_messageChannelSuffix';
... ... @@ -340,14 +384,16 @@ class PlatformHostApi {
}
/// 查询指定id的苹果商品
Future<AppleProductInfo?> requestAppleProductInfo(String productId) async {
/// productId: 苹果商品id
/// baseUnit: 除数基础单位(多少个天、周、月) ->
Future<AppleProductInfo?> requestAppleProductInfo(String productId, int baseUnit) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[productId]);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[productId, baseUnit]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
... ...
... ... @@ -15,18 +15,37 @@ class AppleSignInModel {
}
class AppleProductInfo {
/// 苹果商品id
final String productId;
/// 原价描述
final String originPriceDescription;
final String? priceDescription;
/// 真实价格描述
final String priceDescription;
/// 原价格
final double originPrice;
final double? price;
/// 真实价格, = 0 代表试用商品
final double price;
/// price/unit = 单位价格
final String unitPrice;
/// 当前货币符号
final String currencyCode;
/// 当前用户是否可以试用
final bool isTrialPeriod;
AppleProductInfo({
required this.productId,
required this.originPriceDescription,
this.priceDescription,
required this.priceDescription,
required this.originPrice,
this.price,
required this.price,
required this.unitPrice,
required this.currencyCode,
required this.isTrialPeriod,
});
}
... ... @@ -83,13 +102,20 @@ abstract class PlatformHostApi {
/// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
String getFullUserAgent();
/// 更新用户信息, 有登录态后调用
/// jsonString: UserPreferences的序列化string
/// baseUrl: 请求地址, https://api.doublefeel.cn
void updateLoginInfo(String jsonString, String baseUrl);
/// 请求苹果登录
@async
AppleSignInModel? requestAppleSignIn();
/// 查询指定id的苹果商品
/// productId: 苹果商品id
/// baseUnit: 除数基础单位(多少个天、周、月) ->
@async
AppleProductInfo? requestAppleProductInfo(String productId);
AppleProductInfo? requestAppleProductInfo(String productId, int baseUnit);
/// 从服务器下单后请求苹果支付
@async
... ...
... ... @@ -133,10 +133,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
... ... @@ -149,10 +149,10 @@ packages:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
code_builder:
dependency: transitive
description:
... ... @@ -165,10 +165,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
version: "1.19.1"
convert:
dependency: transitive
description:
... ... @@ -237,10 +237,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.3"
ffi:
dependency: transitive
description:
... ... @@ -426,7 +426,7 @@ packages:
dependency: transitive
description:
path: image_cropper_for_web
ref: "br_v9.1.0_ohos"
ref: "65c2c99891882ea59732959a672f3d5993a837bb"
resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb"
url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git"
source: git
... ... @@ -435,7 +435,7 @@ packages:
dependency: transitive
description:
path: image_cropper_platform_interface
ref: "br_v9.1.0_ohos"
ref: "65c2c99891882ea59732959a672f3d5993a837bb"
resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb"
url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git"
source: git
... ... @@ -517,10 +517,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.19.0"
version: "0.20.2"
io:
dependency: transitive
description:
... ... @@ -549,26 +549,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "10.0.7"
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
... ... @@ -597,10 +597,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
... ... @@ -613,10 +613,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.17.0"
mime:
dependency: transitive
description:
... ... @@ -645,10 +645,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.9.1"
path_provider:
dependency: transitive
description:
... ... @@ -966,18 +966,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
stream_transform:
dependency: transitive
description:
... ... @@ -1006,10 +1006,10 @@ packages:
dependency: "direct main"
description:
name: table_calendar
sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
version: "3.2.0"
term_glyph:
dependency: transitive
description:
... ... @@ -1022,10 +1022,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.3"
version: "0.7.7"
timing:
dependency: transitive
description:
... ... @@ -1054,10 +1054,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
vm_service:
dependency: transitive
description:
... ... @@ -1111,8 +1111,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_android"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3
ref: de942e79c9057b32ad31106508bd87c0d60aef83
resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "4.7.0"
... ... @@ -1120,8 +1120,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_ohos"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3
ref: de942e79c9057b32ad31106508bd87c0d60aef83
resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "4.7.0"
... ... @@ -1129,8 +1129,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_platform_interface"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3
ref: de942e79c9057b32ad31106508bd87c0d60aef83
resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "2.13.1"
... ... @@ -1138,8 +1138,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_wkwebview"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: ecd3d0b6195945ee0660c324d45f1acbc6198cc3
ref: de942e79c9057b32ad31106508bd87c0d60aef83
resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "3.22.0"
... ... @@ -1160,5 +1160,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.6.2 <4.0.0"
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.27.0"
... ...