Commit bb2c3b2b30e0dd43f7ffb702e7a9824c5de3cc48

Authored by 常守达
1 parent cf74440a

feat(route):路由

@@ -38,8 +38,7 @@ class BindPartnerView extends GetView<BindPartnerController> { @@ -38,8 +38,7 @@ class BindPartnerView extends GetView<BindPartnerController> {
38 children: [ 38 children: [
39 _buildGradientBackground(), 39 _buildGradientBackground(),
40 Container( 40 Container(
41 - padding:  
42 - EdgeInsets.only(top: MediaQuery.paddingOf(context).top + 4), 41 + padding: EdgeInsets.only(top: 4),
43 child: ListView( 42 child: ListView(
44 physics: const ClampingScrollPhysics(), 43 physics: const ClampingScrollPhysics(),
45 children: [ 44 children: [
  1 +import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_controller.dart';
  2 +import 'package:doublefeel_flutter/core/network/api/friend_api.dart';
  3 +import 'package:doublefeel_flutter/core/network/api/user_api.dart';
  4 +import 'package:doublefeel_flutter/core/result/app_result.dart';
1 import 'package:doublefeel_flutter/core/services/user_state_service.dart'; 5 import 'package:doublefeel_flutter/core/services/user_state_service.dart';
2 import 'package:doublefeel_flutter/data/local/local_storage.dart'; 6 import 'package:doublefeel_flutter/data/local/local_storage.dart';
3 import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; 7 import 'package:doublefeel_flutter/pigeon/platform_api.g.dart';
4 import 'package:flutter/material.dart'; 8 import 'package:flutter/material.dart';
5 import 'package:flutter/services.dart'; 9 import 'package:flutter/services.dart';
  10 +import 'package:flutter/scheduler.dart';
  11 +
6 import 'package:get/get.dart'; 12 import 'package:get/get.dart';
7 13
8 import '../../../routes/app_pages.dart'; 14 import '../../../routes/app_pages.dart';
9 import '../../purchase/purchase_route_args.dart'; 15 import '../../purchase/purchase_route_args.dart';
10 import '../../app_review_prompt/logic/app_review_prompt_logic.dart'; 16 import '../../app_review_prompt/logic/app_review_prompt_logic.dart';
11 import '../../report_common/models/report_period.dart'; 17 import '../../report_common/models/report_period.dart';
  18 +import 'today_controller.dart';
12 import 'trend/trend_controller.dart'; 19 import 'trend/trend_controller.dart';
13 20
14 class HomeController extends GetxController { 21 class HomeController extends GetxController {
@@ -20,6 +27,7 @@ class HomeController extends GetxController { @@ -20,6 +27,7 @@ class HomeController extends GetxController {
20 ); 27 );
21 final LocalStorage _storage = Get.find<LocalStorage>(); 28 final LocalStorage _storage = Get.find<LocalStorage>();
22 final UserStateService userStateService = Get.find<UserStateService>(); 29 final UserStateService userStateService = Get.find<UserStateService>();
  30 + final PlatformHostApi _platformHostApi = PlatformHostApi();
23 31
24 /// 当前选中的底部 tab 索引 32 /// 当前选中的底部 tab 索引
25 final selectedIndex = 0.obs; 33 final selectedIndex = 0.obs;
@@ -29,6 +37,16 @@ class HomeController extends GetxController { @@ -29,6 +37,16 @@ class HomeController extends GetxController {
29 void onInit() { 37 void onInit() {
30 super.onInit(); 38 super.onInit();
31 _mainChannel.setMethodCallHandler(_handleNativeMethodCall); 39 _mainChannel.setMethodCallHandler(_handleNativeMethodCall);
  40 + SchedulerBinding.instance.addPostFrameCallback((_) async {
  41 + try {
  42 + final route = await _platformHostApi.requestUnhandedUrl();
  43 + // final route ='doublefeel://flutter/home?tab=today&scroll_to_latest_hrv=true';
  44 + // final route = 'doublefeel://flutter/friend-home?user_id=131';
  45 + // final route = 'doublefeel://flutter/home?tab=trend&type=sleep';
  46 + // final route ='doublefeel://flutter/friend-trend?type=sleep&user_id=131';
  47 + _checkFlutterRoute(route);
  48 + } catch (_) {}
  49 + });
32 } 50 }
33 51
34 @override 52 @override
@@ -37,15 +55,6 @@ class HomeController extends GetxController { @@ -37,15 +55,6 @@ class HomeController extends GetxController {
37 super.onClose(); 55 super.onClose();
38 } 56 }
39 57
40 - @override  
41 - void onReady() async {  
42 - final unhandedUrl = await PlatformHostApi().requestUnhandedUrl();  
43 - debugPrint(  
44 - '[HomeController] requestUnhandedUrl: '  
45 - 'result: ${unhandedUrl}',  
46 - );  
47 - }  
48 -  
49 Future<Object?> _handleNativeMethodCall(MethodCall call) async { 58 Future<Object?> _handleNativeMethodCall(MethodCall call) async {
50 switch (call.method) { 59 switch (call.method) {
51 case 'handleFlutterUrl': 60 case 'handleFlutterUrl':
@@ -66,7 +75,7 @@ class HomeController extends GetxController { @@ -66,7 +75,7 @@ class HomeController extends GetxController {
66 _ => '', 75 _ => '',
67 }; 76 };
68 lastFlutterUrl.value = url; 77 lastFlutterUrl.value = url;
69 - debugPrint('[HomeController.handleFlutterUrl] url: $url'); 78 + _checkFlutterRoute(url);
70 return url.isNotEmpty; 79 return url.isNotEmpty;
71 } 80 }
72 81
@@ -107,4 +116,126 @@ class HomeController extends GetxController { @@ -107,4 +116,126 @@ class HomeController extends GetxController {
107 116
108 int _dateKey(DateTime date) => 117 int _dateKey(DateTime date) =>
109 date.year * 10000 + date.month * 100 + date.day; 118 date.year * 10000 + date.month * 100 + date.day;
  119 +
  120 + _checkFlutterRoute(String? route) {
  121 + try {
  122 + if (route == null || route.isEmpty) return;
  123 +
  124 + final uri = Uri.tryParse(route);
  125 + if (uri == null) return;
  126 +
  127 + final path = uri.path; // "/home", "/account-settings" 等
  128 + final params = uri.queryParameters; // {"tab": "today"} 等
  129 +
  130 + _dispatchRoute(path, params);
  131 + } catch (_) {}
  132 + }
  133 +
  134 + /// 根据 path + query 参数分派导航动作
  135 + void _dispatchRoute(String path, Map<String, String> params) {
  136 + switch (path) {
  137 + case AppRoutes.home:
  138 + _switchHomeTab(params['tab'], params);
  139 + break;
  140 + default:
  141 + // 其他已注册路由 → 跳转页面,query 参数作为 arguments 传入
  142 + final isKnown = Get.routeTree.routes.any((r) => r.name == path);
  143 + if (!isKnown) return;
  144 + if (path == Routes.FRIEND_HOME || path == Routes.FRIEND_TREND) {
  145 + final paramUserId = params['user_id'];
  146 + if (paramUserId != null) {
  147 + final friendUserId = int.tryParse(paramUserId);
  148 + if (friendUserId != null) {
  149 + final _friendApi = Get.find<FriendApi>();
  150 + _friendApi.getFriendInfo(friendUserId).then((result) {
  151 + switch (result) {
  152 + case AppSuccess(data: final info):
  153 + {
  154 + if (path == Routes.FRIEND_TREND) {
  155 + TrendType? trendType =
  156 + getTrendType(params['type'] ?? '');
  157 + if (trendType != null) {
  158 + Get.toNamed(path,
  159 + arguments: FriendTrendArguments(
  160 + friendItem: info,
  161 + initialTypeIndex: trendType.tabIndex,
  162 + ));
  163 + }
  164 + } else {
  165 + Get.toNamed(path, arguments: info);
  166 + }
  167 + }
  168 + break;
  169 + case AppFailure():
  170 + break;
  171 + }
  172 + });
  173 + }
  174 + }
  175 + } else {
  176 + Get.toNamed(path, arguments: params.isNotEmpty ? params : null);
  177 + }
  178 + }
  179 + }
  180 +
  181 + /// 根据 tab 名称切换首页底部 tab
  182 + void _switchHomeTab(String? tab, Map<String, String> params) {
  183 + switch (tab) {
  184 + case 'today':
  185 + changeTab(0);
  186 + final scrollToLatestHrv =
  187 + params['scroll_to_latest_hrv']?.toLowerCase() == 'true';
  188 + if (scrollToLatestHrv) {
  189 + SchedulerBinding.instance.addPostFrameCallback((_) {
  190 + final todayCtrl = Get.find<TodayController>();
  191 + if (todayCtrl.scrollController.hasClients) {
  192 + todayCtrl.scrollController.animateTo(
  193 + 850,
  194 + duration: const Duration(milliseconds: 400),
  195 + curve: Curves.easeInOut,
  196 + );
  197 + }
  198 + });
  199 + }
  200 + break;
  201 + case 'trend':
  202 + changeTab(trendTabIndex);
  203 + final type = params['type'];
  204 + if (type != null) {
  205 + SchedulerBinding.instance.addPostFrameCallback((_) {
  206 + TrendType? trendType = getTrendType(type);
  207 + if (trendType != null) {
  208 + openTrend(
  209 + trendType,
  210 + );
  211 + }
  212 + });
  213 + }
  214 + break;
  215 + case 'friends':
  216 + changeTab(2);
  217 + break;
  218 + case 'my':
  219 + changeTab(3);
  220 + break;
  221 + default:
  222 + break;
  223 + }
  224 + }
  225 +
  226 + TrendType? getTrendType(String type) {
  227 + switch (type) {
  228 + case 'sleep':
  229 + return TrendType.sleep;
  230 +
  231 + case 'activity':
  232 + return TrendType.activity;
  233 +
  234 + case 'hrv':
  235 + return TrendType.hrv;
  236 + default:
  237 + break;
  238 + }
  239 + return null;
  240 + }
110 } 241 }
@@ -83,6 +83,7 @@ class TodayController extends GetMaterialController { @@ -83,6 +83,7 @@ class TodayController extends GetMaterialController {
83 final selectedDate = DateTime.now().obs; 83 final selectedDate = DateTime.now().obs;
84 final focusedDay = DateTime.now().obs; 84 final focusedDay = DateTime.now().obs;
85 final scrollOffset = 0.0.obs; 85 final scrollOffset = 0.0.obs;
  86 + final scrollController = ScrollController();
86 87
87 final isLoadingToday = false.obs; 88 final isLoadingToday = false.obs;
88 final showHealthDataAuthCardStatus = 1.obs; 89 final showHealthDataAuthCardStatus = 1.obs;
@@ -158,10 +159,14 @@ class TodayController extends GetMaterialController { @@ -158,10 +159,14 @@ class TodayController extends GetMaterialController {
158 showHrvAdBanner.value = false; 159 showHrvAdBanner.value = false;
159 } else { 160 } else {
160 try { 161 try {
161 - bool hasInstalledWatchSurface =  
162 - await WearEngineHostApi().hasInstalledWatchSurface();  
163 - showHrvAdBanner.value = !hasInstalledWatchSurface;  
164 - } on Exception catch (e) {} 162 + WearEngineHostApi()
  163 + .hasInstalledWatchSurface()
  164 + .then((hasInstalledWatchSurface) {
  165 + showHrvAdBanner.value = !hasInstalledWatchSurface;
  166 + });
  167 + } on Exception catch (e) {
  168 + AppLogger.e(e);
  169 + }
165 } 170 }
166 } 171 }
167 172
@@ -228,7 +233,7 @@ class TodayController extends GetMaterialController { @@ -228,7 +233,7 @@ class TodayController extends GetMaterialController {
228 AppLogger.d("requestHealthClientAuthorization : $success"); 233 AppLogger.d("requestHealthClientAuthorization : $success");
229 if (success) { 234 if (success) {
230 _performDataUpload(); 235 _performDataUpload();
231 - AppToast.show('刷新完成'); 236 + // AppToast.show('刷新完成');
232 } else { 237 } else {
233 // Get.to(NoHealthDataPage( 238 // Get.to(NoHealthDataPage(
234 // onRefresh: _performDataUpload, 239 // onRefresh: _performDataUpload,
@@ -597,5 +602,8 @@ class TodayController extends GetMaterialController { @@ -597,5 +602,8 @@ class TodayController extends GetMaterialController {
597 @override 602 @override
598 void didChangeAppLifecycleState(AppLifecycleState state) { 603 void didChangeAppLifecycleState(AppLifecycleState state) {
599 super.didChangeAppLifecycleState(state); 604 super.didChangeAppLifecycleState(state);
  605 + checkAddFriendVisible();
  606 + checkHrvAdBannerVisible();
  607 + checkHealthDataAuthCardVisible();
600 } 608 }
601 } 609 }
@@ -116,6 +116,7 @@ class TodayTabBody extends StatelessWidget { @@ -116,6 +116,7 @@ class TodayTabBody extends StatelessWidget {
116 double pinnedHeaderHeight, 116 double pinnedHeaderHeight,
117 ) { 117 ) {
118 return CustomScrollView( 118 return CustomScrollView(
  119 + controller: controller.scrollController,
119 physics: const ClampingScrollPhysics(), 120 physics: const ClampingScrollPhysics(),
120 slivers: [ 121 slivers: [
121 SliverToBoxAdapter( 122 SliverToBoxAdapter(
@@ -147,15 +147,26 @@ class _BottomSheetFriendRow extends StatelessWidget { @@ -147,15 +147,26 @@ class _BottomSheetFriendRow extends StatelessWidget {
147 const SizedBox(width: 8), 147 const SizedBox(width: 8),
148 // Name 148 // Name
149 Expanded( 149 Expanded(
150 - child: Text(  
151 - _friendDisplayName(friend),  
152 - style: const TextStyle(  
153 - color: Color(0xFF0F0F11),  
154 - fontSize: 14,  
155 - fontWeight: FontWeight.w500,  
156 - ),  
157 - overflow: TextOverflow.ellipsis,  
158 - ), 150 + child: Text.rich(
  151 + overflow: TextOverflow.ellipsis,
  152 + TextSpan(children: [
  153 + TextSpan(
  154 + text: friend.remarkName?.trim(),
  155 + style: TextStyle(
  156 + color: context.colors.textPrimary,
  157 + fontSize: 14,
  158 + fontWeight: FontWeight.w500,
  159 + ),
  160 + ),
  161 + TextSpan(
  162 + text: '(${friend.remarkName?.trim()})',
  163 + style: TextStyle(
  164 + color: context.colors.textSecondary,
  165 + fontSize: 14,
  166 + fontWeight: FontWeight.w500,
  167 + ),
  168 + ),
  169 + ])),
159 ), 170 ),
160 // Checkbox 171 // Checkbox
161 Container( 172 Container(
@@ -182,14 +193,4 @@ class _BottomSheetFriendRow extends StatelessWidget { @@ -182,14 +193,4 @@ class _BottomSheetFriendRow extends StatelessWidget {
182 ), 193 ),
183 ); 194 );
184 } 195 }
185 -  
186 - String _friendDisplayName(FriendItem friend) {  
187 - final remark = friend.remarkName?.trim();  
188 - if (remark != null && remark.isNotEmpty) return remark;  
189 -  
190 - final nickname = friend.friendNickname?.trim();  
191 - if (nickname != null && nickname.isNotEmpty) return nickname;  
192 -  
193 - return l10n.friendsUnknownFriend;  
194 - }  
195 } 196 }
@@ -9,6 +9,8 @@ class SplashPage extends GetView<SplashController> { @@ -9,6 +9,8 @@ class SplashPage extends GetView<SplashController> {
9 @override 9 @override
10 Widget build(BuildContext context) { 10 Widget build(BuildContext context) {
11 return const Scaffold( 11 return const Scaffold(
  12 + extendBodyBehindAppBar: true,
  13 + extendBody: true,
12 body: Center( 14 body: Center(
13 child: CircularProgressIndicator(), 15 child: CircularProgressIndicator(),
14 ), 16 ),
  1 +import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart';
1 import 'package:doublefeel_flutter/core/network/api/user_api.dart'; 2 import 'package:doublefeel_flutter/core/network/api/user_api.dart';
2 import 'package:get/get.dart'; 3 import 'package:get/get.dart';
3 4
@@ -6,8 +7,11 @@ import '../controllers/user_onboarding_controller.dart'; @@ -6,8 +7,11 @@ import '../controllers/user_onboarding_controller.dart';
6 class UserOnboardingBinding extends Bindings { 7 class UserOnboardingBinding extends Bindings {
7 @override 8 @override
8 void dependencies() { 9 void dependencies() {
9 - Get.lazyPut<UserOnboardingController>(  
10 - () => UserOnboardingController(Get.find<UserApi>()), 10 + Get.put(
  11 + UserOnboardingController(
  12 + Get.find<UserApi>(),
  13 + Get.find<AppleHealthUploadTool>(),
  14 + ),
11 ); 15 );
12 } 16 }
13 } 17 }
  1 +import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart';
  2 +import 'package:doublefeel_flutter/core/logging/app_logger.dart';
1 import 'package:doublefeel_flutter/core/network/api/user_api.dart'; 3 import 'package:doublefeel_flutter/core/network/api/user_api.dart';
2 import 'package:doublefeel_flutter/core/services/user_state_service.dart'; 4 import 'package:doublefeel_flutter/core/services/user_state_service.dart';
3 import 'package:doublefeel_flutter/data/local/user_account_storage.dart'; 5 import 'package:doublefeel_flutter/data/local/user_account_storage.dart';
4 import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; 6 import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
  7 +import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart';
5 import 'package:flutter/material.dart'; 8 import 'package:flutter/material.dart';
6 import 'package:get/get.dart'; 9 import 'package:get/get.dart';
7 import 'package:permission_handler/permission_handler.dart'; 10 import 'package:permission_handler/permission_handler.dart';
@@ -17,8 +20,10 @@ class UserOnboardingController extends GetxController { @@ -17,8 +20,10 @@ class UserOnboardingController extends GetxController {
17 final selections = <int, Set<int>>{}.obs; 20 final selections = <int, Set<int>>{}.obs;
18 final selectionVersion = 0.obs; 21 final selectionVersion = 0.obs;
19 22
20 - UserOnboardingController(this._userApi); 23 + UserOnboardingController(this._userApi, this._appleHealthUploadTool);
21 final UserApi _userApi; 24 final UserApi _userApi;
  25 + final AppleHealthUploadTool _appleHealthUploadTool;
  26 + final HealthKitHostApi _hostApi = HealthKitHostApi();
22 27
23 bool hasSelection(int pageIndex) => 28 bool hasSelection(int pageIndex) =>
24 selections[pageIndex]?.isNotEmpty ?? false; 29 selections[pageIndex]?.isNotEmpty ?? false;
@@ -204,31 +209,84 @@ class UserOnboardingController extends GetxController { @@ -204,31 +209,84 @@ class UserOnboardingController extends GetxController {
204 if (userStateService.isBound) { 209 if (userStateService.isBound) {
205 Get.offAllNamed(AppRoutes.home); 210 Get.offAllNamed(AppRoutes.home);
206 } else { 211 } else {
207 - Get.offAllNamed(AppRoutes.bindPartner); 212 + Get.offAllNamed(AppRoutes.bindPartner, arguments: {'from': 'onboarding'});
  213 + }
  214 + }
  215 +
  216 + /// 上传 Apple Health 新数据。
  217 + Future<void> _performDataUpload() async {
  218 + try {
  219 + final result =
  220 + await _appleHealthUploadTool.uploadAllNewDataFromHealthKit();
  221 + // AppLogger.i(
  222 + // 'Apple Health upload finished: '
  223 + // 'common=${result.commonUploadSuccess}(${result.commonCount}), '
  224 + // 'sleep=${result.sleepUploadSuccess}(${result.sleepCount}), '
  225 + // 'activityTarget=${result.activityTargetUploadSuccess}'
  226 + // '(${result.activityTargetCount}), '
  227 + // 'success=${result.isSuccess}',
  228 + // );
  229 + } catch (error, stackTrace) {
  230 + AppLogger.e('Apple Health upload failed', error, stackTrace);
208 } 231 }
209 } 232 }
210 233
211 Future<void> requestHealthAuthorization() async { 234 Future<void> requestHealthAuthorization() async {
212 if (isAndroid) { 235 if (isAndroid) {
213 return; 236 return;
214 - }  
215 - try {  
216 - await Get.find<HealthKitUploadService>().requestClientAuthorization();  
217 - } catch (error) {  
218 - debugPrint('Health authorization skipped: $error'); 237 + } else {
  238 + if (GetPlatform.isIOS) {
  239 + try {
  240 + final result = await _hostApi.checkHealthAppAuthorization();
  241 + AppLogger.d("checkHealthAppAuthorization : $result");
  242 + if (result.status == 0) {
  243 + bool success = await _hostApi.requestHealthClientAuthorization();
  244 + AppLogger.d("requestHealthClientAuthorization : $success");
  245 + if (success) {
  246 + _performDataUpload();
  247 + } else {
  248 + // Get.to(NoHealthDataPage(
  249 + // onRefresh: _performDataUpload,
  250 + // ));
  251 + }
  252 + return;
  253 + }
  254 + if (result.status == 1) {
  255 + _performDataUpload();
  256 + }
  257 + } catch (e) {}
  258 + }
219 } 259 }
220 } 260 }
221 261
222 Future<void> requestNotificationAuthorization() async { 262 Future<void> requestNotificationAuthorization() async {
223 try { 263 try {
224 - final result = await Permission.notification.request();  
225 - if (result.isGranted) {  
226 - debugPrint('Notification authorization granted');  
227 - } else {  
228 - debugPrint('Notification authorization skipped'); 264 + final status = await Permission.notification.status;
  265 +
  266 + if (status.isGranted) {
  267 + // 已授权,无需再请求
  268 + return;
  269 + }
  270 +
  271 + if (status.isPermanentlyDenied) {
  272 + // 用户永久拒绝(Android),引导去设置
  273 + await openAppSettings();
  274 + return;
229 } 275 }
  276 +
  277 + if (status.isDenied && GetPlatform.isIOS) {
  278 + // iOS 上被拒绝后无法再弹窗,引导去设置
  279 + await openAppSettings();
  280 + return;
  281 + }
  282 +
  283 + // 未决定 → 弹出系统授权弹窗
  284 + final result = await Permission.notification.request();
  285 + AppLogger.d(result.isGranted
  286 + ? 'Notification authorization granted'
  287 + : 'Notification authorization skipped');
230 } catch (error) { 288 } catch (error) {
231 - debugPrint('Notification authorization skipped: $error'); 289 + AppLogger.e('Notification authorization failed: $error');
232 } 290 }
233 } 291 }
234 } 292 }
@@ -83,6 +83,7 @@ abstract final class AppPages { @@ -83,6 +83,7 @@ abstract final class AppPages {
83 GetPage( 83 GetPage(
84 name: AppRoutes.login, 84 name: AppRoutes.login,
85 page: () => const LoginView(), 85 page: () => const LoginView(),
  86 + transition: Transition.fade,
86 binding: LoginBinding(), 87 binding: LoginBinding(),
87 ), 88 ),
88 GetPage( 89 GetPage(
@@ -98,6 +99,7 @@ abstract final class AppPages { @@ -98,6 +99,7 @@ abstract final class AppPages {
98 GetPage( 99 GetPage(
99 name: AppRoutes.home, 100 name: AppRoutes.home,
100 page: () => const HomePage(), 101 page: () => const HomePage(),
  102 + transition: Transition.fade,
101 binding: HomeBinding(), 103 binding: HomeBinding(),
102 ), 104 ),
103 GetPage( 105 GetPage(
@@ -22,6 +22,23 @@ class FriendApi { @@ -22,6 +22,23 @@ class FriendApi {
22 ); 22 );
23 } 23 }
24 24
  25 + Future<AppResult<FriendItem>> getFriendInfo(
  26 + int userId,
  27 + ) {
  28 + return safeCall(
  29 + call: () async {
  30 + final response = await _dioClient.dio.get(
  31 + ApiPaths.friendInfo,
  32 + queryParameters: {
  33 + 'friend_user_id': userId,
  34 + },
  35 + );
  36 + return FriendItem.fromJson(response.data as Map<String, dynamic>);
  37 + },
  38 + errorHandlingPolicy: HttpErrorHandlingPolicy.defaultPolicy,
  39 + );
  40 + }
  41 +
25 Future<AppResult<FriendItem>> addFriendHandleError( 42 Future<AppResult<FriendItem>> addFriendHandleError(
26 String uniqueCode, HttpErrorHandlingPolicy policy) { 43 String uniqueCode, HttpErrorHandlingPolicy policy) {
27 return safeCall( 44 return safeCall(
  1 +import 'package:doublefeel_flutter/data/models/friend/friend_models.dart';
1 import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; 2 import 'package:doublefeel_flutter/pigeon/platform_api.g.dart';
2 3
3 import '../../constants/app_const.dart'; 4 import '../../constants/app_const.dart';
@@ -71,4 +71,5 @@ abstract final class ApiPaths { @@ -71,4 +71,5 @@ abstract final class ApiPaths {
71 71
72 // friends 72 // friends
73 static const friends = '/client/doublefeel/health/v2/friends/'; 73 static const friends = '/client/doublefeel/health/v2/friends/';
  74 + static const friendInfo = '/client/doublefeel/health/v2/friend_info/';
74 } 75 }