Commit a9c81df4cf84ca608cb4c2c991b378c8d57074b1

Authored by 刘宏哲
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 }
@@ -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
  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();
  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 +}
  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) {
  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 +}
  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 +}
  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 }
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 "string": [ 2 "string": [
3 { 3 {
4 "name": "app_name", 4 "name": "app_name",
5 - "value": "doublefeel_flutter" 5 + "value": "DoubleFeel"
6 } 6 }
7 ] 7 ]
8 } 8 }

6.63 KB | W: | H:

3.86 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
  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,
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 }, 10 },
11 { 11 {
12 "name": "EntryAbility_label", 12 "name": "EntryAbility_label",
13 - "value": "doublefeel_flutter" 13 + "value": "DoubleFeel"
14 } 14 }
15 ] 15 ]
16 } 16 }

6.63 KB | W: | H:

3.86 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 }, 10 },
11 { 11 {
12 "name": "EntryAbility_label", 12 "name": "EntryAbility_label",
13 - "value": "doublefeel_flutter" 13 + "value": "DoubleFeel"
14 } 14 }
15 ] 15 ]
16 } 16 }
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 }, 10 },
11 { 11 {
12 "name": "EntryAbility_label", 12 "name": "EntryAbility_label",
13 - "value": "doublefeel_flutter" 13 + "value": "DoubleFeel"
14 } 14 }
15 ] 15 ]
16 } 16 }
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: