Commit a9c81df4cf84ca608cb4c2c991b378c8d57074b1
1 parent
b6e5b8ac
feat(hm): add wechat alipay websocket
Showing
61 changed files
with
2770 additions
and
304 deletions
| @@ -34,6 +34,10 @@ class HealthKitHostApiImpl( | @@ -34,6 +34,10 @@ class HealthKitHostApiImpl( | ||
| 34 | 34 | ||
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | + override fun syncHealthDataToCloud(callback: (Result<Boolean>) -> Unit) { | ||
| 38 | + callback(Result.success(false)) | ||
| 39 | + } | ||
| 40 | + | ||
| 37 | override fun fetchHrvData( | 41 | override fun fetchHrvData( |
| 38 | startTime: Long, | 42 | startTime: Long, |
| 39 | endTime: Long, | 43 | endTime: Long, |
| @@ -88,7 +92,7 @@ class HealthKitHostApiImpl( | @@ -88,7 +92,7 @@ class HealthKitHostApiImpl( | ||
| 88 | endTime: Long, | 92 | endTime: Long, |
| 89 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit | 93 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit |
| 90 | ) { | 94 | ) { |
| 91 | - | 95 | + |
| 92 | } | 96 | } |
| 93 | 97 | ||
| 94 | override fun fetchStandData( | 98 | override fun fetchStandData( |
| @@ -96,7 +100,7 @@ class HealthKitHostApiImpl( | @@ -96,7 +100,7 @@ class HealthKitHostApiImpl( | ||
| 96 | endTime: Long, | 100 | endTime: Long, |
| 97 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit | 101 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit |
| 98 | ) { | 102 | ) { |
| 99 | - | 103 | + |
| 100 | } | 104 | } |
| 101 | 105 | ||
| 102 | override fun fetchStepCountData( | 106 | override fun fetchStepCountData( |
| @@ -104,7 +108,7 @@ class HealthKitHostApiImpl( | @@ -104,7 +108,7 @@ class HealthKitHostApiImpl( | ||
| 104 | endTime: Long, | 108 | endTime: Long, |
| 105 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit | 109 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit |
| 106 | ) { | 110 | ) { |
| 107 | - | 111 | + |
| 108 | } | 112 | } |
| 109 | 113 | ||
| 110 | override fun fetchSleepData( | 114 | override fun fetchSleepData( |
| @@ -112,7 +116,7 @@ class HealthKitHostApiImpl( | @@ -112,7 +116,7 @@ class HealthKitHostApiImpl( | ||
| 112 | endTime: Long, | 116 | endTime: Long, |
| 113 | callback: (Result<List<HealthSleepUploadDataPoint>>) -> Unit | 117 | callback: (Result<List<HealthSleepUploadDataPoint>>) -> Unit |
| 114 | ) { | 118 | ) { |
| 115 | - | 119 | + |
| 116 | } | 120 | } |
| 117 | 121 | ||
| 118 | override fun fetchSleepingWristTemperatureData( | 122 | override fun fetchSleepingWristTemperatureData( |
| @@ -120,7 +124,7 @@ class HealthKitHostApiImpl( | @@ -120,7 +124,7 @@ class HealthKitHostApiImpl( | ||
| 120 | endTime: Long, | 124 | endTime: Long, |
| 121 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit | 125 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit |
| 122 | ) { | 126 | ) { |
| 123 | - | 127 | + |
| 124 | } | 128 | } |
| 125 | 129 | ||
| 126 | override fun fetchRespiratoryRateData( | 130 | override fun fetchRespiratoryRateData( |
| @@ -128,7 +132,7 @@ class HealthKitHostApiImpl( | @@ -128,7 +132,7 @@ class HealthKitHostApiImpl( | ||
| 128 | endTime: Long, | 132 | endTime: Long, |
| 129 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit | 133 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit |
| 130 | ) { | 134 | ) { |
| 131 | - | 135 | + |
| 132 | } | 136 | } |
| 133 | 137 | ||
| 134 | override fun fetchIrregularHeartRhythmData( | 138 | override fun fetchIrregularHeartRhythmData( |
| @@ -136,7 +140,7 @@ class HealthKitHostApiImpl( | @@ -136,7 +140,7 @@ class HealthKitHostApiImpl( | ||
| 136 | endTime: Long, | 140 | endTime: Long, |
| 137 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit | 141 | callback: (Result<List<HealthUploadDataPoint>>) -> Unit |
| 138 | ) { | 142 | ) { |
| 139 | - | 143 | + |
| 140 | } | 144 | } |
| 141 | 145 | ||
| 142 | override fun fetchActivityTargetData( | 146 | override fun fetchActivityTargetData( |
| @@ -144,7 +148,7 @@ class HealthKitHostApiImpl( | @@ -144,7 +148,7 @@ class HealthKitHostApiImpl( | ||
| 144 | endTime: Long, | 148 | endTime: Long, |
| 145 | callback: (Result<HealthActivityTargetData?>) -> Unit | 149 | callback: (Result<HealthActivityTargetData?>) -> Unit |
| 146 | ) { | 150 | ) { |
| 147 | - | 151 | + |
| 148 | } | 152 | } |
| 149 | 153 | ||
| 150 | } | 154 | } |
| @@ -138,6 +138,7 @@ interface HealthKitHostApi { | @@ -138,6 +138,7 @@ interface HealthKitHostApi { | ||
| 138 | /** Opens Huawei Health client authorization UI. Returns whether user granted. */ | 138 | /** Opens Huawei Health client authorization UI. Returns whether user granted. */ |
| 139 | fun checkHealthAppAuthorization(callback: (Result<HealthAuthorization>) -> Unit) | 139 | fun checkHealthAppAuthorization(callback: (Result<HealthAuthorization>) -> Unit) |
| 140 | fun requestHealthClientAuthorization(callback: (Result<Boolean>) -> Unit) | 140 | fun requestHealthClientAuthorization(callback: (Result<Boolean>) -> Unit) |
| 141 | + fun syncHealthDataToCloud(callback: (Result<Boolean>) -> Unit) | ||
| 141 | 142 | ||
| 142 | companion object { | 143 | companion object { |
| 143 | /** The codec used by HealthKitHostApi. */ | 144 | /** The codec used by HealthKitHostApi. */ |
| @@ -184,6 +185,24 @@ interface HealthKitHostApi { | @@ -184,6 +185,24 @@ interface HealthKitHostApi { | ||
| 184 | channel.setMessageHandler(null) | 185 | channel.setMessageHandler(null) |
| 185 | } | 186 | } |
| 186 | } | 187 | } |
| 188 | + run { | ||
| 189 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.syncHealthDataToCloud$separatedMessageChannelSuffix", codec) | ||
| 190 | + if (api != null) { | ||
| 191 | + channel.setMessageHandler { _, reply -> | ||
| 192 | + api.syncHealthDataToCloud{ result: Result<Boolean> -> | ||
| 193 | + val error = result.exceptionOrNull() | ||
| 194 | + if (error != null) { | ||
| 195 | + reply.reply(HealthKitApiPigeonUtils.wrapError(error)) | ||
| 196 | + } else { | ||
| 197 | + val data = result.getOrNull() | ||
| 198 | + reply.reply(HealthKitApiPigeonUtils.wrapResult(data)) | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | + } | ||
| 202 | + } else { | ||
| 203 | + channel.setMessageHandler(null) | ||
| 204 | + } | ||
| 205 | + } | ||
| 187 | } | 206 | } |
| 188 | } | 207 | } |
| 189 | } | 208 | } |
assets/images/premium/ic_payment_alipay.png
0 → 100644
1.51 KB
| @@ -184,6 +184,11 @@ protocol HealthKitHostApi { | @@ -184,6 +184,11 @@ protocol HealthKitHostApi { | ||
| 184 | /// Opens Huawei Health client authorization UI. Returns whether user granted. | 184 | /// Opens Huawei Health client authorization UI. Returns whether user granted. |
| 185 | func checkHealthAppAuthorization(completion: @escaping (Result<HealthAuthorization, Error>) -> Void) | 185 | func checkHealthAppAuthorization(completion: @escaping (Result<HealthAuthorization, Error>) -> Void) |
| 186 | func requestHealthClientAuthorization(completion: @escaping (Result<Bool, Error>) -> Void) | 186 | func requestHealthClientAuthorization(completion: @escaping (Result<Bool, Error>) -> Void) |
| 187 | + /// Requests Huawei Health to upload the user's latest data to its cloud. | ||
| 188 | + /// | ||
| 189 | + /// This only triggers the Health app's configured device-to-cloud sync; it | ||
| 190 | + /// does not wait for, or return, the subsequently available cloud data. | ||
| 191 | + func syncHealthDataToCloud(completion: @escaping (Result<Bool, Error>) -> Void) | ||
| 187 | } | 192 | } |
| 188 | 193 | ||
| 189 | /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. | 194 | /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. |
| @@ -223,5 +228,24 @@ class HealthKitHostApiSetup { | @@ -223,5 +228,24 @@ class HealthKitHostApiSetup { | ||
| 223 | } else { | 228 | } else { |
| 224 | requestHealthClientAuthorizationChannel.setMessageHandler(nil) | 229 | requestHealthClientAuthorizationChannel.setMessageHandler(nil) |
| 225 | } | 230 | } |
| 231 | + /// Requests Huawei Health to upload the user's latest data to its cloud. | ||
| 232 | + /// | ||
| 233 | + /// This only triggers the Health app's configured device-to-cloud sync; it | ||
| 234 | + /// does not wait for, or return, the subsequently available cloud data. | ||
| 235 | + let syncHealthDataToCloudChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.syncHealthDataToCloud\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 236 | + if let api = api { | ||
| 237 | + syncHealthDataToCloudChannel.setMessageHandler { _, reply in | ||
| 238 | + api.syncHealthDataToCloud { result in | ||
| 239 | + switch result { | ||
| 240 | + case .success(let res): | ||
| 241 | + reply(wrapResult(res)) | ||
| 242 | + case .failure(let error): | ||
| 243 | + reply(wrapError(error)) | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | + } | ||
| 247 | + } else { | ||
| 248 | + syncHealthDataToCloudChannel.setMessageHandler(nil) | ||
| 249 | + } | ||
| 226 | } | 250 | } |
| 227 | } | 251 | } |
| @@ -51,4 +51,9 @@ final class HealthKitHostApiImpl: HealthKitHostApi { | @@ -51,4 +51,9 @@ final class HealthKitHostApiImpl: HealthKitHostApi { | ||
| 51 | completion(.success(success)) | 51 | completion(.success(success)) |
| 52 | } | 52 | } |
| 53 | } | 53 | } |
| 54 | + | ||
| 55 | + func syncHealthDataToCloud(completion: @escaping (Result<Bool, Error>) -> Void) { | ||
| 56 | + // Huawei Health manual cloud synchronization is HarmonyOS-specific. | ||
| 57 | + completion(.success(false)) | ||
| 58 | + } | ||
| 54 | } | 59 | } |
| 1 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; | 1 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; |
| 2 | +import 'package:doublefeel_flutter/core/network/api/harmony_api.dart'; | ||
| 2 | import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart'; | 3 | import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart'; |
| 4 | +import 'package:flutter/widgets.dart'; | ||
| 3 | import 'package:get/get.dart'; | 5 | import 'package:get/get.dart'; |
| 4 | 6 | ||
| 5 | import '../../core/config/app_environment_config.dart'; | 7 | import '../../core/config/app_environment_config.dart'; |
| @@ -19,6 +21,7 @@ import '../../core/services/obs_upload_service.dart'; | @@ -19,6 +21,7 @@ import '../../core/services/obs_upload_service.dart'; | ||
| 19 | import '../../core/services/push_service.dart'; | 21 | import '../../core/services/push_service.dart'; |
| 20 | import '../../core/services/thinking_data_service.dart'; | 22 | import '../../core/services/thinking_data_service.dart'; |
| 21 | import '../../core/services/user_state_service.dart'; | 23 | import '../../core/services/user_state_service.dart'; |
| 24 | +import '../../core/services/web_socket_service.dart'; | ||
| 22 | import '../../data/local/local_storage.dart'; | 25 | import '../../data/local/local_storage.dart'; |
| 23 | import '../../data/local/user_account_storage.dart'; | 26 | import '../../data/local/user_account_storage.dart'; |
| 24 | import '../../data/local/user_preferences_storage.dart'; | 27 | import '../../data/local/user_preferences_storage.dart'; |
| @@ -42,6 +45,12 @@ void registerCoreDeps({ | @@ -42,6 +45,12 @@ void registerCoreDeps({ | ||
| 42 | ), | 45 | ), |
| 43 | permanent: true, | 46 | permanent: true, |
| 44 | ); | 47 | ); |
| 48 | + final webSocketService = WebSocketService( | ||
| 49 | + Get.find<AppEnvironmentConfig>(), | ||
| 50 | + Get.find<UserPreferencesStorage>(), | ||
| 51 | + ); | ||
| 52 | + WidgetsBinding.instance.addObserver(webSocketService); | ||
| 53 | + Get.put(webSocketService, permanent: true); | ||
| 45 | } | 54 | } |
| 46 | 55 | ||
| 47 | /// User session, auth APIs, and login-side SDK wiring. | 56 | /// User session, auth APIs, and login-side SDK wiring. |
| @@ -53,6 +62,7 @@ void registerUserSessionDeps() { | @@ -53,6 +62,7 @@ void registerUserSessionDeps() { | ||
| 53 | Get.put(UserApi(dioClient), permanent: true); | 62 | Get.put(UserApi(dioClient), permanent: true); |
| 54 | Get.put(VipApi(dioClient), permanent: true); | 63 | Get.put(VipApi(dioClient), permanent: true); |
| 55 | Get.put(FriendApi(dioClient), permanent: true); | 64 | Get.put(FriendApi(dioClient), permanent: true); |
| 65 | + Get.put(HarmonyApi(dioClient), permanent: true); | ||
| 56 | Get.put<ImService>(ImServiceStub(), permanent: true); | 66 | Get.put<ImService>(ImServiceStub(), permanent: true); |
| 57 | Get.put( | 67 | Get.put( |
| 58 | ThinkingDataService(environmentConfig), | 68 | ThinkingDataService(environmentConfig), |
| @@ -76,6 +86,7 @@ void registerUserSessionDeps() { | @@ -76,6 +86,7 @@ void registerUserSessionDeps() { | ||
| 76 | Get.find<PushService>(), | 86 | Get.find<PushService>(), |
| 77 | Get.find<ThinkingDataService>(), | 87 | Get.find<ThinkingDataService>(), |
| 78 | Get.find<HealthRawDataCoreService>(), | 88 | Get.find<HealthRawDataCoreService>(), |
| 89 | + Get.find<WebSocketService>(), | ||
| 79 | ), | 90 | ), |
| 80 | permanent: true, | 91 | permanent: true, |
| 81 | ); | 92 | ); |
| @@ -40,6 +40,8 @@ abstract final class AppBootstrap { | @@ -40,6 +40,8 @@ abstract final class AppBootstrap { | ||
| 40 | localStorage: local, | 40 | localStorage: local, |
| 41 | ).dependencies(); | 41 | ).dependencies(); |
| 42 | 42 | ||
| 43 | + await userPreferencesStorage.syncLoginInfoToNative(); | ||
| 44 | + | ||
| 43 | LoadingService.init(); | 45 | LoadingService.init(); |
| 44 | 46 | ||
| 45 | if (local.termsAgreed) { | 47 | if (local.termsAgreed) { |
| 1 | import 'dart:math' as math; | 1 | import 'dart:math' as math; |
| 2 | 2 | ||
| 3 | +import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; | ||
| 4 | +import 'package:doublefeel_flutter/app/widget/circular_gradient_progress/arc_progress_widget.dart'; | ||
| 3 | import 'package:flutter/material.dart'; | 5 | import 'package:flutter/material.dart'; |
| 4 | 6 | ||
| 5 | import '../models/activity_burn_report_models.dart'; | 7 | import '../models/activity_burn_report_models.dart'; |
| 6 | 8 | ||
| 7 | -class ActivityBurnRing extends StatefulWidget { | 9 | +/// 活动消耗进度环。 |
| 10 | +/// | ||
| 11 | +/// 页面只依赖这一公共组件:OHOS 使用开口弧形样式,其它平台保留原有 | ||
| 12 | +/// 的三层闭合圆环,避免在各个报表页面重复处理平台分支。 | ||
| 13 | +class ActivityBurnRing extends StatelessWidget { | ||
| 8 | const ActivityBurnRing({ | 14 | const ActivityBurnRing({ |
| 9 | super.key, | 15 | super.key, |
| 10 | required this.report, | 16 | required this.report, |
| @@ -17,10 +23,84 @@ class ActivityBurnRing extends StatefulWidget { | @@ -17,10 +23,84 @@ class ActivityBurnRing extends StatefulWidget { | ||
| 17 | final bool animate; | 23 | final bool animate; |
| 18 | 24 | ||
| 19 | @override | 25 | @override |
| 20 | - State<ActivityBurnRing> createState() => _ActivityBurnRingState(); | 26 | + Widget build(BuildContext context) { |
| 27 | + if (isOhos()) { | ||
| 28 | + return _OhosActivityBurnRing( | ||
| 29 | + report: report, | ||
| 30 | + size: size, | ||
| 31 | + animate: animate, | ||
| 32 | + ); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + return _CircularActivityBurnRing( | ||
| 36 | + report: report, | ||
| 37 | + size: size, | ||
| 38 | + animate: animate, | ||
| 39 | + ); | ||
| 40 | + } | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +class _OhosActivityBurnRing extends StatelessWidget { | ||
| 44 | + const _OhosActivityBurnRing({ | ||
| 45 | + required this.report, | ||
| 46 | + required this.size, | ||
| 47 | + required this.animate, | ||
| 48 | + }); | ||
| 49 | + | ||
| 50 | + final ActivityBurnReport? report; | ||
| 51 | + final double size; | ||
| 52 | + final bool animate; | ||
| 53 | + | ||
| 54 | + @override | ||
| 55 | + Widget build(BuildContext context) { | ||
| 56 | + final hasData = report?.hasData == true; | ||
| 57 | + | ||
| 58 | + return ArcMultiProgressWidget( | ||
| 59 | + size: size, | ||
| 60 | + // Keep the visual proportion of the original 200px circular ring. | ||
| 61 | + strokeWidth: size * 0.1, | ||
| 62 | + gap: size * 0.02, | ||
| 63 | + startAngleDeg: 140, | ||
| 64 | + sweepTotalDeg: 260, | ||
| 65 | + animate: animate, | ||
| 66 | + items: [ | ||
| 67 | + ArcProgressItem( | ||
| 68 | + ratio: hasData ? report?.activeEnergy?.rawProgress : null, | ||
| 69 | + color: const Color(0xFFF84C38), | ||
| 70 | + backgroundColor: const Color(0xFFFEE6E4), | ||
| 71 | + ), | ||
| 72 | + ArcProgressItem( | ||
| 73 | + ratio: hasData ? report?.exerciseMinutes?.rawProgress : null, | ||
| 74 | + color: const Color(0xFFF6A523), | ||
| 75 | + backgroundColor: const Color(0xFFFDF2E2), | ||
| 76 | + ), | ||
| 77 | + ArcProgressItem( | ||
| 78 | + ratio: hasData ? report?.standHours?.rawProgress : null, | ||
| 79 | + color: const Color(0xFF3A9BFB), | ||
| 80 | + backgroundColor: const Color(0xFFE3F1FD), | ||
| 81 | + ), | ||
| 82 | + ], | ||
| 83 | + ); | ||
| 84 | + } | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +class _CircularActivityBurnRing extends StatefulWidget { | ||
| 88 | + const _CircularActivityBurnRing({ | ||
| 89 | + required this.report, | ||
| 90 | + required this.size, | ||
| 91 | + required this.animate, | ||
| 92 | + }); | ||
| 93 | + | ||
| 94 | + final ActivityBurnReport? report; | ||
| 95 | + final double size; | ||
| 96 | + final bool animate; | ||
| 97 | + | ||
| 98 | + @override | ||
| 99 | + State<_CircularActivityBurnRing> createState() => | ||
| 100 | + _CircularActivityBurnRingState(); | ||
| 21 | } | 101 | } |
| 22 | 102 | ||
| 23 | -class _ActivityBurnRingState extends State<ActivityBurnRing> | 103 | +class _CircularActivityBurnRingState extends State<_CircularActivityBurnRing> |
| 24 | with SingleTickerProviderStateMixin { | 104 | with SingleTickerProviderStateMixin { |
| 25 | late final AnimationController _controller; | 105 | late final AnimationController _controller; |
| 26 | late Animation<_RingProgress> _animation; | 106 | late Animation<_RingProgress> _animation; |
| @@ -38,7 +118,7 @@ class _ActivityBurnRingState extends State<ActivityBurnRing> | @@ -38,7 +118,7 @@ class _ActivityBurnRingState extends State<ActivityBurnRing> | ||
| 38 | } | 118 | } |
| 39 | 119 | ||
| 40 | @override | 120 | @override |
| 41 | - void didUpdateWidget(covariant ActivityBurnRing oldWidget) { | 121 | + void didUpdateWidget(covariant _CircularActivityBurnRing oldWidget) { |
| 42 | super.didUpdateWidget(oldWidget); | 122 | super.didUpdateWidget(oldWidget); |
| 43 | final target = _target; | 123 | final target = _target; |
| 44 | if (_RingProgress.fromReport(oldWidget.report) != target || | 124 | if (_RingProgress.fromReport(oldWidget.report) != target || |
| @@ -65,10 +145,8 @@ class _ActivityBurnRingState extends State<ActivityBurnRing> | @@ -65,10 +145,8 @@ class _ActivityBurnRingState extends State<ActivityBurnRing> | ||
| 65 | if (visualDelta == 0) return Duration.zero; | 145 | if (visualDelta == 0) return Duration.zero; |
| 66 | 146 | ||
| 67 | // A tiny drawable change should not spend a full second creeping forward. | 147 | // A tiny drawable change should not spend a full second creeping forward. |
| 68 | - final milliseconds = (1000 * visualDelta.clamp(0, 1)) | ||
| 69 | - .round() | ||
| 70 | - .clamp(150, 1000) | ||
| 71 | - .toInt(); | 148 | + final milliseconds = |
| 149 | + (1000 * visualDelta.clamp(0, 1)).round().clamp(150, 1000).toInt(); | ||
| 72 | return Duration(milliseconds: milliseconds); | 150 | return Duration(milliseconds: milliseconds); |
| 73 | } | 151 | } |
| 74 | 152 |
| @@ -107,6 +107,7 @@ class SubmitFeedbackController extends GetxController { | @@ -107,6 +107,7 @@ class SubmitFeedbackController extends GetxController { | ||
| 107 | image.path, | 107 | image.path, |
| 108 | _isVideo(image) ? HResourceType.video : HResourceType.image, | 108 | _isVideo(image) ? HResourceType.video : HResourceType.image, |
| 109 | ); | 109 | ); |
| 110 | + print("------, submit: $ossPath"); | ||
| 110 | if (ossPath != null && ossPath.isNotEmpty) { | 111 | if (ossPath != null && ossPath.isNotEmpty) { |
| 111 | selectedPaths.add(ossPath); | 112 | selectedPaths.add(ossPath); |
| 112 | } | 113 | } |
| @@ -2,6 +2,7 @@ import 'dart:math' as math; | @@ -2,6 +2,7 @@ import 'dart:math' as math; | ||
| 2 | 2 | ||
| 3 | import 'package:doublefeel_flutter/app/modules/friends/models/friend_stress_state.dart'; | 3 | import 'package:doublefeel_flutter/app/modules/friends/models/friend_stress_state.dart'; |
| 4 | import 'package:doublefeel_flutter/app/modules/sleep_report/models/sleep_report_models.dart'; | 4 | import 'package:doublefeel_flutter/app/modules/sleep_report/models/sleep_report_models.dart'; |
| 5 | +import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; | ||
| 5 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; | 6 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; |
| 6 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 7 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 7 | import 'package:doublefeel_flutter/r.dart'; | 8 | import 'package:doublefeel_flutter/r.dart'; |
| @@ -278,7 +279,7 @@ class _FriendCardHeader extends StatelessWidget { | @@ -278,7 +279,7 @@ class _FriendCardHeader extends StatelessWidget { | ||
| 278 | value: FriendCardAction.editRemark, | 279 | value: FriendCardAction.editRemark, |
| 279 | child: Center(child: Text(context.l10n.friendsEditRemark)), | 280 | child: Center(child: Text(context.l10n.friendsEditRemark)), |
| 280 | ), | 281 | ), |
| 281 | - if (!isOnWatchFace) | 282 | + if (!isOhos() && !isOnWatchFace) |
| 282 | PopupMenuItem( | 283 | PopupMenuItem( |
| 283 | height: 42, | 284 | height: 42, |
| 284 | value: FriendCardAction.watchFace, | 285 | value: FriendCardAction.watchFace, |
| @@ -7,6 +7,7 @@ import '../../../../core/config/app_environment.dart'; | @@ -7,6 +7,7 @@ import '../../../../core/config/app_environment.dart'; | ||
| 7 | import '../../../../core/config/app_environment_config.dart'; | 7 | import '../../../../core/config/app_environment_config.dart'; |
| 8 | import '../../../../core/constants/app_const.dart'; | 8 | import '../../../../core/constants/app_const.dart'; |
| 9 | import '../../../../core/network/dio_client.dart'; | 9 | import '../../../../core/network/dio_client.dart'; |
| 10 | +import '../../../../core/services/web_socket_service.dart'; | ||
| 10 | import '../../../../core/util/app_toast.dart'; | 11 | import '../../../../core/util/app_toast.dart'; |
| 11 | 12 | ||
| 12 | class DebugEnvironmentView extends StatelessWidget { | 13 | class DebugEnvironmentView extends StatelessWidget { |
| @@ -69,6 +70,7 @@ class DebugEnvironmentView extends StatelessWidget { | @@ -69,6 +70,7 @@ class DebugEnvironmentView extends StatelessWidget { | ||
| 69 | 70 | ||
| 70 | await environmentConfig.setEnvironment(value); | 71 | await environmentConfig.setEnvironment(value); |
| 71 | dioClient.refreshBaseUrl(); | 72 | dioClient.refreshBaseUrl(); |
| 73 | + Get.find<WebSocketService>().reconnect(); | ||
| 72 | AppToast.show( | 74 | AppToast.show( |
| 73 | '环境已切换: ${environmentConfig.serverBaseUrl}', | 75 | '环境已切换: ${environmentConfig.serverBaseUrl}', |
| 74 | ); | 76 | ); |
| @@ -3,12 +3,9 @@ import 'dart:math' as math; | @@ -3,12 +3,9 @@ import 'dart:math' as math; | ||
| 3 | 3 | ||
| 4 | import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; | 4 | import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; |
| 5 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; | 5 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; |
| 6 | -import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 7 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 6 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| 8 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; | 7 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; |
| 9 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 8 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 10 | - | ||
| 11 | -import 'package:flutter/foundation.dart'; | ||
| 12 | import 'package:flutter/gestures.dart'; | 9 | import 'package:flutter/gestures.dart'; |
| 13 | import 'package:flutter/material.dart'; | 10 | import 'package:flutter/material.dart'; |
| 14 | import 'package:flutter/services.dart'; | 11 | import 'package:flutter/services.dart'; |
| @@ -64,7 +64,10 @@ class MembershipOfferController extends GetxController { | @@ -64,7 +64,10 @@ class MembershipOfferController extends GetxController { | ||
| 64 | if (result == AliPayResultCode.success) { | 64 | if (result == AliPayResultCode.success) { |
| 65 | _trackSuccessPayOrder(); | 65 | _trackSuccessPayOrder(); |
| 66 | await _completeSuccessfulPurchase(); | 66 | await _completeSuccessfulPurchase(); |
| 67 | + } else if (result == AliPayResultCode.cancel) { | ||
| 68 | + AppToast.show(l10n.purchaseApplePaymentCancelled); | ||
| 67 | } else if (result == AliPayResultCode.error) { | 69 | } else if (result == AliPayResultCode.error) { |
| 70 | + AppToast.show(l10n.purchaseApplePaymentFailed); | ||
| 68 | if (isFromOnboard) { | 71 | if (isFromOnboard) { |
| 69 | Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true}); | 72 | Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true}); |
| 70 | } | 73 | } |
| 1 | import 'package:doublefeel_flutter/app/modules/membership_offer/controllers/membership_offer_controller.dart'; | 1 | import 'package:doublefeel_flutter/app/modules/membership_offer/controllers/membership_offer_controller.dart'; |
| 2 | import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | 2 | import 'package:doublefeel_flutter/app/routes/app_pages.dart'; |
| 3 | import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; | 3 | import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; |
| 4 | +import 'package:doublefeel_flutter/app/widget/payment_confirmation_bottom_sheet.dart'; | ||
| 4 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 5 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| 5 | import 'package:doublefeel_flutter/data/models/pay/pay_models.dart'; | 6 | import 'package:doublefeel_flutter/data/models/pay/pay_models.dart'; |
| 6 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 7 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| @@ -37,6 +38,23 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | @@ -37,6 +38,23 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | ||
| 37 | : l10n.freeRedemptionOffer, | 38 | : l10n.freeRedemptionOffer, |
| 38 | enabled: true, | 39 | enabled: true, |
| 39 | onPressed: () { | 40 | onPressed: () { |
| 41 | + if (isOhos()) { | ||
| 42 | + final product = controller.yearlyProduct.value; | ||
| 43 | + if (product == null) { | ||
| 44 | + controller.unlock(); | ||
| 45 | + return; | ||
| 46 | + } | ||
| 47 | + showPaymentConfirmationBottomSheet( | ||
| 48 | + amount: _displayPriceOhos(product), | ||
| 49 | + productName: _nonEmpty(product.name) ?? 'DoubleFeel年度会员', | ||
| 50 | + paymentMethod: const PaymentMethod( | ||
| 51 | + name: '支付宝支付', | ||
| 52 | + iconAsset: 'assets/images/premium/ic_payment_alipay.png', | ||
| 53 | + ), | ||
| 54 | + onPay: controller.unlock, | ||
| 55 | + ); | ||
| 56 | + return; | ||
| 57 | + } | ||
| 40 | controller.unlock(); | 58 | controller.unlock(); |
| 41 | }, | 59 | }, |
| 42 | ), | 60 | ), |
| @@ -7,6 +7,7 @@ import 'package:doublefeel_flutter/core/constants/app_const.dart'; | @@ -7,6 +7,7 @@ import 'package:doublefeel_flutter/core/constants/app_const.dart'; | ||
| 7 | import 'package:doublefeel_flutter/core/constants/intent_keys.dart'; | 7 | import 'package:doublefeel_flutter/core/constants/intent_keys.dart'; |
| 8 | import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; | 8 | import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; |
| 9 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; | 9 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; |
| 10 | +import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; | ||
| 10 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 11 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 11 | import 'package:doublefeel_flutter/core/services/app_config_service.dart'; | 12 | import 'package:doublefeel_flutter/core/services/app_config_service.dart'; |
| 12 | import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | 13 | import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; |
| @@ -16,7 +17,6 @@ import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; | @@ -16,7 +17,6 @@ import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; | ||
| 16 | import 'package:doublefeel_flutter/data/models/pay/apple_pay_order_response.dart'; | 17 | import 'package:doublefeel_flutter/data/models/pay/apple_pay_order_response.dart'; |
| 17 | import 'package:doublefeel_flutter/data/models/pay/pay_models.dart'; | 18 | import 'package:doublefeel_flutter/data/models/pay/pay_models.dart'; |
| 18 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 19 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 19 | -import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; | ||
| 20 | import 'package:flutter/material.dart'; | 20 | import 'package:flutter/material.dart'; |
| 21 | import 'package:get/get.dart'; | 21 | import 'package:get/get.dart'; |
| 22 | 22 | ||
| @@ -53,6 +53,7 @@ class PurchaseController extends GetxController { | @@ -53,6 +53,7 @@ class PurchaseController extends GetxController { | ||
| 53 | final UserPreferencesStorage _userPreferences = | 53 | final UserPreferencesStorage _userPreferences = |
| 54 | Get.find<UserPreferencesStorage>(); | 54 | Get.find<UserPreferencesStorage>(); |
| 55 | final AppPlatformHostApi _platformHostApi = AppPlatformHostApi(); | 55 | final AppPlatformHostApi _platformHostApi = AppPlatformHostApi(); |
| 56 | + final AlipayHostApi _alipayHostApi = AlipayHostApi(); | ||
| 56 | 57 | ||
| 57 | final scrollController = ScrollController(); | 58 | final scrollController = ScrollController(); |
| 58 | final planScrollController = ScrollController(); | 59 | final planScrollController = ScrollController(); |
| @@ -135,6 +136,12 @@ class PurchaseController extends GetxController { | @@ -135,6 +136,12 @@ class PurchaseController extends GetxController { | ||
| 135 | ); | 136 | ); |
| 136 | } | 137 | } |
| 137 | 138 | ||
| 139 | + /// Whether the current purchase flow uses HarmonyOS payment methods. | ||
| 140 | + bool get isOhos => _isOhos; | ||
| 141 | + | ||
| 142 | + /// The plan currently selected on the purchase page. | ||
| 143 | + PurchasePlan? get selectedPlan => _selectedPlan; | ||
| 144 | + | ||
| 138 | Future<void> restorePurchase() async { | 145 | Future<void> restorePurchase() async { |
| 139 | if (isRestoring.value || isUnlocking.value) return; | 146 | if (isRestoring.value || isUnlocking.value) return; |
| 140 | 147 | ||
| @@ -156,6 +163,15 @@ class PurchaseController extends GetxController { | @@ -156,6 +163,15 @@ class PurchaseController extends GetxController { | ||
| 156 | _trackClickVipCenter('立即购买'); | 163 | _trackClickVipCenter('立即购买'); |
| 157 | final plan = _selectedPlan; | 164 | final plan = _selectedPlan; |
| 158 | final productId = plan?.product?.id; | 165 | final productId = plan?.product?.id; |
| 166 | + if (_isOhos) { | ||
| 167 | + if (productId == null) { | ||
| 168 | + AppToast.show(l10n.purchaseProductInfoUnavailable); | ||
| 169 | + return; | ||
| 170 | + } | ||
| 171 | + await _unlockWithAlipay(productId); | ||
| 172 | + return; | ||
| 173 | + } | ||
| 174 | + | ||
| 159 | final appleProductId = plan?.appleProductId; | 175 | final appleProductId = plan?.appleProductId; |
| 160 | if (productId == null || appleProductId == null || appleProductId.isEmpty) { | 176 | if (productId == null || appleProductId == null || appleProductId.isEmpty) { |
| 161 | AppToast.show(l10n.purchaseProductInfoUnavailable); | 177 | AppToast.show(l10n.purchaseProductInfoUnavailable); |
| @@ -187,6 +203,41 @@ class PurchaseController extends GetxController { | @@ -187,6 +203,41 @@ class PurchaseController extends GetxController { | ||
| 187 | } | 203 | } |
| 188 | } | 204 | } |
| 189 | 205 | ||
| 206 | + /// Starts an App Pay transaction using the server-generated, RSA2-signed | ||
| 207 | + /// order string. The Alipay App ID is part of that order string, so neither | ||
| 208 | + /// the merchant private key nor any client-side signing is needed here. | ||
| 209 | + Future<void> _unlockWithAlipay(int productId) async { | ||
| 210 | + isUnlocking.value = true; | ||
| 211 | + try { | ||
| 212 | + final orderResult = await _payApi.createOrder(productId: productId); | ||
| 213 | + if (orderResult case AppFailure<CreatePayOrderResponse>(:final error)) { | ||
| 214 | + AppToast.show(error.displayMessage); | ||
| 215 | + return; | ||
| 216 | + } | ||
| 217 | + if (orderResult is! AppSuccess<CreatePayOrderResponse>) return; | ||
| 218 | + | ||
| 219 | + final prepayData = _nonEmpty(orderResult.data.prepayData); | ||
| 220 | + if (prepayData == null) { | ||
| 221 | + AppToast.show(l10n.purchaseOrderInfoUnavailable); | ||
| 222 | + return; | ||
| 223 | + } | ||
| 224 | + | ||
| 225 | + final result = await _alipayHostApi.launchAliPay(prepayData); | ||
| 226 | + if (result == AliPayResultCode.success) { | ||
| 227 | + _trackSuccessPayOrder(); | ||
| 228 | + await _completeSuccessfulPurchase(); | ||
| 229 | + } else if (result == AliPayResultCode.cancel) { | ||
| 230 | + AppToast.show(l10n.purchaseApplePaymentCancelled); | ||
| 231 | + } else if (result == AliPayResultCode.error) { | ||
| 232 | + AppToast.show(l10n.purchaseApplePaymentFailed); | ||
| 233 | + } | ||
| 234 | + } catch (_) { | ||
| 235 | + AppToast.show(l10n.purchaseApplePaymentFailed); | ||
| 236 | + } finally { | ||
| 237 | + isUnlocking.value = false; | ||
| 238 | + } | ||
| 239 | + } | ||
| 240 | + | ||
| 190 | Future<void> loadProductList() async { | 241 | Future<void> loadProductList() async { |
| 191 | isLoadingProducts.value = true; | 242 | isLoadingProducts.value = true; |
| 192 | plans.clear(); | 243 | plans.clear(); |
| @@ -196,7 +247,7 @@ class PurchaseController extends GetxController { | @@ -196,7 +247,7 @@ class PurchaseController extends GetxController { | ||
| 196 | if (result is! AppSuccess<PayProductListResponse>) return; | 247 | if (result is! AppSuccess<PayProductListResponse>) return; |
| 197 | 248 | ||
| 198 | final products = (result.data.productList ?? const <PayProduct>[]) | 249 | final products = (result.data.productList ?? const <PayProduct>[]) |
| 199 | - .where((product) => _nonEmpty(product.appleId) != null) | 250 | + .where((product) => _isOhos || _nonEmpty(product.appleId) != null) |
| 200 | .toList(); | 251 | .toList(); |
| 201 | plans.assignAll(products.map(_buildPlan)); | 252 | plans.assignAll(products.map(_buildPlan)); |
| 202 | final defaultProductIndex = products.indexWhere( | 253 | final defaultProductIndex = products.indexWhere( |
| @@ -205,11 +256,13 @@ class PurchaseController extends GetxController { | @@ -205,11 +256,13 @@ class PurchaseController extends GetxController { | ||
| 205 | selectedIndex.value = defaultProductIndex >= 0 ? defaultProductIndex : 0; | 256 | selectedIndex.value = defaultProductIndex >= 0 ? defaultProductIndex : 0; |
| 206 | isLoadingProducts.value = false; | 257 | isLoadingProducts.value = false; |
| 207 | 258 | ||
| 208 | - await Future.wait( | ||
| 209 | - products.indexed.map( | ||
| 210 | - (entry) => _loadAppleProductInfo(entry.$1, entry.$2), | ||
| 211 | - ), | ||
| 212 | - ); | 259 | + if (!_isOhos) { |
| 260 | + await Future.wait( | ||
| 261 | + products.indexed.map( | ||
| 262 | + (entry) => _loadAppleProductInfo(entry.$1, entry.$2), | ||
| 263 | + ), | ||
| 264 | + ); | ||
| 265 | + } | ||
| 213 | } finally { | 266 | } finally { |
| 214 | isLoadingProducts.value = false; | 267 | isLoadingProducts.value = false; |
| 215 | } | 268 | } |
| @@ -343,6 +396,24 @@ class PurchaseController extends GetxController { | @@ -343,6 +396,24 @@ class PurchaseController extends GetxController { | ||
| 343 | } | 396 | } |
| 344 | 397 | ||
| 345 | PurchasePlan _buildPlan(PayProduct product, [AppleProductInfo? appleInfo]) { | 398 | PurchasePlan _buildPlan(PayProduct product, [AppleProductInfo? appleInfo]) { |
| 399 | + if (_isOhos) { | ||
| 400 | + // The product API may use 0 as the default value when no discount is | ||
| 401 | + // configured. Treat only a positive discount price as an active offer; | ||
| 402 | + // otherwise preserve the product's regular price. | ||
| 403 | + final discountPrice = product.discountPrice; | ||
| 404 | + final price = discountPrice != null && discountPrice > 0 | ||
| 405 | + ? discountPrice | ||
| 406 | + : product.price; | ||
| 407 | + return PurchasePlan( | ||
| 408 | + title: _nonEmpty(product.name) ?? '', | ||
| 409 | + subtitle: _nonEmpty(product.content?.description) ?? '', | ||
| 410 | + price: price == null ? '' : _formatAlipayPrice(price), | ||
| 411 | + badge: _nonEmpty(product.content?.label) ?? '', | ||
| 412 | + product: product, | ||
| 413 | + actualPrice: price, | ||
| 414 | + ); | ||
| 415 | + } | ||
| 416 | + | ||
| 346 | var productName = appleInfo?.productName ?? _nonEmpty(product.name) ?? ''; | 417 | var productName = appleInfo?.productName ?? _nonEmpty(product.name) ?? ''; |
| 347 | return PurchasePlan( | 418 | return PurchasePlan( |
| 348 | title: productName, | 419 | title: productName, |
| @@ -382,6 +453,12 @@ class PurchaseController extends GetxController { | @@ -382,6 +453,12 @@ class PurchaseController extends GetxController { | ||
| 382 | return appleInfo.originPrice.round(); | 453 | return appleInfo.originPrice.round(); |
| 383 | } | 454 | } |
| 384 | 455 | ||
| 456 | + String _formatAlipayPrice(int amountInCents) { | ||
| 457 | + return '¥${(amountInCents / 100).toStringAsFixed(2)}'; | ||
| 458 | + } | ||
| 459 | + | ||
| 460 | + bool get _isOhos => resolveAppPigeonPlatform() == AppPigeonPlatform.ohos; | ||
| 461 | + | ||
| 385 | String? _nonEmpty(String? value) { | 462 | String? _nonEmpty(String? value) { |
| 386 | final trimmed = value?.trim(); | 463 | final trimmed = value?.trim(); |
| 387 | if (trimmed == null || trimmed.isEmpty) return null; | 464 | if (trimmed == null || trimmed.isEmpty) return null; |
| 1 | import 'package:doublefeel_flutter/app/utils/assets_helper.dart'; | 1 | import 'package:doublefeel_flutter/app/utils/assets_helper.dart'; |
| 2 | +import 'package:doublefeel_flutter/app/widget/payment_confirmation_bottom_sheet.dart'; | ||
| 2 | import 'package:doublefeel_flutter/app/widget/premium_benefit_list_view.dart'; | 3 | import 'package:doublefeel_flutter/app/widget/premium_benefit_list_view.dart'; |
| 3 | import 'package:doublefeel_flutter/core/util/size_extensions.dart'; | 4 | import 'package:doublefeel_flutter/core/util/size_extensions.dart'; |
| 4 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 5 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| @@ -85,7 +86,29 @@ class PurchaseView extends GetView<PurchaseController> { | @@ -85,7 +86,29 @@ class PurchaseView extends GetView<PurchaseController> { | ||
| 85 | () => PurchaseBottomBar( | 86 | () => PurchaseBottomBar( |
| 86 | label: context.l10n.purchaseUnlockNow, | 87 | label: context.l10n.purchaseUnlockNow, |
| 87 | loading: controller.isUnlocking.value, | 88 | loading: controller.isUnlocking.value, |
| 88 | - onPressed: controller.unlock, | 89 | + onPressed: () { |
| 90 | + if (controller.isOhos) { | ||
| 91 | + final plan = controller.selectedPlan; | ||
| 92 | + if (plan == null) { | ||
| 93 | + controller.unlock(); | ||
| 94 | + return; | ||
| 95 | + } | ||
| 96 | + showPaymentConfirmationBottomSheet( | ||
| 97 | + amount: plan.price, | ||
| 98 | + productName: plan.title.isEmpty | ||
| 99 | + ? 'DoubleFeel终身会员' | ||
| 100 | + : plan.title, | ||
| 101 | + paymentMethod: const PaymentMethod( | ||
| 102 | + name: '支付宝支付', | ||
| 103 | + iconAsset: | ||
| 104 | + 'assets/images/premium/ic_payment_alipay.png', | ||
| 105 | + ), | ||
| 106 | + onPay: controller.unlock, | ||
| 107 | + ); | ||
| 108 | + return; | ||
| 109 | + } | ||
| 110 | + controller.unlock(); | ||
| 111 | + }, | ||
| 89 | onTermsTap: controller.openUserTerms, | 112 | onTermsTap: controller.openUserTerms, |
| 90 | onPrivacyTap: controller.openPrivacyPolicy, | 113 | onPrivacyTap: controller.openPrivacyPolicy, |
| 91 | ), | 114 | ), |
| 1 | import 'dart:math' as math; | 1 | import 'dart:math' as math; |
| 2 | 2 | ||
| 3 | +import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; | ||
| 4 | +import 'package:doublefeel_flutter/app/widget/circular_gradient_progress/arc_progress_widget.dart'; | ||
| 3 | import 'package:flutter/material.dart'; | 5 | import 'package:flutter/material.dart'; |
| 4 | 6 | ||
| 5 | import '../models/sleep_report_models.dart'; | 7 | import '../models/sleep_report_models.dart'; |
| 6 | 8 | ||
| 7 | -class SleepQualityRing extends StatefulWidget { | 9 | +/// 睡眠报告进度环。 |
| 10 | +/// | ||
| 11 | +/// OHOS 使用开口弧形样式,其它平台维持原有的闭合双环;报表页面无需 | ||
| 12 | +/// 自行处理平台差异。 | ||
| 13 | +class SleepQualityRing extends StatelessWidget { | ||
| 8 | const SleepQualityRing({ | 14 | const SleepQualityRing({ |
| 9 | super.key, | 15 | super.key, |
| 10 | required this.report, | 16 | required this.report, |
| @@ -13,10 +19,66 @@ class SleepQualityRing extends StatefulWidget { | @@ -13,10 +19,66 @@ class SleepQualityRing extends StatefulWidget { | ||
| 13 | final SleepReport? report; | 19 | final SleepReport? report; |
| 14 | 20 | ||
| 15 | @override | 21 | @override |
| 16 | - State<SleepQualityRing> createState() => _SleepQualityRingState(); | 22 | + Widget build(BuildContext context) => isOhos() |
| 23 | + ? _OhosSleepQualityRing(report: report) | ||
| 24 | + : _CircularSleepQualityRing(report: report); | ||
| 17 | } | 25 | } |
| 18 | 26 | ||
| 19 | -class _SleepQualityRingState extends State<SleepQualityRing> | 27 | +class _OhosSleepQualityRing extends StatelessWidget { |
| 28 | + const _OhosSleepQualityRing({required this.report}); | ||
| 29 | + | ||
| 30 | + final SleepReport? report; | ||
| 31 | + | ||
| 32 | + @override | ||
| 33 | + Widget build(BuildContext context) { | ||
| 34 | + final durationSeconds = | ||
| 35 | + report?.sleepDurationSeconds ?? (report?.duration?.minutes ?? 0) * 60; | ||
| 36 | + final targetSeconds = report?.targetDurationSeconds; | ||
| 37 | + final durationRatio = | ||
| 38 | + durationSeconds <= 0 || targetSeconds == null || targetSeconds <= 0 | ||
| 39 | + ? null | ||
| 40 | + : (durationSeconds / targetSeconds).clamp(0, 1).toDouble(); | ||
| 41 | + final qualityScore = report?.validQualityScore; | ||
| 42 | + | ||
| 43 | + return ArcMultiProgressWidget( | ||
| 44 | + size: 200, | ||
| 45 | + strokeWidth: 22, | ||
| 46 | + gap: 4, | ||
| 47 | + startAngleDeg: 140, | ||
| 48 | + sweepTotalDeg: 260, | ||
| 49 | + duration: const Duration(seconds: 1), | ||
| 50 | + curve: Curves.easeInOutQuad, | ||
| 51 | + items: [ | ||
| 52 | + ArcProgressItem( | ||
| 53 | + ratio: durationRatio, | ||
| 54 | + color: const Color(0xFF7B59EE), | ||
| 55 | + backgroundColor: const Color(0xFFECE7FB), | ||
| 56 | + noDataBackgroundColor: const Color(0xFFF0F1F5), | ||
| 57 | + ), | ||
| 58 | + ArcProgressItem( | ||
| 59 | + ratio: qualityScore == null | ||
| 60 | + ? null | ||
| 61 | + : (qualityScore.clamp(0, 100) / 100).toDouble(), | ||
| 62 | + color: const Color(0xFF638BFB), | ||
| 63 | + backgroundColor: const Color(0xFFE8EFFD), | ||
| 64 | + noDataBackgroundColor: const Color(0xFFF0F1F5), | ||
| 65 | + ), | ||
| 66 | + ], | ||
| 67 | + ); | ||
| 68 | + } | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +class _CircularSleepQualityRing extends StatefulWidget { | ||
| 72 | + const _CircularSleepQualityRing({required this.report}); | ||
| 73 | + | ||
| 74 | + final SleepReport? report; | ||
| 75 | + | ||
| 76 | + @override | ||
| 77 | + State<_CircularSleepQualityRing> createState() => | ||
| 78 | + _CircularSleepQualityRingState(); | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +class _CircularSleepQualityRingState extends State<_CircularSleepQualityRing> | ||
| 20 | with SingleTickerProviderStateMixin { | 82 | with SingleTickerProviderStateMixin { |
| 21 | late final AnimationController _controller; | 83 | late final AnimationController _controller; |
| 22 | late Animation<double> _durationProgress; | 84 | late Animation<double> _durationProgress; |
| @@ -46,7 +108,7 @@ class _SleepQualityRingState extends State<SleepQualityRing> | @@ -46,7 +108,7 @@ class _SleepQualityRingState extends State<SleepQualityRing> | ||
| 46 | } | 108 | } |
| 47 | 109 | ||
| 48 | @override | 110 | @override |
| 49 | - void didUpdateWidget(covariant SleepQualityRing oldWidget) { | 111 | + void didUpdateWidget(covariant _CircularSleepQualityRing oldWidget) { |
| 50 | super.didUpdateWidget(oldWidget); | 112 | super.didUpdateWidget(oldWidget); |
| 51 | final oldQualityProgress = | 113 | final oldQualityProgress = |
| 52 | ((oldWidget.report?.validQualityScore ?? 0).clamp(0, 100) / 100) | 114 | ((oldWidget.report?.validQualityScore ?? 0).clamp(0, 100) / 100) |
| 1 | +import 'package:get/get.dart'; | ||
| 2 | + | ||
| 3 | +import '../controllers/hw_health_auth_webview_controller.dart'; | ||
| 4 | + | ||
| 5 | +class HwHealthAuthWebviewBinding extends Bindings { | ||
| 6 | + @override | ||
| 7 | + void dependencies() { | ||
| 8 | + Get.lazyPut<HwHealthAuthWebviewController>( | ||
| 9 | + HwHealthAuthWebviewController.new, | ||
| 10 | + ); | ||
| 11 | + } | ||
| 12 | +} |
| 1 | +import 'package:get/get.dart'; | ||
| 2 | +import 'package:webview_flutter/webview_flutter.dart'; | ||
| 3 | + | ||
| 4 | +/// OAuth callback payload returned after Huawei Health authorization finishes. | ||
| 5 | +class HealthAuthEvent { | ||
| 6 | + const HealthAuthEvent({this.code, this.state, this.error}); | ||
| 7 | + | ||
| 8 | + final String? code; | ||
| 9 | + final String? state; | ||
| 10 | + final String? error; | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +/// Arguments for the Huawei Health authorization WebView route. | ||
| 14 | +class HwHealthAuthWebviewArguments { | ||
| 15 | + const HwHealthAuthWebviewArguments({ | ||
| 16 | + required this.authorizationUrl, | ||
| 17 | + this.onHealthAuthEvent, | ||
| 18 | + }); | ||
| 19 | + | ||
| 20 | + final String authorizationUrl; | ||
| 21 | + final void Function(HealthAuthEvent event)? onHealthAuthEvent; | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +/// GetX controller for the dedicated Huawei Health Kit OAuth WebView. | ||
| 25 | +class HwHealthAuthWebviewController extends GetxController { | ||
| 26 | + static const _callbackUrl = | ||
| 27 | + 'https://h5hosting.dbankcdn.com/cch5/healthkit/oauth-h5/oauth-callback.html'; | ||
| 28 | + | ||
| 29 | + late final WebViewController webViewController; | ||
| 30 | + final isLoading = true.obs; | ||
| 31 | + bool _callbackHandled = false; | ||
| 32 | + | ||
| 33 | + HwHealthAuthWebviewArguments? get _arguments => | ||
| 34 | + Get.arguments is HwHealthAuthWebviewArguments | ||
| 35 | + ? Get.arguments as HwHealthAuthWebviewArguments | ||
| 36 | + : null; | ||
| 37 | + | ||
| 38 | + String get authorizationUrl => _arguments?.authorizationUrl ?? ''; | ||
| 39 | + | ||
| 40 | + @override | ||
| 41 | + void onInit() { | ||
| 42 | + super.onInit(); | ||
| 43 | + final target = Uri.tryParse(authorizationUrl); | ||
| 44 | + webViewController = WebViewController() | ||
| 45 | + ..setJavaScriptMode(JavaScriptMode.unrestricted) | ||
| 46 | + ..setNavigationDelegate( | ||
| 47 | + NavigationDelegate( | ||
| 48 | + onNavigationRequest: _handleNavigation, | ||
| 49 | + onPageStarted: (_) => isLoading.value = true, | ||
| 50 | + onPageFinished: (_) => isLoading.value = false, | ||
| 51 | + onWebResourceError: (_) => isLoading.value = false, | ||
| 52 | + ), | ||
| 53 | + ) | ||
| 54 | + ..loadRequest(target ?? Uri.parse('about:blank')); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + NavigationDecision _handleNavigation(NavigationRequest request) { | ||
| 58 | + final uri = Uri.tryParse(request.url); | ||
| 59 | + if (uri == null) return NavigationDecision.prevent; | ||
| 60 | + | ||
| 61 | + final baseUrl = Uri( | ||
| 62 | + scheme: uri.scheme, | ||
| 63 | + host: uri.host, | ||
| 64 | + port: uri.hasPort ? uri.port : null, | ||
| 65 | + path: uri.path, | ||
| 66 | + ).toString(); | ||
| 67 | + if (baseUrl != _callbackUrl) return NavigationDecision.navigate; | ||
| 68 | + | ||
| 69 | + if (!_callbackHandled) { | ||
| 70 | + _callbackHandled = true; | ||
| 71 | + final event = HealthAuthEvent( | ||
| 72 | + code: uri.queryParameters['code'], | ||
| 73 | + state: uri.queryParameters['state'], | ||
| 74 | + error: uri.queryParameters['error'], | ||
| 75 | + ); | ||
| 76 | + _postHealthAuthEvent(event); | ||
| 77 | + Get.back<HealthAuthEvent>(result: event); | ||
| 78 | + } | ||
| 79 | + return NavigationDecision.prevent; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + void _postHealthAuthEvent(HealthAuthEvent event) { | ||
| 83 | + _arguments?.onHealthAuthEvent?.call(event); | ||
| 84 | + } | ||
| 85 | +} |
| 1 | +import 'package:flutter/material.dart'; | ||
| 2 | +import 'package:get/get.dart'; | ||
| 3 | +import 'package:webview_flutter/webview_flutter.dart'; | ||
| 4 | + | ||
| 5 | +import '../controllers/hw_health_auth_webview_controller.dart'; | ||
| 6 | + | ||
| 7 | +/// Dedicated WebView for the Huawei Health Kit OAuth authorization flow. | ||
| 8 | +class HwHealthAuthWebviewPage extends GetView<HwHealthAuthWebviewController> { | ||
| 9 | + const HwHealthAuthWebviewPage({super.key}); | ||
| 10 | + | ||
| 11 | + @override | ||
| 12 | + Widget build(BuildContext context) { | ||
| 13 | + return Scaffold( | ||
| 14 | + appBar: AppBar(title: const Text('')), | ||
| 15 | + body: Stack( | ||
| 16 | + children: [ | ||
| 17 | + WebViewWidget(controller: controller.webViewController), | ||
| 18 | + Obx( | ||
| 19 | + () => controller.isLoading.value | ||
| 20 | + ? const Center(child: CircularProgressIndicator()) | ||
| 21 | + : const SizedBox.shrink(), | ||
| 22 | + ), | ||
| 23 | + ], | ||
| 24 | + ), | ||
| 25 | + ); | ||
| 26 | + } | ||
| 27 | +} |
| @@ -3,10 +3,13 @@ import 'package:doublefeel_flutter/app/modules/friends/views/friend_home_page.da | @@ -3,10 +3,13 @@ import 'package:doublefeel_flutter/app/modules/friends/views/friend_home_page.da | ||
| 3 | import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_controller.dart'; | 3 | import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_controller.dart'; |
| 4 | import 'package:doublefeel_flutter/app/modules/membership_offer/bindings/membership_detail_binding.dart'; | 4 | import 'package:doublefeel_flutter/app/modules/membership_offer/bindings/membership_detail_binding.dart'; |
| 5 | import 'package:doublefeel_flutter/app/modules/membership_offer/views/membership_detail_view.dart'; | 5 | import 'package:doublefeel_flutter/app/modules/membership_offer/views/membership_detail_view.dart'; |
| 6 | +import 'package:doublefeel_flutter/app/modules/webview/bindings/hw_health_auth_webview_binding.dart'; | ||
| 7 | +import 'package:doublefeel_flutter/app/modules/webview/views/hw_health_auth_webview_page.dart'; | ||
| 6 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; | 8 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; |
| 7 | - | ||
| 8 | import 'package:get/get.dart'; | 9 | import 'package:get/get.dart'; |
| 9 | 10 | ||
| 11 | +import '../modules/account_settings/bindings/account_settings_binding.dart'; | ||
| 12 | +import '../modules/account_settings/views/account_settings_view.dart'; | ||
| 10 | import '../modules/bind_partner/bindings/bind_partner_binding.dart'; | 13 | import '../modules/bind_partner/bindings/bind_partner_binding.dart'; |
| 11 | import '../modules/bind_partner/views/bind_partner_view.dart'; | 14 | import '../modules/bind_partner/views/bind_partner_view.dart'; |
| 12 | import '../modules/config/bindings/developer_options_binding.dart'; | 15 | import '../modules/config/bindings/developer_options_binding.dart'; |
| @@ -17,6 +20,8 @@ import '../modules/devtools/bindings/route_list_binding.dart'; | @@ -17,6 +20,8 @@ import '../modules/devtools/bindings/route_list_binding.dart'; | ||
| 17 | import '../modules/devtools/views/route_list_view.dart'; | 20 | import '../modules/devtools/views/route_list_view.dart'; |
| 18 | import '../modules/feedback/feedback_list/bindings/feedback_list_binding.dart'; | 21 | import '../modules/feedback/feedback_list/bindings/feedback_list_binding.dart'; |
| 19 | import '../modules/feedback/feedback_list/views/feedback_list_view.dart'; | 22 | import '../modules/feedback/feedback_list/views/feedback_list_view.dart'; |
| 23 | +import '../modules/feedback/submit_feedback/bindings/submit_feedback_binding.dart'; | ||
| 24 | +import '../modules/feedback/submit_feedback/views/submit_feedback_view.dart'; | ||
| 20 | import '../modules/friends/bindings/add_friend_binding.dart'; | 25 | import '../modules/friends/bindings/add_friend_binding.dart'; |
| 21 | import '../modules/friends/bindings/friend_trend_binding.dart'; | 26 | import '../modules/friends/bindings/friend_trend_binding.dart'; |
| 22 | import '../modules/friends/views/add_friend_view.dart'; | 27 | import '../modules/friends/views/add_friend_view.dart'; |
| @@ -25,10 +30,13 @@ import '../modules/help/bindings/help_binding.dart'; | @@ -25,10 +30,13 @@ import '../modules/help/bindings/help_binding.dart'; | ||
| 25 | import '../modules/help/views/help_view.dart'; | 30 | import '../modules/help/views/help_view.dart'; |
| 26 | import '../modules/home/bindings/home_binding.dart'; | 31 | import '../modules/home/bindings/home_binding.dart'; |
| 27 | import '../modules/home/views/home_page.dart'; | 32 | import '../modules/home/views/home_page.dart'; |
| 33 | +import '../modules/home/widgets/my/security_email_views.dart'; | ||
| 28 | import '../modules/login/bindings/login_binding.dart'; | 34 | import '../modules/login/bindings/login_binding.dart'; |
| 29 | import '../modules/login/views/debug_environment_view.dart'; | 35 | import '../modules/login/views/debug_environment_view.dart'; |
| 30 | import '../modules/login/views/login_view.dart'; | 36 | import '../modules/login/views/login_view.dart'; |
| 31 | import '../modules/login/views/phone_login_view.dart'; | 37 | import '../modules/login/views/phone_login_view.dart'; |
| 38 | +import '../modules/membership_offer/bindings/membership_offer_binding.dart'; | ||
| 39 | +import '../modules/membership_offer/views/membership_offer_view.dart'; | ||
| 32 | import '../modules/premium/bindings/premium_activated_binding.dart'; | 40 | import '../modules/premium/bindings/premium_activated_binding.dart'; |
| 33 | import '../modules/premium/views/premium_activated_view.dart'; | 41 | import '../modules/premium/views/premium_activated_view.dart'; |
| 34 | import '../modules/privacy_settings/bindings/privacy_settings_binding.dart'; | 42 | import '../modules/privacy_settings/bindings/privacy_settings_binding.dart'; |
| @@ -37,20 +45,13 @@ import '../modules/purchase/bindings/purchase_binding.dart'; | @@ -37,20 +45,13 @@ import '../modules/purchase/bindings/purchase_binding.dart'; | ||
| 37 | import '../modules/purchase/views/purchase_view.dart'; | 45 | import '../modules/purchase/views/purchase_view.dart'; |
| 38 | import '../modules/splash/bindings/splash_binding.dart'; | 46 | import '../modules/splash/bindings/splash_binding.dart'; |
| 39 | import '../modules/splash/views/splash_page.dart'; | 47 | import '../modules/splash/views/splash_page.dart'; |
| 40 | -import '../modules/feedback/submit_feedback/bindings/submit_feedback_binding.dart'; | ||
| 41 | -import '../modules/feedback/submit_feedback/views/submit_feedback_view.dart'; | ||
| 42 | import '../modules/user_onboarding/bindings/user_onboarding_binding.dart'; | 48 | import '../modules/user_onboarding/bindings/user_onboarding_binding.dart'; |
| 43 | import '../modules/user_onboarding/views/user_onboarding_view.dart'; | 49 | import '../modules/user_onboarding/views/user_onboarding_view.dart'; |
| 44 | -import '../modules/membership_offer/bindings/membership_offer_binding.dart'; | ||
| 45 | -import '../modules/membership_offer/views/membership_offer_view.dart'; | ||
| 46 | import '../modules/watch_theme/bindings/watch_theme_binding.dart'; | 50 | import '../modules/watch_theme/bindings/watch_theme_binding.dart'; |
| 47 | -import '../modules/watch_theme/views/custom_watch_theme_preview_view.dart'; | ||
| 48 | import '../modules/watch_theme/views/create_watch_theme_view.dart'; | 51 | import '../modules/watch_theme/views/create_watch_theme_view.dart'; |
| 52 | +import '../modules/watch_theme/views/custom_watch_theme_preview_view.dart'; | ||
| 49 | import '../modules/watch_theme/views/watch_theme_preview_view.dart'; | 53 | import '../modules/watch_theme/views/watch_theme_preview_view.dart'; |
| 50 | import '../modules/watch_theme/views/watch_theme_view.dart'; | 54 | import '../modules/watch_theme/views/watch_theme_view.dart'; |
| 51 | -import '../modules/account_settings/bindings/account_settings_binding.dart'; | ||
| 52 | -import '../modules/account_settings/views/account_settings_view.dart'; | ||
| 53 | -import '../modules/home/widgets/my/security_email_views.dart'; | ||
| 54 | import '../modules/webview/bindings/webview_binding.dart'; | 55 | import '../modules/webview/bindings/webview_binding.dart'; |
| 55 | import '../modules/webview/views/webview_page.dart'; | 56 | import '../modules/webview/views/webview_page.dart'; |
| 56 | 57 | ||
| @@ -108,6 +109,11 @@ abstract final class AppPages { | @@ -108,6 +109,11 @@ abstract final class AppPages { | ||
| 108 | binding: WebviewBinding(), | 109 | binding: WebviewBinding(), |
| 109 | ), | 110 | ), |
| 110 | GetPage( | 111 | GetPage( |
| 112 | + name: Routes.HW_HEALTH_AUTH_WEB_VIEW, | ||
| 113 | + page: () => const HwHealthAuthWebviewPage(), | ||
| 114 | + binding: HwHealthAuthWebviewBinding(), | ||
| 115 | + ), | ||
| 116 | + GetPage( | ||
| 111 | name: AppRoutes.userOnboarding, | 117 | name: AppRoutes.userOnboarding, |
| 112 | page: () => const UserOnboardingView(), | 118 | page: () => const UserOnboardingView(), |
| 113 | binding: UserOnboardingBinding(), | 119 | binding: UserOnboardingBinding(), |
| @@ -29,6 +29,7 @@ abstract class Routes { | @@ -29,6 +29,7 @@ abstract class Routes { | ||
| 29 | static const ADD_SECURITY_EMAIL = _Paths.ADD_SECURITY_EMAIL; | 29 | static const ADD_SECURITY_EMAIL = _Paths.ADD_SECURITY_EMAIL; |
| 30 | static const CHANGE_SECURITY_EMAIL = _Paths.CHANGE_SECURITY_EMAIL; | 30 | static const CHANGE_SECURITY_EMAIL = _Paths.CHANGE_SECURITY_EMAIL; |
| 31 | static const RESET_PASSWORD = _Paths.RESET_PASSWORD; | 31 | static const RESET_PASSWORD = _Paths.RESET_PASSWORD; |
| 32 | + static const HW_HEALTH_AUTH_WEB_VIEW = _Paths.HW_HEALTH_AUTH_WEB_VIEW; | ||
| 32 | } | 33 | } |
| 33 | 34 | ||
| 34 | abstract class _Paths { | 35 | abstract class _Paths { |
| @@ -59,4 +60,5 @@ abstract class _Paths { | @@ -59,4 +60,5 @@ abstract class _Paths { | ||
| 59 | static const ADD_SECURITY_EMAIL = '/add-security-email'; | 60 | static const ADD_SECURITY_EMAIL = '/add-security-email'; |
| 60 | static const CHANGE_SECURITY_EMAIL = '/change-security-email'; | 61 | static const CHANGE_SECURITY_EMAIL = '/change-security-email'; |
| 61 | static const RESET_PASSWORD = '/reset-password'; | 62 | static const RESET_PASSWORD = '/reset-password'; |
| 63 | + static const HW_HEALTH_AUTH_WEB_VIEW = '/hw-health-auth-webview'; | ||
| 62 | } | 64 | } |
| 1 | +import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | ||
| 2 | +import 'package:flutter/material.dart'; | ||
| 3 | +import 'package:get/get.dart'; | ||
| 4 | + | ||
| 5 | +/// A payment method displayed in [PaymentConfirmationBottomSheet]. | ||
| 6 | +class PaymentMethod { | ||
| 7 | + const PaymentMethod({ | ||
| 8 | + required this.name, | ||
| 9 | + required this.iconAsset, | ||
| 10 | + }); | ||
| 11 | + | ||
| 12 | + final String name; | ||
| 13 | + final String iconAsset; | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +/// Opens the shared payment confirmation bottom sheet. | ||
| 17 | +void showPaymentConfirmationBottomSheet({ | ||
| 18 | + required String amount, | ||
| 19 | + required String productName, | ||
| 20 | + required PaymentMethod paymentMethod, | ||
| 21 | + required VoidCallback onPay, | ||
| 22 | +}) { | ||
| 23 | + Get.bottomSheet<void>( | ||
| 24 | + PaymentConfirmationBottomSheet( | ||
| 25 | + amount: amount, | ||
| 26 | + productName: productName, | ||
| 27 | + paymentMethod: paymentMethod, | ||
| 28 | + onPay: onPay, | ||
| 29 | + ), | ||
| 30 | + barrierColor: Colors.black.withValues(alpha: 0.72), | ||
| 31 | + isScrollControlled: true, | ||
| 32 | + enableDrag: false, | ||
| 33 | + ); | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +/// Shared confirmation sheet for a single, preselected payment method. | ||
| 37 | +class PaymentConfirmationBottomSheet extends StatelessWidget { | ||
| 38 | + const PaymentConfirmationBottomSheet({ | ||
| 39 | + super.key, | ||
| 40 | + required this.amount, | ||
| 41 | + required this.productName, | ||
| 42 | + required this.paymentMethod, | ||
| 43 | + required this.onPay, | ||
| 44 | + }); | ||
| 45 | + | ||
| 46 | + final String amount; | ||
| 47 | + final String productName; | ||
| 48 | + final PaymentMethod paymentMethod; | ||
| 49 | + final VoidCallback onPay; | ||
| 50 | + | ||
| 51 | + @override | ||
| 52 | + Widget build(BuildContext context) { | ||
| 53 | + return Container( | ||
| 54 | + decoration: const BoxDecoration( | ||
| 55 | + color: Color(0xFFF6F3FF), | ||
| 56 | + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), | ||
| 57 | + ), | ||
| 58 | + child: SafeArea( | ||
| 59 | + top: false, | ||
| 60 | + child: Padding( | ||
| 61 | + padding: const EdgeInsets.fromLTRB(16, 14, 16, 18), | ||
| 62 | + child: Column( | ||
| 63 | + mainAxisSize: MainAxisSize.min, | ||
| 64 | + children: [ | ||
| 65 | + SizedBox( | ||
| 66 | + height: 30, | ||
| 67 | + width: double.infinity, | ||
| 68 | + child: Stack( | ||
| 69 | + alignment: Alignment.center, | ||
| 70 | + children: [ | ||
| 71 | + const Text( | ||
| 72 | + '待支付', | ||
| 73 | + style: TextStyle( | ||
| 74 | + color: Color(0xFF0F0F11), | ||
| 75 | + fontSize: 16, | ||
| 76 | + fontWeight: FontWeight.w600, | ||
| 77 | + ), | ||
| 78 | + ), | ||
| 79 | + Positioned( | ||
| 80 | + left: 0, | ||
| 81 | + child: IconButton( | ||
| 82 | + onPressed: Get.back, | ||
| 83 | + icon: const Icon(Icons.close_rounded), | ||
| 84 | + color: const Color(0xFF8961F8), | ||
| 85 | + iconSize: 22, | ||
| 86 | + splashRadius: 20, | ||
| 87 | + ), | ||
| 88 | + ), | ||
| 89 | + ], | ||
| 90 | + ), | ||
| 91 | + ), | ||
| 92 | + const SizedBox(height: 18), | ||
| 93 | + Text( | ||
| 94 | + amount, | ||
| 95 | + style: const TextStyle( | ||
| 96 | + color: Colors.black, | ||
| 97 | + fontSize: 40, | ||
| 98 | + fontWeight: FontWeight.w700, | ||
| 99 | + height: 1, | ||
| 100 | + ), | ||
| 101 | + ), | ||
| 102 | + const SizedBox(height: 12), | ||
| 103 | + Row( | ||
| 104 | + mainAxisAlignment: MainAxisAlignment.center, | ||
| 105 | + children: [ | ||
| 106 | + Container( | ||
| 107 | + width: 16, | ||
| 108 | + height: 16, | ||
| 109 | + clipBehavior: Clip.antiAlias, | ||
| 110 | + padding: EdgeInsets.all(1), | ||
| 111 | + decoration: ShapeDecoration( | ||
| 112 | + shape: RoundedRectangleBorder( | ||
| 113 | + borderRadius: BorderRadius.circular(20), | ||
| 114 | + ), | ||
| 115 | + color: context.colors.primary), | ||
| 116 | + child: Image.asset('assets/images/common/ic_pro.png'), | ||
| 117 | + ), | ||
| 118 | + const SizedBox(width: 2), | ||
| 119 | + Text( | ||
| 120 | + productName, | ||
| 121 | + style: const TextStyle( | ||
| 122 | + color: Color(0xFF78787D), | ||
| 123 | + fontSize: 14, | ||
| 124 | + ), | ||
| 125 | + ), | ||
| 126 | + ], | ||
| 127 | + ), | ||
| 128 | + const SizedBox(height: 22), | ||
| 129 | + const Divider(height: 1, color: Color(0xFFE3DEEF)), | ||
| 130 | + const Padding( | ||
| 131 | + padding: EdgeInsets.only(top: 22, bottom: 14), | ||
| 132 | + child: Align( | ||
| 133 | + alignment: Alignment.centerLeft, | ||
| 134 | + child: Text( | ||
| 135 | + '支付方式', | ||
| 136 | + style: TextStyle( | ||
| 137 | + color: Color(0xFF0F0F11), | ||
| 138 | + fontSize: 15, | ||
| 139 | + fontWeight: FontWeight.w600, | ||
| 140 | + ), | ||
| 141 | + ), | ||
| 142 | + ), | ||
| 143 | + ), | ||
| 144 | + _PaymentMethodTile(paymentMethod: paymentMethod), | ||
| 145 | + const SizedBox(height: 20), | ||
| 146 | + SizedBox( | ||
| 147 | + width: 280, | ||
| 148 | + height: 48, | ||
| 149 | + child: ElevatedButton( | ||
| 150 | + onPressed: () { | ||
| 151 | + Get.back(); | ||
| 152 | + onPay(); | ||
| 153 | + }, | ||
| 154 | + style: ElevatedButton.styleFrom( | ||
| 155 | + elevation: 0, | ||
| 156 | + backgroundColor: const Color(0xFF8961F8), | ||
| 157 | + foregroundColor: Colors.white, | ||
| 158 | + shape: const StadiumBorder(), | ||
| 159 | + textStyle: const TextStyle( | ||
| 160 | + fontSize: 16, | ||
| 161 | + fontWeight: FontWeight.w600, | ||
| 162 | + ), | ||
| 163 | + ), | ||
| 164 | + child: const Text('立即支付'), | ||
| 165 | + ), | ||
| 166 | + ), | ||
| 167 | + ], | ||
| 168 | + ), | ||
| 169 | + ), | ||
| 170 | + ), | ||
| 171 | + ); | ||
| 172 | + } | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +class _PaymentMethodTile extends StatelessWidget { | ||
| 176 | + const _PaymentMethodTile({required this.paymentMethod}); | ||
| 177 | + | ||
| 178 | + final PaymentMethod paymentMethod; | ||
| 179 | + | ||
| 180 | + @override | ||
| 181 | + Widget build(BuildContext context) { | ||
| 182 | + return Container( | ||
| 183 | + height: 56, | ||
| 184 | + padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 185 | + decoration: BoxDecoration( | ||
| 186 | + color: Colors.white, | ||
| 187 | + borderRadius: BorderRadius.circular(16), | ||
| 188 | + border: Border.all(color: const Color(0xFF845EEE)), | ||
| 189 | + ), | ||
| 190 | + child: Row( | ||
| 191 | + children: [ | ||
| 192 | + Image.asset(paymentMethod.iconAsset, width: 28, height: 28), | ||
| 193 | + const SizedBox(width: 10), | ||
| 194 | + Text( | ||
| 195 | + paymentMethod.name, | ||
| 196 | + style: const TextStyle( | ||
| 197 | + color: Color(0xFF0F0F11), | ||
| 198 | + fontSize: 15, | ||
| 199 | + ), | ||
| 200 | + ), | ||
| 201 | + const Spacer(), | ||
| 202 | + const Icon(Icons.check_circle_rounded, | ||
| 203 | + color: Color(0xFF8961F8), size: 22), | ||
| 204 | + ], | ||
| 205 | + ), | ||
| 206 | + ); | ||
| 207 | + } | ||
| 208 | +} |
| @@ -96,6 +96,23 @@ class AppEnvironmentConfig { | @@ -96,6 +96,23 @@ class AppEnvironmentConfig { | ||
| 96 | return resolveServerUrl(regionUrl); | 96 | return resolveServerUrl(regionUrl); |
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | + /// WebSocket endpoint resolved from the active API host. | ||
| 100 | + /// | ||
| 101 | + /// This keeps the socket in the same China/global and dev/xlab environment | ||
| 102 | + /// as the REST APIs. | ||
| 103 | + String get webSocketUrl { | ||
| 104 | + final serverUri = Uri.parse(serverBaseUrl); | ||
| 105 | + final webSocketScheme = serverUri.scheme == 'https' ? 'wss' : 'ws'; | ||
| 106 | + return serverUri | ||
| 107 | + .replace( | ||
| 108 | + scheme: webSocketScheme, | ||
| 109 | + path: AppConst.webSocketPath, | ||
| 110 | + query: null, | ||
| 111 | + fragment: null, | ||
| 112 | + ) | ||
| 113 | + .toString(); | ||
| 114 | + } | ||
| 115 | + | ||
| 99 | String resolveServerUrl(String url) { | 116 | String resolveServerUrl(String url) { |
| 100 | return resolveServerUrlFor(environment.value, url); | 117 | return resolveServerUrlFor(environment.value, url); |
| 101 | } | 118 | } |
| @@ -5,6 +5,9 @@ abstract final class AppConst { | @@ -5,6 +5,9 @@ abstract final class AppConst { | ||
| 5 | static const String serverBaseUrl = 'https://api.doublefeel.cn'; | 5 | static const String serverBaseUrl = 'https://api.doublefeel.cn'; |
| 6 | static const String serverBaseUrlGlobal = 'https://api-oversea.doublefeel.cn'; | 6 | static const String serverBaseUrlGlobal = 'https://api-oversea.doublefeel.cn'; |
| 7 | 7 | ||
| 8 | + /// WebSocket path supplied by the DoubleFeel server. | ||
| 9 | + static const String webSocketPath = '/client/doublefeel/ws/'; | ||
| 10 | + | ||
| 8 | static const String prefixXlabServerHost = 'xlab'; | 11 | static const String prefixXlabServerHost = 'xlab'; |
| 9 | static const String prefixDevServerHost = 'dev'; | 12 | static const String prefixDevServerHost = 'dev'; |
| 10 | 13 |
lib/core/network/api/harmony_api.dart
0 → 100644
| 1 | +import 'package:doublefeel_flutter/core/error/http_error_handling_policy.dart'; | ||
| 2 | +import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/huawei_health_data_type.dart'; | ||
| 3 | +import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart'; | ||
| 4 | +import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart'; | ||
| 5 | +import 'package:doublefeel_flutter/data/models/harmony/privacy_records.dart'; | ||
| 6 | + | ||
| 7 | +import '../../result/app_result.dart'; | ||
| 8 | +import '../../result/safe_call.dart'; | ||
| 9 | +import '../api_paths.dart'; | ||
| 10 | +import '../dio_client.dart'; | ||
| 11 | + | ||
| 12 | +class HarmonyApi { | ||
| 13 | + HarmonyApi(this._dioClient); | ||
| 14 | + | ||
| 15 | + final DioClient _dioClient; | ||
| 16 | + | ||
| 17 | + Future<AppResult<void>> postHmAuth(String code) { | ||
| 18 | + return safeCall( | ||
| 19 | + call: () async { | ||
| 20 | + await _dioClient.dio.post( | ||
| 21 | + ApiPaths.hmAuth, | ||
| 22 | + data: { | ||
| 23 | + 'code': code, | ||
| 24 | + }, | ||
| 25 | + ); | ||
| 26 | + }, | ||
| 27 | + ); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + Future<AppResult<PrivacyRecordsResp>> getPrivacyRecords() { | ||
| 31 | + return safeCall( | ||
| 32 | + call: () async { | ||
| 33 | + final response = await _dioClient.dio.get(ApiPaths.hmPrivacyRecords); | ||
| 34 | + return PrivacyRecordsResp.fromJson( | ||
| 35 | + response.data as Map<String, dynamic>, | ||
| 36 | + ); | ||
| 37 | + }, | ||
| 38 | + errorHandlingPolicy: | ||
| 39 | + HttpErrorHandlingPolicy(excludeErrorCodeList: {-100}), | ||
| 40 | + ); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /// startDate: 开始日期20260801 | ||
| 44 | + /// endDate: 截止日期20260801,和开始日期最大相隔31天 | ||
| 45 | + Future<AppResult<HmHealthData>> getHealthData( | ||
| 46 | + HuaweiHealthDataType dataType, int startDate, int endDate) { | ||
| 47 | + return safeCall( | ||
| 48 | + call: () async { | ||
| 49 | + final queryParameters = <String, dynamic>{ | ||
| 50 | + 'date_type': dataType.dataType, | ||
| 51 | + 'start_date': startDate, | ||
| 52 | + 'end_date': endDate, | ||
| 53 | + }; | ||
| 54 | + | ||
| 55 | + final response = await _dioClient.dio.get( | ||
| 56 | + ApiPaths.hmHealthData, | ||
| 57 | + queryParameters: queryParameters, | ||
| 58 | + ); | ||
| 59 | + return HmHealthData.fromJson( | ||
| 60 | + response.data as Map<String, dynamic>, | ||
| 61 | + ); | ||
| 62 | + }, | ||
| 63 | + ); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + /// startDate: 开始日期20260801 | ||
| 67 | + /// endDate: 截止日期20260801,和开始日期最大相隔31天 | ||
| 68 | + Future<AppResult<HmSleepData>> getSleepData(int startDate, int endDate) { | ||
| 69 | + return safeCall( | ||
| 70 | + call: () async { | ||
| 71 | + final queryParameters = <String, dynamic>{ | ||
| 72 | + 'start_date': startDate, | ||
| 73 | + 'end_date': endDate, | ||
| 74 | + }; | ||
| 75 | + | ||
| 76 | + final response = await _dioClient.dio.get( | ||
| 77 | + ApiPaths.hmSleepData, | ||
| 78 | + queryParameters: queryParameters, | ||
| 79 | + ); | ||
| 80 | + return HmSleepData.fromJson( | ||
| 81 | + response.data as Map<String, dynamic>, | ||
| 82 | + ); | ||
| 83 | + }, | ||
| 84 | + ); | ||
| 85 | + } | ||
| 86 | +} |
| @@ -90,4 +90,11 @@ abstract final class ApiPaths { | @@ -90,4 +90,11 @@ abstract final class ApiPaths { | ||
| 90 | static const friends = '/client/doublefeel/health/v2/friends/'; | 90 | static const friends = '/client/doublefeel/health/v2/friends/'; |
| 91 | static const friendInfo = '/client/doublefeel/health/v2/friend_info/'; | 91 | static const friendInfo = '/client/doublefeel/health/v2/friend_info/'; |
| 92 | static const friendsSort = '/client/doublefeel/health/v2/friends/sort/'; | 92 | static const friendsSort = '/client/doublefeel/health/v2/friends/sort/'; |
| 93 | + | ||
| 94 | + // Harmony | ||
| 95 | + static const hmAuth = '/client/doublefeel/huawei_hm/auth/'; | ||
| 96 | + static const hmHealthData = '/client/doublefeel/huawei_hm/health_data/'; | ||
| 97 | + static const hmSleepData = '/client/doublefeel/huawei_hm/sleep_data/'; | ||
| 98 | + static const hmPrivacyRecords = '/client/doublefeel/huawei_hm/privacy_records/'; | ||
| 99 | + | ||
| 93 | } | 100 | } |
| 1 | -import 'package:flutter/foundation.dart'; | ||
| 2 | -import 'package:doublefeel_flutter/pigeon/alipay_api.g.dart' as alipay; | 1 | +import 'package:doublefeel_flutter/app/modules/webview/controllers/hw_health_auth_webview_controller.dart'; |
| 2 | +import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | ||
| 3 | +import 'package:doublefeel_flutter/core/network/api/harmony_api.dart'; | ||
| 4 | +import 'package:doublefeel_flutter/core/result/app_result.dart'; | ||
| 5 | +import 'package:doublefeel_flutter/core/util/huawei_health_oauth.dart'; | ||
| 3 | import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart' as health_kit; | 6 | import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart' as health_kit; |
| 4 | import 'package:doublefeel_flutter/pigeon/health_kit_raw_data_api.g.dart' | 7 | import 'package:doublefeel_flutter/pigeon/health_kit_raw_data_api.g.dart' |
| 5 | as health_raw; | 8 | as health_raw; |
| 6 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart' as platform; | 9 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart' as platform; |
| 7 | import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart' as wear; | 10 | import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart' as wear; |
| 11 | +import 'package:flutter/foundation.dart'; | ||
| 12 | +import 'package:get/get.dart'; | ||
| 8 | 13 | ||
| 9 | export 'package:doublefeel_flutter/pigeon/alipay_api.g.dart'; | 14 | export 'package:doublefeel_flutter/pigeon/alipay_api.g.dart'; |
| 10 | export 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; | 15 | export 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; |
| @@ -62,10 +67,7 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | @@ -62,10 +67,7 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | ||
| 62 | 67 | ||
| 63 | Future<String> getFullUserAgent() => dispatch( | 68 | Future<String> getFullUserAgent() => dispatch( |
| 64 | ios: () => _api.getFullUserAgent(), | 69 | ios: () => _api.getFullUserAgent(), |
| 65 | - ohos: () async { | ||
| 66 | - //TODO: - 组装agent | ||
| 67 | - return ""; | ||
| 68 | - }, | 70 | + ohos: () => _api.getFullUserAgent(), |
| 69 | ); | 71 | ); |
| 70 | 72 | ||
| 71 | Future<bool> isChinaRegion() => dispatch( | 73 | Future<bool> isChinaRegion() => dispatch( |
| @@ -76,50 +78,31 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | @@ -76,50 +78,31 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | ||
| 76 | 78 | ||
| 77 | Future<int> getApnsAuthStatus() => dispatch( | 79 | Future<int> getApnsAuthStatus() => dispatch( |
| 78 | ios: () => _api.getApnsAuthStatus(), | 80 | ios: () => _api.getApnsAuthStatus(), |
| 79 | - ohos: () async { | ||
| 80 | - //TODO: - 检查通知权限 | ||
| 81 | - return 0; | ||
| 82 | - }, | 81 | + ohos: () => _api.getApnsAuthStatus(), |
| 83 | ); | 82 | ); |
| 84 | 83 | ||
| 85 | Future<bool> requestNotificationAuth() => dispatch( | 84 | Future<bool> requestNotificationAuth() => dispatch( |
| 86 | ios: () => _api.requestNotificationAuth(), | 85 | ios: () => _api.requestNotificationAuth(), |
| 87 | - ohos: () async { | ||
| 88 | - //TODO: - 请求通知权限 | ||
| 89 | - return false; | ||
| 90 | - }, | 86 | + ohos: () => _api.requestNotificationAuth(), |
| 91 | ); | 87 | ); |
| 92 | 88 | ||
| 93 | Future<bool> isDebugEnvoriment() => dispatch( | 89 | Future<bool> isDebugEnvoriment() => dispatch( |
| 94 | ios: () => _api.isDebugEnvoriment(), | 90 | ios: () => _api.isDebugEnvoriment(), |
| 95 | - ohos: () async { | ||
| 96 | - return false; | ||
| 97 | - }); | 91 | + ohos: () => _api.isDebugEnvoriment()); |
| 98 | 92 | ||
| 99 | Future<bool> shareText(String text) => dispatch( | 93 | Future<bool> shareText(String text) => dispatch( |
| 100 | - ios: () => _api.shareText(text), | ||
| 101 | - ohos: () async { | ||
| 102 | - //TODO: - 分享文本(配对信息) | ||
| 103 | - return false; | ||
| 104 | - }); | 94 | + ios: () => _api.shareText(text), ohos: () => _api.shareText(text)); |
| 105 | 95 | ||
| 106 | Future<bool> shareFileData(Uint8List databytes) => dispatch( | 96 | Future<bool> shareFileData(Uint8List databytes) => dispatch( |
| 107 | ios: () => _api.shareFileData(databytes), | 97 | ios: () => _api.shareFileData(databytes), |
| 108 | - ohos: () async { | ||
| 109 | - return false; | ||
| 110 | - }); | 98 | + ohos: () => _api.shareFileData(databytes)); |
| 111 | 99 | ||
| 112 | Future<void> updateLoginInfo(String jsonString, String baseUrl) => dispatch( | 100 | Future<void> updateLoginInfo(String jsonString, String baseUrl) => dispatch( |
| 113 | ios: () => _api.updateLoginInfo(jsonString, baseUrl), | 101 | ios: () => _api.updateLoginInfo(jsonString, baseUrl), |
| 114 | - ohos: () async { | ||
| 115 | - return; | ||
| 116 | - }); | 102 | + ohos: () => _api.updateLoginInfo(jsonString, baseUrl)); |
| 117 | 103 | ||
| 118 | - Future<void> logout() => dispatch( | ||
| 119 | - ios: () => _api.logout(), | ||
| 120 | - ohos: () async { | ||
| 121 | - return; | ||
| 122 | - }); | 104 | + Future<void> logout() => |
| 105 | + dispatch(ios: () => _api.logout(), ohos: () => _api.logout()); | ||
| 123 | 106 | ||
| 124 | Future<void> refreshVip() => dispatch( | 107 | Future<void> refreshVip() => dispatch( |
| 125 | ios: () => _api.refreshVip(), | 108 | ios: () => _api.refreshVip(), |
| @@ -148,16 +131,12 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | @@ -148,16 +131,12 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | ||
| 148 | Future<bool> nativeHandleUrl(String urlString) => dispatch( | 131 | Future<bool> nativeHandleUrl(String urlString) => dispatch( |
| 149 | ios: () => _api.nativeHandleUrl(urlString), | 132 | ios: () => _api.nativeHandleUrl(urlString), |
| 150 | ohos: () async { | 133 | ohos: () async { |
| 151 | - //TODO: - 处理url跳转 | ||
| 152 | return false; | 134 | return false; |
| 153 | }); | 135 | }); |
| 154 | 136 | ||
| 155 | Future<String?> requestUnhandedUrl() => dispatch( | 137 | Future<String?> requestUnhandedUrl() => dispatch( |
| 156 | ios: () => _api.requestUnhandedUrl(), | 138 | ios: () => _api.requestUnhandedUrl(), |
| 157 | - ohos: () async { | ||
| 158 | - //TODO: - 请求未处理的url(通知点击启动app后,启动flutter引擎交给flutter处理url) | ||
| 159 | - return null; | ||
| 160 | - }); | 139 | + ohos: () => _api.requestUnhandedUrl()); |
| 161 | 140 | ||
| 162 | Future<platform.AppleSignInModel?> requestAppleSignIn() => dispatch( | 141 | Future<platform.AppleSignInModel?> requestAppleSignIn() => dispatch( |
| 163 | ios: () => _api.requestAppleSignIn(), | 142 | ios: () => _api.requestAppleSignIn(), |
| @@ -174,10 +153,7 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | @@ -174,10 +153,7 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | ||
| 174 | Future<bool> sendEmail(String mailTo, String title, String content) => | 153 | Future<bool> sendEmail(String mailTo, String title, String content) => |
| 175 | dispatch( | 154 | dispatch( |
| 176 | ios: () => _api.sendEmail(mailTo, title, content), | 155 | ios: () => _api.sendEmail(mailTo, title, content), |
| 177 | - ohos: () async { | ||
| 178 | - //TODO: - 发送邮件 | ||
| 179 | - return false; | ||
| 180 | - }); | 156 | + ohos: () => _api.sendEmail(mailTo, title, content)); |
| 181 | 157 | ||
| 182 | Future<platform.AppleProductInfo?> requestAppleProductInfo( | 158 | Future<platform.AppleProductInfo?> requestAppleProductInfo( |
| 183 | String productId, | 159 | String productId, |
| @@ -211,9 +187,8 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | @@ -211,9 +187,8 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | ||
| 211 | ) => | 187 | ) => |
| 212 | dispatch( | 188 | dispatch( |
| 213 | ios: () => _api.uploadFile(filePath, resourceType), | 189 | ios: () => _api.uploadFile(filePath, resourceType), |
| 214 | - ohos: () async { | ||
| 215 | - //TODO: - 上传文件(头像) | ||
| 216 | - return null; | 190 | + ohos: () { |
| 191 | + return _api.uploadFile(filePath, resourceType); | ||
| 217 | }); | 192 | }); |
| 218 | 193 | ||
| 219 | Future<String?> performCropImage( | 194 | Future<String?> performCropImage( |
| @@ -238,10 +213,8 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | @@ -238,10 +213,8 @@ class AppPlatformHostApi extends _AppPigeonApiFacade { | ||
| 238 | dispatch( | 213 | dispatch( |
| 239 | ios: () => _api.sendLocalNotification( | 214 | ios: () => _api.sendLocalNotification( |
| 240 | dataType, dateTime, title, content, link), | 215 | dataType, dateTime, title, content, link), |
| 241 | - ohos: () async { | ||
| 242 | - //TODO: - 发送本地通知 | ||
| 243 | - return false; | ||
| 244 | - }); | 216 | + ohos: () => _api.sendLocalNotification( |
| 217 | + dataType, dateTime, title, content, link)); | ||
| 245 | } | 218 | } |
| 246 | 219 | ||
| 247 | class AppHealthKitHostApi extends _AppPigeonApiFacade { | 220 | class AppHealthKitHostApi extends _AppPigeonApiFacade { |
| @@ -257,16 +230,52 @@ class AppHealthKitHostApi extends _AppPigeonApiFacade { | @@ -257,16 +230,52 @@ class AppHealthKitHostApi extends _AppPigeonApiFacade { | ||
| 257 | dispatch( | 230 | dispatch( |
| 258 | ios: () => _api.checkHealthAppAuthorization(), | 231 | ios: () => _api.checkHealthAppAuthorization(), |
| 259 | ohos: () async { | 232 | ohos: () async { |
| 260 | - //TODO: - 检查华为健康授权状态 | ||
| 261 | - return health_kit.HealthAuthorization(status: -1, hasData: false); | 233 | + final harmonyApi = Get.find<HarmonyApi>(); |
| 234 | + var result = await harmonyApi.getPrivacyRecords(); | ||
| 235 | + var resp = 2; | ||
| 236 | + switch (result) { | ||
| 237 | + case AppSuccess(data: final info): | ||
| 238 | + resp = info.opinion ?? 2; | ||
| 239 | + break; | ||
| 240 | + case AppFailure(): | ||
| 241 | + resp = 2; | ||
| 242 | + break; | ||
| 243 | + } | ||
| 244 | + return health_kit.HealthAuthorization(status: resp, hasData: true); | ||
| 262 | }); | 245 | }); |
| 263 | 246 | ||
| 264 | Future<bool> requestHealthClientAuthorization() => dispatch( | 247 | Future<bool> requestHealthClientAuthorization() => dispatch( |
| 265 | ios: () => _api.requestHealthClientAuthorization(), | 248 | ios: () => _api.requestHealthClientAuthorization(), |
| 266 | ohos: () async { | 249 | ohos: () async { |
| 267 | - //TODO: - 请求华为健康授权 | ||
| 268 | - return false; | 250 | + final state = HuaweiHealthOAuth.createState(); |
| 251 | + final result = await Get.toNamed( | ||
| 252 | + Routes.HW_HEALTH_AUTH_WEB_VIEW, | ||
| 253 | + arguments: HwHealthAuthWebviewArguments( | ||
| 254 | + authorizationUrl: | ||
| 255 | + HuaweiHealthOAuth.buildAuthorizationUrl(state: state), | ||
| 256 | + ), | ||
| 257 | + ); | ||
| 258 | + final event = result is HealthAuthEvent ? result : null; | ||
| 259 | + if (event == null || event.state != state) return false; | ||
| 260 | + if (event.error != null || event.code == null) { | ||
| 261 | + // 用户取消或华为授权失败 | ||
| 262 | + return false; | ||
| 263 | + } | ||
| 264 | + final code = event.code!; | ||
| 265 | + final harmonyApi = Get.find<HarmonyApi>(); | ||
| 266 | + await harmonyApi.postHmAuth(code); | ||
| 267 | + return true; | ||
| 269 | }); | 268 | }); |
| 269 | + | ||
| 270 | + /// Requests Huawei Health to synchronize its local data to the cloud. | ||
| 271 | + /// | ||
| 272 | + /// Available on HarmonyOS only. A `true` result means the request was | ||
| 273 | + /// accepted; cloud data may become available shortly afterwards. | ||
| 274 | + Future<bool> syncHealthDataToCloud() => dispatch( | ||
| 275 | + ios: () async => false, | ||
| 276 | + android: () async => false, | ||
| 277 | + ohos: () => _api.syncHealthDataToCloud(), | ||
| 278 | + ); | ||
| 270 | } | 279 | } |
| 271 | 280 | ||
| 272 | class AppHealthKitRawDataHostApi extends _AppPigeonApiFacade { | 281 | class AppHealthKitRawDataHostApi extends _AppPigeonApiFacade { |
| 1 | +import 'dart:async'; | ||
| 2 | + | ||
| 3 | +import 'package:doublefeel_flutter/app/utils/platform_compact.dart'; | ||
| 4 | +import 'package:flutter/services.dart'; | ||
| 5 | +import 'package:flutter/widgets.dart'; | ||
| 6 | + | ||
| 1 | import '../constants/app_const.dart'; | 7 | import '../constants/app_const.dart'; |
| 2 | import '../logging/app_logger.dart'; | 8 | import '../logging/app_logger.dart'; |
| 3 | import '../network/api/user_api.dart'; | 9 | import '../network/api/user_api.dart'; |
| 4 | -import '../util/platform.dart'; | 10 | +import '../platform/pigeon_api_facade.dart'; |
| 11 | +import '../result/app_result.dart'; | ||
| 5 | 12 | ||
| 6 | -/// HMS Push — stub until SDK is integrated. | ||
| 7 | -class PushService { | 13 | +/// Reports the HarmonyOS Push Kit token for the currently logged-in user. |
| 14 | +class PushService with WidgetsBindingObserver { | ||
| 8 | PushService(this._userApi); | 15 | PushService(this._userApi); |
| 9 | 16 | ||
| 10 | final UserApi _userApi; | 17 | final UserApi _userApi; |
| 18 | + static const _ohosPushChannel = MethodChannel('doublefeel_flutter/ohos_push'); | ||
| 19 | + static const _retryDelays = <Duration>[ | ||
| 20 | + Duration(seconds: 5), | ||
| 21 | + Duration(seconds: 30), | ||
| 22 | + Duration(minutes: 2), | ||
| 23 | + ]; | ||
| 24 | + | ||
| 25 | + Timer? _retryTimer; | ||
| 26 | + bool _isRegistrationActive = false; | ||
| 27 | + bool _needsRegistration = false; | ||
| 28 | + bool _isUploading = false; | ||
| 29 | + bool _isObservingLifecycle = false; | ||
| 30 | + int _retryAttempt = 0; | ||
| 11 | 31 | ||
| 12 | Future<void> registerPushToken() async { | 32 | Future<void> registerPushToken() async { |
| 13 | - if (!isAndroid) { | 33 | + if (isOhos()) { |
| 34 | + return; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + _isRegistrationActive = true; | ||
| 38 | + _needsRegistration = true; | ||
| 39 | + _retryAttempt = 0; | ||
| 40 | + _retryTimer?.cancel(); | ||
| 41 | + _retryTimer = null; | ||
| 42 | + _startObservingLifecycle(); | ||
| 43 | + await _reportPushToken(); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + /// Cancels retries when the authenticated session ends. | ||
| 47 | + void cancelPushTokenRegistration() { | ||
| 48 | + _isRegistrationActive = false; | ||
| 49 | + _needsRegistration = false; | ||
| 50 | + _retryAttempt = 0; | ||
| 51 | + _retryTimer?.cancel(); | ||
| 52 | + _retryTimer = null; | ||
| 53 | + _stopObservingLifecycle(); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @override | ||
| 57 | + void didChangeAppLifecycleState(AppLifecycleState state) { | ||
| 58 | + if (state == AppLifecycleState.resumed && | ||
| 59 | + _needsRegistration && | ||
| 60 | + _retryTimer == null && | ||
| 61 | + _retryAttempt < _retryDelays.length) { | ||
| 62 | + unawaited(_retryPendingPushToken()); | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + Future<void> _retryPendingPushToken() async { | ||
| 67 | + if (!_isRegistrationActive || !_needsRegistration || _isUploading) { | ||
| 68 | + return; | ||
| 69 | + } | ||
| 70 | + await _reportPushToken(); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + Future<void> _reportPushToken() async { | ||
| 74 | + if (!_isRegistrationActive || !_needsRegistration || _isUploading) { | ||
| 75 | + return; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + _isUploading = true; | ||
| 79 | + var reported = false; | ||
| 80 | + | ||
| 81 | + try { | ||
| 82 | + // Always ask Push Kit again at login: tokens may be replaced after an | ||
| 83 | + // app reinstall, device migration, or system upgrade. | ||
| 84 | + final token = await _ohosPushChannel.invokeMethod<String>('getPushToken'); | ||
| 85 | + if (!_isRegistrationActive) { | ||
| 86 | + return; | ||
| 87 | + } | ||
| 88 | + if (token == null || token.trim().isEmpty) { | ||
| 89 | + AppLogger.w('HarmonyOS Push Kit returned an empty token'); | ||
| 90 | + return; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + final deviceInfo = await AppPlatformHostApi().getFullUserAgent(); | ||
| 94 | + if (!_isRegistrationActive) { | ||
| 95 | + return; | ||
| 96 | + } | ||
| 97 | + final result = await _userApi.postPushToken( | ||
| 98 | + pushToken: token, | ||
| 99 | + deviceInfo: deviceInfo, | ||
| 100 | + pushPlatform: AppConst.pushPlatformHuawei, | ||
| 101 | + ); | ||
| 102 | + if (result is AppFailure<void>) { | ||
| 103 | + AppLogger.w('Failed to report HarmonyOS push token', result.error); | ||
| 104 | + return; | ||
| 105 | + } | ||
| 106 | + AppLogger.i('HarmonyOS push token reported'); | ||
| 107 | + reported = true; | ||
| 108 | + } on PlatformException catch (error) { | ||
| 109 | + AppLogger.w('Failed to get HarmonyOS push token: ${error.code}', error); | ||
| 110 | + } catch (error) { | ||
| 111 | + AppLogger.w('Failed to register HarmonyOS push token', error); | ||
| 112 | + } finally { | ||
| 113 | + _isUploading = false; | ||
| 114 | + if (reported) { | ||
| 115 | + _needsRegistration = false; | ||
| 116 | + _retryTimer?.cancel(); | ||
| 117 | + _retryTimer = null; | ||
| 118 | + _stopObservingLifecycle(); | ||
| 119 | + } else if (_isRegistrationActive && _needsRegistration) { | ||
| 120 | + _scheduleRetry(); | ||
| 121 | + } | ||
| 122 | + } | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + void _scheduleRetry() { | ||
| 126 | + if (!_needsRegistration || _retryTimer != null) { | ||
| 127 | + return; | ||
| 128 | + } | ||
| 129 | + if (_retryAttempt >= _retryDelays.length) { | ||
| 130 | + _stopObservingLifecycle(); | ||
| 131 | + return; | ||
| 132 | + } | ||
| 133 | + final delay = _retryDelays[_retryAttempt++]; | ||
| 134 | + _retryTimer = Timer(delay, () { | ||
| 135 | + _retryTimer = null; | ||
| 136 | + unawaited(_retryPendingPushToken()); | ||
| 137 | + }); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + void _startObservingLifecycle() { | ||
| 141 | + if (_isObservingLifecycle) { | ||
| 142 | + return; | ||
| 143 | + } | ||
| 144 | + WidgetsBinding.instance.addObserver(this); | ||
| 145 | + _isObservingLifecycle = true; | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + void _stopObservingLifecycle() { | ||
| 149 | + if (!_isObservingLifecycle) { | ||
| 14 | return; | 150 | return; |
| 15 | } | 151 | } |
| 16 | - // AppLogger.i('PushService.registerPushToken: HMS SDK pending'); | ||
| 17 | - // await _userApi.postPushToken( | ||
| 18 | - // pushToken: 'pending-hms-token', | ||
| 19 | - // deviceInfo: 'flutter', | ||
| 20 | - // pushPlatform: AppConst.pushPlatformHuawei, | ||
| 21 | - // ); | 152 | + WidgetsBinding.instance.removeObserver(this); |
| 153 | + _isObservingLifecycle = false; | ||
| 22 | } | 154 | } |
| 23 | } | 155 | } |
| 1 | +/// Data types accepted by the Huawei Health raw-data API. | ||
| 2 | +/// | ||
| 3 | +/// Keep these numeric values aligned with Huawei's `data_type` contract. | ||
| 4 | +enum HuaweiHealthDataType { | ||
| 5 | + /// HRV(心率变异性) | ||
| 6 | + hrv(1), | ||
| 7 | + | ||
| 8 | + /// 心率 | ||
| 9 | + heartRate(2), | ||
| 10 | + | ||
| 11 | + /// 血氧饱和度 | ||
| 12 | + bloodOxygenSaturation(3), | ||
| 13 | + | ||
| 14 | + /// 活动量 | ||
| 15 | + activity(4), | ||
| 16 | + | ||
| 17 | + /// 锻炼时长 | ||
| 18 | + exerciseDuration(5), | ||
| 19 | + | ||
| 20 | + /// 站立时长 | ||
| 21 | + standingDuration(6), | ||
| 22 | + | ||
| 23 | + /// 步数 | ||
| 24 | + stepCount(7), | ||
| 25 | + | ||
| 26 | + /// 步行心率 | ||
| 27 | + walkingHeartRate(8), | ||
| 28 | + | ||
| 29 | + /// 静息心率 | ||
| 30 | + restingHeartRate(9), | ||
| 31 | + | ||
| 32 | + /// 睡眠心率 | ||
| 33 | + sleepingHeartRate(10), | ||
| 34 | + | ||
| 35 | + /// 手腕温度 | ||
| 36 | + wristTemperature(11), | ||
| 37 | + | ||
| 38 | + /// 睡眠呼吸频率 | ||
| 39 | + sleepingRespiratoryRate(12), | ||
| 40 | + | ||
| 41 | + /// 房颤 | ||
| 42 | + atrialFibrillation(13); | ||
| 43 | + | ||
| 44 | + const HuaweiHealthDataType(this.dataType); | ||
| 45 | + | ||
| 46 | + final int dataType; | ||
| 47 | +} |
| 1 | +import 'dart:async'; | ||
| 2 | + | ||
| 1 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; | 3 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; |
| 2 | import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart'; | 4 | import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart'; |
| 3 | import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; | 5 | import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; |
| @@ -8,6 +10,7 @@ import '../../data/local/user_preferences_storage.dart'; | @@ -8,6 +10,7 @@ import '../../data/local/user_preferences_storage.dart'; | ||
| 8 | import 'im_service.dart'; | 10 | import 'im_service.dart'; |
| 9 | import 'push_service.dart'; | 11 | import 'push_service.dart'; |
| 10 | import 'thinking_data_service.dart'; | 12 | import 'thinking_data_service.dart'; |
| 13 | +import 'web_socket_service.dart'; | ||
| 11 | 14 | ||
| 12 | /// Login lifecycle and SDK initialization. | 15 | /// Login lifecycle and SDK initialization. |
| 13 | class UserStateService { | 16 | class UserStateService { |
| @@ -19,6 +22,7 @@ class UserStateService { | @@ -19,6 +22,7 @@ class UserStateService { | ||
| 19 | this._pushService, | 22 | this._pushService, |
| 20 | this._thinkingDataService, | 23 | this._thinkingDataService, |
| 21 | this._healthRawDataCoreService, | 24 | this._healthRawDataCoreService, |
| 25 | + this._webSocketService, | ||
| 22 | ); | 26 | ); |
| 23 | 27 | ||
| 24 | final AppEnvironmentConfig _environmentConfig; | 28 | final AppEnvironmentConfig _environmentConfig; |
| @@ -28,6 +32,7 @@ class UserStateService { | @@ -28,6 +32,7 @@ class UserStateService { | ||
| 28 | final PushService _pushService; | 32 | final PushService _pushService; |
| 29 | final ThinkingDataService _thinkingDataService; | 33 | final ThinkingDataService _thinkingDataService; |
| 30 | final HealthRawDataCoreService _healthRawDataCoreService; | 34 | final HealthRawDataCoreService _healthRawDataCoreService; |
| 35 | + final WebSocketService _webSocketService; | ||
| 31 | 36 | ||
| 32 | bool sdksInitialized = false; | 37 | bool sdksInitialized = false; |
| 33 | 38 | ||
| @@ -44,13 +49,18 @@ class UserStateService { | @@ -44,13 +49,18 @@ class UserStateService { | ||
| 44 | _trackLogin(); | 49 | _trackLogin(); |
| 45 | _setUserProfile(); | 50 | _setUserProfile(); |
| 46 | _healthRawDataCoreService.openDatabase(); | 51 | _healthRawDataCoreService.openDatabase(); |
| 52 | + // Push Kit may wait for its service/network. The token is still reported | ||
| 53 | + // after login, but must not delay the rest of the authenticated session. | ||
| 54 | + unawaited(_pushService.registerPushToken()); | ||
| 47 | await _imService.connect(); | 55 | await _imService.connect(); |
| 48 | - await _pushService.registerPushToken(); | 56 | + _webSocketService.start(); |
| 49 | } | 57 | } |
| 50 | 58 | ||
| 51 | Future<void> onLogout({bool callServerLogout = true}) async { | 59 | Future<void> onLogout({bool callServerLogout = true}) async { |
| 52 | _trackLogout(); | 60 | _trackLogout(); |
| 61 | + _pushService.cancelPushTokenRegistration(); | ||
| 53 | _imService.disconnect(); | 62 | _imService.disconnect(); |
| 63 | + _webSocketService.stop(); | ||
| 54 | _healthRawDataCoreService.closeDatabase(); | 64 | _healthRawDataCoreService.closeDatabase(); |
| 55 | if (callServerLogout) { | 65 | if (callServerLogout) { |
| 56 | await _userApi.logout(); | 66 | await _userApi.logout(); |
lib/core/services/web_socket_service.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | +import 'dart:convert'; | ||
| 3 | +import 'dart:io'; | ||
| 4 | + | ||
| 5 | +import 'package:flutter/widgets.dart'; | ||
| 6 | + | ||
| 7 | +import '../../data/local/user_preferences_storage.dart'; | ||
| 8 | +import '../config/app_environment_config.dart'; | ||
| 9 | +import '../constants/network_const.dart'; | ||
| 10 | +import '../logging/app_logger.dart'; | ||
| 11 | +import '../network/user_agent_provider.dart'; | ||
| 12 | + | ||
| 13 | +/// Keeps the authenticated server WebSocket alive while the app is foregrounded. | ||
| 14 | +/// | ||
| 15 | +/// Feature modules subscribe to their own server-defined [WebSocketEvent.xTag] | ||
| 16 | +/// through [eventsFor]. The service owns transport only and contains no | ||
| 17 | +/// feature-specific business behavior. | ||
| 18 | +class WebSocketService with WidgetsBindingObserver { | ||
| 19 | + WebSocketService(this._environmentConfig, this._userPreferencesStorage); | ||
| 20 | + | ||
| 21 | + static const _connectTimeout = Duration(seconds: 15); | ||
| 22 | + static const _initialReconnectDelay = Duration(seconds: 1); | ||
| 23 | + static const _maxReconnectDelay = Duration(seconds: 60); | ||
| 24 | + | ||
| 25 | + final AppEnvironmentConfig _environmentConfig; | ||
| 26 | + final UserPreferencesStorage _userPreferencesStorage; | ||
| 27 | + final StreamController<String> _messages = StreamController.broadcast(); | ||
| 28 | + final StreamController<WebSocketConnectionState> _states = | ||
| 29 | + StreamController.broadcast(); | ||
| 30 | + final StreamController<WebSocketEvent> _events = StreamController.broadcast(); | ||
| 31 | + | ||
| 32 | + WebSocket? _socket; | ||
| 33 | + StreamSubscription<dynamic>? _socketSubscription; | ||
| 34 | + Timer? _reconnectTimer; | ||
| 35 | + int _reconnectAttempts = 0; | ||
| 36 | + int _connectionGeneration = 0; | ||
| 37 | + bool _shouldStayConnected = false; | ||
| 38 | + bool _isConnecting = false; | ||
| 39 | + bool _disposed = false; | ||
| 40 | + | ||
| 41 | + /// Raw text messages sent by the server. | ||
| 42 | + Stream<String> get messages => _messages.stream; | ||
| 43 | + | ||
| 44 | + /// Connection changes, useful for diagnostics or a connection indicator. | ||
| 45 | + Stream<WebSocketConnectionState> get states => _states.stream; | ||
| 46 | + | ||
| 47 | + /// Parsed server events. Invalid JSON messages remain available from | ||
| 48 | + /// [messages] but are not emitted here. | ||
| 49 | + Stream<WebSocketEvent> get events => _events.stream; | ||
| 50 | + | ||
| 51 | + /// Returns only events for a server-defined `x_tag`. | ||
| 52 | + /// | ||
| 53 | + /// Each feature owns its tag and subscription lifecycle, keeping unrelated | ||
| 54 | + /// WebSocket business logic out of this shared transport service. | ||
| 55 | + Stream<WebSocketEvent> eventsFor(String xTag) => | ||
| 56 | + events.where((event) => event.xTag == xTag); | ||
| 57 | + | ||
| 58 | + bool get isConnected => _socket != null && !_isConnecting; | ||
| 59 | + | ||
| 60 | + /// Starts the connection when an authenticated session is available. | ||
| 61 | + void start() { | ||
| 62 | + if (_disposed) return; | ||
| 63 | + _shouldStayConnected = true; | ||
| 64 | + _connectIfNeeded(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /// Stops the connection permanently, until [start] is called again. | ||
| 68 | + void stop() { | ||
| 69 | + _shouldStayConnected = false; | ||
| 70 | + _cancelReconnect(); | ||
| 71 | + _closeSocket(); | ||
| 72 | + _emitState(WebSocketConnectionState.disconnected); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /// Sends a text frame when the connection is open. | ||
| 76 | + bool send(String message) { | ||
| 77 | + final socket = _socket; | ||
| 78 | + if (socket == null || _isConnecting) return false; | ||
| 79 | + socket.add(message); | ||
| 80 | + return true; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + /// Reconnects immediately, for example after an environment change. | ||
| 84 | + void reconnect() { | ||
| 85 | + if (_disposed || !_shouldStayConnected) return; | ||
| 86 | + _reconnectAttempts = 0; | ||
| 87 | + _cancelReconnect(); | ||
| 88 | + _closeSocket(); | ||
| 89 | + _connectIfNeeded(); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + void _connectIfNeeded() { | ||
| 93 | + if (_disposed || | ||
| 94 | + !_shouldStayConnected || | ||
| 95 | + _isConnecting || | ||
| 96 | + _socket != null || | ||
| 97 | + _reconnectTimer != null) { | ||
| 98 | + return; | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + final accessToken = _userPreferencesStorage.accessToken; | ||
| 102 | + if (accessToken.isEmpty) { | ||
| 103 | + _emitState(WebSocketConnectionState.disconnected); | ||
| 104 | + return; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + _connect(accessToken); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + Future<void> _connect(String accessToken) async { | ||
| 111 | + _isConnecting = true; | ||
| 112 | + final generation = ++_connectionGeneration; | ||
| 113 | + _emitState(WebSocketConnectionState.connecting); | ||
| 114 | + | ||
| 115 | + try { | ||
| 116 | + var closeLateSocket = false; | ||
| 117 | + final connectFuture = WebSocket.connect( | ||
| 118 | + _environmentConfig.webSocketUrl, | ||
| 119 | + headers: { | ||
| 120 | + NetworkConst.headerAccessToken: accessToken, | ||
| 121 | + NetworkConst.headerUserAgent: UserAgentProvider.userAgent, | ||
| 122 | + NetworkConst.headerAcceptLanguage: | ||
| 123 | + WidgetsBinding.instance.platformDispatcher.locale.languageCode, | ||
| 124 | + }, | ||
| 125 | + ); | ||
| 126 | + unawaited( | ||
| 127 | + connectFuture.then( | ||
| 128 | + (socket) { | ||
| 129 | + if (closeLateSocket) { | ||
| 130 | + return socket.close( | ||
| 131 | + WebSocketStatus.normalClosure, | ||
| 132 | + 'connection timed out', | ||
| 133 | + ); | ||
| 134 | + } | ||
| 135 | + }, | ||
| 136 | + onError: (_, __) {}, | ||
| 137 | + ).catchError((_) {}), | ||
| 138 | + ); | ||
| 139 | + final socket = await connectFuture.timeout( | ||
| 140 | + _connectTimeout, | ||
| 141 | + onTimeout: () { | ||
| 142 | + closeLateSocket = true; | ||
| 143 | + throw TimeoutException('WebSocket connection timed out'); | ||
| 144 | + }, | ||
| 145 | + ); | ||
| 146 | + | ||
| 147 | + if (!_isCurrentConnection(generation)) { | ||
| 148 | + await socket.close(); | ||
| 149 | + return; | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + _socket = socket..pingInterval = const Duration(seconds: 30); | ||
| 153 | + _reconnectAttempts = 0; | ||
| 154 | + _isConnecting = false; | ||
| 155 | + _emitState(WebSocketConnectionState.connected); | ||
| 156 | + _socketSubscription = socket.listen( | ||
| 157 | + _onMessage, | ||
| 158 | + onError: (Object error, StackTrace stackTrace) { | ||
| 159 | + _handleDisconnected(generation, error); | ||
| 160 | + }, | ||
| 161 | + onDone: () => _handleDisconnected(generation, null), | ||
| 162 | + cancelOnError: true, | ||
| 163 | + ); | ||
| 164 | + } on WebSocketException catch (error, stackTrace) { | ||
| 165 | + if (!_isCurrentConnection(generation)) return; | ||
| 166 | + _isConnecting = false; | ||
| 167 | + AppLogger.w('WebSocket connection failed', error, stackTrace); | ||
| 168 | + _emitState(WebSocketConnectionState.disconnected); | ||
| 169 | + if (_isAuthenticationHandshakeError(error.httpStatusCode)) { | ||
| 170 | + AppLogger.w( | ||
| 171 | + 'WebSocket authentication failed; wait for a refreshed session', | ||
| 172 | + error, | ||
| 173 | + ); | ||
| 174 | + _shouldStayConnected = false; | ||
| 175 | + _cancelReconnect(); | ||
| 176 | + return; | ||
| 177 | + } | ||
| 178 | + _scheduleReconnect(); | ||
| 179 | + } catch (error, stackTrace) { | ||
| 180 | + if (!_isCurrentConnection(generation)) return; | ||
| 181 | + _isConnecting = false; | ||
| 182 | + AppLogger.w('WebSocket connection failed', error, stackTrace); | ||
| 183 | + _emitState(WebSocketConnectionState.disconnected); | ||
| 184 | + _scheduleReconnect(); | ||
| 185 | + } | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + bool _isAuthenticationHandshakeError(int? statusCode) => | ||
| 189 | + statusCode == HttpStatus.badRequest || | ||
| 190 | + statusCode == HttpStatus.unauthorized; | ||
| 191 | + | ||
| 192 | + bool _isCurrentConnection(int generation) => | ||
| 193 | + !_disposed && _shouldStayConnected && generation == _connectionGeneration; | ||
| 194 | + | ||
| 195 | + void _onMessage(dynamic data) { | ||
| 196 | + if (data is! String) return; | ||
| 197 | + if (!_messages.isClosed) _messages.add(data); | ||
| 198 | + | ||
| 199 | + final event = WebSocketEvent.tryParse(data); | ||
| 200 | + if (event != null && !_events.isClosed) _events.add(event); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + void _handleDisconnected(int generation, Object? error) { | ||
| 204 | + if (!_isCurrentConnection(generation)) return; | ||
| 205 | + _socket = null; | ||
| 206 | + _socketSubscription = null; | ||
| 207 | + _isConnecting = false; | ||
| 208 | + if (error != null) { | ||
| 209 | + AppLogger.w('WebSocket disconnected with an error', error); | ||
| 210 | + } | ||
| 211 | + _emitState(WebSocketConnectionState.disconnected); | ||
| 212 | + _scheduleReconnect(); | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + void _scheduleReconnect() { | ||
| 216 | + if (_disposed || !_shouldStayConnected || _reconnectTimer != null) return; | ||
| 217 | + final exponent = _reconnectAttempts.clamp(0, 10); | ||
| 218 | + final multiplier = 1 << exponent; | ||
| 219 | + final delayMilliseconds = | ||
| 220 | + (_initialReconnectDelay.inMilliseconds * multiplier).clamp( | ||
| 221 | + _initialReconnectDelay.inMilliseconds, | ||
| 222 | + _maxReconnectDelay.inMilliseconds, | ||
| 223 | + ); | ||
| 224 | + _reconnectAttempts++; | ||
| 225 | + _reconnectTimer = Timer( | ||
| 226 | + Duration(milliseconds: delayMilliseconds), | ||
| 227 | + () { | ||
| 228 | + _reconnectTimer = null; | ||
| 229 | + _connectIfNeeded(); | ||
| 230 | + }, | ||
| 231 | + ); | ||
| 232 | + } | ||
| 233 | + | ||
| 234 | + void _cancelReconnect() { | ||
| 235 | + _reconnectTimer?.cancel(); | ||
| 236 | + _reconnectTimer = null; | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + void _closeSocket() { | ||
| 240 | + _connectionGeneration++; | ||
| 241 | + final subscription = _socketSubscription; | ||
| 242 | + _socketSubscription = null; | ||
| 243 | + if (subscription != null) unawaited(subscription.cancel()); | ||
| 244 | + final socket = _socket; | ||
| 245 | + _socket = null; | ||
| 246 | + _isConnecting = false; | ||
| 247 | + if (socket != null) { | ||
| 248 | + unawaited(socket.close(WebSocketStatus.normalClosure, 'client closed')); | ||
| 249 | + } | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + void _emitState(WebSocketConnectionState state) { | ||
| 253 | + if (!_states.isClosed) _states.add(state); | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + @override | ||
| 257 | + void didChangeAppLifecycleState(AppLifecycleState state) { | ||
| 258 | + switch (state) { | ||
| 259 | + case AppLifecycleState.resumed: | ||
| 260 | + _connectIfNeeded(); | ||
| 261 | + return; | ||
| 262 | + case AppLifecycleState.inactive: | ||
| 263 | + return; | ||
| 264 | + case AppLifecycleState.hidden: | ||
| 265 | + case AppLifecycleState.paused: | ||
| 266 | + case AppLifecycleState.detached: | ||
| 267 | + _cancelReconnect(); | ||
| 268 | + _closeSocket(); | ||
| 269 | + _emitState(WebSocketConnectionState.disconnected); | ||
| 270 | + return; | ||
| 271 | + } | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + void dispose() { | ||
| 275 | + if (_disposed) return; | ||
| 276 | + _disposed = true; | ||
| 277 | + WidgetsBinding.instance.removeObserver(this); | ||
| 278 | + _cancelReconnect(); | ||
| 279 | + _closeSocket(); | ||
| 280 | + unawaited(_messages.close()); | ||
| 281 | + unawaited(_states.close()); | ||
| 282 | + unawaited(_events.close()); | ||
| 283 | + } | ||
| 284 | +} | ||
| 285 | + | ||
| 286 | +enum WebSocketConnectionState { disconnected, connecting, connected } | ||
| 287 | + | ||
| 288 | +/// Generic server WebSocket frame. | ||
| 289 | +class WebSocketEvent { | ||
| 290 | + const WebSocketEvent({ | ||
| 291 | + required this.messageId, | ||
| 292 | + required this.messageType, | ||
| 293 | + required this.toUserId, | ||
| 294 | + required this.senderId, | ||
| 295 | + required this.body, | ||
| 296 | + required this.xTag, | ||
| 297 | + }); | ||
| 298 | + | ||
| 299 | + final String? messageId; | ||
| 300 | + final String? messageType; | ||
| 301 | + final int? toUserId; | ||
| 302 | + final int? senderId; | ||
| 303 | + final Object? body; | ||
| 304 | + final String? xTag; | ||
| 305 | + | ||
| 306 | + static WebSocketEvent? tryParse(String message) { | ||
| 307 | + try { | ||
| 308 | + final decoded = jsonDecode(message); | ||
| 309 | + if (decoded is! Map) return null; | ||
| 310 | + final map = Map<String, dynamic>.from(decoded); | ||
| 311 | + return WebSocketEvent( | ||
| 312 | + messageId: map['msg_id'] as String?, | ||
| 313 | + messageType: map['msg_type'] as String?, | ||
| 314 | + toUserId: (map['to_id'] as num?)?.toInt(), | ||
| 315 | + senderId: (map['sender_id'] as num?)?.toInt(), | ||
| 316 | + body: map['msg_body'], | ||
| 317 | + xTag: map['x_tag'] as String?, | ||
| 318 | + ); | ||
| 319 | + } catch (_) { | ||
| 320 | + return null; | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | +} |
lib/core/util/huawei_health_oauth.dart
0 → 100644
| 1 | +import 'dart:convert'; | ||
| 2 | +import 'dart:math'; | ||
| 3 | + | ||
| 4 | +/// Huawei Health Kit OAuth scopes required by DoubleFeel. | ||
| 5 | +abstract final class HuaweiHealthScopes { | ||
| 6 | + | ||
| 7 | + // 步数 | ||
| 8 | + // static const stepRead = 'https://www.huawei.com/healthkit/step.read'; | ||
| 9 | + | ||
| 10 | + // 活动小时数 | ||
| 11 | + // static const activeHoursRead = 'https://www.huawei.com/healthkit/activehours.read'; | ||
| 12 | + | ||
| 13 | + // 实时心率 | ||
| 14 | + // static const realtimeHeartRead = 'https://www.huawei.com/healthkit/extend/realtimeheart.read'; | ||
| 15 | + | ||
| 16 | + // 中高强度 | ||
| 17 | + // static const strengthRead = 'https://www.huawei.com/healthkit/strength.read'; | ||
| 18 | + | ||
| 19 | + //低血氧 | ||
| 20 | + // static const oxygenSaturationRead = 'https://www.huawei.com/healthkit/oxygensaturation.read'; | ||
| 21 | + | ||
| 22 | + // 日常活动数据 | ||
| 23 | + static const dailyActivitySummaryRead = "https://www.huawei.com/healthkit/dailyactivitysummary.read"; | ||
| 24 | + // Calories | ||
| 25 | + static const caloriesRead = 'https://www.huawei.com/healthkit/calories.read'; | ||
| 26 | + // 运动目标 | ||
| 27 | + static const goalsRead = "https://www.huawei.com/healthkit/goals.read"; | ||
| 28 | + // 锻炼记录概要 | ||
| 29 | + static const activityRecordRead = "https://www.huawei.com/healthkit/activityrecord.read"; | ||
| 30 | + // 锻炼记录关联的原子采样明细数据权限 | ||
| 31 | + static const activityRead = "https://www.huawei.com/healthkit/activity.read"; | ||
| 32 | + // 心率 | ||
| 33 | + static const heartRateRead = 'https://www.huawei.com/healthkit/heartrate.read'; | ||
| 34 | + // 压力 | ||
| 35 | + static const stressRead = 'https://www.huawei.com/healthkit/stress.read'; | ||
| 36 | + // 睡眠 | ||
| 37 | + static const sleepRead = 'https://www.huawei.com/healthkit/sleep.read'; | ||
| 38 | + | ||
| 39 | + // 年数据 | ||
| 40 | + static const historyDataOpenYear = 'https://www.huawei.com/healthkit/historydata.open.year'; | ||
| 41 | + // 手动同步数据 | ||
| 42 | + static const cloudsync = 'https://www.huawei.com/healthkit/huaweihealthdata.cloudsync'; | ||
| 43 | + | ||
| 44 | + static const requested = <String>[ | ||
| 45 | + dailyActivitySummaryRead, | ||
| 46 | + caloriesRead, | ||
| 47 | + goalsRead, | ||
| 48 | + activityRecordRead, | ||
| 49 | + activityRead, | ||
| 50 | + heartRateRead, | ||
| 51 | + stressRead, | ||
| 52 | + sleepRead, | ||
| 53 | + historyDataOpenYear, | ||
| 54 | + cloudsync, | ||
| 55 | + 'openid', | ||
| 56 | + 'profile', | ||
| 57 | + ]; | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +/// Builds Huawei Health Kit OAuth authorization URLs in the Flutter layer. | ||
| 61 | +abstract final class HuaweiHealthOAuth { | ||
| 62 | + static const _clientId = '6917602291754541850'; | ||
| 63 | + static const _redirectUri = | ||
| 64 | + 'https://h5hosting.dbankcdn.com/cch5/healthkit/oauth-h5/oauth-callback.html'; | ||
| 65 | + static final Random _random = Random.secure(); | ||
| 66 | + | ||
| 67 | + /// Generates an OAuth state value. Persist and validate it after redirect. | ||
| 68 | + static String createState() { | ||
| 69 | + final bytes = List<int>.generate(32, (_) => _random.nextInt(256)); | ||
| 70 | + return base64UrlEncode(bytes).replaceAll('=', ''); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + /// Returns the Huawei authorization URL for the configured Health Kit scopes. | ||
| 74 | + /// | ||
| 75 | + /// Pass a persisted [state] so the callback can be verified against it. | ||
| 76 | + static String buildAuthorizationUrl({String? state}) { | ||
| 77 | + return Uri.https( | ||
| 78 | + 'oauth-login.cloud.huawei.com', | ||
| 79 | + '/oauth2/v3/authorize', | ||
| 80 | + <String, String>{ | ||
| 81 | + 'client_id': _clientId, | ||
| 82 | + 'response_type': 'code', | ||
| 83 | + 'redirect_uri': _redirectUri, | ||
| 84 | + 'scope': HuaweiHealthScopes.requested.join(' '), | ||
| 85 | + 'state': state ?? createState(), | ||
| 86 | + 'display': 'touch', | ||
| 87 | + 'access_type': 'offline', | ||
| 88 | + }, | ||
| 89 | + ).toString(); | ||
| 90 | + } | ||
| 91 | +} |
| 1 | import 'dart:convert'; | 1 | import 'dart:convert'; |
| 2 | 2 | ||
| 3 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; | 3 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; |
| 4 | -import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart'; | ||
| 5 | import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; | 4 | import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart'; |
| 5 | +import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart'; | ||
| 6 | import 'package:get/get.dart'; | 6 | import 'package:get/get.dart'; |
| 7 | import 'package:shared_preferences/shared_preferences.dart'; | 7 | import 'package:shared_preferences/shared_preferences.dart'; |
| 8 | 8 | ||
| @@ -45,9 +45,11 @@ class UserPreferencesStorage { | @@ -45,9 +45,11 @@ class UserPreferencesStorage { | ||
| 45 | } catch (_) { | 45 | } catch (_) { |
| 46 | preferences.value = UserPreferences.empty; | 46 | preferences.value = UserPreferences.empty; |
| 47 | } | 47 | } |
| 48 | - _syncLoginInfoToNative(userInfo: preferences.value); | ||
| 49 | } | 48 | } |
| 50 | 49 | ||
| 50 | + Future<void> syncLoginInfoToNative() => | ||
| 51 | + _syncLoginInfoToNative(userInfo: preferences.value); | ||
| 52 | + | ||
| 51 | Future<void> _syncLoginInfoToNative( | 53 | Future<void> _syncLoginInfoToNative( |
| 52 | {required UserPreferences userInfo}) async { | 54 | {required UserPreferences userInfo}) async { |
| 53 | if (userInfo.accessToken.isEmpty) { | 55 | if (userInfo.accessToken.isEmpty) { |
lib/data/models/harmony/hm_health_data.dart
0 → 100644
| 1 | +class HmHealthData { | ||
| 2 | + HmHealthData({ | ||
| 3 | + this.list, | ||
| 4 | + }); | ||
| 5 | + | ||
| 6 | + HmHealthData.fromJson(dynamic json) { | ||
| 7 | + if (json['list'] != null) { | ||
| 8 | + list = []; | ||
| 9 | + json['list'].forEach((v) { | ||
| 10 | + list?.add(HmHealthDataItem.fromJson(v)); | ||
| 11 | + }); | ||
| 12 | + } | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + List<HmHealthDataItem>? list; | ||
| 16 | + | ||
| 17 | + Map<String, dynamic> toJson() { | ||
| 18 | + final map = <String, dynamic>{}; | ||
| 19 | + if (list != null) { | ||
| 20 | + map['list'] = list?.map((v) => v.toJson()).toList(); | ||
| 21 | + } | ||
| 22 | + return map; | ||
| 23 | + } | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +class HmHealthDataItem { | ||
| 27 | + HmHealthDataItem({ | ||
| 28 | + this.time, | ||
| 29 | + this.dataType, | ||
| 30 | + this.value, | ||
| 31 | + this.isAsleep, | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + HmHealthDataItem.fromJson(dynamic json) { | ||
| 35 | + time = json['time']; | ||
| 36 | + dataType = json['data_type']; | ||
| 37 | + value = json['value']; | ||
| 38 | + isAsleep = json['is_asleep']; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + num? time; | ||
| 42 | + num? dataType; | ||
| 43 | + num? value; | ||
| 44 | + num? isAsleep; | ||
| 45 | + | ||
| 46 | + Map<String, dynamic> toJson() { | ||
| 47 | + final map = <String, dynamic>{}; | ||
| 48 | + map['time'] = time; | ||
| 49 | + map['data_type'] = dataType; | ||
| 50 | + map['value'] = value; | ||
| 51 | + map['is_asleep'] = isAsleep; | ||
| 52 | + return map; | ||
| 53 | + } | ||
| 54 | +} |
lib/data/models/harmony/hm_sleep_data.dart
0 → 100644
| 1 | +class HmSleepData { | ||
| 2 | + HmSleepData({ | ||
| 3 | + this.list, | ||
| 4 | + }); | ||
| 5 | + | ||
| 6 | + HmSleepData.fromJson(dynamic json) { | ||
| 7 | + if (json['list'] != null) { | ||
| 8 | + list = []; | ||
| 9 | + json['list'].forEach((v) { | ||
| 10 | + list?.add(HmSleepDataItem.fromJson(v)); | ||
| 11 | + }); | ||
| 12 | + } | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + List<HmSleepDataItem>? list; | ||
| 16 | + | ||
| 17 | + Map<String, dynamic> toJson() { | ||
| 18 | + final map = <String, dynamic>{}; | ||
| 19 | + if (list != null) { | ||
| 20 | + map['list'] = list?.map((v) => v.toJson()).toList(); | ||
| 21 | + } | ||
| 22 | + return map; | ||
| 23 | + } | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +class HmSleepDataItem { | ||
| 27 | + HmSleepDataItem({ | ||
| 28 | + this.fromTime, | ||
| 29 | + this.toTime, | ||
| 30 | + this.dataType, | ||
| 31 | + }); | ||
| 32 | + | ||
| 33 | + HmSleepDataItem.fromJson(dynamic json) { | ||
| 34 | + fromTime = json['from_time']; | ||
| 35 | + toTime = json['to_time']; | ||
| 36 | + dataType = json['data_type']; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + num? fromTime; | ||
| 40 | + num? toTime; | ||
| 41 | + num? dataType; | ||
| 42 | + | ||
| 43 | + Map<String, dynamic> toJson() { | ||
| 44 | + final map = <String, dynamic>{}; | ||
| 45 | + map['from_time'] = fromTime; | ||
| 46 | + map['to_time'] = toTime; | ||
| 47 | + map['data_type'] = dataType; | ||
| 48 | + return map; | ||
| 49 | + } | ||
| 50 | +} |
lib/data/models/harmony/privacy_records.dart
0 → 100644
| 1 | +// ─── Responses ─────────────────────────────────────────────────────────────── | ||
| 2 | + | ||
| 3 | +class PrivacyRecordsResp { | ||
| 4 | + const PrivacyRecordsResp({this.opinion}); | ||
| 5 | + | ||
| 6 | + final int? opinion; | ||
| 7 | + | ||
| 8 | + factory PrivacyRecordsResp.fromJson(Map<String, dynamic> json) { | ||
| 9 | + return PrivacyRecordsResp(opinion: json['opinion'] as int?); | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + Map<String, dynamic> toJson() { | ||
| 13 | + final val = <String, dynamic>{}; | ||
| 14 | + if (opinion != null) val['opinion'] = opinion; | ||
| 15 | + return val; | ||
| 16 | + } | ||
| 17 | +} |
| @@ -173,4 +173,36 @@ class HealthKitHostApi { | @@ -173,4 +173,36 @@ class HealthKitHostApi { | ||
| 173 | return (pigeonVar_replyList[0] as bool?)!; | 173 | return (pigeonVar_replyList[0] as bool?)!; |
| 174 | } | 174 | } |
| 175 | } | 175 | } |
| 176 | + | ||
| 177 | + /// Requests Huawei Health to upload the user's latest data to its cloud. | ||
| 178 | + /// | ||
| 179 | + /// This only triggers the Health app's configured device-to-cloud sync; it | ||
| 180 | + /// does not wait for, or return, the subsequently available cloud data. | ||
| 181 | + Future<bool> syncHealthDataToCloud() async { | ||
| 182 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.syncHealthDataToCloud$pigeonVar_messageChannelSuffix'; | ||
| 183 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 184 | + pigeonVar_channelName, | ||
| 185 | + pigeonChannelCodec, | ||
| 186 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 187 | + ); | ||
| 188 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null); | ||
| 189 | + final List<Object?>? pigeonVar_replyList = | ||
| 190 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 191 | + if (pigeonVar_replyList == null) { | ||
| 192 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 193 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 194 | + throw PlatformException( | ||
| 195 | + code: pigeonVar_replyList[0]! as String, | ||
| 196 | + message: pigeonVar_replyList[1] as String?, | ||
| 197 | + details: pigeonVar_replyList[2], | ||
| 198 | + ); | ||
| 199 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 200 | + throw PlatformException( | ||
| 201 | + code: 'null-error', | ||
| 202 | + message: 'Host platform returned null value for non-null return value.', | ||
| 203 | + ); | ||
| 204 | + } else { | ||
| 205 | + return (pigeonVar_replyList[0] as bool?)!; | ||
| 206 | + } | ||
| 207 | + } | ||
| 176 | } | 208 | } |
| @@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
| 3 | "bundleName": "com.doublefeel.hmapp", | 3 | "bundleName": "com.doublefeel.hmapp", |
| 4 | "vendor": "example", | 4 | "vendor": "example", |
| 5 | "versionCode": 1000000, | 5 | "versionCode": 1000000, |
| 6 | - "versionName": "2.4.5", | 6 | + "versionName": "2.7.0", |
| 7 | "icon": "$media:app_icon", | 7 | "icon": "$media:app_icon", |
| 8 | "label": "$string:app_name" | 8 | "label": "$string:app_name" |
| 9 | } | 9 | } |
ohos/apk_build_readme.txt
0 → 100644
| 1 | +======================== | ||
| 2 | +QA | ||
| 3 | +======================== | ||
| 4 | + | ||
| 5 | +# 构建 QA 包(给测试人员使用) | ||
| 6 | +hvigorw --mode module -p module=entry@qa -p product=qa -p buildMode=release assembleHap | ||
| 7 | + | ||
| 8 | + | ||
| 9 | +======================== | ||
| 10 | +Release | ||
| 11 | +======================== | ||
| 12 | + | ||
| 13 | +# 构建 Release 包 | ||
| 14 | +hvigorw --mode module -p module=entry@release -p product=release -p buildMode=release assembleHap |
| @@ -47,7 +47,51 @@ | @@ -47,7 +47,51 @@ | ||
| 47 | "signingConfig": "auto", | 47 | "signingConfig": "auto", |
| 48 | "targetSdkVersion": "6.0.0(20)", | 48 | "targetSdkVersion": "6.0.0(20)", |
| 49 | "compatibleSdkVersion": "6.0.0(20)", | 49 | "compatibleSdkVersion": "6.0.0(20)", |
| 50 | - "runtimeOS": "HarmonyOS" | 50 | + "runtimeOS": "HarmonyOS", |
| 51 | + "buildOption": { | ||
| 52 | + "strictMode": { | ||
| 53 | + "useNormalizedOHMUrl": true | ||
| 54 | + }, | ||
| 55 | + "arkOptions": { | ||
| 56 | + "buildProfileFields": { | ||
| 57 | + "enableEnvironmentSwitch": false | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + }, | ||
| 62 | + { | ||
| 63 | + "name": "qa", | ||
| 64 | + "signingConfig": "release", | ||
| 65 | + "targetSdkVersion": "6.0.0(20)", | ||
| 66 | + "compatibleSdkVersion": "6.0.0(20)", | ||
| 67 | + "runtimeOS": "HarmonyOS", | ||
| 68 | + "buildOption": { | ||
| 69 | + "strictMode": { | ||
| 70 | + "useNormalizedOHMUrl": true | ||
| 71 | + }, | ||
| 72 | + "arkOptions": { | ||
| 73 | + "buildProfileFields": { | ||
| 74 | + "enableEnvironmentSwitch": true | ||
| 75 | + } | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | + }, | ||
| 79 | + { | ||
| 80 | + "name": "release", | ||
| 81 | + "signingConfig": "release", | ||
| 82 | + "targetSdkVersion": "6.0.0(20)", | ||
| 83 | + "compatibleSdkVersion": "6.0.0(20)", | ||
| 84 | + "runtimeOS": "HarmonyOS", | ||
| 85 | + "buildOption": { | ||
| 86 | + "strictMode": { | ||
| 87 | + "useNormalizedOHMUrl": true | ||
| 88 | + }, | ||
| 89 | + "arkOptions": { | ||
| 90 | + "buildProfileFields": { | ||
| 91 | + "enableEnvironmentSwitch": false | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + } | ||
| 51 | } | 95 | } |
| 52 | ], | 96 | ], |
| 53 | "buildModeSet": [ | 97 | "buildModeSet": [ |
| @@ -70,7 +114,9 @@ | @@ -70,7 +114,9 @@ | ||
| 70 | { | 114 | { |
| 71 | "name": "default", | 115 | "name": "default", |
| 72 | "applyToProducts": [ | 116 | "applyToProducts": [ |
| 73 | - "default" | 117 | + "default", |
| 118 | + "qa", | ||
| 119 | + "release" | ||
| 74 | ] | 120 | ] |
| 75 | } | 121 | } |
| 76 | ] | 122 | ] |
| 1 | - | ||
| 2 | { | 1 | { |
| 3 | "name": "entry", | 2 | "name": "entry", |
| 4 | "version": "1.0.0", | 3 | "version": "1.0.0", |
| @@ -6,6 +5,9 @@ | @@ -6,6 +5,9 @@ | ||
| 6 | "main": "", | 5 | "main": "", |
| 7 | "author": "", | 6 | "author": "", |
| 8 | "license": "", | 7 | "license": "", |
| 9 | - "dependencies": {}, | 8 | + "dependencies": { |
| 9 | + "@obs/esdk-obs-harmony": "3.24.9" | ||
| 10 | + }, | ||
| 11 | + "devDependencies": {}, | ||
| 12 | + "dynamicDependencies": {} | ||
| 10 | } | 13 | } |
| 11 | - |
| @@ -6,8 +6,17 @@ import { WeChatLoginBridge } from '../native/WeChatLoginBridge'; | @@ -6,8 +6,17 @@ import { WeChatLoginBridge } from '../native/WeChatLoginBridge'; | ||
| 6 | import { WeChatHostApi } from '../pigeon/WeChatApi'; | 6 | import { WeChatHostApi } from '../pigeon/WeChatApi'; |
| 7 | import Want from '@ohos.app.ability.Want'; | 7 | import Want from '@ohos.app.ability.Want'; |
| 8 | import AbilityConstant from '@ohos.app.ability.AbilityConstant'; | 8 | import AbilityConstant from '@ohos.app.ability.AbilityConstant'; |
| 9 | +import Any from '@ohos/flutter_ohos/src/main/ets/plugin/common/Any'; | ||
| 10 | +import MethodCall from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall'; | ||
| 11 | +import MethodChannel, { MethodCallHandler, MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel'; | ||
| 12 | +import { BusinessError } from '@kit.BasicServicesKit'; | ||
| 13 | +import { pushService } from '@kit.PushKit'; | ||
| 9 | 14 | ||
| 10 | -export default class EntryAbility extends FlutterAbility { | 15 | +export default class EntryAbility extends FlutterAbility implements MethodCallHandler { |
| 16 | + private static readonly FLUTTER_MAIN_CHANNEL = 'doublefeel_flutter_main_channel'; | ||
| 17 | + private static readonly OHOS_PUSH_CHANNEL = 'doublefeel_flutter/ohos_push'; | ||
| 18 | + private flutterUrlChannel?: MethodChannel; | ||
| 19 | + private pushTokenChannel?: MethodChannel; | ||
| 11 | private weChatLoginBridge?: WeChatLoginBridge; | 20 | private weChatLoginBridge?: WeChatLoginBridge; |
| 12 | 21 | ||
| 13 | configureFlutterEngine(flutterEngine: FlutterEngine) { | 22 | configureFlutterEngine(flutterEngine: FlutterEngine) { |
| @@ -17,6 +26,17 @@ export default class EntryAbility extends FlutterAbility { | @@ -17,6 +26,17 @@ export default class EntryAbility extends FlutterAbility { | ||
| 17 | flutterEngine.getDartExecutor()!.getBinaryMessenger()!, | 26 | flutterEngine.getDartExecutor()!.getBinaryMessenger()!, |
| 18 | this.context, | 27 | this.context, |
| 19 | ) | 28 | ) |
| 29 | + this.flutterUrlChannel = new MethodChannel( | ||
| 30 | + flutterEngine.getDartExecutor()!.getBinaryMessenger()!, | ||
| 31 | + EntryAbility.FLUTTER_MAIN_CHANNEL, | ||
| 32 | + ) | ||
| 33 | + this.pushTokenChannel = new MethodChannel( | ||
| 34 | + flutterEngine.getDartExecutor()!.getBinaryMessenger()!, | ||
| 35 | + EntryAbility.OHOS_PUSH_CHANNEL, | ||
| 36 | + ) | ||
| 37 | + this.pushTokenChannel.setMethodCallHandler(this) | ||
| 38 | + NativePigeonRegistrar.setAppForeground(true) | ||
| 39 | + NativePigeonRegistrar.handleWant(this.getWant()) | ||
| 20 | this.weChatLoginBridge = new WeChatLoginBridge(this.context) | 40 | this.weChatLoginBridge = new WeChatLoginBridge(this.context) |
| 21 | WeChatHostApi.setup( | 41 | WeChatHostApi.setup( |
| 22 | flutterEngine.getDartExecutor()!.getBinaryMessenger()!, | 42 | flutterEngine.getDartExecutor()!.getBinaryMessenger()!, |
| @@ -25,8 +45,58 @@ export default class EntryAbility extends FlutterAbility { | @@ -25,8 +45,58 @@ export default class EntryAbility extends FlutterAbility { | ||
| 25 | this.weChatLoginBridge.handleWant(this.getWant()) | 45 | this.weChatLoginBridge.handleWant(this.getWant()) |
| 26 | } | 46 | } |
| 27 | 47 | ||
| 48 | + onMethodCall(call: MethodCall, result: MethodResult): void { | ||
| 49 | + if (call.method !== 'getPushToken') { | ||
| 50 | + result.notImplemented() | ||
| 51 | + return | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + // Push Kit obtains a token asynchronously. Do not request notification | ||
| 55 | + // permission here: token issuance and the user's notification preference | ||
| 56 | + // are independent, and permission is requested by the product flow. | ||
| 57 | + pushService.getToken() | ||
| 58 | + .then((token: string) => result.success(token)) | ||
| 59 | + .catch((error: BusinessError) => { | ||
| 60 | + result.error('push_token_unavailable', error.message, error.code) | ||
| 61 | + }) | ||
| 62 | + } | ||
| 63 | + | ||
| 28 | onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { | 64 | onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { |
| 29 | super.onNewWant(want, launchParams) | 65 | super.onNewWant(want, launchParams) |
| 66 | + NativePigeonRegistrar.handleWant(want) | ||
| 67 | + this.forwardFlutterUrl(want) | ||
| 30 | this.weChatLoginBridge?.handleWant(want) | 68 | this.weChatLoginBridge?.handleWant(want) |
| 31 | } | 69 | } |
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * Mirrors iOS notification/deep-link handling for an existing Flutter | ||
| 73 | + * engine. Keep the URL cached until Flutter confirms it handled the route, | ||
| 74 | + * so a not-yet-ready engine can still consume it during startup. | ||
| 75 | + */ | ||
| 76 | + private forwardFlutterUrl(want: Want): void { | ||
| 77 | + const url = want.uri; | ||
| 78 | + if (url === undefined || url.length === 0) { | ||
| 79 | + return; | ||
| 80 | + } | ||
| 81 | + const result: MethodResult = { | ||
| 82 | + success: (handled: Any): void => { | ||
| 83 | + if (handled === true) { | ||
| 84 | + NativePigeonRegistrar.clearUnhandedUrl(url) | ||
| 85 | + } | ||
| 86 | + }, | ||
| 87 | + error: (_code: string, _message: string, _details: Any): void => {}, | ||
| 88 | + notImplemented: (): void => {}, | ||
| 89 | + } | ||
| 90 | + this.flutterUrlChannel?.invokeMethod('handleFlutterUrl', { url }, result) | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + onForeground(): void { | ||
| 94 | + super.onForeground() | ||
| 95 | + NativePigeonRegistrar.setAppForeground(true) | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + onBackground(): void { | ||
| 99 | + super.onBackground() | ||
| 100 | + NativePigeonRegistrar.setAppForeground(false) | ||
| 101 | + } | ||
| 32 | } | 102 | } |
| 1 | -import { AlipayHostApi, AliPayResultCode } from '../pigeon/AlipayApi'; | 1 | +import { |
| 2 | + AlipayHostApi, AliPayResultCode, FlutterError, Result as PigeonResult, | ||
| 3 | +} from '../pigeon/AlipayApi'; | ||
| 4 | +import { Pay } from '@cashier_alipay/cashiersdk'; | ||
| 5 | + | ||
| 6 | +const ALIPAY_APP_ID = '2021006185683254'; | ||
| 2 | 7 | ||
| 3 | /** | 8 | /** |
| 4 | - * Alipay host implementation for HarmonyOS. | 9 | + * Alipay SDK bridge for HarmonyOS. |
| 5 | * | 10 | * |
| 6 | - * Currently a stub – replace with the real Alipay HarmonyOS SDK call | ||
| 7 | - * once the SDK is available. | 11 | + * `prepayData` is an order string produced and signed by the server. Do not |
| 12 | + * construct or sign it in the app: the order string contains the Alipay App ID | ||
| 13 | + * (2021006185683254) and must be signed with the merchant private key. | ||
| 8 | */ | 14 | */ |
| 9 | export class AlipayHostApiImpl extends AlipayHostApi { | 15 | export class AlipayHostApiImpl extends AlipayHostApi { |
| 10 | - launchAliPay(_prepayData: string): AliPayResultCode { | ||
| 11 | - return AliPayResultCode.UNSUPPORTED; | 16 | + launchAliPay( |
| 17 | + prepayData: string, | ||
| 18 | + callback: PigeonResult<AliPayResultCode>, | ||
| 19 | + ): void { | ||
| 20 | + console.log("------", "launchAliPay prepayData: " + prepayData) | ||
| 21 | + new Pay().pay(prepayData, true).then((result) => { | ||
| 22 | + switch (result.get('resultStatus')) { | ||
| 23 | + case '9000': | ||
| 24 | + callback.success(AliPayResultCode.SUCCESS); | ||
| 25 | + return; | ||
| 26 | + case '6001': | ||
| 27 | + callback.success(AliPayResultCode.CANCEL); | ||
| 28 | + return; | ||
| 29 | + default: | ||
| 30 | + callback.success(AliPayResultCode.ERROR); | ||
| 31 | + return; | ||
| 32 | + } | ||
| 33 | + }).catch(() => callback.success(AliPayResultCode.ERROR)); | ||
| 12 | } | 34 | } |
| 13 | } | 35 | } |
| @@ -77,18 +77,20 @@ export class HealthKitHostApiImpl extends HealthKitHostApi { | @@ -77,18 +77,20 @@ export class HealthKitHostApiImpl extends HealthKitHostApi { | ||
| 77 | }); | 77 | }); |
| 78 | } | 78 | } |
| 79 | 79 | ||
| 80 | - override cancelHealthAppAuthorization(): boolean { | ||
| 81 | - return false; | 80 | + /** |
| 81 | + * Triggers the manual device-to-cloud synchronization exposed by Huawei | ||
| 82 | + * Health. The application must have obtained the "manual data sync" | ||
| 83 | + * permission in the Health Service Kit console before this call can work. | ||
| 84 | + */ | ||
| 85 | + syncHealthDataToCloud(result: Result<boolean>): void { | ||
| 86 | + this.syncAllHealthData().then(() => { | ||
| 87 | + result.success(true); | ||
| 88 | + }).catch((error: Error) => { | ||
| 89 | + console.error(`syncHealthDataToCloud failed: ${error.name}: ${error.message}`); | ||
| 90 | + result.success(false); | ||
| 91 | + }); | ||
| 82 | } | 92 | } |
| 83 | 93 | ||
| 84 | - | ||
| 85 | - // cancelHealthAppAuthorization(result: Result<boolean>): void { | ||
| 86 | - // this.ensureInitialized() | ||
| 87 | - // .then(() => healthStore.cancelAuthorizations()) | ||
| 88 | - // .then(() => result.success(true)) | ||
| 89 | - // .catch(() => result.success(false)); | ||
| 90 | - // } | ||
| 91 | - | ||
| 92 | private async getAuthorization(): Promise<healthStore.AuthorizationResponse> { | 94 | private async getAuthorization(): Promise<healthStore.AuthorizationResponse> { |
| 93 | await this.ensureInitialized(); | 95 | await this.ensureInitialized(); |
| 94 | return healthStore.getAuthorizations(this.authorizationRequest); | 96 | return healthStore.getAuthorizations(this.authorizationRequest); |
| @@ -99,6 +101,11 @@ export class HealthKitHostApiImpl extends HealthKitHostApi { | @@ -99,6 +101,11 @@ export class HealthKitHostApiImpl extends HealthKitHostApi { | ||
| 99 | return healthStore.requestAuthorizations(this.context, this.authorizationRequest); | 101 | return healthStore.requestAuthorizations(this.context, this.authorizationRequest); |
| 100 | } | 102 | } |
| 101 | 103 | ||
| 104 | + private async syncAllHealthData(): Promise<void> { | ||
| 105 | + await this.ensureInitialized(); | ||
| 106 | + await healthStore.syncAll(); | ||
| 107 | + } | ||
| 108 | + | ||
| 102 | private async ensureInitialized(): Promise<void> { | 109 | private async ensureInitialized(): Promise<void> { |
| 103 | await healthStore.init(this.context); | 110 | await healthStore.init(this.context); |
| 104 | } | 111 | } |
| @@ -5,13 +5,32 @@ import { BinaryMessenger } from '@ohos/flutter_ohos/src/main/ets/plugin/common/B | @@ -5,13 +5,32 @@ import { BinaryMessenger } from '@ohos/flutter_ohos/src/main/ets/plugin/common/B | ||
| 5 | import { AlipayHostApiImpl } from './AlipayHostApiImpl'; | 5 | import { AlipayHostApiImpl } from './AlipayHostApiImpl'; |
| 6 | import { HealthKitHostApiImpl } from './HealthKitHostApiImpl'; | 6 | import { HealthKitHostApiImpl } from './HealthKitHostApiImpl'; |
| 7 | import { WearEngineHostApiImpl } from './WearEngineHostApiImpl'; | 7 | import { WearEngineHostApiImpl } from './WearEngineHostApiImpl'; |
| 8 | +import { PlatformHostApi } from '../pigeon/PlatformApi'; | ||
| 9 | +import { PlatformHostApiImpl } from './PlatformHostApiImpl'; | ||
| 8 | import common from '@ohos.app.ability.common'; | 10 | import common from '@ohos.app.ability.common'; |
| 11 | +import Want from '@ohos.app.ability.Want'; | ||
| 9 | 12 | ||
| 10 | /** Registers all Pigeon host-API implementations with the Flutter binary messenger. */ | 13 | /** Registers all Pigeon host-API implementations with the Flutter binary messenger. */ |
| 11 | export class NativePigeonRegistrar { | 14 | export class NativePigeonRegistrar { |
| 15 | + private static platformHostApi?: PlatformHostApiImpl; | ||
| 16 | + | ||
| 12 | static register(binaryMessenger: BinaryMessenger, context: common.UIAbilityContext): void { | 17 | static register(binaryMessenger: BinaryMessenger, context: common.UIAbilityContext): void { |
| 13 | HealthKitHostApi.setup(binaryMessenger, new HealthKitHostApiImpl(context)); | 18 | HealthKitHostApi.setup(binaryMessenger, new HealthKitHostApiImpl(context)); |
| 14 | WearEngineHostApi.setup(binaryMessenger, new WearEngineHostApiImpl()); | 19 | WearEngineHostApi.setup(binaryMessenger, new WearEngineHostApiImpl()); |
| 15 | AlipayHostApi.setup(binaryMessenger, new AlipayHostApiImpl()); | 20 | AlipayHostApi.setup(binaryMessenger, new AlipayHostApiImpl()); |
| 21 | + NativePigeonRegistrar.platformHostApi = new PlatformHostApiImpl(context); | ||
| 22 | + PlatformHostApi.setup(binaryMessenger, NativePigeonRegistrar.platformHostApi); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + static handleWant(want: Want): void { | ||
| 26 | + NativePigeonRegistrar.platformHostApi?.handleWant(want); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + static clearUnhandedUrl(url: string): void { | ||
| 30 | + NativePigeonRegistrar.platformHostApi?.clearUnhandedUrl(url); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + static setAppForeground(isForeground: boolean): void { | ||
| 34 | + NativePigeonRegistrar.platformHostApi?.setAppForeground(isForeground); | ||
| 16 | } | 35 | } |
| 17 | } | 36 | } |
| 1 | +import ObsClient from '@obs/esdk-obs-harmony'; | ||
| 2 | +import { http } from '@kit.NetworkKit'; | ||
| 3 | +import { notificationManager } from '@kit.NotificationKit'; | ||
| 4 | +import { systemShare } from '@kit.ShareKit'; | ||
| 5 | +import { uniformTypeDescriptor as utd } from '@kit.ArkData'; | ||
| 6 | +import { bundleManager, wantAgent } from '@kit.AbilityKit'; | ||
| 7 | +import { productViewManager } from '@kit.AppGalleryKit'; | ||
| 8 | +import { BusinessError, request } from '@kit.BasicServicesKit'; | ||
| 9 | +import preferences from '@ohos.data.preferences'; | ||
| 10 | +import webview from '@ohos.web.webview'; | ||
| 11 | +import deviceInfo from '@ohos.deviceInfo'; | ||
| 12 | +import display from '@ohos.display'; | ||
| 13 | +import fileIo from '@ohos.file.fs'; | ||
| 14 | +import fileUri from '@ohos.file.fileuri'; | ||
| 15 | +import common from '@ohos.app.ability.common'; | ||
| 16 | +import Want from '@ohos.app.ability.Want'; | ||
| 17 | +import { | ||
| 18 | + AppleProductInfo, | ||
| 19 | + AppleProductPaymentResult, | ||
| 20 | + AppleSignInModel, | ||
| 21 | + FlutterError, | ||
| 22 | + GoogleSignInModel, | ||
| 23 | + HResourceType, | ||
| 24 | + PlatformHostApi, | ||
| 25 | + Result, | ||
| 26 | +} from '../pigeon/PlatformApi'; | ||
| 27 | +import BuildProfile from 'BuildProfile'; | ||
| 28 | + | ||
| 29 | +interface SessionSnapshot { | ||
| 30 | + access_token?: string; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +interface TempCredential { | ||
| 34 | + access_key?: string; | ||
| 35 | + secret_key?: string; | ||
| 36 | + security_token?: string; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +interface TempTokenResponse { | ||
| 40 | + token?: TempCredential; | ||
| 41 | + keys?: string[]; | ||
| 42 | + host?: string; | ||
| 43 | + endpoint?: string; | ||
| 44 | + bucket?: string; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +const NOTIFICATION_PREFERENCES_NAME = 'platform_host_api'; | ||
| 48 | +const NOTIFICATION_AUTH_REQUESTED_KEY = 'notification_auth_requested'; | ||
| 49 | + | ||
| 50 | +/** | ||
| 51 | + * HarmonyOS implementation of the shared PlatformHostApi. | ||
| 52 | + * | ||
| 53 | + * Its OBS flow deliberately matches iOS: request a scoped, short-lived | ||
| 54 | + * credential from the API, then upload directly to OBS with the native SDK. | ||
| 55 | + */ | ||
| 56 | +export class PlatformHostApiImpl extends PlatformHostApi { | ||
| 57 | + private readonly context: common.UIAbilityContext; | ||
| 58 | + private baseUrl: string = ''; | ||
| 59 | + private accessToken: string = ''; | ||
| 60 | + private unhandedUrl: string | undefined; | ||
| 61 | + private isAppForeground: boolean = true; | ||
| 62 | + | ||
| 63 | + constructor(context: common.UIAbilityContext) { | ||
| 64 | + super(); | ||
| 65 | + this.context = context; | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + getFullUserAgent(): string { | ||
| 69 | + let versionCode = ''; | ||
| 70 | + let versionName = ''; | ||
| 71 | + try { | ||
| 72 | + const bundleInfo = bundleManager.getBundleInfoForSelfSync( | ||
| 73 | + bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION, | ||
| 74 | + ); | ||
| 75 | + versionCode = bundleInfo.versionCode.toString(); | ||
| 76 | + versionName = bundleInfo.versionName; | ||
| 77 | + } catch (_error) { | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + let hardware = ''; | ||
| 81 | + let osVersion = ''; | ||
| 82 | + try { | ||
| 83 | + hardware = deviceInfo.hardwareModel || deviceInfo.productModel; | ||
| 84 | + osVersion = deviceInfo.osFullName; | ||
| 85 | + } catch (_error) { | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + let height = ''; | ||
| 89 | + let width = ''; | ||
| 90 | + try { | ||
| 91 | + const screen = display.getDefaultDisplaySync(); | ||
| 92 | + height = screen.height.toString(); | ||
| 93 | + width = screen.width.toString(); | ||
| 94 | + } catch (_error) { | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + // Do not leave a leading space when HarmonyOS cannot provide the WebView | ||
| 98 | + // UA. A leading space can be rendered as a folded HTTP header line. | ||
| 99 | + // Strip line breaks as well, since a User-Agent must be a single header | ||
| 100 | + // value. | ||
| 101 | + const webUserAgent = this.getDefaultWebUserAgent() | ||
| 102 | + .replace(/[\r\n]+/g, ' ') | ||
| 103 | + .trim(); | ||
| 104 | + const appUserAgent = `doublefeel/${versionCode}(${versionName})` + | ||
| 105 | + `(${hardware}; HarmonyOS ${osVersion}; ${height}x${width})`; | ||
| 106 | + return webUserAgent.length > 0 | ||
| 107 | + ? `${webUserAgent} ${appUserAgent}` | ||
| 108 | + : appUserAgent; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + private getDefaultWebUserAgent(): string { | ||
| 112 | + try { | ||
| 113 | + return webview.WebviewController.getDefaultUserAgent(); | ||
| 114 | + } catch (_error) { | ||
| 115 | + return ''; | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + isChinaRegion(result: Result<boolean>): void { | ||
| 120 | + result.success(true); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + getApnsAuthStatus(result: Result<number>): void { | ||
| 124 | + this.getNotificationAuthorizationStatus() | ||
| 125 | + .then((status: number) => result.success(status)) | ||
| 126 | + .catch((error: Error) => result.error(this.toPlatformFlutterError(error, 'notification_status_failed'))); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + requestNotificationAuth(result: Result<boolean>): void { | ||
| 130 | + notificationManager.isNotificationEnabled() | ||
| 131 | + .then((enabled: boolean) => { | ||
| 132 | + if (enabled) { | ||
| 133 | + result.success(true); | ||
| 134 | + return; | ||
| 135 | + } | ||
| 136 | + return notificationManager.requestEnableNotification(this.context) | ||
| 137 | + .then(() => this.markNotificationAuthorizationRequested()) | ||
| 138 | + .then(() => notificationManager.isNotificationEnabled()) | ||
| 139 | + .then((granted: boolean) => result.success(granted)); | ||
| 140 | + }) | ||
| 141 | + .catch((error: Error) => { | ||
| 142 | + // HarmonyOS reports 1600004 when the notification dialog cannot be | ||
| 143 | + // shown again after the user has declined it. This is a normal denied | ||
| 144 | + // result, not a platform-call failure. Take the user to the app | ||
| 145 | + // settings immediately because the dialog cannot be reopened. | ||
| 146 | + if ((error as BusinessError).code === 1600004) { | ||
| 147 | + return this.markNotificationAuthorizationRequested() | ||
| 148 | + .then(() => this.openNotificationSettings()) | ||
| 149 | + .then(() => result.success(false)) | ||
| 150 | + .catch((storeError: Error) => | ||
| 151 | + result.error(this.toPlatformFlutterError(storeError, 'notification_permission_failed')), | ||
| 152 | + ); | ||
| 153 | + } | ||
| 154 | + return result.error(this.toPlatformFlutterError(error, 'notification_permission_failed')); | ||
| 155 | + }); | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + isDebugEnvoriment(): boolean { | ||
| 159 | + return BuildProfile.DEBUG || BuildProfile.enableEnvironmentSwitch; | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + shareText(text: string, result: Result<boolean>): void { | ||
| 163 | + const data = new systemShare.SharedData({ | ||
| 164 | + utd: utd.UniformDataType.TEXT, | ||
| 165 | + content: text, | ||
| 166 | + title: text, | ||
| 167 | + }); | ||
| 168 | + this.showSharedData(data, result); | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + shareFileData(databytes: number[], result: Result<boolean>): void { | ||
| 172 | + if (databytes.length === 0) { | ||
| 173 | + result.success(false); | ||
| 174 | + return; | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + const filePath = | ||
| 178 | + `${this.context.cacheDir}/doublefeel-share-${Date.now()}-${Math.floor(Math.random() * 1000000)}.data`; | ||
| 179 | + try { | ||
| 180 | + const file = fileIo.openSync( | ||
| 181 | + filePath, | ||
| 182 | + fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE | fileIo.OpenMode.TRUNC, | ||
| 183 | + ); | ||
| 184 | + try { | ||
| 185 | + fileIo.writeSync(file.fd, new Uint8Array(databytes).buffer); | ||
| 186 | + } finally { | ||
| 187 | + fileIo.closeSync(file); | ||
| 188 | + } | ||
| 189 | + const data = new systemShare.SharedData({ | ||
| 190 | + utd: utd.UniformDataType.FILE, | ||
| 191 | + uri: fileUri.getUriFromPath(filePath), | ||
| 192 | + title: 'doublefeel-data.data', | ||
| 193 | + }); | ||
| 194 | + this.showSharedData(data, result, () => this.deleteSharedFile(filePath)); | ||
| 195 | + } catch (error) { | ||
| 196 | + this.deleteSharedFile(filePath); | ||
| 197 | + result.error(this.toPlatformFlutterError(error, 'share_file_failed')); | ||
| 198 | + } | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + private showSharedData( | ||
| 202 | + data: systemShare.SharedData, | ||
| 203 | + result: Result<boolean>, | ||
| 204 | + onFinished?: () => void, | ||
| 205 | + ): void { | ||
| 206 | + const controller = new systemShare.ShareController(data); | ||
| 207 | + let settled = false; | ||
| 208 | + const onShareCompleted = (_shareResult: systemShare.ShareOperationResult): void => { | ||
| 209 | + finish(true); | ||
| 210 | + }; | ||
| 211 | + const onDismiss = (): void => { | ||
| 212 | + finish(false); | ||
| 213 | + }; | ||
| 214 | + const finish = (shared: boolean): void => { | ||
| 215 | + if (settled) { | ||
| 216 | + return; | ||
| 217 | + } | ||
| 218 | + settled = true; | ||
| 219 | + controller.off('shareCompleted', onShareCompleted); | ||
| 220 | + controller.off('dismiss', onDismiss); | ||
| 221 | + onFinished?.(); | ||
| 222 | + result.success(shared); | ||
| 223 | + }; | ||
| 224 | + | ||
| 225 | + controller.on('shareCompleted', onShareCompleted); | ||
| 226 | + controller.on('dismiss', onDismiss); | ||
| 227 | + controller.show(this.context, {}) | ||
| 228 | + .catch((error: Error) => { | ||
| 229 | + if (settled) { | ||
| 230 | + return; | ||
| 231 | + } | ||
| 232 | + settled = true; | ||
| 233 | + controller.off('shareCompleted', onShareCompleted); | ||
| 234 | + controller.off('dismiss', onDismiss); | ||
| 235 | + onFinished?.(); | ||
| 236 | + result.error(this.toPlatformFlutterError(error, 'share_failed')); | ||
| 237 | + }); | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + private deleteSharedFile(filePath: string): void { | ||
| 241 | + try { | ||
| 242 | + fileIo.unlinkSync(filePath); | ||
| 243 | + } catch (_error) { | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + updateLoginInfo(jsonString: string, baseUrl: string): void { | ||
| 248 | + try { | ||
| 249 | + const session = JSON.parse(jsonString) as SessionSnapshot; | ||
| 250 | + this.accessToken = session.access_token ?? ''; | ||
| 251 | + this.baseUrl = baseUrl; | ||
| 252 | + } catch (_error) { | ||
| 253 | + this.accessToken = ''; | ||
| 254 | + this.baseUrl = ''; | ||
| 255 | + } | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + logout(): void { | ||
| 259 | + this.accessToken = ''; | ||
| 260 | + this.baseUrl = ''; | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + refreshVip(): void { | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + refreshWatchAppAndWidgets(): void { | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + requestAppReview(result: Result<boolean>): void { | ||
| 270 | + // AppGallery Kit opens this app's product page, where users can submit a | ||
| 271 | + // review. This is the HarmonyOS equivalent of opening the App Store review | ||
| 272 | + // page on iOS. | ||
| 273 | + const want: Want = { | ||
| 274 | + parameters: { | ||
| 275 | + bundleName: this.context.abilityInfo.bundleName, | ||
| 276 | + }, | ||
| 277 | + }; | ||
| 278 | + try { | ||
| 279 | + productViewManager.loadProduct(this.context, want); | ||
| 280 | + result.success(true); | ||
| 281 | + } catch (error) { | ||
| 282 | + result.error(this.toPlatformFlutterError(error as Error, 'request_app_review_failed')); | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + jumpAppSetting(): boolean { | ||
| 287 | + return false; | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + nativeHandleUrl(_urlString: string): boolean { | ||
| 291 | + return false; | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + requestUnhandedUrl(): string | undefined { | ||
| 295 | + const url = this.unhandedUrl; | ||
| 296 | + this.unhandedUrl = undefined; | ||
| 297 | + return url; | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + clearUnhandedUrl(url: string): void { | ||
| 301 | + if (this.unhandedUrl === url) { | ||
| 302 | + this.unhandedUrl = undefined; | ||
| 303 | + } | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + handleWant(want: Want): void { | ||
| 307 | + if (want.uri !== undefined && want.uri.length > 0) { | ||
| 308 | + this.unhandedUrl = want.uri; | ||
| 309 | + } | ||
| 310 | + } | ||
| 311 | + | ||
| 312 | + setAppForeground(isForeground: boolean): void { | ||
| 313 | + this.isAppForeground = isForeground; | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + requestAppleSignIn(result: Result<AppleSignInModel | undefined>): void { | ||
| 317 | + result.success(undefined); | ||
| 318 | + } | ||
| 319 | + | ||
| 320 | + requestGoogleSignIn(result: Result<GoogleSignInModel | undefined>): void { | ||
| 321 | + result.success(undefined); | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + sendEmail(mailTo: string, title: string, content: string, result: Result<boolean>): void { | ||
| 325 | + if (mailTo.length === 0) { | ||
| 326 | + result.success(false); | ||
| 327 | + return; | ||
| 328 | + } | ||
| 329 | + const want: Want = { | ||
| 330 | + action: 'ohos.want.action.sendToData', | ||
| 331 | + uri: `mailto:${encodeURIComponent(mailTo)}?subject=${encodeURIComponent(title)}&body=${encodeURIComponent( | ||
| 332 | + content)}`, | ||
| 333 | + type: 'message/rfc822', | ||
| 334 | + }; | ||
| 335 | + this.context.startAbility(want) | ||
| 336 | + .then(() => result.success(true)) | ||
| 337 | + .catch((error: Error) => result.error(this.toPlatformFlutterError(error, 'send_email_failed'))); | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + requestAppleProductInfo( | ||
| 341 | + _productId: string, | ||
| 342 | + _baseUnit: number, | ||
| 343 | + result: Result<AppleProductInfo | undefined>, | ||
| 344 | + ): void { | ||
| 345 | + result.success(undefined); | ||
| 346 | + } | ||
| 347 | + | ||
| 348 | + performApplePayment( | ||
| 349 | + _productId: string, | ||
| 350 | + _uuid: string, | ||
| 351 | + result: Result<AppleProductPaymentResult | undefined>, | ||
| 352 | + ): void { | ||
| 353 | + result.success(undefined); | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + performRestore(result: Result<boolean>): void { | ||
| 357 | + result.success(false); | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + uploadFile(filePath: string, resourceType: HResourceType, result: Result<string | undefined>): void { | ||
| 361 | + this.upload(filePath, resourceType) | ||
| 362 | + .then((fileUrl: string) => { | ||
| 363 | + console.log("------, uploadFile success: " + fileUrl) | ||
| 364 | + return result.success(fileUrl) | ||
| 365 | + }) | ||
| 366 | + .catch((error: Error) => { | ||
| 367 | + console.log("------, uploadFile error: " + error) | ||
| 368 | + return result.error(this.toFlutterError(error)) | ||
| 369 | + }); | ||
| 370 | + } | ||
| 371 | + | ||
| 372 | + performCropImage( | ||
| 373 | + _imageUrl: string, | ||
| 374 | + _maxKB: number | undefined, | ||
| 375 | + _width: number | undefined, | ||
| 376 | + _height: number | undefined, | ||
| 377 | + result: Result<string | undefined>, | ||
| 378 | + ): void { | ||
| 379 | + result.success(undefined); | ||
| 380 | + } | ||
| 381 | + | ||
| 382 | + sendLocalNotification( | ||
| 383 | + dataType: number, | ||
| 384 | + dateTime: number, | ||
| 385 | + title: string, | ||
| 386 | + content: string, | ||
| 387 | + link: string, | ||
| 388 | + result: Result<boolean>, | ||
| 389 | + ): void { | ||
| 390 | + if (this.isAppForeground) { | ||
| 391 | + result.success(false); | ||
| 392 | + return; | ||
| 393 | + } | ||
| 394 | + const notificationId = Math.abs(Math.trunc(dataType * 1000000000 + dateTime)) % 2147483647; | ||
| 395 | + notificationManager.isNotificationEnabled() | ||
| 396 | + .then((enabled: boolean) => { | ||
| 397 | + if (!enabled) { | ||
| 398 | + result.success(false); | ||
| 399 | + return; | ||
| 400 | + } | ||
| 401 | + return this.publishLocalNotification(notificationId, title, content, link) | ||
| 402 | + .then(() => result.success(true)); | ||
| 403 | + }) | ||
| 404 | + .catch((error: Error) => result.error(this.toPlatformFlutterError(error, 'local_notification_failed'))); | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + private async getNotificationAuthorizationStatus(): Promise<number> { | ||
| 408 | + if (await notificationManager.isNotificationEnabled()) { | ||
| 409 | + return 1; | ||
| 410 | + } | ||
| 411 | + const store = await preferences.getPreferences(this.context, NOTIFICATION_PREFERENCES_NAME); | ||
| 412 | + return store.getSync(NOTIFICATION_AUTH_REQUESTED_KEY, false) as boolean ? -1 : 0; | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | + private async markNotificationAuthorizationRequested(): Promise<void> { | ||
| 416 | + const store = await preferences.getPreferences(this.context, NOTIFICATION_PREFERENCES_NAME); | ||
| 417 | + store.putSync(NOTIFICATION_AUTH_REQUESTED_KEY, true); | ||
| 418 | + await store.flush(); | ||
| 419 | + } | ||
| 420 | + | ||
| 421 | + private async openNotificationSettings(): Promise<void> { | ||
| 422 | + const want: Want = { | ||
| 423 | + abilityName: 'com.huawei.hmos.settings.MainAbility', | ||
| 424 | + bundleName: 'com.huawei.hmos.settings', | ||
| 425 | + uri: 'application_info_entry', | ||
| 426 | + parameters: { | ||
| 427 | + pushParams: this.context.applicationInfo.name, | ||
| 428 | + }, | ||
| 429 | + }; | ||
| 430 | + await this.context.startAbility(want); | ||
| 431 | + } | ||
| 432 | + | ||
| 433 | + private async publishLocalNotification( | ||
| 434 | + notificationId: number, | ||
| 435 | + title: string, | ||
| 436 | + content: string, | ||
| 437 | + link: string, | ||
| 438 | + ): Promise<void> { | ||
| 439 | + const notificationWant: Want = { | ||
| 440 | + bundleName: this.context.abilityInfo.bundleName, | ||
| 441 | + abilityName: this.context.abilityInfo.name, | ||
| 442 | + uri: link, | ||
| 443 | + }; | ||
| 444 | + const notificationWantAgent = await wantAgent.getWantAgent({ | ||
| 445 | + wants: [notificationWant], | ||
| 446 | + actionType: wantAgent.OperationType.START_ABILITY, | ||
| 447 | + requestCode: notificationId, | ||
| 448 | + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], | ||
| 449 | + }); | ||
| 450 | + await notificationManager.publish({ | ||
| 451 | + id: notificationId, | ||
| 452 | + content: { | ||
| 453 | + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, | ||
| 454 | + normal: { title, text: content, additionalText: link }, | ||
| 455 | + }, | ||
| 456 | + wantAgent: notificationWantAgent, | ||
| 457 | + }); | ||
| 458 | + } | ||
| 459 | + | ||
| 460 | + private async upload(filePath: string, resourceType: HResourceType): Promise<string> { | ||
| 461 | + console.log("------", "uploadFile: " + filePath + " " + resourceType) | ||
| 462 | + if (filePath.length === 0) { | ||
| 463 | + throw new Error('The upload file path is empty.'); | ||
| 464 | + } | ||
| 465 | + | ||
| 466 | + const config = await this.requestUploadConfig(resourceType); | ||
| 467 | + const obsClient = new ObsClient({ | ||
| 468 | + AccessKeyId: config.accessKey, | ||
| 469 | + SecretAccessKey: config.secretKey, | ||
| 470 | + SecurityToken: config.securityToken, | ||
| 471 | + Server: this.normalizeHttpsUrl(config.endpoint), | ||
| 472 | + }); | ||
| 473 | + await this.uploadFileWithPostSignature(obsClient, config, filePath, resourceType); | ||
| 474 | + return `${this.normalizeHttpsUrl(config.host).replace(/\/$/, '')}/${config.objectKey}`; | ||
| 475 | + } | ||
| 476 | + | ||
| 477 | + private async requestUploadConfig(resourceType: HResourceType): Promise<RequiredUploadConfig> { | ||
| 478 | + if (this.baseUrl.length === 0 || this.accessToken.length === 0) { | ||
| 479 | + throw new Error('A logged-in session is required before uploading a file.'); | ||
| 480 | + } | ||
| 481 | + | ||
| 482 | + const request = http.createHttp(); | ||
| 483 | + try { | ||
| 484 | + const response = await request.request( | ||
| 485 | + `${this.baseUrl.replace(/\/$/, '')}/client/doublefeel/obs/temp_token/`, | ||
| 486 | + { | ||
| 487 | + method: http.RequestMethod.POST, | ||
| 488 | + header: { | ||
| 489 | + 'Content-Type': 'application/json', | ||
| 490 | + 'access_token': this.accessToken, | ||
| 491 | + }, | ||
| 492 | + extraData: JSON.stringify({ | ||
| 493 | + file_type: resourceType === HResourceType.IMAGE ? 'png' : 'mp4', | ||
| 494 | + scene: 'avatar', | ||
| 495 | + count: 1, | ||
| 496 | + }), | ||
| 497 | + expectDataType: http.HttpDataType.STRING, | ||
| 498 | + }, | ||
| 499 | + ); | ||
| 500 | + if (response.responseCode < 200 || response.responseCode >= 300) { | ||
| 501 | + throw new Error(`Requesting OBS credentials failed (HTTP ${response.responseCode}).`); | ||
| 502 | + } | ||
| 503 | + const tokenInfo = JSON.parse(response.result as string) as TempTokenResponse; | ||
| 504 | + const accessKey = tokenInfo.token?.access_key; | ||
| 505 | + const secretKey = tokenInfo.token?.secret_key; | ||
| 506 | + const securityToken = tokenInfo.token?.security_token; | ||
| 507 | + const objectKey = tokenInfo.keys?.[0]; | ||
| 508 | + if (!accessKey || !secretKey || !securityToken || !objectKey || !tokenInfo.host || !tokenInfo.endpoint || | ||
| 509 | + !tokenInfo.bucket) { | ||
| 510 | + throw new Error('The OBS credential response is incomplete.'); | ||
| 511 | + } | ||
| 512 | + return { | ||
| 513 | + accessKey, | ||
| 514 | + secretKey, | ||
| 515 | + securityToken, | ||
| 516 | + objectKey, | ||
| 517 | + host: tokenInfo.host, | ||
| 518 | + endpoint: tokenInfo.endpoint, | ||
| 519 | + bucket: tokenInfo.bucket, | ||
| 520 | + }; | ||
| 521 | + } finally { | ||
| 522 | + request.destroy(); | ||
| 523 | + } | ||
| 524 | + } | ||
| 525 | + | ||
| 526 | + private normalizeHttpsUrl(value: string): string { | ||
| 527 | + return value.startsWith('http://') || value.startsWith('https://') ? value : `https://${value}`; | ||
| 528 | + } | ||
| 529 | + | ||
| 530 | + private toFlutterError(error: Error): FlutterError { | ||
| 531 | + return new FlutterError('obs_upload_failed', 'OBSUploadError', error.message); | ||
| 532 | + } | ||
| 533 | + | ||
| 534 | + private toPlatformFlutterError(error: Error, code: string): FlutterError { | ||
| 535 | + return new FlutterError(code, 'PlatformHostApiError', error.message); | ||
| 536 | + } | ||
| 537 | + | ||
| 538 | + /** | ||
| 539 | + * Streams a local file through Harmony's upload task. The OBS putObject | ||
| 540 | + * API accepts only ArrayBuffer for binary content, which is unsuitable for | ||
| 541 | + * the feedback flow's 200 MB video limit. | ||
| 542 | + */ | ||
| 543 | + private async uploadFileWithPostSignature( | ||
| 544 | + obsClient: ObsClient, | ||
| 545 | + config: RequiredUploadConfig, | ||
| 546 | + filePath: string, | ||
| 547 | + resourceType: HResourceType, | ||
| 548 | + ): Promise<void> { | ||
| 549 | + const stagedFile = await this.stageFileForUpload(filePath); | ||
| 550 | + try { | ||
| 551 | + const contentType = this.contentType(filePath, resourceType); | ||
| 552 | + const signature = obsClient.createPostSignatureSync({ | ||
| 553 | + Bucket: config.bucket, | ||
| 554 | + Key: config.objectKey, | ||
| 555 | + Expires: 300, | ||
| 556 | + FormParams: { | ||
| 557 | + 'content-type': contentType, | ||
| 558 | + 'success_action_status': '200', | ||
| 559 | + }, | ||
| 560 | + }); | ||
| 561 | + const uploadTask = await request.uploadFile(this.context, { | ||
| 562 | + url: this.formUploadUrl(config), | ||
| 563 | + method: 'POST', | ||
| 564 | + header: {}, | ||
| 565 | + files: [{ | ||
| 566 | + filename: config.objectKey, | ||
| 567 | + name: 'file', | ||
| 568 | + uri: stagedFile.uri, | ||
| 569 | + type: contentType, | ||
| 570 | + }], | ||
| 571 | + data: [ | ||
| 572 | + { name: 'key', value: config.objectKey }, | ||
| 573 | + { name: 'AccessKeyId', value: config.accessKey }, | ||
| 574 | + { name: 'policy', value: signature.Policy }, | ||
| 575 | + { name: 'signature', value: signature.Signature }, | ||
| 576 | + { name: 'x-obs-security-token', value: config.securityToken }, | ||
| 577 | + { name: 'content-type', value: contentType }, | ||
| 578 | + { name: 'success_action_status', value: '200' }, | ||
| 579 | + ], | ||
| 580 | + }); | ||
| 581 | + await this.waitForUploadTask(uploadTask); | ||
| 582 | + } finally { | ||
| 583 | + if (stagedFile.shouldDelete) { | ||
| 584 | + this.deleteSharedFile(stagedFile.path); | ||
| 585 | + } | ||
| 586 | + } | ||
| 587 | + } | ||
| 588 | + | ||
| 589 | + private async stageFileForUpload(filePath: string): Promise<StagedUploadFile> { | ||
| 590 | + const cachePrefix = `${this.context.cacheDir}/`; | ||
| 591 | + if (filePath.startsWith(cachePrefix)) { | ||
| 592 | + return { | ||
| 593 | + path: filePath, | ||
| 594 | + uri: `internal://cache/${filePath.substring(cachePrefix.length)}`, | ||
| 595 | + shouldDelete: false, | ||
| 596 | + }; | ||
| 597 | + } | ||
| 598 | + | ||
| 599 | + const extensionIndex = filePath.lastIndexOf('.'); | ||
| 600 | + const extension = extensionIndex >= 0 ? filePath.substring(extensionIndex) : ''; | ||
| 601 | + const path = `${this.context.cacheDir}/obs-upload-${Date.now()}-${Math.floor(Math.random() * 1000000)}${extension}`; | ||
| 602 | + try { | ||
| 603 | + await fileIo.copyFile(filePath, path); | ||
| 604 | + } catch (_error) { | ||
| 605 | + this.deleteSharedFile(path); | ||
| 606 | + // ArkTS only permits Error instances to be thrown. The file API may | ||
| 607 | + // report a platform-specific value, so wrap it in a standard Error. | ||
| 608 | + throw new Error('Failed to stage the file for OBS upload.'); | ||
| 609 | + } | ||
| 610 | + return { | ||
| 611 | + path, | ||
| 612 | + uri: `internal://cache/${path.substring(cachePrefix.length)}`, | ||
| 613 | + shouldDelete: true, | ||
| 614 | + }; | ||
| 615 | + } | ||
| 616 | + | ||
| 617 | + private formUploadUrl(config: RequiredUploadConfig): string { | ||
| 618 | + const endpoint = this.normalizeHttpsUrl(config.endpoint).replace(/\/+$/, ''); | ||
| 619 | + const endpointMatch = endpoint.match(/^(https?):\/\/(.+)$/); | ||
| 620 | + if (!endpointMatch) { | ||
| 621 | + throw new Error('The OBS endpoint is invalid.'); | ||
| 622 | + } | ||
| 623 | + const scheme = endpointMatch[1]; | ||
| 624 | + const host = endpointMatch[2]; | ||
| 625 | + return host.startsWith(`${config.bucket}.`) | ||
| 626 | + ? `${scheme}://${host}` | ||
| 627 | + : `${scheme}://${config.bucket}.${host}`; | ||
| 628 | + } | ||
| 629 | + | ||
| 630 | + private waitForUploadTask(uploadTask: request.UploadTask): Promise<void> { | ||
| 631 | + return new Promise((resolve, reject) => { | ||
| 632 | + uploadTask.on('complete', (taskStates) => { | ||
| 633 | + const taskState = taskStates[0]; | ||
| 634 | + // Harmony's UploadTask uses 0 (rather than an HTTP status code) to | ||
| 635 | + // indicate that the task completed successfully. | ||
| 636 | + if (taskState && taskState.responseCode === 0) { | ||
| 637 | + resolve(); | ||
| 638 | + return; | ||
| 639 | + } | ||
| 640 | + reject(new Error(`OBS form upload failed (task code ${taskState?.responseCode ?? 'unknown'}).`)); | ||
| 641 | + }); | ||
| 642 | + uploadTask.on('fail', (taskStates) => { | ||
| 643 | + const taskState = taskStates[0]; | ||
| 644 | + reject(new Error(`OBS form upload failed: ${taskState?.message ?? 'unknown error'}.`)); | ||
| 645 | + }); | ||
| 646 | + }); | ||
| 647 | + } | ||
| 648 | + | ||
| 649 | + private contentType(filePath: string, resourceType: HResourceType): string { | ||
| 650 | + const lowerPath = filePath.toLowerCase(); | ||
| 651 | + if (lowerPath.endsWith('.jpg') || lowerPath.endsWith('.jpeg')) { | ||
| 652 | + return 'image/jpeg'; | ||
| 653 | + } | ||
| 654 | + if (lowerPath.endsWith('.heic')) { | ||
| 655 | + return 'image/heic'; | ||
| 656 | + } | ||
| 657 | + if (lowerPath.endsWith('.webp')) { | ||
| 658 | + return 'image/webp'; | ||
| 659 | + } | ||
| 660 | + if (lowerPath.endsWith('.gif')) { | ||
| 661 | + return 'image/gif'; | ||
| 662 | + } | ||
| 663 | + if (lowerPath.endsWith('.mov')) { | ||
| 664 | + return 'video/quicktime'; | ||
| 665 | + } | ||
| 666 | + return resourceType === HResourceType.IMAGE ? 'image/png' : 'video/mp4'; | ||
| 667 | + } | ||
| 668 | +} | ||
| 669 | + | ||
| 670 | +interface RequiredUploadConfig { | ||
| 671 | + accessKey: string; | ||
| 672 | + secretKey: string; | ||
| 673 | + securityToken: string; | ||
| 674 | + objectKey: string; | ||
| 675 | + host: string; | ||
| 676 | + endpoint: string; | ||
| 677 | + bucket: string; | ||
| 678 | +} | ||
| 679 | + | ||
| 680 | +interface StagedUploadFile { | ||
| 681 | + path: string; | ||
| 682 | + uri: string; | ||
| 683 | + shouldDelete: boolean; | ||
| 684 | +} |
| @@ -16,7 +16,7 @@ import { FlutterError, Result, WeChatHostApi } from '../pigeon/WeChatApi'; | @@ -16,7 +16,7 @@ import { FlutterError, Result, WeChatHostApi } from '../pigeon/WeChatApi'; | ||
| 16 | * Fill in the AppID created for the HarmonyOS application in WeChat Open | 16 | * Fill in the AppID created for the HarmonyOS application in WeChat Open |
| 17 | * Platform. Keep AppSecret on the server only. | 17 | * Platform. Keep AppSecret on the server only. |
| 18 | */ | 18 | */ |
| 19 | -const WECHAT_APP_ID = ''; | 19 | +const WECHAT_APP_ID = 'wx42b603acf3dd68f5'; |
| 20 | 20 | ||
| 21 | /** Bridges the native OpenSDK authorization callback to Flutter. */ | 21 | /** Bridges the native OpenSDK authorization callback to Flutter. */ |
| 22 | export class WeChatLoginBridge extends WeChatHostApi implements WXApiEventHandler { | 22 | export class WeChatLoginBridge extends WeChatHostApi implements WXApiEventHandler { |
| @@ -103,10 +103,16 @@ export class PigeonCodec extends StandardMessageCodec { | @@ -103,10 +103,16 @@ export class PigeonCodec extends StandardMessageCodec { | ||
| 103 | } | 103 | } |
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | +export interface Result<T> { | ||
| 107 | + success(result: T): void; | ||
| 108 | + | ||
| 109 | + error(error: Error): void; | ||
| 110 | +} | ||
| 111 | + | ||
| 106 | /* Generated abstract class from Pigeon that represents a handler of messages from Flutter.*/ | 112 | /* Generated abstract class from Pigeon that represents a handler of messages from Flutter.*/ |
| 107 | export abstract class AlipayHostApi { | 113 | export abstract class AlipayHostApi { |
| 108 | 114 | ||
| 109 | - abstract launchAliPay(prepayData: string): AliPayResultCode; | 115 | + abstract launchAliPay(prepayData: string, result: Result<AliPayResultCode>): void; |
| 110 | /** The codec used by AlipayHostApi. */ | 116 | /** The codec used by AlipayHostApi. */ |
| 111 | static getCodec(): MessageCodec<Object> { | 117 | static getCodec(): MessageCodec<Object> { |
| 112 | return PigeonCodec.INSTANCE; | 118 | return PigeonCodec.INSTANCE; |
| @@ -130,16 +136,18 @@ export abstract class AlipayHostApi { | @@ -130,16 +136,18 @@ export abstract class AlipayHostApi { | ||
| 130 | reply.reply(wrapError(new Error('Invalid Pigeon message: expected at least 1 argument(s).'))); | 136 | reply.reply(wrapError(new Error('Invalid Pigeon message: expected at least 1 argument(s).'))); |
| 131 | return; | 137 | return; |
| 132 | } | 138 | } |
| 133 | - let res: Array<Object | null> = []; | ||
| 134 | - try { | ||
| 135 | - let pigeonEnumResult: AliPayResultCode = api!.launchAliPay(args[0] as string); | ||
| 136 | - let output: ESObject = pigeonEnumResult === null || pigeonEnumResult === undefined ? null : new AliPayResultCodeEnum(AliPayResultCode[pigeonEnumResult as number]); | ||
| 137 | - res.push(output); | ||
| 138 | - } catch (error) { | ||
| 139 | - let wrappedError: Array<Object | null> = wrapError(error); | ||
| 140 | - res = wrappedError; | 139 | + class ResultImp implements Result<AliPayResultCode> { |
| 140 | + success(result: AliPayResultCode): void { | ||
| 141 | + let res: Array<Object | null> = []; | ||
| 142 | + res.push(new AliPayResultCodeEnum(AliPayResultCode[result as number])); | ||
| 143 | + reply.reply(res); | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + error(error: Error): void { | ||
| 147 | + reply.reply(wrapError(error)); | ||
| 148 | + } | ||
| 141 | } | 149 | } |
| 142 | - reply.reply(res); | 150 | + api!.launchAliPay(args[0] as string, new ResultImp()); |
| 143 | } }); | 151 | } }); |
| 144 | } else { | 152 | } else { |
| 145 | channel.setMessageHandler(null); | 153 | channel.setMessageHandler(null); |
| @@ -139,6 +139,13 @@ export abstract class HealthKitHostApi { | @@ -139,6 +139,13 @@ export abstract class HealthKitHostApi { | ||
| 139 | abstract checkHealthAppAuthorization(result: Result<HealthAuthorization>): void; | 139 | abstract checkHealthAppAuthorization(result: Result<HealthAuthorization>): void; |
| 140 | 140 | ||
| 141 | abstract requestHealthClientAuthorization(result: Result<boolean>): void; | 141 | abstract requestHealthClientAuthorization(result: Result<boolean>): void; |
| 142 | + /* | ||
| 143 | + * Requests Huawei Health to upload the user's latest data to its cloud. | ||
| 144 | + * | ||
| 145 | + * This only triggers the Health app's configured device-to-cloud sync; it | ||
| 146 | + * does not wait for, or return, the subsequently available cloud data. | ||
| 147 | + */ | ||
| 148 | + abstract syncHealthDataToCloud(result: Result<boolean>): void; | ||
| 142 | /** The codec used by HealthKitHostApi. */ | 149 | /** The codec used by HealthKitHostApi. */ |
| 143 | static getCodec(): MessageCodec<Object> { | 150 | static getCodec(): MessageCodec<Object> { |
| 144 | return PigeonCodec.INSTANCE; | 151 | return PigeonCodec.INSTANCE; |
| @@ -200,5 +207,32 @@ export abstract class HealthKitHostApi { | @@ -200,5 +207,32 @@ export abstract class HealthKitHostApi { | ||
| 200 | channel.setMessageHandler(null); | 207 | channel.setMessageHandler(null); |
| 201 | } | 208 | } |
| 202 | } | 209 | } |
| 210 | + { | ||
| 211 | + let channel: BasicMessageChannel<Object> = | ||
| 212 | + new BasicMessageChannel( | ||
| 213 | + binaryMessenger, 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.syncHealthDataToCloud' + separatedMessageChannelSuffix, HealthKitHostApi.getCodec()); | ||
| 214 | + if (api != null) { | ||
| 215 | + channel.setMessageHandler({ | ||
| 216 | + onMessage(message: Object, reply: Reply<Object>) { | ||
| 217 | + class ResultImp implements Result<boolean>{ | ||
| 218 | + success(result: boolean): void { | ||
| 219 | + let res: Array<Object | null> = []; | ||
| 220 | + res.push(result); | ||
| 221 | + reply.reply(res); | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + error(error: Error): void { | ||
| 225 | + let wrappedError: Array<Object | null> = wrapError(error); | ||
| 226 | + reply.reply(wrappedError); | ||
| 227 | + } | ||
| 228 | + } | ||
| 229 | + let resultCallback: Result<boolean> = new ResultImp(); | ||
| 230 | + | ||
| 231 | + api!.syncHealthDataToCloud(resultCallback); | ||
| 232 | + } }); | ||
| 233 | + } else { | ||
| 234 | + channel.setMessageHandler(null); | ||
| 235 | + } | ||
| 236 | + } | ||
| 203 | } | 237 | } |
| 204 | } | 238 | } |
| @@ -10,7 +10,9 @@ | @@ -10,7 +10,9 @@ | ||
| 10 | "querySchemes": [ | 10 | "querySchemes": [ |
| 11 | "huaweischeme", | 11 | "huaweischeme", |
| 12 | "weixin", | 12 | "weixin", |
| 13 | - "wxopensdk" | 13 | + "wxopensdk", |
| 14 | + "https", | ||
| 15 | + "alipays" | ||
| 14 | ], | 16 | ], |
| 15 | "deliveryWithInstall": true, | 17 | "deliveryWithInstall": true, |
| 16 | "installationFree": false, | 18 | "installationFree": false, |
| 1 | { | 1 | { |
| 2 | "modelVersion": "5.0.0", | 2 | "modelVersion": "5.0.0", |
| 3 | - "name": "doublefeel_flutter", | ||
| 4 | - "version": "1.0.0", | 3 | + "name": "DoubleFeel", |
| 4 | + "version": "2.7.0", | ||
| 5 | "description": "Please describe the basic information.", | 5 | "description": "Please describe the basic information.", |
| 6 | "main": "", | 6 | "main": "", |
| 7 | "author": "", | 7 | "author": "", |
| 8 | "license": "", | 8 | "license": "", |
| 9 | "dependencies": { | 9 | "dependencies": { |
| 10 | + "@cashier_alipay/cashiersdk": "15.8.43", | ||
| 10 | "@tencent/wechat_open_sdk": "1.0.21" | 11 | "@tencent/wechat_open_sdk": "1.0.21" |
| 11 | }, | 12 | }, |
| 12 | "devDependencies": { | 13 | "devDependencies": { |
| 13 | "@ohos/hypium": "1.0.6" | 14 | "@ohos/hypium": "1.0.6" |
| 14 | - } | ||
| 15 | -} | 15 | + }, |
| 16 | + "dynamicDependencies": {} | ||
| 17 | +} |
| @@ -34,4 +34,11 @@ abstract class HealthKitHostApi { | @@ -34,4 +34,11 @@ abstract class HealthKitHostApi { | ||
| 34 | 34 | ||
| 35 | @async | 35 | @async |
| 36 | bool requestHealthClientAuthorization(); | 36 | bool requestHealthClientAuthorization(); |
| 37 | + | ||
| 38 | + /// Requests Huawei Health to upload the user's latest data to its cloud. | ||
| 39 | + /// | ||
| 40 | + /// This only triggers the Health app's configured device-to-cloud sync; it | ||
| 41 | + /// does not wait for, or return, the subsequently available cloud data. | ||
| 42 | + @async | ||
| 43 | + bool syncHealthDataToCloud(); | ||
| 37 | } | 44 | } |
| @@ -6,7 +6,7 @@ packages: | @@ -6,7 +6,7 @@ packages: | ||
| 6 | description: | 6 | description: |
| 7 | name: _fe_analyzer_shared | 7 | name: _fe_analyzer_shared |
| 8 | sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f | 8 | sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f |
| 9 | - url: "https://pub.dev" | 9 | + url: "https://pub.flutter-io.cn" |
| 10 | source: hosted | 10 | source: hosted |
| 11 | version: "85.0.0" | 11 | version: "85.0.0" |
| 12 | analyzer: | 12 | analyzer: |
| @@ -14,7 +14,7 @@ packages: | @@ -14,7 +14,7 @@ packages: | ||
| 14 | description: | 14 | description: |
| 15 | name: analyzer | 15 | name: analyzer |
| 16 | sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" | 16 | sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" |
| 17 | - url: "https://pub.dev" | 17 | + url: "https://pub.flutter-io.cn" |
| 18 | source: hosted | 18 | source: hosted |
| 19 | version: "7.7.1" | 19 | version: "7.7.1" |
| 20 | archive: | 20 | archive: |
| @@ -22,7 +22,7 @@ packages: | @@ -22,7 +22,7 @@ packages: | ||
| 22 | description: | 22 | description: |
| 23 | name: archive | 23 | name: archive |
| 24 | sha256: ace891da0862b0e4cabbb064ee3fd87b2728b898949fdb366d83fe98342c9f19 | 24 | sha256: ace891da0862b0e4cabbb064ee3fd87b2728b898949fdb366d83fe98342c9f19 |
| 25 | - url: "https://pub.dev" | 25 | + url: "https://pub.flutter-io.cn" |
| 26 | source: hosted | 26 | source: hosted |
| 27 | version: "4.2.0" | 27 | version: "4.2.0" |
| 28 | args: | 28 | args: |
| @@ -30,7 +30,7 @@ packages: | @@ -30,7 +30,7 @@ packages: | ||
| 30 | description: | 30 | description: |
| 31 | name: args | 31 | name: args |
| 32 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 | 32 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 |
| 33 | - url: "https://pub.dev" | 33 | + url: "https://pub.flutter-io.cn" |
| 34 | source: hosted | 34 | source: hosted |
| 35 | version: "2.7.0" | 35 | version: "2.7.0" |
| 36 | async: | 36 | async: |
| @@ -38,7 +38,7 @@ packages: | @@ -38,7 +38,7 @@ packages: | ||
| 38 | description: | 38 | description: |
| 39 | name: async | 39 | name: async |
| 40 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" | 40 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" |
| 41 | - url: "https://pub.dev" | 41 | + url: "https://pub.flutter-io.cn" |
| 42 | source: hosted | 42 | source: hosted |
| 43 | version: "2.11.0" | 43 | version: "2.11.0" |
| 44 | boolean_selector: | 44 | boolean_selector: |
| @@ -46,7 +46,7 @@ packages: | @@ -46,7 +46,7 @@ packages: | ||
| 46 | description: | 46 | description: |
| 47 | name: boolean_selector | 47 | name: boolean_selector |
| 48 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" | 48 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" |
| 49 | - url: "https://pub.dev" | 49 | + url: "https://pub.flutter-io.cn" |
| 50 | source: hosted | 50 | source: hosted |
| 51 | version: "2.1.1" | 51 | version: "2.1.1" |
| 52 | build: | 52 | build: |
| @@ -54,7 +54,7 @@ packages: | @@ -54,7 +54,7 @@ packages: | ||
| 54 | description: | 54 | description: |
| 55 | name: build | 55 | name: build |
| 56 | sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 | 56 | sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 |
| 57 | - url: "https://pub.dev" | 57 | + url: "https://pub.flutter-io.cn" |
| 58 | source: hosted | 58 | source: hosted |
| 59 | version: "2.4.2" | 59 | version: "2.4.2" |
| 60 | build_config: | 60 | build_config: |
| @@ -62,7 +62,7 @@ packages: | @@ -62,7 +62,7 @@ packages: | ||
| 62 | description: | 62 | description: |
| 63 | name: build_config | 63 | name: build_config |
| 64 | sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" | 64 | sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" |
| 65 | - url: "https://pub.dev" | 65 | + url: "https://pub.flutter-io.cn" |
| 66 | source: hosted | 66 | source: hosted |
| 67 | version: "1.1.2" | 67 | version: "1.1.2" |
| 68 | build_daemon: | 68 | build_daemon: |
| @@ -70,7 +70,7 @@ packages: | @@ -70,7 +70,7 @@ packages: | ||
| 70 | description: | 70 | description: |
| 71 | name: build_daemon | 71 | name: build_daemon |
| 72 | sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" | 72 | sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" |
| 73 | - url: "https://pub.dev" | 73 | + url: "https://pub.flutter-io.cn" |
| 74 | source: hosted | 74 | source: hosted |
| 75 | version: "4.0.4" | 75 | version: "4.0.4" |
| 76 | build_resolvers: | 76 | build_resolvers: |
| @@ -78,7 +78,7 @@ packages: | @@ -78,7 +78,7 @@ packages: | ||
| 78 | description: | 78 | description: |
| 79 | name: build_resolvers | 79 | name: build_resolvers |
| 80 | sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 | 80 | sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 |
| 81 | - url: "https://pub.dev" | 81 | + url: "https://pub.flutter-io.cn" |
| 82 | source: hosted | 82 | source: hosted |
| 83 | version: "2.4.4" | 83 | version: "2.4.4" |
| 84 | build_runner: | 84 | build_runner: |
| @@ -86,7 +86,7 @@ packages: | @@ -86,7 +86,7 @@ packages: | ||
| 86 | description: | 86 | description: |
| 87 | name: build_runner | 87 | name: build_runner |
| 88 | sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" | 88 | sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" |
| 89 | - url: "https://pub.dev" | 89 | + url: "https://pub.flutter-io.cn" |
| 90 | source: hosted | 90 | source: hosted |
| 91 | version: "2.4.15" | 91 | version: "2.4.15" |
| 92 | build_runner_core: | 92 | build_runner_core: |
| @@ -94,7 +94,7 @@ packages: | @@ -94,7 +94,7 @@ packages: | ||
| 94 | description: | 94 | description: |
| 95 | name: build_runner_core | 95 | name: build_runner_core |
| 96 | sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" | 96 | sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" |
| 97 | - url: "https://pub.dev" | 97 | + url: "https://pub.flutter-io.cn" |
| 98 | source: hosted | 98 | source: hosted |
| 99 | version: "8.0.0" | 99 | version: "8.0.0" |
| 100 | built_collection: | 100 | built_collection: |
| @@ -102,7 +102,7 @@ packages: | @@ -102,7 +102,7 @@ packages: | ||
| 102 | description: | 102 | description: |
| 103 | name: built_collection | 103 | name: built_collection |
| 104 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" | 104 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" |
| 105 | - url: "https://pub.dev" | 105 | + url: "https://pub.flutter-io.cn" |
| 106 | source: hosted | 106 | source: hosted |
| 107 | version: "5.1.1" | 107 | version: "5.1.1" |
| 108 | built_value: | 108 | built_value: |
| @@ -110,7 +110,7 @@ packages: | @@ -110,7 +110,7 @@ packages: | ||
| 110 | description: | 110 | description: |
| 111 | name: built_value | 111 | name: built_value |
| 112 | sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" | 112 | sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" |
| 113 | - url: "https://pub.dev" | 113 | + url: "https://pub.flutter-io.cn" |
| 114 | source: hosted | 114 | source: hosted |
| 115 | version: "8.12.7" | 115 | version: "8.12.7" |
| 116 | cached_network_image: | 116 | cached_network_image: |
| @@ -118,7 +118,7 @@ packages: | @@ -118,7 +118,7 @@ packages: | ||
| 118 | description: | 118 | description: |
| 119 | name: cached_network_image | 119 | name: cached_network_image |
| 120 | sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" | 120 | sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" |
| 121 | - url: "https://pub.dev" | 121 | + url: "https://pub.flutter-io.cn" |
| 122 | source: hosted | 122 | source: hosted |
| 123 | version: "3.4.1" | 123 | version: "3.4.1" |
| 124 | cached_network_image_platform_interface: | 124 | cached_network_image_platform_interface: |
| @@ -126,7 +126,7 @@ packages: | @@ -126,7 +126,7 @@ packages: | ||
| 126 | description: | 126 | description: |
| 127 | name: cached_network_image_platform_interface | 127 | name: cached_network_image_platform_interface |
| 128 | sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" | 128 | sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" |
| 129 | - url: "https://pub.dev" | 129 | + url: "https://pub.flutter-io.cn" |
| 130 | source: hosted | 130 | source: hosted |
| 131 | version: "4.1.1" | 131 | version: "4.1.1" |
| 132 | cached_network_image_web: | 132 | cached_network_image_web: |
| @@ -134,7 +134,7 @@ packages: | @@ -134,7 +134,7 @@ packages: | ||
| 134 | description: | 134 | description: |
| 135 | name: cached_network_image_web | 135 | name: cached_network_image_web |
| 136 | sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" | 136 | sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" |
| 137 | - url: "https://pub.dev" | 137 | + url: "https://pub.flutter-io.cn" |
| 138 | source: hosted | 138 | source: hosted |
| 139 | version: "1.3.1" | 139 | version: "1.3.1" |
| 140 | characters: | 140 | characters: |
| @@ -142,7 +142,7 @@ packages: | @@ -142,7 +142,7 @@ packages: | ||
| 142 | description: | 142 | description: |
| 143 | name: characters | 143 | name: characters |
| 144 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" | 144 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" |
| 145 | - url: "https://pub.dev" | 145 | + url: "https://pub.flutter-io.cn" |
| 146 | source: hosted | 146 | source: hosted |
| 147 | version: "1.3.0" | 147 | version: "1.3.0" |
| 148 | checked_yaml: | 148 | checked_yaml: |
| @@ -150,7 +150,7 @@ packages: | @@ -150,7 +150,7 @@ packages: | ||
| 150 | description: | 150 | description: |
| 151 | name: checked_yaml | 151 | name: checked_yaml |
| 152 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff | 152 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff |
| 153 | - url: "https://pub.dev" | 153 | + url: "https://pub.flutter-io.cn" |
| 154 | source: hosted | 154 | source: hosted |
| 155 | version: "2.0.3" | 155 | version: "2.0.3" |
| 156 | clock: | 156 | clock: |
| @@ -158,7 +158,7 @@ packages: | @@ -158,7 +158,7 @@ packages: | ||
| 158 | description: | 158 | description: |
| 159 | name: clock | 159 | name: clock |
| 160 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf | 160 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf |
| 161 | - url: "https://pub.dev" | 161 | + url: "https://pub.flutter-io.cn" |
| 162 | source: hosted | 162 | source: hosted |
| 163 | version: "1.1.1" | 163 | version: "1.1.1" |
| 164 | code_builder: | 164 | code_builder: |
| @@ -166,7 +166,7 @@ packages: | @@ -166,7 +166,7 @@ packages: | ||
| 166 | description: | 166 | description: |
| 167 | name: code_builder | 167 | name: code_builder |
| 168 | sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" | 168 | sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" |
| 169 | - url: "https://pub.dev" | 169 | + url: "https://pub.flutter-io.cn" |
| 170 | source: hosted | 170 | source: hosted |
| 171 | version: "4.10.1" | 171 | version: "4.10.1" |
| 172 | collection: | 172 | collection: |
| @@ -174,7 +174,7 @@ packages: | @@ -174,7 +174,7 @@ packages: | ||
| 174 | description: | 174 | description: |
| 175 | name: collection | 175 | name: collection |
| 176 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf | 176 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf |
| 177 | - url: "https://pub.dev" | 177 | + url: "https://pub.flutter-io.cn" |
| 178 | source: hosted | 178 | source: hosted |
| 179 | version: "1.19.0" | 179 | version: "1.19.0" |
| 180 | convert: | 180 | convert: |
| @@ -182,7 +182,7 @@ packages: | @@ -182,7 +182,7 @@ packages: | ||
| 182 | description: | 182 | description: |
| 183 | name: convert | 183 | name: convert |
| 184 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 | 184 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 |
| 185 | - url: "https://pub.dev" | 185 | + url: "https://pub.flutter-io.cn" |
| 186 | source: hosted | 186 | source: hosted |
| 187 | version: "3.1.2" | 187 | version: "3.1.2" |
| 188 | cross_file: | 188 | cross_file: |
| @@ -190,7 +190,7 @@ packages: | @@ -190,7 +190,7 @@ packages: | ||
| 190 | description: | 190 | description: |
| 191 | name: cross_file | 191 | name: cross_file |
| 192 | sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" | 192 | sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" |
| 193 | - url: "https://pub.dev" | 193 | + url: "https://pub.flutter-io.cn" |
| 194 | source: hosted | 194 | source: hosted |
| 195 | version: "0.3.4+2" | 195 | version: "0.3.4+2" |
| 196 | crypto: | 196 | crypto: |
| @@ -198,7 +198,7 @@ packages: | @@ -198,7 +198,7 @@ packages: | ||
| 198 | description: | 198 | description: |
| 199 | name: crypto | 199 | name: crypto |
| 200 | sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf | 200 | sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf |
| 201 | - url: "https://pub.dev" | 201 | + url: "https://pub.flutter-io.cn" |
| 202 | source: hosted | 202 | source: hosted |
| 203 | version: "3.0.7" | 203 | version: "3.0.7" |
| 204 | cupertino_icons: | 204 | cupertino_icons: |
| @@ -206,7 +206,7 @@ packages: | @@ -206,7 +206,7 @@ packages: | ||
| 206 | description: | 206 | description: |
| 207 | name: cupertino_icons | 207 | name: cupertino_icons |
| 208 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 | 208 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 |
| 209 | - url: "https://pub.dev" | 209 | + url: "https://pub.flutter-io.cn" |
| 210 | source: hosted | 210 | source: hosted |
| 211 | version: "1.0.8" | 211 | version: "1.0.8" |
| 212 | dart_style: | 212 | dart_style: |
| @@ -214,7 +214,7 @@ packages: | @@ -214,7 +214,7 @@ packages: | ||
| 214 | description: | 214 | description: |
| 215 | name: dart_style | 215 | name: dart_style |
| 216 | sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" | 216 | sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" |
| 217 | - url: "https://pub.dev" | 217 | + url: "https://pub.flutter-io.cn" |
| 218 | source: hosted | 218 | source: hosted |
| 219 | version: "3.0.1" | 219 | version: "3.0.1" |
| 220 | dio: | 220 | dio: |
| @@ -222,7 +222,7 @@ packages: | @@ -222,7 +222,7 @@ packages: | ||
| 222 | description: | 222 | description: |
| 223 | name: dio | 223 | name: dio |
| 224 | sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c" | 224 | sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c" |
| 225 | - url: "https://pub.dev" | 225 | + url: "https://pub.flutter-io.cn" |
| 226 | source: hosted | 226 | source: hosted |
| 227 | version: "5.11.0" | 227 | version: "5.11.0" |
| 228 | dio_web_adapter: | 228 | dio_web_adapter: |
| @@ -230,7 +230,7 @@ packages: | @@ -230,7 +230,7 @@ packages: | ||
| 230 | description: | 230 | description: |
| 231 | name: dio_web_adapter | 231 | name: dio_web_adapter |
| 232 | sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c" | 232 | sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c" |
| 233 | - url: "https://pub.dev" | 233 | + url: "https://pub.flutter-io.cn" |
| 234 | source: hosted | 234 | source: hosted |
| 235 | version: "2.2.1" | 235 | version: "2.2.1" |
| 236 | equatable: | 236 | equatable: |
| @@ -238,7 +238,7 @@ packages: | @@ -238,7 +238,7 @@ packages: | ||
| 238 | description: | 238 | description: |
| 239 | name: equatable | 239 | name: equatable |
| 240 | sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" | 240 | sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" |
| 241 | - url: "https://pub.dev" | 241 | + url: "https://pub.flutter-io.cn" |
| 242 | source: hosted | 242 | source: hosted |
| 243 | version: "2.1.0" | 243 | version: "2.1.0" |
| 244 | fake_async: | 244 | fake_async: |
| @@ -246,7 +246,7 @@ packages: | @@ -246,7 +246,7 @@ packages: | ||
| 246 | description: | 246 | description: |
| 247 | name: fake_async | 247 | name: fake_async |
| 248 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" | 248 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" |
| 249 | - url: "https://pub.dev" | 249 | + url: "https://pub.flutter-io.cn" |
| 250 | source: hosted | 250 | source: hosted |
| 251 | version: "1.3.1" | 251 | version: "1.3.1" |
| 252 | ffi: | 252 | ffi: |
| @@ -254,7 +254,7 @@ packages: | @@ -254,7 +254,7 @@ packages: | ||
| 254 | description: | 254 | description: |
| 255 | name: ffi | 255 | name: ffi |
| 256 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" | 256 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" |
| 257 | - url: "https://pub.dev" | 257 | + url: "https://pub.flutter-io.cn" |
| 258 | source: hosted | 258 | source: hosted |
| 259 | version: "2.1.3" | 259 | version: "2.1.3" |
| 260 | file: | 260 | file: |
| @@ -262,7 +262,7 @@ packages: | @@ -262,7 +262,7 @@ packages: | ||
| 262 | description: | 262 | description: |
| 263 | name: file | 263 | name: file |
| 264 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 | 264 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 |
| 265 | - url: "https://pub.dev" | 265 | + url: "https://pub.flutter-io.cn" |
| 266 | source: hosted | 266 | source: hosted |
| 267 | version: "7.0.1" | 267 | version: "7.0.1" |
| 268 | file_selector_linux: | 268 | file_selector_linux: |
| @@ -270,7 +270,7 @@ packages: | @@ -270,7 +270,7 @@ packages: | ||
| 270 | description: | 270 | description: |
| 271 | name: file_selector_linux | 271 | name: file_selector_linux |
| 272 | sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" | 272 | sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" |
| 273 | - url: "https://pub.dev" | 273 | + url: "https://pub.flutter-io.cn" |
| 274 | source: hosted | 274 | source: hosted |
| 275 | version: "0.9.3+2" | 275 | version: "0.9.3+2" |
| 276 | file_selector_macos: | 276 | file_selector_macos: |
| @@ -278,7 +278,7 @@ packages: | @@ -278,7 +278,7 @@ packages: | ||
| 278 | description: | 278 | description: |
| 279 | name: file_selector_macos | 279 | name: file_selector_macos |
| 280 | sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" | 280 | sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" |
| 281 | - url: "https://pub.dev" | 281 | + url: "https://pub.flutter-io.cn" |
| 282 | source: hosted | 282 | source: hosted |
| 283 | version: "0.9.4+3" | 283 | version: "0.9.4+3" |
| 284 | file_selector_platform_interface: | 284 | file_selector_platform_interface: |
| @@ -286,7 +286,7 @@ packages: | @@ -286,7 +286,7 @@ packages: | ||
| 286 | description: | 286 | description: |
| 287 | name: file_selector_platform_interface | 287 | name: file_selector_platform_interface |
| 288 | sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b | 288 | sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b |
| 289 | - url: "https://pub.dev" | 289 | + url: "https://pub.flutter-io.cn" |
| 290 | source: hosted | 290 | source: hosted |
| 291 | version: "2.6.2" | 291 | version: "2.6.2" |
| 292 | file_selector_windows: | 292 | file_selector_windows: |
| @@ -294,7 +294,7 @@ packages: | @@ -294,7 +294,7 @@ packages: | ||
| 294 | description: | 294 | description: |
| 295 | name: file_selector_windows | 295 | name: file_selector_windows |
| 296 | sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" | 296 | sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" |
| 297 | - url: "https://pub.dev" | 297 | + url: "https://pub.flutter-io.cn" |
| 298 | source: hosted | 298 | source: hosted |
| 299 | version: "0.9.3+4" | 299 | version: "0.9.3+4" |
| 300 | fixnum: | 300 | fixnum: |
| @@ -302,7 +302,7 @@ packages: | @@ -302,7 +302,7 @@ packages: | ||
| 302 | description: | 302 | description: |
| 303 | name: fixnum | 303 | name: fixnum |
| 304 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be | 304 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be |
| 305 | - url: "https://pub.dev" | 305 | + url: "https://pub.flutter-io.cn" |
| 306 | source: hosted | 306 | source: hosted |
| 307 | version: "1.1.1" | 307 | version: "1.1.1" |
| 308 | fl_chart: | 308 | fl_chart: |
| @@ -310,7 +310,7 @@ packages: | @@ -310,7 +310,7 @@ packages: | ||
| 310 | description: | 310 | description: |
| 311 | name: fl_chart | 311 | name: fl_chart |
| 312 | sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" | 312 | sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" |
| 313 | - url: "https://pub.dev" | 313 | + url: "https://pub.flutter-io.cn" |
| 314 | source: hosted | 314 | source: hosted |
| 315 | version: "0.70.2" | 315 | version: "0.70.2" |
| 316 | flutter: | 316 | flutter: |
| @@ -323,7 +323,7 @@ packages: | @@ -323,7 +323,7 @@ packages: | ||
| 323 | description: | 323 | description: |
| 324 | name: flutter_cache_manager | 324 | name: flutter_cache_manager |
| 325 | sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" | 325 | sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" |
| 326 | - url: "https://pub.dev" | 326 | + url: "https://pub.flutter-io.cn" |
| 327 | source: hosted | 327 | source: hosted |
| 328 | version: "3.4.1" | 328 | version: "3.4.1" |
| 329 | flutter_lints: | 329 | flutter_lints: |
| @@ -331,7 +331,7 @@ packages: | @@ -331,7 +331,7 @@ packages: | ||
| 331 | description: | 331 | description: |
| 332 | name: flutter_lints | 332 | name: flutter_lints |
| 333 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" | 333 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" |
| 334 | - url: "https://pub.dev" | 334 | + url: "https://pub.flutter-io.cn" |
| 335 | source: hosted | 335 | source: hosted |
| 336 | version: "5.0.0" | 336 | version: "5.0.0" |
| 337 | flutter_localizations: | 337 | flutter_localizations: |
| @@ -344,7 +344,7 @@ packages: | @@ -344,7 +344,7 @@ packages: | ||
| 344 | description: | 344 | description: |
| 345 | name: flutter_plugin_android_lifecycle | 345 | name: flutter_plugin_android_lifecycle |
| 346 | sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" | 346 | sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" |
| 347 | - url: "https://pub.dev" | 347 | + url: "https://pub.flutter-io.cn" |
| 348 | source: hosted | 348 | source: hosted |
| 349 | version: "2.0.29" | 349 | version: "2.0.29" |
| 350 | flutter_test: | 350 | flutter_test: |
| @@ -357,7 +357,7 @@ packages: | @@ -357,7 +357,7 @@ packages: | ||
| 357 | description: | 357 | description: |
| 358 | name: flutter_timezone | 358 | name: flutter_timezone |
| 359 | sha256: "869677426fde92dbe170fb7d2d4929f2a8343c2f5f62f08b0bb64f908630b073" | 359 | sha256: "869677426fde92dbe170fb7d2d4929f2a8343c2f5f62f08b0bb64f908630b073" |
| 360 | - url: "https://pub.dev" | 360 | + url: "https://pub.flutter-io.cn" |
| 361 | source: hosted | 361 | source: hosted |
| 362 | version: "5.1.0" | 362 | version: "5.1.0" |
| 363 | flutter_web_plugins: | 363 | flutter_web_plugins: |
| @@ -379,7 +379,7 @@ packages: | @@ -379,7 +379,7 @@ packages: | ||
| 379 | description: | 379 | description: |
| 380 | name: frontend_server_client | 380 | name: frontend_server_client |
| 381 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 | 381 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 |
| 382 | - url: "https://pub.dev" | 382 | + url: "https://pub.flutter-io.cn" |
| 383 | source: hosted | 383 | source: hosted |
| 384 | version: "4.0.0" | 384 | version: "4.0.0" |
| 385 | get: | 385 | get: |
| @@ -387,7 +387,7 @@ packages: | @@ -387,7 +387,7 @@ packages: | ||
| 387 | description: | 387 | description: |
| 388 | name: get | 388 | name: get |
| 389 | sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" | 389 | sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" |
| 390 | - url: "https://pub.dev" | 390 | + url: "https://pub.flutter-io.cn" |
| 391 | source: hosted | 391 | source: hosted |
| 392 | version: "4.7.3" | 392 | version: "4.7.3" |
| 393 | glob: | 393 | glob: |
| @@ -395,7 +395,7 @@ packages: | @@ -395,7 +395,7 @@ packages: | ||
| 395 | description: | 395 | description: |
| 396 | name: glob | 396 | name: glob |
| 397 | sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de | 397 | sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de |
| 398 | - url: "https://pub.dev" | 398 | + url: "https://pub.flutter-io.cn" |
| 399 | source: hosted | 399 | source: hosted |
| 400 | version: "2.1.3" | 400 | version: "2.1.3" |
| 401 | graphs: | 401 | graphs: |
| @@ -403,7 +403,7 @@ packages: | @@ -403,7 +403,7 @@ packages: | ||
| 403 | description: | 403 | description: |
| 404 | name: graphs | 404 | name: graphs |
| 405 | sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" | 405 | sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" |
| 406 | - url: "https://pub.dev" | 406 | + url: "https://pub.flutter-io.cn" |
| 407 | source: hosted | 407 | source: hosted |
| 408 | version: "2.3.2" | 408 | version: "2.3.2" |
| 409 | http: | 409 | http: |
| @@ -411,7 +411,7 @@ packages: | @@ -411,7 +411,7 @@ packages: | ||
| 411 | description: | 411 | description: |
| 412 | name: http | 412 | name: http |
| 413 | sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" | 413 | sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" |
| 414 | - url: "https://pub.dev" | 414 | + url: "https://pub.flutter-io.cn" |
| 415 | source: hosted | 415 | source: hosted |
| 416 | version: "1.6.0" | 416 | version: "1.6.0" |
| 417 | http_multi_server: | 417 | http_multi_server: |
| @@ -419,7 +419,7 @@ packages: | @@ -419,7 +419,7 @@ packages: | ||
| 419 | description: | 419 | description: |
| 420 | name: http_multi_server | 420 | name: http_multi_server |
| 421 | sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 | 421 | sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 |
| 422 | - url: "https://pub.dev" | 422 | + url: "https://pub.flutter-io.cn" |
| 423 | source: hosted | 423 | source: hosted |
| 424 | version: "3.2.2" | 424 | version: "3.2.2" |
| 425 | http_parser: | 425 | http_parser: |
| @@ -427,31 +427,33 @@ packages: | @@ -427,31 +427,33 @@ packages: | ||
| 427 | description: | 427 | description: |
| 428 | name: http_parser | 428 | name: http_parser |
| 429 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" | 429 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" |
| 430 | - url: "https://pub.dev" | 430 | + url: "https://pub.flutter-io.cn" |
| 431 | source: hosted | 431 | source: hosted |
| 432 | version: "4.1.2" | 432 | version: "4.1.2" |
| 433 | image_cropper: | 433 | image_cropper: |
| 434 | dependency: "direct main" | 434 | dependency: "direct main" |
| 435 | description: | 435 | description: |
| 436 | - name: image_cropper | ||
| 437 | - sha256: "4e9c96c029eb5a23798da1b6af39787f964da6ffc78fd8447c140542a9f7c6fc" | ||
| 438 | - url: "https://pub.dev" | ||
| 439 | - source: hosted | 436 | + path: image_cropper |
| 437 | + ref: "br_v9.1.0_ohos" | ||
| 438 | + resolved-ref: b1b45b1a5333571095569d53d01720c505fff983 | ||
| 439 | + url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git" | ||
| 440 | + source: git | ||
| 440 | version: "9.1.0" | 441 | version: "9.1.0" |
| 441 | image_cropper_for_web: | 442 | image_cropper_for_web: |
| 442 | dependency: transitive | 443 | dependency: transitive |
| 443 | description: | 444 | description: |
| 444 | - name: image_cropper_for_web | ||
| 445 | - sha256: fd81ebe36f636576094377aab32673c4e5d1609b32dec16fad98d2b71f1250a9 | ||
| 446 | - url: "https://pub.dev" | ||
| 447 | - source: hosted | 445 | + path: image_cropper_for_web |
| 446 | + ref: "br_v9.1.0_ohos" | ||
| 447 | + resolved-ref: b1b45b1a5333571095569d53d01720c505fff983 | ||
| 448 | + url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git" | ||
| 449 | + source: git | ||
| 448 | version: "6.1.0" | 450 | version: "6.1.0" |
| 449 | image_cropper_platform_interface: | 451 | image_cropper_platform_interface: |
| 450 | dependency: "direct overridden" | 452 | dependency: "direct overridden" |
| 451 | description: | 453 | description: |
| 452 | name: image_cropper_platform_interface | 454 | name: image_cropper_platform_interface |
| 453 | sha256: "6ca6b81769abff9a4dcc3bbd3d75f5dfa9de6b870ae9613c8cd237333a4283af" | 455 | sha256: "6ca6b81769abff9a4dcc3bbd3d75f5dfa9de6b870ae9613c8cd237333a4283af" |
| 454 | - url: "https://pub.dev" | 456 | + url: "https://pub.flutter-io.cn" |
| 455 | source: hosted | 457 | source: hosted |
| 456 | version: "7.1.0" | 458 | version: "7.1.0" |
| 457 | image_picker: | 459 | image_picker: |
| @@ -459,7 +461,7 @@ packages: | @@ -459,7 +461,7 @@ packages: | ||
| 459 | description: | 461 | description: |
| 460 | name: image_picker | 462 | name: image_picker |
| 461 | sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" | 463 | sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" |
| 462 | - url: "https://pub.dev" | 464 | + url: "https://pub.flutter-io.cn" |
| 463 | source: hosted | 465 | source: hosted |
| 464 | version: "1.2.0" | 466 | version: "1.2.0" |
| 465 | image_picker_android: | 467 | image_picker_android: |
| @@ -467,7 +469,7 @@ packages: | @@ -467,7 +469,7 @@ packages: | ||
| 467 | description: | 469 | description: |
| 468 | name: image_picker_android | 470 | name: image_picker_android |
| 469 | sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c | 471 | sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c |
| 470 | - url: "https://pub.dev" | 472 | + url: "https://pub.flutter-io.cn" |
| 471 | source: hosted | 473 | source: hosted |
| 472 | version: "0.8.13" | 474 | version: "0.8.13" |
| 473 | image_picker_for_web: | 475 | image_picker_for_web: |
| @@ -475,7 +477,7 @@ packages: | @@ -475,7 +477,7 @@ packages: | ||
| 475 | description: | 477 | description: |
| 476 | name: image_picker_for_web | 478 | name: image_picker_for_web |
| 477 | sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" | 479 | sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" |
| 478 | - url: "https://pub.dev" | 480 | + url: "https://pub.flutter-io.cn" |
| 479 | source: hosted | 481 | source: hosted |
| 480 | version: "3.1.0" | 482 | version: "3.1.0" |
| 481 | image_picker_ios: | 483 | image_picker_ios: |
| @@ -483,7 +485,7 @@ packages: | @@ -483,7 +485,7 @@ packages: | ||
| 483 | description: | 485 | description: |
| 484 | name: image_picker_ios | 486 | name: image_picker_ios |
| 485 | sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e | 487 | sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e |
| 486 | - url: "https://pub.dev" | 488 | + url: "https://pub.flutter-io.cn" |
| 487 | source: hosted | 489 | source: hosted |
| 488 | version: "0.8.13" | 490 | version: "0.8.13" |
| 489 | image_picker_linux: | 491 | image_picker_linux: |
| @@ -491,7 +493,7 @@ packages: | @@ -491,7 +493,7 @@ packages: | ||
| 491 | description: | 493 | description: |
| 492 | name: image_picker_linux | 494 | name: image_picker_linux |
| 493 | sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" | 495 | sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" |
| 494 | - url: "https://pub.dev" | 496 | + url: "https://pub.flutter-io.cn" |
| 495 | source: hosted | 497 | source: hosted |
| 496 | version: "0.2.2" | 498 | version: "0.2.2" |
| 497 | image_picker_macos: | 499 | image_picker_macos: |
| @@ -499,7 +501,7 @@ packages: | @@ -499,7 +501,7 @@ packages: | ||
| 499 | description: | 501 | description: |
| 500 | name: image_picker_macos | 502 | name: image_picker_macos |
| 501 | sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 | 503 | sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 |
| 502 | - url: "https://pub.dev" | 504 | + url: "https://pub.flutter-io.cn" |
| 503 | source: hosted | 505 | source: hosted |
| 504 | version: "0.2.2" | 506 | version: "0.2.2" |
| 505 | image_picker_ohos: | 507 | image_picker_ohos: |
| @@ -516,7 +518,7 @@ packages: | @@ -516,7 +518,7 @@ packages: | ||
| 516 | description: | 518 | description: |
| 517 | name: image_picker_platform_interface | 519 | name: image_picker_platform_interface |
| 518 | sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" | 520 | sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" |
| 519 | - url: "https://pub.dev" | 521 | + url: "https://pub.flutter-io.cn" |
| 520 | source: hosted | 522 | source: hosted |
| 521 | version: "2.11.0" | 523 | version: "2.11.0" |
| 522 | image_picker_windows: | 524 | image_picker_windows: |
| @@ -524,7 +526,7 @@ packages: | @@ -524,7 +526,7 @@ packages: | ||
| 524 | description: | 526 | description: |
| 525 | name: image_picker_windows | 527 | name: image_picker_windows |
| 526 | sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae | 528 | sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae |
| 527 | - url: "https://pub.dev" | 529 | + url: "https://pub.flutter-io.cn" |
| 528 | source: hosted | 530 | source: hosted |
| 529 | version: "0.2.2" | 531 | version: "0.2.2" |
| 530 | intl: | 532 | intl: |
| @@ -532,7 +534,7 @@ packages: | @@ -532,7 +534,7 @@ packages: | ||
| 532 | description: | 534 | description: |
| 533 | name: intl | 535 | name: intl |
| 534 | sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf | 536 | sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf |
| 535 | - url: "https://pub.dev" | 537 | + url: "https://pub.flutter-io.cn" |
| 536 | source: hosted | 538 | source: hosted |
| 537 | version: "0.19.0" | 539 | version: "0.19.0" |
| 538 | io: | 540 | io: |
| @@ -540,7 +542,7 @@ packages: | @@ -540,7 +542,7 @@ packages: | ||
| 540 | description: | 542 | description: |
| 541 | name: io | 543 | name: io |
| 542 | sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b | 544 | sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b |
| 543 | - url: "https://pub.dev" | 545 | + url: "https://pub.flutter-io.cn" |
| 544 | source: hosted | 546 | source: hosted |
| 545 | version: "1.0.5" | 547 | version: "1.0.5" |
| 546 | js: | 548 | js: |
| @@ -548,7 +550,7 @@ packages: | @@ -548,7 +550,7 @@ packages: | ||
| 548 | description: | 550 | description: |
| 549 | name: js | 551 | name: js |
| 550 | sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf | 552 | sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf |
| 551 | - url: "https://pub.dev" | 553 | + url: "https://pub.flutter-io.cn" |
| 552 | source: hosted | 554 | source: hosted |
| 553 | version: "0.7.1" | 555 | version: "0.7.1" |
| 554 | json_annotation: | 556 | json_annotation: |
| @@ -556,7 +558,7 @@ packages: | @@ -556,7 +558,7 @@ packages: | ||
| 556 | description: | 558 | description: |
| 557 | name: json_annotation | 559 | name: json_annotation |
| 558 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" | 560 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" |
| 559 | - url: "https://pub.dev" | 561 | + url: "https://pub.flutter-io.cn" |
| 560 | source: hosted | 562 | source: hosted |
| 561 | version: "4.9.0" | 563 | version: "4.9.0" |
| 562 | leak_tracker: | 564 | leak_tracker: |
| @@ -564,7 +566,7 @@ packages: | @@ -564,7 +566,7 @@ packages: | ||
| 564 | description: | 566 | description: |
| 565 | name: leak_tracker | 567 | name: leak_tracker |
| 566 | sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" | 568 | sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" |
| 567 | - url: "https://pub.dev" | 569 | + url: "https://pub.flutter-io.cn" |
| 568 | source: hosted | 570 | source: hosted |
| 569 | version: "10.0.7" | 571 | version: "10.0.7" |
| 570 | leak_tracker_flutter_testing: | 572 | leak_tracker_flutter_testing: |
| @@ -572,7 +574,7 @@ packages: | @@ -572,7 +574,7 @@ packages: | ||
| 572 | description: | 574 | description: |
| 573 | name: leak_tracker_flutter_testing | 575 | name: leak_tracker_flutter_testing |
| 574 | sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" | 576 | sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" |
| 575 | - url: "https://pub.dev" | 577 | + url: "https://pub.flutter-io.cn" |
| 576 | source: hosted | 578 | source: hosted |
| 577 | version: "3.0.8" | 579 | version: "3.0.8" |
| 578 | leak_tracker_testing: | 580 | leak_tracker_testing: |
| @@ -580,7 +582,7 @@ packages: | @@ -580,7 +582,7 @@ packages: | ||
| 580 | description: | 582 | description: |
| 581 | name: leak_tracker_testing | 583 | name: leak_tracker_testing |
| 582 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" | 584 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" |
| 583 | - url: "https://pub.dev" | 585 | + url: "https://pub.flutter-io.cn" |
| 584 | source: hosted | 586 | source: hosted |
| 585 | version: "3.0.1" | 587 | version: "3.0.1" |
| 586 | lints: | 588 | lints: |
| @@ -588,7 +590,7 @@ packages: | @@ -588,7 +590,7 @@ packages: | ||
| 588 | description: | 590 | description: |
| 589 | name: lints | 591 | name: lints |
| 590 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 | 592 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 |
| 591 | - url: "https://pub.dev" | 593 | + url: "https://pub.flutter-io.cn" |
| 592 | source: hosted | 594 | source: hosted |
| 593 | version: "5.1.1" | 595 | version: "5.1.1" |
| 594 | logger: | 596 | logger: |
| @@ -596,7 +598,7 @@ packages: | @@ -596,7 +598,7 @@ packages: | ||
| 596 | description: | 598 | description: |
| 597 | name: logger | 599 | name: logger |
| 598 | sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c" | 600 | sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c" |
| 599 | - url: "https://pub.dev" | 601 | + url: "https://pub.flutter-io.cn" |
| 600 | source: hosted | 602 | source: hosted |
| 601 | version: "2.7.0" | 603 | version: "2.7.0" |
| 602 | logging: | 604 | logging: |
| @@ -604,7 +606,7 @@ packages: | @@ -604,7 +606,7 @@ packages: | ||
| 604 | description: | 606 | description: |
| 605 | name: logging | 607 | name: logging |
| 606 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 | 608 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 |
| 607 | - url: "https://pub.dev" | 609 | + url: "https://pub.flutter-io.cn" |
| 608 | source: hosted | 610 | source: hosted |
| 609 | version: "1.3.0" | 611 | version: "1.3.0" |
| 610 | lottie: | 612 | lottie: |
| @@ -612,7 +614,7 @@ packages: | @@ -612,7 +614,7 @@ packages: | ||
| 612 | description: | 614 | description: |
| 613 | name: lottie | 615 | name: lottie |
| 614 | sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 | 616 | sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 |
| 615 | - url: "https://pub.dev" | 617 | + url: "https://pub.flutter-io.cn" |
| 616 | source: hosted | 618 | source: hosted |
| 617 | version: "3.3.1" | 619 | version: "3.3.1" |
| 618 | matcher: | 620 | matcher: |
| @@ -620,7 +622,7 @@ packages: | @@ -620,7 +622,7 @@ packages: | ||
| 620 | description: | 622 | description: |
| 621 | name: matcher | 623 | name: matcher |
| 622 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb | 624 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb |
| 623 | - url: "https://pub.dev" | 625 | + url: "https://pub.flutter-io.cn" |
| 624 | source: hosted | 626 | source: hosted |
| 625 | version: "0.12.16+1" | 627 | version: "0.12.16+1" |
| 626 | material_color_utilities: | 628 | material_color_utilities: |
| @@ -628,7 +630,7 @@ packages: | @@ -628,7 +630,7 @@ packages: | ||
| 628 | description: | 630 | description: |
| 629 | name: material_color_utilities | 631 | name: material_color_utilities |
| 630 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec | 632 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec |
| 631 | - url: "https://pub.dev" | 633 | + url: "https://pub.flutter-io.cn" |
| 632 | source: hosted | 634 | source: hosted |
| 633 | version: "0.11.1" | 635 | version: "0.11.1" |
| 634 | meta: | 636 | meta: |
| @@ -636,7 +638,7 @@ packages: | @@ -636,7 +638,7 @@ packages: | ||
| 636 | description: | 638 | description: |
| 637 | name: meta | 639 | name: meta |
| 638 | sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" | 640 | sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" |
| 639 | - url: "https://pub.dev" | 641 | + url: "https://pub.flutter-io.cn" |
| 640 | source: hosted | 642 | source: hosted |
| 641 | version: "1.19.0" | 643 | version: "1.19.0" |
| 642 | mime: | 644 | mime: |
| @@ -644,7 +646,7 @@ packages: | @@ -644,7 +646,7 @@ packages: | ||
| 644 | description: | 646 | description: |
| 645 | name: mime | 647 | name: mime |
| 646 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" | 648 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" |
| 647 | - url: "https://pub.dev" | 649 | + url: "https://pub.flutter-io.cn" |
| 648 | source: hosted | 650 | source: hosted |
| 649 | version: "2.0.0" | 651 | version: "2.0.0" |
| 650 | octo_image: | 652 | octo_image: |
| @@ -652,7 +654,7 @@ packages: | @@ -652,7 +654,7 @@ packages: | ||
| 652 | description: | 654 | description: |
| 653 | name: octo_image | 655 | name: octo_image |
| 654 | sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" | 656 | sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" |
| 655 | - url: "https://pub.dev" | 657 | + url: "https://pub.flutter-io.cn" |
| 656 | source: hosted | 658 | source: hosted |
| 657 | version: "2.1.0" | 659 | version: "2.1.0" |
| 658 | package_config: | 660 | package_config: |
| @@ -660,7 +662,7 @@ packages: | @@ -660,7 +662,7 @@ packages: | ||
| 660 | description: | 662 | description: |
| 661 | name: package_config | 663 | name: package_config |
| 662 | sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc | 664 | sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc |
| 663 | - url: "https://pub.dev" | 665 | + url: "https://pub.flutter-io.cn" |
| 664 | source: hosted | 666 | source: hosted |
| 665 | version: "2.2.0" | 667 | version: "2.2.0" |
| 666 | path: | 668 | path: |
| @@ -668,7 +670,7 @@ packages: | @@ -668,7 +670,7 @@ packages: | ||
| 668 | description: | 670 | description: |
| 669 | name: path | 671 | name: path |
| 670 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" | 672 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" |
| 671 | - url: "https://pub.dev" | 673 | + url: "https://pub.flutter-io.cn" |
| 672 | source: hosted | 674 | source: hosted |
| 673 | version: "1.9.0" | 675 | version: "1.9.0" |
| 674 | path_provider: | 676 | path_provider: |
| @@ -676,7 +678,7 @@ packages: | @@ -676,7 +678,7 @@ packages: | ||
| 676 | description: | 678 | description: |
| 677 | name: path_provider | 679 | name: path_provider |
| 678 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" | 680 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" |
| 679 | - url: "https://pub.dev" | 681 | + url: "https://pub.flutter-io.cn" |
| 680 | source: hosted | 682 | source: hosted |
| 681 | version: "2.1.5" | 683 | version: "2.1.5" |
| 682 | path_provider_android: | 684 | path_provider_android: |
| @@ -684,7 +686,7 @@ packages: | @@ -684,7 +686,7 @@ packages: | ||
| 684 | description: | 686 | description: |
| 685 | name: path_provider_android | 687 | name: path_provider_android |
| 686 | sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 | 688 | sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 |
| 687 | - url: "https://pub.dev" | 689 | + url: "https://pub.flutter-io.cn" |
| 688 | source: hosted | 690 | source: hosted |
| 689 | version: "2.2.17" | 691 | version: "2.2.17" |
| 690 | path_provider_foundation: | 692 | path_provider_foundation: |
| @@ -692,7 +694,7 @@ packages: | @@ -692,7 +694,7 @@ packages: | ||
| 692 | description: | 694 | description: |
| 693 | name: path_provider_foundation | 695 | name: path_provider_foundation |
| 694 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" | 696 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" |
| 695 | - url: "https://pub.dev" | 697 | + url: "https://pub.flutter-io.cn" |
| 696 | source: hosted | 698 | source: hosted |
| 697 | version: "2.4.1" | 699 | version: "2.4.1" |
| 698 | path_provider_linux: | 700 | path_provider_linux: |
| @@ -700,7 +702,7 @@ packages: | @@ -700,7 +702,7 @@ packages: | ||
| 700 | description: | 702 | description: |
| 701 | name: path_provider_linux | 703 | name: path_provider_linux |
| 702 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 | 704 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 |
| 703 | - url: "https://pub.dev" | 705 | + url: "https://pub.flutter-io.cn" |
| 704 | source: hosted | 706 | source: hosted |
| 705 | version: "2.2.1" | 707 | version: "2.2.1" |
| 706 | path_provider_platform_interface: | 708 | path_provider_platform_interface: |
| @@ -708,7 +710,7 @@ packages: | @@ -708,7 +710,7 @@ packages: | ||
| 708 | description: | 710 | description: |
| 709 | name: path_provider_platform_interface | 711 | name: path_provider_platform_interface |
| 710 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" | 712 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" |
| 711 | - url: "https://pub.dev" | 713 | + url: "https://pub.flutter-io.cn" |
| 712 | source: hosted | 714 | source: hosted |
| 713 | version: "2.1.2" | 715 | version: "2.1.2" |
| 714 | path_provider_windows: | 716 | path_provider_windows: |
| @@ -716,7 +718,7 @@ packages: | @@ -716,7 +718,7 @@ packages: | ||
| 716 | description: | 718 | description: |
| 717 | name: path_provider_windows | 719 | name: path_provider_windows |
| 718 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 | 720 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 |
| 719 | - url: "https://pub.dev" | 721 | + url: "https://pub.flutter-io.cn" |
| 720 | source: hosted | 722 | source: hosted |
| 721 | version: "2.3.0" | 723 | version: "2.3.0" |
| 722 | permission_handler: | 724 | permission_handler: |
| @@ -724,7 +726,7 @@ packages: | @@ -724,7 +726,7 @@ packages: | ||
| 724 | description: | 726 | description: |
| 725 | name: permission_handler | 727 | name: permission_handler |
| 726 | sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" | 728 | sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" |
| 727 | - url: "https://pub.dev" | 729 | + url: "https://pub.flutter-io.cn" |
| 728 | source: hosted | 730 | source: hosted |
| 729 | version: "11.4.0" | 731 | version: "11.4.0" |
| 730 | permission_handler_android: | 732 | permission_handler_android: |
| @@ -732,7 +734,7 @@ packages: | @@ -732,7 +734,7 @@ packages: | ||
| 732 | description: | 734 | description: |
| 733 | name: permission_handler_android | 735 | name: permission_handler_android |
| 734 | sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc | 736 | sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc |
| 735 | - url: "https://pub.dev" | 737 | + url: "https://pub.flutter-io.cn" |
| 736 | source: hosted | 738 | source: hosted |
| 737 | version: "12.1.0" | 739 | version: "12.1.0" |
| 738 | permission_handler_apple: | 740 | permission_handler_apple: |
| @@ -740,7 +742,7 @@ packages: | @@ -740,7 +742,7 @@ packages: | ||
| 740 | description: | 742 | description: |
| 741 | name: permission_handler_apple | 743 | name: permission_handler_apple |
| 742 | sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8 | 744 | sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8 |
| 743 | - url: "https://pub.dev" | 745 | + url: "https://pub.flutter-io.cn" |
| 744 | source: hosted | 746 | source: hosted |
| 745 | version: "9.6.1" | 747 | version: "9.6.1" |
| 746 | permission_handler_html: | 748 | permission_handler_html: |
| @@ -748,7 +750,7 @@ packages: | @@ -748,7 +750,7 @@ packages: | ||
| 748 | description: | 750 | description: |
| 749 | name: permission_handler_html | 751 | name: permission_handler_html |
| 750 | sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" | 752 | sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" |
| 751 | - url: "https://pub.dev" | 753 | + url: "https://pub.flutter-io.cn" |
| 752 | source: hosted | 754 | source: hosted |
| 753 | version: "0.1.4+1" | 755 | version: "0.1.4+1" |
| 754 | permission_handler_ohos: | 756 | permission_handler_ohos: |
| @@ -765,7 +767,7 @@ packages: | @@ -765,7 +767,7 @@ packages: | ||
| 765 | description: | 767 | description: |
| 766 | name: permission_handler_platform_interface | 768 | name: permission_handler_platform_interface |
| 767 | sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 | 769 | sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 |
| 768 | - url: "https://pub.dev" | 770 | + url: "https://pub.flutter-io.cn" |
| 769 | source: hosted | 771 | source: hosted |
| 770 | version: "4.4.0" | 772 | version: "4.4.0" |
| 771 | permission_handler_windows: | 773 | permission_handler_windows: |
| @@ -773,7 +775,7 @@ packages: | @@ -773,7 +775,7 @@ packages: | ||
| 773 | description: | 775 | description: |
| 774 | name: permission_handler_windows | 776 | name: permission_handler_windows |
| 775 | sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd | 777 | sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd |
| 776 | - url: "https://pub.dev" | 778 | + url: "https://pub.flutter-io.cn" |
| 777 | source: hosted | 779 | source: hosted |
| 778 | version: "0.2.2" | 780 | version: "0.2.2" |
| 779 | pigeon: | 781 | pigeon: |
| @@ -790,7 +792,7 @@ packages: | @@ -790,7 +792,7 @@ packages: | ||
| 790 | description: | 792 | description: |
| 791 | name: platform | 793 | name: platform |
| 792 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" | 794 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" |
| 793 | - url: "https://pub.dev" | 795 | + url: "https://pub.flutter-io.cn" |
| 794 | source: hosted | 796 | source: hosted |
| 795 | version: "3.1.6" | 797 | version: "3.1.6" |
| 796 | plugin_platform_interface: | 798 | plugin_platform_interface: |
| @@ -798,7 +800,7 @@ packages: | @@ -798,7 +800,7 @@ packages: | ||
| 798 | description: | 800 | description: |
| 799 | name: plugin_platform_interface | 801 | name: plugin_platform_interface |
| 800 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" | 802 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" |
| 801 | - url: "https://pub.dev" | 803 | + url: "https://pub.flutter-io.cn" |
| 802 | source: hosted | 804 | source: hosted |
| 803 | version: "2.1.8" | 805 | version: "2.1.8" |
| 804 | pool: | 806 | pool: |
| @@ -806,7 +808,7 @@ packages: | @@ -806,7 +808,7 @@ packages: | ||
| 806 | description: | 808 | description: |
| 807 | name: pool | 809 | name: pool |
| 808 | sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" | 810 | sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" |
| 809 | - url: "https://pub.dev" | 811 | + url: "https://pub.flutter-io.cn" |
| 810 | source: hosted | 812 | source: hosted |
| 811 | version: "1.5.2" | 813 | version: "1.5.2" |
| 812 | posix: | 814 | posix: |
| @@ -814,7 +816,7 @@ packages: | @@ -814,7 +816,7 @@ packages: | ||
| 814 | description: | 816 | description: |
| 815 | name: posix | 817 | name: posix |
| 816 | sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e | 818 | sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e |
| 817 | - url: "https://pub.dev" | 819 | + url: "https://pub.flutter-io.cn" |
| 818 | source: hosted | 820 | source: hosted |
| 819 | version: "6.5.2" | 821 | version: "6.5.2" |
| 820 | pretty_dio_logger: | 822 | pretty_dio_logger: |
| @@ -822,7 +824,7 @@ packages: | @@ -822,7 +824,7 @@ packages: | ||
| 822 | description: | 824 | description: |
| 823 | name: pretty_dio_logger | 825 | name: pretty_dio_logger |
| 824 | sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" | 826 | sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" |
| 825 | - url: "https://pub.dev" | 827 | + url: "https://pub.flutter-io.cn" |
| 826 | source: hosted | 828 | source: hosted |
| 827 | version: "1.4.0" | 829 | version: "1.4.0" |
| 828 | pub_semver: | 830 | pub_semver: |
| @@ -830,7 +832,7 @@ packages: | @@ -830,7 +832,7 @@ packages: | ||
| 830 | description: | 832 | description: |
| 831 | name: pub_semver | 833 | name: pub_semver |
| 832 | sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" | 834 | sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" |
| 833 | - url: "https://pub.dev" | 835 | + url: "https://pub.flutter-io.cn" |
| 834 | source: hosted | 836 | source: hosted |
| 835 | version: "2.2.0" | 837 | version: "2.2.0" |
| 836 | pubspec_parse: | 838 | pubspec_parse: |
| @@ -838,7 +840,7 @@ packages: | @@ -838,7 +840,7 @@ packages: | ||
| 838 | description: | 840 | description: |
| 839 | name: pubspec_parse | 841 | name: pubspec_parse |
| 840 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" | 842 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" |
| 841 | - url: "https://pub.dev" | 843 | + url: "https://pub.flutter-io.cn" |
| 842 | source: hosted | 844 | source: hosted |
| 843 | version: "1.5.0" | 845 | version: "1.5.0" |
| 844 | rxdart: | 846 | rxdart: |
| @@ -846,7 +848,7 @@ packages: | @@ -846,7 +848,7 @@ packages: | ||
| 846 | description: | 848 | description: |
| 847 | name: rxdart | 849 | name: rxdart |
| 848 | sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" | 850 | sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" |
| 849 | - url: "https://pub.dev" | 851 | + url: "https://pub.flutter-io.cn" |
| 850 | source: hosted | 852 | source: hosted |
| 851 | version: "0.28.0" | 853 | version: "0.28.0" |
| 852 | share_plus: | 854 | share_plus: |
| @@ -881,7 +883,7 @@ packages: | @@ -881,7 +883,7 @@ packages: | ||
| 881 | description: | 883 | description: |
| 882 | name: shared_preferences_android | 884 | name: shared_preferences_android |
| 883 | sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" | 885 | sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" |
| 884 | - url: "https://pub.dev" | 886 | + url: "https://pub.flutter-io.cn" |
| 885 | source: hosted | 887 | source: hosted |
| 886 | version: "2.4.11" | 888 | version: "2.4.11" |
| 887 | shared_preferences_foundation: | 889 | shared_preferences_foundation: |
| @@ -889,7 +891,7 @@ packages: | @@ -889,7 +891,7 @@ packages: | ||
| 889 | description: | 891 | description: |
| 890 | name: shared_preferences_foundation | 892 | name: shared_preferences_foundation |
| 891 | sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" | 893 | sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" |
| 892 | - url: "https://pub.dev" | 894 | + url: "https://pub.flutter-io.cn" |
| 893 | source: hosted | 895 | source: hosted |
| 894 | version: "2.5.4" | 896 | version: "2.5.4" |
| 895 | shared_preferences_linux: | 897 | shared_preferences_linux: |
| @@ -897,7 +899,7 @@ packages: | @@ -897,7 +899,7 @@ packages: | ||
| 897 | description: | 899 | description: |
| 898 | name: shared_preferences_linux | 900 | name: shared_preferences_linux |
| 899 | sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" | 901 | sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" |
| 900 | - url: "https://pub.dev" | 902 | + url: "https://pub.flutter-io.cn" |
| 901 | source: hosted | 903 | source: hosted |
| 902 | version: "2.4.1" | 904 | version: "2.4.1" |
| 903 | shared_preferences_ohos: | 905 | shared_preferences_ohos: |
| @@ -914,7 +916,7 @@ packages: | @@ -914,7 +916,7 @@ packages: | ||
| 914 | description: | 916 | description: |
| 915 | name: shared_preferences_platform_interface | 917 | name: shared_preferences_platform_interface |
| 916 | sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" | 918 | sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" |
| 917 | - url: "https://pub.dev" | 919 | + url: "https://pub.flutter-io.cn" |
| 918 | source: hosted | 920 | source: hosted |
| 919 | version: "2.4.1" | 921 | version: "2.4.1" |
| 920 | shared_preferences_web: | 922 | shared_preferences_web: |
| @@ -922,7 +924,7 @@ packages: | @@ -922,7 +924,7 @@ packages: | ||
| 922 | description: | 924 | description: |
| 923 | name: shared_preferences_web | 925 | name: shared_preferences_web |
| 924 | sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 | 926 | sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 |
| 925 | - url: "https://pub.dev" | 927 | + url: "https://pub.flutter-io.cn" |
| 926 | source: hosted | 928 | source: hosted |
| 927 | version: "2.4.3" | 929 | version: "2.4.3" |
| 928 | shared_preferences_windows: | 930 | shared_preferences_windows: |
| @@ -930,7 +932,7 @@ packages: | @@ -930,7 +932,7 @@ packages: | ||
| 930 | description: | 932 | description: |
| 931 | name: shared_preferences_windows | 933 | name: shared_preferences_windows |
| 932 | sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" | 934 | sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" |
| 933 | - url: "https://pub.dev" | 935 | + url: "https://pub.flutter-io.cn" |
| 934 | source: hosted | 936 | source: hosted |
| 935 | version: "2.4.1" | 937 | version: "2.4.1" |
| 936 | shelf: | 938 | shelf: |
| @@ -938,7 +940,7 @@ packages: | @@ -938,7 +940,7 @@ packages: | ||
| 938 | description: | 940 | description: |
| 939 | name: shelf | 941 | name: shelf |
| 940 | sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 | 942 | sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 |
| 941 | - url: "https://pub.dev" | 943 | + url: "https://pub.flutter-io.cn" |
| 942 | source: hosted | 944 | source: hosted |
| 943 | version: "1.4.2" | 945 | version: "1.4.2" |
| 944 | shelf_web_socket: | 946 | shelf_web_socket: |
| @@ -946,7 +948,7 @@ packages: | @@ -946,7 +948,7 @@ packages: | ||
| 946 | description: | 948 | description: |
| 947 | name: shelf_web_socket | 949 | name: shelf_web_socket |
| 948 | sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" | 950 | sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" |
| 949 | - url: "https://pub.dev" | 951 | + url: "https://pub.flutter-io.cn" |
| 950 | source: hosted | 952 | source: hosted |
| 951 | version: "3.0.0" | 953 | version: "3.0.0" |
| 952 | simple_gesture_detector: | 954 | simple_gesture_detector: |
| @@ -954,7 +956,7 @@ packages: | @@ -954,7 +956,7 @@ packages: | ||
| 954 | description: | 956 | description: |
| 955 | name: simple_gesture_detector | 957 | name: simple_gesture_detector |
| 956 | sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 | 958 | sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 |
| 957 | - url: "https://pub.dev" | 959 | + url: "https://pub.flutter-io.cn" |
| 958 | source: hosted | 960 | source: hosted |
| 959 | version: "0.2.1" | 961 | version: "0.2.1" |
| 960 | sky_engine: | 962 | sky_engine: |
| @@ -967,7 +969,7 @@ packages: | @@ -967,7 +969,7 @@ packages: | ||
| 967 | description: | 969 | description: |
| 968 | name: source_span | 970 | name: source_span |
| 969 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" | 971 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" |
| 970 | - url: "https://pub.dev" | 972 | + url: "https://pub.flutter-io.cn" |
| 971 | source: hosted | 973 | source: hosted |
| 972 | version: "1.10.0" | 974 | version: "1.10.0" |
| 973 | sqflite: | 975 | sqflite: |
| @@ -984,7 +986,7 @@ packages: | @@ -984,7 +986,7 @@ packages: | ||
| 984 | description: | 986 | description: |
| 985 | name: sqflite_android | 987 | name: sqflite_android |
| 986 | sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" | 988 | sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" |
| 987 | - url: "https://pub.dev" | 989 | + url: "https://pub.flutter-io.cn" |
| 988 | source: hosted | 990 | source: hosted |
| 989 | version: "2.4.0" | 991 | version: "2.4.0" |
| 990 | sqflite_common: | 992 | sqflite_common: |
| @@ -992,7 +994,7 @@ packages: | @@ -992,7 +994,7 @@ packages: | ||
| 992 | description: | 994 | description: |
| 993 | name: sqflite_common | 995 | name: sqflite_common |
| 994 | sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" | 996 | sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" |
| 995 | - url: "https://pub.dev" | 997 | + url: "https://pub.flutter-io.cn" |
| 996 | source: hosted | 998 | source: hosted |
| 997 | version: "2.5.4+6" | 999 | version: "2.5.4+6" |
| 998 | sqflite_darwin: | 1000 | sqflite_darwin: |
| @@ -1000,7 +1002,7 @@ packages: | @@ -1000,7 +1002,7 @@ packages: | ||
| 1000 | description: | 1002 | description: |
| 1001 | name: sqflite_darwin | 1003 | name: sqflite_darwin |
| 1002 | sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" | 1004 | sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" |
| 1003 | - url: "https://pub.dev" | 1005 | + url: "https://pub.flutter-io.cn" |
| 1004 | source: hosted | 1006 | source: hosted |
| 1005 | version: "2.4.1+1" | 1007 | version: "2.4.1+1" |
| 1006 | sqflite_ohos: | 1008 | sqflite_ohos: |
| @@ -1017,7 +1019,7 @@ packages: | @@ -1017,7 +1019,7 @@ packages: | ||
| 1017 | description: | 1019 | description: |
| 1018 | name: sqflite_platform_interface | 1020 | name: sqflite_platform_interface |
| 1019 | sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" | 1021 | sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" |
| 1020 | - url: "https://pub.dev" | 1022 | + url: "https://pub.flutter-io.cn" |
| 1021 | source: hosted | 1023 | source: hosted |
| 1022 | version: "2.4.0" | 1024 | version: "2.4.0" |
| 1023 | stack_trace: | 1025 | stack_trace: |
| @@ -1025,7 +1027,7 @@ packages: | @@ -1025,7 +1027,7 @@ packages: | ||
| 1025 | description: | 1027 | description: |
| 1026 | name: stack_trace | 1028 | name: stack_trace |
| 1027 | sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" | 1029 | sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" |
| 1028 | - url: "https://pub.dev" | 1030 | + url: "https://pub.flutter-io.cn" |
| 1029 | source: hosted | 1031 | source: hosted |
| 1030 | version: "1.12.0" | 1032 | version: "1.12.0" |
| 1031 | stream_channel: | 1033 | stream_channel: |
| @@ -1033,7 +1035,7 @@ packages: | @@ -1033,7 +1035,7 @@ packages: | ||
| 1033 | description: | 1035 | description: |
| 1034 | name: stream_channel | 1036 | name: stream_channel |
| 1035 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 | 1037 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 |
| 1036 | - url: "https://pub.dev" | 1038 | + url: "https://pub.flutter-io.cn" |
| 1037 | source: hosted | 1039 | source: hosted |
| 1038 | version: "2.1.2" | 1040 | version: "2.1.2" |
| 1039 | stream_transform: | 1041 | stream_transform: |
| @@ -1041,7 +1043,7 @@ packages: | @@ -1041,7 +1043,7 @@ packages: | ||
| 1041 | description: | 1043 | description: |
| 1042 | name: stream_transform | 1044 | name: stream_transform |
| 1043 | sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 | 1045 | sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 |
| 1044 | - url: "https://pub.dev" | 1046 | + url: "https://pub.flutter-io.cn" |
| 1045 | source: hosted | 1047 | source: hosted |
| 1046 | version: "2.1.1" | 1048 | version: "2.1.1" |
| 1047 | string_scanner: | 1049 | string_scanner: |
| @@ -1049,7 +1051,7 @@ packages: | @@ -1049,7 +1051,7 @@ packages: | ||
| 1049 | description: | 1051 | description: |
| 1050 | name: string_scanner | 1052 | name: string_scanner |
| 1051 | sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" | 1053 | sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" |
| 1052 | - url: "https://pub.dev" | 1054 | + url: "https://pub.flutter-io.cn" |
| 1053 | source: hosted | 1055 | source: hosted |
| 1054 | version: "1.3.0" | 1056 | version: "1.3.0" |
| 1055 | synchronized: | 1057 | synchronized: |
| @@ -1057,7 +1059,7 @@ packages: | @@ -1057,7 +1059,7 @@ packages: | ||
| 1057 | description: | 1059 | description: |
| 1058 | name: synchronized | 1060 | name: synchronized |
| 1059 | sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" | 1061 | sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" |
| 1060 | - url: "https://pub.dev" | 1062 | + url: "https://pub.flutter-io.cn" |
| 1061 | source: hosted | 1063 | source: hosted |
| 1062 | version: "3.3.0+3" | 1064 | version: "3.3.0+3" |
| 1063 | table_calendar: | 1065 | table_calendar: |
| @@ -1065,7 +1067,7 @@ packages: | @@ -1065,7 +1067,7 @@ packages: | ||
| 1065 | description: | 1067 | description: |
| 1066 | name: table_calendar | 1068 | name: table_calendar |
| 1067 | sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63 | 1069 | sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63 |
| 1068 | - url: "https://pub.dev" | 1070 | + url: "https://pub.flutter-io.cn" |
| 1069 | source: hosted | 1071 | source: hosted |
| 1070 | version: "3.1.3" | 1072 | version: "3.1.3" |
| 1071 | term_glyph: | 1073 | term_glyph: |
| @@ -1073,7 +1075,7 @@ packages: | @@ -1073,7 +1075,7 @@ packages: | ||
| 1073 | description: | 1075 | description: |
| 1074 | name: term_glyph | 1076 | name: term_glyph |
| 1075 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 | 1077 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 |
| 1076 | - url: "https://pub.dev" | 1078 | + url: "https://pub.flutter-io.cn" |
| 1077 | source: hosted | 1079 | source: hosted |
| 1078 | version: "1.2.1" | 1080 | version: "1.2.1" |
| 1079 | test_api: | 1081 | test_api: |
| @@ -1081,7 +1083,7 @@ packages: | @@ -1081,7 +1083,7 @@ packages: | ||
| 1081 | description: | 1083 | description: |
| 1082 | name: test_api | 1084 | name: test_api |
| 1083 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" | 1085 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" |
| 1084 | - url: "https://pub.dev" | 1086 | + url: "https://pub.flutter-io.cn" |
| 1085 | source: hosted | 1087 | source: hosted |
| 1086 | version: "0.7.3" | 1088 | version: "0.7.3" |
| 1087 | thinking_analytics: | 1089 | thinking_analytics: |
| @@ -1089,7 +1091,7 @@ packages: | @@ -1089,7 +1091,7 @@ packages: | ||
| 1089 | description: | 1091 | description: |
| 1090 | name: thinking_analytics | 1092 | name: thinking_analytics |
| 1091 | sha256: b01cac0b5482e71c1d75c44c77d27f427662cc65a77b7bc3c8b49617d7a01e02 | 1093 | sha256: b01cac0b5482e71c1d75c44c77d27f427662cc65a77b7bc3c8b49617d7a01e02 |
| 1092 | - url: "https://pub.dev" | 1094 | + url: "https://pub.flutter-io.cn" |
| 1093 | source: hosted | 1095 | source: hosted |
| 1094 | version: "3.3.3" | 1096 | version: "3.3.3" |
| 1095 | timing: | 1097 | timing: |
| @@ -1097,7 +1099,7 @@ packages: | @@ -1097,7 +1099,7 @@ packages: | ||
| 1097 | description: | 1099 | description: |
| 1098 | name: timing | 1100 | name: timing |
| 1099 | sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" | 1101 | sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" |
| 1100 | - url: "https://pub.dev" | 1102 | + url: "https://pub.flutter-io.cn" |
| 1101 | source: hosted | 1103 | source: hosted |
| 1102 | version: "1.0.2" | 1104 | version: "1.0.2" |
| 1103 | typed_data: | 1105 | typed_data: |
| @@ -1105,7 +1107,7 @@ packages: | @@ -1105,7 +1107,7 @@ packages: | ||
| 1105 | description: | 1107 | description: |
| 1106 | name: typed_data | 1108 | name: typed_data |
| 1107 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 | 1109 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 |
| 1108 | - url: "https://pub.dev" | 1110 | + url: "https://pub.flutter-io.cn" |
| 1109 | source: hosted | 1111 | source: hosted |
| 1110 | version: "1.4.0" | 1112 | version: "1.4.0" |
| 1111 | url_launcher_linux: | 1113 | url_launcher_linux: |
| @@ -1113,7 +1115,7 @@ packages: | @@ -1113,7 +1115,7 @@ packages: | ||
| 1113 | description: | 1115 | description: |
| 1114 | name: url_launcher_linux | 1116 | name: url_launcher_linux |
| 1115 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" | 1117 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" |
| 1116 | - url: "https://pub.dev" | 1118 | + url: "https://pub.flutter-io.cn" |
| 1117 | source: hosted | 1119 | source: hosted |
| 1118 | version: "3.2.1" | 1120 | version: "3.2.1" |
| 1119 | url_launcher_platform_interface: | 1121 | url_launcher_platform_interface: |
| @@ -1121,7 +1123,7 @@ packages: | @@ -1121,7 +1123,7 @@ packages: | ||
| 1121 | description: | 1123 | description: |
| 1122 | name: url_launcher_platform_interface | 1124 | name: url_launcher_platform_interface |
| 1123 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" | 1125 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" |
| 1124 | - url: "https://pub.dev" | 1126 | + url: "https://pub.flutter-io.cn" |
| 1125 | source: hosted | 1127 | source: hosted |
| 1126 | version: "2.3.2" | 1128 | version: "2.3.2" |
| 1127 | url_launcher_web: | 1129 | url_launcher_web: |
| @@ -1129,7 +1131,7 @@ packages: | @@ -1129,7 +1131,7 @@ packages: | ||
| 1129 | description: | 1131 | description: |
| 1130 | name: url_launcher_web | 1132 | name: url_launcher_web |
| 1131 | sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" | 1133 | sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" |
| 1132 | - url: "https://pub.dev" | 1134 | + url: "https://pub.flutter-io.cn" |
| 1133 | source: hosted | 1135 | source: hosted |
| 1134 | version: "2.4.1" | 1136 | version: "2.4.1" |
| 1135 | url_launcher_windows: | 1137 | url_launcher_windows: |
| @@ -1137,7 +1139,7 @@ packages: | @@ -1137,7 +1139,7 @@ packages: | ||
| 1137 | description: | 1139 | description: |
| 1138 | name: url_launcher_windows | 1140 | name: url_launcher_windows |
| 1139 | sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" | 1141 | sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" |
| 1140 | - url: "https://pub.dev" | 1142 | + url: "https://pub.flutter-io.cn" |
| 1141 | source: hosted | 1143 | source: hosted |
| 1142 | version: "3.1.4" | 1144 | version: "3.1.4" |
| 1143 | uuid: | 1145 | uuid: |
| @@ -1145,7 +1147,7 @@ packages: | @@ -1145,7 +1147,7 @@ packages: | ||
| 1145 | description: | 1147 | description: |
| 1146 | name: uuid | 1148 | name: uuid |
| 1147 | sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" | 1149 | sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" |
| 1148 | - url: "https://pub.dev" | 1150 | + url: "https://pub.flutter-io.cn" |
| 1149 | source: hosted | 1151 | source: hosted |
| 1150 | version: "4.6.0" | 1152 | version: "4.6.0" |
| 1151 | vector_math: | 1153 | vector_math: |
| @@ -1153,7 +1155,7 @@ packages: | @@ -1153,7 +1155,7 @@ packages: | ||
| 1153 | description: | 1155 | description: |
| 1154 | name: vector_math | 1156 | name: vector_math |
| 1155 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" | 1157 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" |
| 1156 | - url: "https://pub.dev" | 1158 | + url: "https://pub.flutter-io.cn" |
| 1157 | source: hosted | 1159 | source: hosted |
| 1158 | version: "2.1.4" | 1160 | version: "2.1.4" |
| 1159 | video_thumbnail: | 1161 | video_thumbnail: |
| @@ -1161,7 +1163,7 @@ packages: | @@ -1161,7 +1163,7 @@ packages: | ||
| 1161 | description: | 1163 | description: |
| 1162 | name: video_thumbnail | 1164 | name: video_thumbnail |
| 1163 | sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" | 1165 | sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" |
| 1164 | - url: "https://pub.dev" | 1166 | + url: "https://pub.flutter-io.cn" |
| 1165 | source: hosted | 1167 | source: hosted |
| 1166 | version: "0.5.6" | 1168 | version: "0.5.6" |
| 1167 | vm_service: | 1169 | vm_service: |
| @@ -1169,7 +1171,7 @@ packages: | @@ -1169,7 +1171,7 @@ packages: | ||
| 1169 | description: | 1171 | description: |
| 1170 | name: vm_service | 1172 | name: vm_service |
| 1171 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b | 1173 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b |
| 1172 | - url: "https://pub.dev" | 1174 | + url: "https://pub.flutter-io.cn" |
| 1173 | source: hosted | 1175 | source: hosted |
| 1174 | version: "14.3.0" | 1176 | version: "14.3.0" |
| 1175 | watcher: | 1177 | watcher: |
| @@ -1177,7 +1179,7 @@ packages: | @@ -1177,7 +1179,7 @@ packages: | ||
| 1177 | description: | 1179 | description: |
| 1178 | name: watcher | 1180 | name: watcher |
| 1179 | sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" | 1181 | sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" |
| 1180 | - url: "https://pub.dev" | 1182 | + url: "https://pub.flutter-io.cn" |
| 1181 | source: hosted | 1183 | source: hosted |
| 1182 | version: "1.2.1" | 1184 | version: "1.2.1" |
| 1183 | web: | 1185 | web: |
| @@ -1185,7 +1187,7 @@ packages: | @@ -1185,7 +1187,7 @@ packages: | ||
| 1185 | description: | 1187 | description: |
| 1186 | name: web | 1188 | name: web |
| 1187 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" | 1189 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" |
| 1188 | - url: "https://pub.dev" | 1190 | + url: "https://pub.flutter-io.cn" |
| 1189 | source: hosted | 1191 | source: hosted |
| 1190 | version: "1.1.1" | 1192 | version: "1.1.1" |
| 1191 | web_socket: | 1193 | web_socket: |
| @@ -1193,7 +1195,7 @@ packages: | @@ -1193,7 +1195,7 @@ packages: | ||
| 1193 | description: | 1195 | description: |
| 1194 | name: web_socket | 1196 | name: web_socket |
| 1195 | sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" | 1197 | sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" |
| 1196 | - url: "https://pub.dev" | 1198 | + url: "https://pub.flutter-io.cn" |
| 1197 | source: hosted | 1199 | source: hosted |
| 1198 | version: "1.0.1" | 1200 | version: "1.0.1" |
| 1199 | web_socket_channel: | 1201 | web_socket_channel: |
| @@ -1201,7 +1203,7 @@ packages: | @@ -1201,7 +1203,7 @@ packages: | ||
| 1201 | description: | 1203 | description: |
| 1202 | name: web_socket_channel | 1204 | name: web_socket_channel |
| 1203 | sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 | 1205 | sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 |
| 1204 | - url: "https://pub.dev" | 1206 | + url: "https://pub.flutter-io.cn" |
| 1205 | source: hosted | 1207 | source: hosted |
| 1206 | version: "3.0.3" | 1208 | version: "3.0.3" |
| 1207 | webview_flutter: | 1209 | webview_flutter: |
| @@ -1254,7 +1256,7 @@ packages: | @@ -1254,7 +1256,7 @@ packages: | ||
| 1254 | description: | 1256 | description: |
| 1255 | name: win32 | 1257 | name: win32 |
| 1256 | sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e | 1258 | sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e |
| 1257 | - url: "https://pub.dev" | 1259 | + url: "https://pub.flutter-io.cn" |
| 1258 | source: hosted | 1260 | source: hosted |
| 1259 | version: "5.10.1" | 1261 | version: "5.10.1" |
| 1260 | xdg_directories: | 1262 | xdg_directories: |
| @@ -1262,7 +1264,7 @@ packages: | @@ -1262,7 +1264,7 @@ packages: | ||
| 1262 | description: | 1264 | description: |
| 1263 | name: xdg_directories | 1265 | name: xdg_directories |
| 1264 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" | 1266 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" |
| 1265 | - url: "https://pub.dev" | 1267 | + url: "https://pub.flutter-io.cn" |
| 1266 | source: hosted | 1268 | source: hosted |
| 1267 | version: "1.1.0" | 1269 | version: "1.1.0" |
| 1268 | yaml: | 1270 | yaml: |
| @@ -1270,7 +1272,7 @@ packages: | @@ -1270,7 +1272,7 @@ packages: | ||
| 1270 | description: | 1272 | description: |
| 1271 | name: yaml | 1273 | name: yaml |
| 1272 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce | 1274 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce |
| 1273 | - url: "https://pub.dev" | 1275 | + url: "https://pub.flutter-io.cn" |
| 1274 | source: hosted | 1276 | source: hosted |
| 1275 | version: "3.1.3" | 1277 | version: "3.1.3" |
| 1276 | sdks: | 1278 | sdks: |
| 1 | name: doublefeel_flutter | 1 | name: doublefeel_flutter |
| 2 | description: "A new Flutter project." | 2 | description: "A new Flutter project." |
| 3 | -publish_to: 'none' | 3 | +publish_to: 'none' |
| 4 | 4 | ||
| 5 | -version: 2.5.0+100 | 5 | +version: 2.7.0+1000000 |
| 6 | 6 | ||
| 7 | environment: | 7 | environment: |
| 8 | sdk: ^3.6.2 | 8 | sdk: ^3.6.2 |
| @@ -21,7 +21,7 @@ dependencies: | @@ -21,7 +21,7 @@ dependencies: | ||
| 21 | fl_chart: ^0.70.2 | 21 | fl_chart: ^0.70.2 |
| 22 | logger: ^2.6.2 | 22 | logger: ^2.6.2 |
| 23 | webview_flutter: | 23 | webview_flutter: |
| 24 | - git: | 24 | + git: |
| 25 | url: https://gitcode.com/openharmony-tpc/flutter_packages.git | 25 | url: https://gitcode.com/openharmony-tpc/flutter_packages.git |
| 26 | path: packages/webview_flutter/webview_flutter | 26 | path: packages/webview_flutter/webview_flutter |
| 27 | ref: br_webview_flutter-v4.13.0_ohos | 27 | ref: br_webview_flutter-v4.13.0_ohos |
| @@ -35,12 +35,12 @@ dependencies: | @@ -35,12 +35,12 @@ dependencies: | ||
| 35 | git: | 35 | git: |
| 36 | url: https://gitcode.com/openharmony-sig/flutter_packages.git | 36 | url: https://gitcode.com/openharmony-sig/flutter_packages.git |
| 37 | path: packages/image_picker/image_picker_ohos | 37 | path: packages/image_picker/image_picker_ohos |
| 38 | - image_cropper: 9.1.0 | ||
| 39 | - # image_cropper: | ||
| 40 | - # git: | ||
| 41 | - # url: https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git | ||
| 42 | - # path: ./image_cropper | ||
| 43 | - # ref: br_v9.1.0_ohos | 38 | + # image_cropper: 9.1.0 |
| 39 | + image_cropper: | ||
| 40 | + git: | ||
| 41 | + url: https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git | ||
| 42 | + path: ./image_cropper | ||
| 43 | + ref: br_v9.1.0_ohos | ||
| 44 | permission_handler: ^11.3.1 | 44 | permission_handler: ^11.3.1 |
| 45 | permission_handler_ohos: | 45 | permission_handler_ohos: |
| 46 | git: | 46 | git: |
-
Please register or login to post a comment