Commit 1e225e34da6a3a0f188c699a6c5ec20fe605802e

Authored by 常守达
1 parent 99691eb0

fix(ui): 下发会员优惠

@@ -5,6 +5,7 @@ import 'package:get/get.dart'; @@ -5,6 +5,7 @@ import 'package:get/get.dart';
5 import '../../core/config/app_environment_config.dart'; 5 import '../../core/config/app_environment_config.dart';
6 import '../../core/error/app_error_handler.dart'; 6 import '../../core/error/app_error_handler.dart';
7 import '../../core/network/api/config_api.dart'; 7 import '../../core/network/api/config_api.dart';
  8 +import '../../core/services/app_config_service.dart';
8 import '../../core/network/api/health_api.dart'; 9 import '../../core/network/api/health_api.dart';
9 import '../../core/network/api/interaction_api.dart'; 10 import '../../core/network/api/interaction_api.dart';
10 import '../../core/network/api/obs_api.dart'; 11 import '../../core/network/api/obs_api.dart';
@@ -89,9 +90,10 @@ void registerPayDeps(DioClient dioClient) { @@ -89,9 +90,10 @@ void registerPayDeps(DioClient dioClient) {
89 Get.lazyPut(() => PayApi(dioClient), fenix: true); 90 Get.lazyPut(() => PayApi(dioClient), fenix: true);
90 } 91 }
91 92
92 -/// Remote config API. 93 +/// Remote config API + global AppConfig service.
93 void registerConfigDeps(DioClient dioClient) { 94 void registerConfigDeps(DioClient dioClient) {
94 Get.lazyPut(() => ConfigApi(dioClient), fenix: true); 95 Get.lazyPut(() => ConfigApi(dioClient), fenix: true);
  96 + Get.put(AppConfigService(Get.find<ConfigApi>()), permanent: true);
95 } 97 }
96 98
97 /// Watch theme API. 99 /// Watch theme API.
  1 +import 'dart:async';
  2 +
1 import 'package:flutter/widgets.dart'; 3 import 'package:flutter/widgets.dart';
2 import 'package:get/get.dart'; 4 import 'package:get/get.dart';
3 5
@@ -5,6 +7,7 @@ import '../../core/config/app_environment_config.dart'; @@ -5,6 +7,7 @@ import '../../core/config/app_environment_config.dart';
5 import '../../core/logging/app_logger.dart'; 7 import '../../core/logging/app_logger.dart';
6 import '../../core/network/user_agent_provider.dart'; 8 import '../../core/network/user_agent_provider.dart';
7 import '../../core/services/user_state_service.dart'; 9 import '../../core/services/user_state_service.dart';
  10 +import '../../core/services/app_config_service.dart';
8 import '../../data/local/local_storage.dart'; 11 import '../../data/local/local_storage.dart';
9 import '../../data/local/user_account_storage.dart'; 12 import '../../data/local/user_account_storage.dart';
10 import '../../data/local/user_preferences_storage.dart'; 13 import '../../data/local/user_preferences_storage.dart';
@@ -41,6 +44,7 @@ abstract final class AppBootstrap { @@ -41,6 +44,7 @@ abstract final class AppBootstrap {
41 44
42 if (local.termsAgreed) { 45 if (local.termsAgreed) {
43 await Get.find<UserStateService>().initializeSdks(); 46 await Get.find<UserStateService>().initializeSdks();
  47 + unawaited(AppConfigService.to.fetch());
44 } 48 }
45 } 49 }
46 } 50 }
@@ -4,6 +4,7 @@ import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measuremen @@ -4,6 +4,7 @@ import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measuremen
4 import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart'; 4 import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart';
5 import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_bottom_slogan.dart'; 5 import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_bottom_slogan.dart';
6 import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_date_picker_sheet.dart'; 6 import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_date_picker_sheet.dart';
  7 +import 'package:doublefeel_flutter/core/services/app_config_service.dart';
7 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; 8 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
8 9
9 import 'package:doublefeel_flutter/core/services/user_state_service.dart'; 10 import 'package:doublefeel_flutter/core/services/user_state_service.dart';
@@ -214,7 +215,8 @@ class TodayTabBody extends StatelessWidget { @@ -214,7 +215,8 @@ class TodayTabBody extends StatelessWidget {
214 : const SizedBox.shrink(), 215 : const SizedBox.shrink(),
215 ), 216 ),
216 Obx(() { 217 Obx(() {
217 - return Get.find<UserStateService>().isVip 218 + return Get.find<UserStateService>().isVip ||
  219 + !AppConfigService.to.config.showYearVipDiscount
218 ? const SizedBox.shrink() 220 ? const SizedBox.shrink()
219 : GestureDetector( 221 : GestureDetector(
220 onTap: () { 222 onTap: () {
@@ -744,7 +746,8 @@ class _TopDateBar extends StatelessWidget { @@ -744,7 +746,8 @@ class _TopDateBar extends StatelessWidget {
744 ], 746 ],
745 const Spacer(), 747 const Spacer(),
746 Obx(() { 748 Obx(() {
747 - return Get.find<UserStateService>().isVip 749 + return Get.find<UserStateService>().isVip ||
  750 + !AppConfigService.to.config.showYearVipDiscount
748 ? const SizedBox.shrink() 751 ? const SizedBox.shrink()
749 : GestureDetector( 752 : GestureDetector(
750 onTap: () { 753 onTap: () {
@@ -3,6 +3,8 @@ import 'dart:convert'; @@ -3,6 +3,8 @@ import 'dart:convert';
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/constants/app_const.dart'; 4 import 'package:doublefeel_flutter/core/constants/app_const.dart';
5 import 'package:doublefeel_flutter/core/logging/app_logger.dart'; 5 import 'package:doublefeel_flutter/core/logging/app_logger.dart';
  6 +import 'package:doublefeel_flutter/core/network/api/config_api.dart';
  7 +import 'package:doublefeel_flutter/core/services/app_config_service.dart';
6 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; 8 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
7 import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; 9 import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
8 import 'package:doublefeel_flutter/l10n/gen/app_localizations.dart'; 10 import 'package:doublefeel_flutter/l10n/gen/app_localizations.dart';
@@ -81,6 +83,7 @@ class LoginController extends GetxController { @@ -81,6 +83,7 @@ class LoginController extends GetxController {
81 ta.track('enter_doublefeel_welcome_page'); 83 ta.track('enter_doublefeel_welcome_page');
82 needTrackWelcomepage = false; 84 needTrackWelcomepage = false;
83 } 85 }
  86 + unawaited(AppConfigService.to.fetch());
84 } catch (e) { 87 } catch (e) {
85 AppLogger.e('initializeSdks error: $e'); 88 AppLogger.e('initializeSdks error: $e');
86 } 89 }
@@ -6,6 +6,7 @@ import 'package:doublefeel_flutter/core/constants/intent_keys.dart'; @@ -6,6 +6,7 @@ import 'package:doublefeel_flutter/core/constants/intent_keys.dart';
6 import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; 6 import 'package:doublefeel_flutter/core/network/api/pay_api.dart';
7 import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; 7 import 'package:doublefeel_flutter/core/network/api/vip_api.dart';
8 import 'package:doublefeel_flutter/core/result/app_result.dart'; 8 import 'package:doublefeel_flutter/core/result/app_result.dart';
  9 +import 'package:doublefeel_flutter/core/services/app_config_service.dart';
9 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; 10 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
10 import 'package:doublefeel_flutter/core/util/app_toast.dart'; 11 import 'package:doublefeel_flutter/core/util/app_toast.dart';
11 import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; 12 import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
@@ -91,7 +92,8 @@ class PurchaseController extends GetxController { @@ -91,7 +92,8 @@ class PurchaseController extends GetxController {
91 92
92 void onBackPressed() { 93 void onBackPressed() {
93 if (isFromOnboard) { 94 if (isFromOnboard) {
94 - if (Get.arguments?['is_membership_offer'] == true) { 95 + if (Get.arguments?['is_membership_offer'] == true ||
  96 + !AppConfigService.to.config.showYearVipDiscount) {
95 Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true}); 97 Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
96 } else { 98 } else {
97 Get.offNamed( 99 Get.offNamed(
@@ -22,4 +22,18 @@ class ConfigApi { @@ -22,4 +22,18 @@ class ConfigApi {
22 }, 22 },
23 ); 23 );
24 } 24 }
  25 +
  26 + Future<AppResult<AppConfig>> getAppConfigList() {
  27 + return safeCall(
  28 + call: () async {
  29 + final response = await _dioClient.dio.get(
  30 + ApiPaths.appConfig,
  31 + options: _dioClient.noTokenOptions(),
  32 + );
  33 + return AppConfig.fromJson(
  34 + response.data as Map<String, dynamic>,
  35 + );
  36 + },
  37 + );
  38 + }
25 } 39 }
@@ -58,6 +58,7 @@ abstract final class ApiPaths { @@ -58,6 +58,7 @@ abstract final class ApiPaths {
58 58
59 // Config 59 // Config
60 static const dynamicConfigList = '/client/doublefeel/dynamic_config/list/'; 60 static const dynamicConfigList = '/client/doublefeel/dynamic_config/list/';
  61 + static const appConfig = '/client/doublefeel/app_config/';
61 static const contentCheckText = '/client/doublefeel/content_check/text/'; 62 static const contentCheckText = '/client/doublefeel/content_check/text/';
62 63
63 // OBS 64 // OBS
  1 +import 'package:doublefeel_flutter/core/result/app_result.dart';
  2 +import 'package:get/get.dart';
  3 +
  4 +import '../logging/app_logger.dart';
  5 +import '../network/api/config_api.dart';
  6 +import '../../data/models/config/config_models.dart';
  7 +
  8 +/// Globally accessible service that fetches and caches [AppConfig] from the
  9 +/// server. Registered with [permanent: true] so GetX never disposes it.
  10 +///
  11 +/// Usage (anywhere in the app):
  12 +/// ```dart
  13 +/// // One-off read
  14 +/// final show = AppConfigService.to.config.showYearVipDiscount;
  15 +///
  16 +/// // Reactive (inside Obx / ever)
  17 +/// Obx(() => AppConfigService.to.config.showYearVipDiscount ? ... : ...)
  18 +/// ```
  19 +class AppConfigService extends GetxService {
  20 + AppConfigService(this._configApi);
  21 +
  22 + static AppConfigService get to => Get.find();
  23 +
  24 + final ConfigApi _configApi;
  25 + final Rx<AppConfig> _config = const AppConfig().obs;
  26 +
  27 + /// Latest remote [AppConfig]. Defaults to [AppConfig()] until [fetch] succeeds.
  28 + AppConfig get config => _config.value;
  29 +
  30 + /// Reactive [AppConfig] stream — use inside `Obx` / `ever`.
  31 + Rx<AppConfig> get configRx => _config;
  32 +
  33 + /// Fetches [AppConfig] from the server and updates [config].
  34 + /// Safe to call multiple times; failures are logged and ignored.
  35 + Future<void> fetch() async {
  36 + final res = await _configApi.getAppConfigList();
  37 + switch (res) {
  38 + case AppSuccess(:final data):
  39 + {
  40 + _config.value = data;
  41 + AppLogger.i('AppConfigService: config fetched — $data');
  42 + }
  43 + break;
  44 + case AppFailure():
  45 + {
  46 + AppLogger.w('AppConfigService: fetch failed', res.error);
  47 + }
  48 + break;
  49 + }
  50 + }
  51 +}
@@ -51,3 +51,17 @@ class ConfigData { @@ -51,3 +51,17 @@ class ConfigData {
51 return val; 51 return val;
52 } 52 }
53 } 53 }
  54 +
  55 +class AppConfig {
  56 + const AppConfig({
  57 + this.showYearVipDiscount = true,
  58 + });
  59 +
  60 + final bool showYearVipDiscount;
  61 +
  62 + factory AppConfig.fromJson(Map<String, dynamic> json) {
  63 + return AppConfig(
  64 + showYearVipDiscount: json['show_year_vip_discount'] as bool,
  65 + );
  66 + }
  67 +}