فهرست منبع

[new]增加会员试用功能

zk 10 ماه پیش
والد
کامیت
c192374644

+ 16 - 0
assets/string/base/string.xml

@@ -237,4 +237,20 @@
     <string name="logout_account">注销账号</string>
     <string name="account_logout_success">注销成功</string>
     <string name="record_number">备案号:皖ICP备2023011908号-2A</string>
+    <string name="permission_setting">快速设置</string>
+    <string name="permission_location_setting">定位权限开启</string>
+    <string name="permission_location_setting_subtitle">
+        定位权限需要设置为本应用选择“始终允许”,才可以正常查看轨迹。
+    </string>
+    <string name="permission_battery_optimization">电池优化白名单</string>
+    <string name="permission_battery_optimization_subtitle">
+        系统可能会为了省电,关闭后台的应用,避免出现轨迹异常,需要将本应用加入保护名单。
+    </string>
+    <string name="permission_background_operation">后台运行权限</string>
+    <string name="permission_background_operation_subtitle">
+        将本应用加入后台权限名单,可以一定程度保护app在后台正常运行。
+    </string>
+    <string name="permission_setting_success">设置成功</string>
+    <string name="member_free_code_error_toast">每位用户只能领取一次试用</string>
+    <string name="member_free_code_is_member">您已经是会员了</string>
 </resources>

+ 3 - 0
lib/base/base_response.dart

@@ -4,7 +4,10 @@ part 'base_response.g.dart';
 
 @JsonSerializable(genericArgumentFactories: true)
 class BaseResponse<T> {
+  @JsonKey(name: 'code')
   int? code;
+
+  @JsonKey(name: 'msg')
   String? message;
   T? data;
 

+ 2 - 2
lib/base/base_response.g.dart

@@ -13,7 +13,7 @@ BaseResponse<T> _$BaseResponseFromJson<T>(
     BaseResponse<T>(
       _$nullableGenericFromJson(json['data'], fromJsonT),
       (json['code'] as num?)?.toInt(),
-      json['message'] as String?,
+      json['msg'] as String?,
     );
 
 Map<String, dynamic> _$BaseResponseToJson<T>(
@@ -22,7 +22,7 @@ Map<String, dynamic> _$BaseResponseToJson<T>(
 ) =>
     <String, dynamic>{
       'code': instance.code,
-      'message': instance.message,
+      'msg': instance.message,
       'data': _$nullableGenericToJson(instance.data, toJsonT),
     };
 

+ 5 - 0
lib/data/api/atmob_api.dart

@@ -18,6 +18,7 @@ import 'package:location/data/api/response/contact_may_day_all_response.dart';
 import 'package:location/data/api/response/friends_list_response.dart';
 import 'package:location/data/api/response/login_response.dart';
 import 'package:location/data/api/response/member_status_response.dart';
+import 'package:location/data/api/response/member_trial_response.dart';
 import 'package:location/data/api/response/message_response.dart';
 import 'package:location/data/api/response/query_track_response.dart';
 import 'package:location/data/api/response/request_friend_list_response.dart';
@@ -134,4 +135,8 @@ abstract class AtmobApi {
 
   @POST("/s/v1/user/clear")
   Future<BaseResponse> userClear(@Body() AppBaseRequest request);
+
+  @POST("/s/v1/member/trial")
+  Future<BaseResponse<MemberTrialResponse>> memberTrial(
+      @Body() AppBaseRequest request);
 }

+ 38 - 0
lib/data/api/atmob_api.g.dart

@@ -1054,6 +1054,44 @@ class _AtmobApi implements AtmobApi {
     return _value;
   }
 
+  @override
+  Future<BaseResponse<MemberTrialResponse>> memberTrial(
+      AppBaseRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<MemberTrialResponse>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/s/v1/member/trial',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<MemberTrialResponse> _value;
+    try {
+      _value = BaseResponse<MemberTrialResponse>.fromJson(
+        _result.data!,
+        (json) => MemberTrialResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
   RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
     if (T != dynamic &&
         !(requestOptions.responseType == ResponseType.bytes ||

+ 14 - 0
lib/data/api/response/member_trial_response.dart

@@ -0,0 +1,14 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'member_trial_response.g.dart';
+
+@JsonSerializable()
+class MemberTrialResponse {
+  @JsonKey(name: 'freeMemberMinutes')
+  int freeMemberMinutes;
+
+  MemberTrialResponse({required this.freeMemberMinutes});
+
+  factory MemberTrialResponse.fromJson(Map<String, dynamic> json) =>
+      _$MemberTrialResponseFromJson(json);
+}

+ 18 - 0
lib/data/api/response/member_trial_response.g.dart

@@ -0,0 +1,18 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'member_trial_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+MemberTrialResponse _$MemberTrialResponseFromJson(Map<String, dynamic> json) =>
+    MemberTrialResponse(
+      freeMemberMinutes: (json['freeMemberMinutes'] as num).toInt(),
+    );
+
+Map<String, dynamic> _$MemberTrialResponseToJson(
+        MemberTrialResponse instance) =>
+    <String, dynamic>{
+      'freeMemberMinutes': instance.freeMemberMinutes,
+    };

+ 2 - 2
lib/data/consts/error_code.dart

@@ -18,8 +18,8 @@ class ErrorCode {
   static const int smsSendFailed = 1202; //短信发送失败,请核实手机号码
 
   /// 会员服务相关错误码
-  static const int getMemberFree = 1300;
-  static const int isMember = 1301;
+  static const int getMemberFree = 1300; //每位用户只能领取一次试用
+  static const int isMember = 1301; //您已经是会员了
 }
 
 /// 错误码扩展方法

+ 0 - 1
lib/data/repositories/config_repository.dart

@@ -5,7 +5,6 @@ import 'package:location/data/api/request/configs_request.dart';
 import 'package:location/data/bean/config_bean.dart';
 import 'package:location/data/repositories/friends_repository.dart';
 import 'package:location/utils/async_util.dart';
-import 'package:location/utils/atmob_log.dart';
 import 'package:location/utils/http_handler.dart';
 
 import '../api/response/configs_response.dart';

+ 18 - 0
lib/data/repositories/member_repository.dart

@@ -0,0 +1,18 @@
+import 'package:injectable/injectable.dart';
+import 'package:location/base/app_base_request.dart';
+import 'package:location/data/api/atmob_api.dart';
+import 'package:location/utils/http_handler.dart';
+
+@lazySingleton
+class MemberRepository {
+  final AtmobApi atmobApi;
+
+  MemberRepository(this.atmobApi);
+
+  Future<int> memberTrial() {
+    return atmobApi
+        .memberTrial(AppBaseRequest())
+        .then(HttpHandler.handle(false))
+        .then((response) => response.freeMemberMinutes);
+  }
+}

+ 136 - 0
lib/di/get_it.config.dart

@@ -0,0 +1,136 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// InjectableConfigGenerator
+// **************************************************************************
+
+// ignore_for_file: type=lint
+// coverage:ignore-file
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:dio/dio.dart' as _i361;
+import 'package:get_it/get_it.dart' as _i174;
+import 'package:injectable/injectable.dart' as _i526;
+
+import '../data/api/atmob_api.dart' as _i243;
+import '../data/repositories/account_repository.dart' as _i20;
+import '../data/repositories/config_repository.dart' as _i825;
+import '../data/repositories/contact_repository.dart' as _i850;
+import '../data/repositories/friends_repository.dart' as _i1053;
+import '../data/repositories/member_repository.dart' as _i814;
+import '../data/repositories/message_repository.dart' as _i791;
+import '../data/repositories/track_repository.dart' as _i240;
+import '../data/repositories/urgent_contact_repository.dart' as _i983;
+import '../module/about/about_controller.dart' as _i256;
+import '../module/add_friend/add_friend_dialog_controller.dart' as _i897;
+import '../module/browser/browser_controller.dart' as _i923;
+import '../module/feedback/feed_back_controller.dart' as _i769;
+import '../module/friend/friend_controller.dart' as _i821;
+import '../module/friend/setting/friend_setting_controller.dart' as _i492;
+import '../module/login/login_controller.dart' as _i1008;
+import '../module/main/main_controller.dart' as _i731;
+import '../module/member/member_controller.dart' as _i269;
+import '../module/mine/mine_controller.dart' as _i732;
+import '../module/news/news_controller.dart' as _i489;
+import '../module/news/pending_list/news_pending_list_controller.dart' as _i433;
+import '../module/permission/permission_setting_controller.dart' as _i108;
+import '../module/splash/splash_controller.dart' as _i973;
+import '../module/track/track_controller.dart' as _i518;
+import '../module/urgent_contact/add_contact/add_urgent_contact_controller.dart'
+    as _i955;
+import '../module/urgent_contact/urgent_contact_controller.dart' as _i720;
+import '../socket/atmob_location_client.dart' as _i220;
+import 'network_module.dart' as _i567;
+
+extension GetItInjectableX on _i174.GetIt {
+// initializes the registration of main-scope dependencies inside of GetIt
+  _i174.GetIt init({
+    String? environment,
+    _i526.EnvironmentFilter? environmentFilter,
+  }) {
+    final gh = _i526.GetItHelper(
+      this,
+      environment,
+      environmentFilter,
+    );
+    final networkModule = _$NetworkModule();
+    gh.factory<_i923.BrowserController>(() => _i923.BrowserController());
+    gh.factory<_i769.FeedBackController>(() => _i769.FeedBackController());
+    gh.factory<_i269.MemberController>(() => _i269.MemberController());
+    gh.factory<_i973.SplashController>(() => _i973.SplashController());
+    gh.factory<_i256.AboutController>(() => _i256.AboutController());
+    gh.factory<_i108.PermissionSettingController>(
+        () => _i108.PermissionSettingController());
+    gh.singleton<_i361.Dio>(() => networkModule.createDefaultDio());
+    gh.lazySingleton<_i220.AtmobLocationClient>(
+        () => _i220.AtmobLocationClient());
+    gh.singleton<_i243.AtmobApi>(
+        () => networkModule.provideAtmobApi(gh<_i361.Dio>()));
+    gh.lazySingleton<_i20.AccountRepository>(
+        () => _i20.AccountRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i850.ContactRepository>(
+        () => _i850.ContactRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i1053.FriendsRepository>(
+        () => _i1053.FriendsRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i791.MessageRepository>(
+        () => _i791.MessageRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i240.TrackRepository>(
+        () => _i240.TrackRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i983.UrgentContactRepository>(
+        () => _i983.UrgentContactRepository(gh<_i243.AtmobApi>()));
+    gh.lazySingleton<_i814.MemberRepository>(
+        () => _i814.MemberRepository(gh<_i243.AtmobApi>()));
+    gh.factory<_i1008.LoginController>(
+        () => _i1008.LoginController(gh<_i20.AccountRepository>()));
+    gh.factory<_i489.NewsController>(
+        () => _i489.NewsController(gh<_i791.MessageRepository>()));
+    gh.lazySingleton<_i825.ConfigRepository>(() => _i825.ConfigRepository(
+          gh<_i243.AtmobApi>(),
+          gh<_i1053.FriendsRepository>(),
+        ));
+    gh.factory<_i821.FriendController>(() => _i821.FriendController(
+          gh<_i20.AccountRepository>(),
+          gh<_i1053.FriendsRepository>(),
+        ));
+    gh.factory<_i518.TrackController>(() => _i518.TrackController(
+          gh<_i240.TrackRepository>(),
+          gh<_i1053.FriendsRepository>(),
+          gh<_i20.AccountRepository>(),
+        ));
+    gh.factory<_i433.NewsPendingListController>(
+        () => _i433.NewsPendingListController(
+              gh<_i791.MessageRepository>(),
+              gh<_i1053.FriendsRepository>(),
+            ));
+    gh.factory<_i731.MainController>(() => _i731.MainController(
+          gh<_i1053.FriendsRepository>(),
+          gh<_i20.AccountRepository>(),
+          gh<_i791.MessageRepository>(),
+          gh<_i220.AtmobLocationClient>(),
+          gh<_i983.UrgentContactRepository>(),
+          gh<_i825.ConfigRepository>(),
+        ));
+    gh.factory<_i955.AddUrgentContactController>(
+        () => _i955.AddUrgentContactController(
+              gh<_i983.UrgentContactRepository>(),
+              gh<_i20.AccountRepository>(),
+            ));
+    gh.factory<_i720.UrgentContactController>(
+        () => _i720.UrgentContactController(
+              gh<_i983.UrgentContactRepository>(),
+              gh<_i20.AccountRepository>(),
+            ));
+    gh.factory<_i897.AddFriendDialogController>(
+        () => _i897.AddFriendDialogController(gh<_i1053.FriendsRepository>()));
+    gh.factory<_i492.FriendSettingController>(
+        () => _i492.FriendSettingController(gh<_i1053.FriendsRepository>()));
+    gh.factory<_i732.MineController>(() => _i732.MineController(
+          gh<_i20.AccountRepository>(),
+          gh<_i825.ConfigRepository>(),
+          gh<_i814.MemberRepository>(),
+        ));
+    return this;
+  }
+}
+
+class _$NetworkModule extends _i567.NetworkModule {}

+ 34 - 3
lib/module/mine/mine_controller.dart

@@ -3,27 +3,36 @@ import 'package:get/get_core/src/get_main.dart';
 import 'package:injectable/injectable.dart';
 import 'package:location/base/base_controller.dart';
 import 'package:location/data/bean/member_status_info.dart';
+import 'package:location/data/consts/error_code.dart';
+import 'package:location/data/repositories/config_repository.dart';
 import 'package:location/handler/error_handler.dart';
 import 'package:location/module/feedback/feed_back_page.dart';
 import 'package:location/module/login/login_page.dart';
 import 'package:location/module/urgent_contact/urgent_contact_page.dart';
 import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/http_handler.dart';
 import '../../data/repositories/account_repository.dart';
+import '../../data/repositories/member_repository.dart';
 import '../../dialog/common_alert_dialog_impl.dart';
-import '../../dialog/common_confirm_dialog_impl.dart';
 import '../../sdk/qiyu/qi_yu_helper.dart';
 import '../../sdk/wechat/wechat_share_util.dart';
 import '../../utils/toast_util.dart';
 import '../about/about_page.dart';
+import '../permission/permission_setting_page.dart';
 
 @injectable
 class MineController extends BaseController {
   final AccountRepository accountRepository;
+  final ConfigRepository configRepository;
+  final MemberRepository memberRepository;
 
-  MineController(this.accountRepository);
+  MineController(
+      this.accountRepository, this.configRepository, this.memberRepository);
 
   bool get isLogin => accountRepository.isLogin.value;
 
+  bool? get isOpenFreeMember => configRepository.isOpenFreeMember.value;
+
   MemberStatusInfo? get memberStatusInfo =>
       accountRepository.memberStatusInfo.value;
 
@@ -50,7 +59,9 @@ class MineController extends BaseController {
     QiYuHelper.openCustomService();
   }
 
-  onPermissionSettingClick() {}
+  onPermissionSettingClick() {
+    PermissionSettingPage.start();
+  }
 
   onAccountFeedbackClick() {
     FeedBackPage.start();
@@ -89,4 +100,24 @@ class MineController extends BaseController {
   onUrgentContactClick() {
     UrgentContactPage.start();
   }
+
+  void onMemberTryOutClick() {
+    memberRepository.memberTrial().then((freeMemberMinutes) {
+      ToastUtil.show('已获得$freeMemberMinutes分钟会员试用');
+    }).catchError((error) {
+      if (error is ServerErrorException) {
+        if (error.code == ErrorCode.getMemberFree) {
+          ToastUtil.show(StringName.memberFreeCodeErrorToast);
+        } else if (error.code == ErrorCode.isMember) {
+          ToastUtil.show(StringName.memberFreeCodeIsmember);
+        } else if (error.code == ErrorCode.noLoginError) {
+          ToastUtil.show(StringName.accountNoLogin);
+        } else {
+          ToastUtil.show(error.message);
+        }
+      } else {
+        ErrorHandler.toastError(error);
+      }
+    });
+  }
 }

+ 58 - 44
lib/module/mine/mine_page.dart

@@ -53,7 +53,13 @@ class MinePage extends BasePage<MineController> {
                   SizedBox(width: 10.w),
                   buildLoginInfo(),
                   Spacer(),
-                  buildMemberTryOutView()
+                  Obx(() {
+                    return Visibility(
+                        visible: controller.isOpenFreeMember == true,
+                        child: GestureDetector(
+                            onTap: controller.onMemberTryOutClick,
+                            child: buildMemberTryOutView()));
+                  })
                 ],
               ),
               SizedBox(height: 20.w),
@@ -96,51 +102,59 @@ class MinePage extends BasePage<MineController> {
           children: [
             AspectRatio(
                 aspectRatio: 332 / 57, child: SizedBox(width: double.infinity)),
-            Stack(
-              children: [
-                Container(
-                  margin: EdgeInsets.symmetric(horizontal: 14.w),
-                  width: double.infinity,
-                  height: 50.w,
-                  decoration: BoxDecoration(
-                    borderRadius: BorderRadius.only(
-                        bottomLeft: Radius.circular(8.w),
-                        bottomRight: Radius.circular(8.w)),
-                    gradient: LinearGradient(
-                        begin: Alignment.centerLeft,
-                        end: Alignment.centerRight,
-                        colors: ['#FFF8DA'.color, '#FFF1BA'.color]),
+            Obx(() {
+              return Visibility(
+                visible: controller.isOpenFreeMember == true,
+                child: GestureDetector(
+                  onTap: controller.onMemberTryOutClick,
+                  child: Stack(
+                    children: [
+                      Container(
+                        margin: EdgeInsets.symmetric(horizontal: 14.w),
+                        width: double.infinity,
+                        height: 50.w,
+                        decoration: BoxDecoration(
+                          borderRadius: BorderRadius.only(
+                              bottomLeft: Radius.circular(8.w),
+                              bottomRight: Radius.circular(8.w)),
+                          gradient: LinearGradient(
+                              begin: Alignment.centerLeft,
+                              end: Alignment.centerRight,
+                              colors: ['#FFF8DA'.color, '#FFF1BA'.color]),
+                        ),
+                      ),
+                      Positioned(
+                        bottom: 0,
+                        left: 0,
+                        right: 0,
+                        child: Container(
+                          margin: EdgeInsets.symmetric(horizontal: 14.w),
+                          height: 32.w,
+                          child: Row(
+                            children: [
+                              SizedBox(width: 15.w),
+                              Assets.images.iconExperiment
+                                  .image(width: 16.w, height: 16.w),
+                              SizedBox(width: 4.w),
+                              Text(StringName.memberExperienceVip,
+                                  style: TextStyle(
+                                      fontSize: 13.sp, color: '#8A5F03'.color)),
+                              Spacer(),
+                              Text(StringName.memberExperienceVipReceive,
+                                  style: TextStyle(
+                                      fontSize: 13.sp, color: '#8A5F03'.color)),
+                              Assets.images.iconMemberVipReceiveArrow
+                                  .image(width: 16.w, height: 16.w),
+                              SizedBox(width: 13.w),
+                            ],
+                          ),
+                        ),
+                      )
+                    ],
                   ),
                 ),
-                Positioned(
-                  bottom: 0,
-                  left: 0,
-                  right: 0,
-                  child: Container(
-                    margin: EdgeInsets.symmetric(horizontal: 14.w),
-                    height: 32.w,
-                    child: Row(
-                      children: [
-                        SizedBox(width: 15.w),
-                        Assets.images.iconExperiment
-                            .image(width: 16.w, height: 16.w),
-                        SizedBox(width: 4.w),
-                        Text(StringName.memberExperienceVip,
-                            style: TextStyle(
-                                fontSize: 13.sp, color: '#8A5F03'.color)),
-                        Spacer(),
-                        Text(StringName.memberExperienceVipReceive,
-                            style: TextStyle(
-                                fontSize: 13.sp, color: '#8A5F03'.color)),
-                        Assets.images.iconMemberVipReceiveArrow
-                            .image(width: 16.w, height: 16.w),
-                        SizedBox(width: 13.w),
-                      ],
-                    ),
-                  ),
-                )
-              ],
-            )
+              );
+            })
           ],
         ),
         buildMemberCard()

+ 60 - 0
lib/module/permission/permission_setting_controller.dart

@@ -0,0 +1,60 @@
+import 'dart:io';
+
+import 'package:get/get.dart';
+import 'package:injectable/injectable.dart';
+import 'package:location/base/base_controller.dart';
+import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/app_info_util.dart';
+import 'package:location/utils/toast_util.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+import '../../utils/permission_util.dart';
+
+@injectable
+class PermissionSettingController extends BaseController {
+  final RxBool _permissionShowBattery = false.obs;
+  final RxBool _permissionShowBackgroundRun = false.obs;
+  final RxBool _permissionShowTip = false.obs;
+
+  bool get permissionShowBattery => _permissionShowBattery.value;
+
+  bool get permissionShowBackgroundRun => _permissionShowBackgroundRun.value;
+
+  bool get permissionShowTip => _permissionShowTip.value;
+
+  @override
+  void onReady() {
+    super.onReady();
+    if (Platform.isAndroid) {
+      _permissionShowBattery.value = true;
+      _permissionShowBackgroundRun.value = true;
+      _permissionShowTip.value = true;
+    }
+  }
+
+  void openLocationSetting() async {
+    bool isGranted = await PermissionUtil.checkLocationPermission();
+    if (!isGranted) {
+      isGranted = await PermissionUtil.requestLocationPermission();
+    }
+    if (isGranted) {
+      isGranted = await PermissionUtil.checkShowLocationAlways();
+      if (isGranted) {
+        ToastUtil.show(StringName.permissionSettingSuccess);
+      } else {
+        isGranted = await PermissionUtil.requestShowLocationAlways();
+        if (isGranted) {
+          ToastUtil.show(StringName.permissionSettingSuccess);
+        } else {
+          ToastUtil.show(StringName.permissionLocationSettingSubtitle);
+        }
+      }
+    } else {
+      ToastUtil.show(StringName.permissionRequestFail);
+    }
+  }
+
+  void openBatterySetting() {}
+
+  void openBackgroundRunSetting() async {}
+}

+ 136 - 0
lib/module/permission/permission_setting_page.dart

@@ -0,0 +1,136 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:get/get_core/src/get_main.dart';
+import 'package:location/base/base_page.dart';
+import 'package:location/module/permission/permission_setting_controller.dart';
+import 'package:location/resource/colors.gen.dart';
+import 'package:location/resource/string.gen.dart';
+import 'package:location/utils/common_expand.dart';
+import 'package:location/widget/common_view.dart';
+
+import '../../router/app_pages.dart';
+
+class PermissionSettingPage extends BasePage<PermissionSettingController> {
+  const PermissionSettingPage({super.key});
+
+  static void start() {
+    Get.toNamed(RoutePath.permissionSetting);
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Column(
+      children: [
+        CommonView.buildAppBar(StringName.mineFunPermissionSetting,
+            titleCenter: false),
+        SizedBox(height: 12.w),
+        Expanded(
+            child: ListView(
+          children: [
+            buildPermissionItem(StringName.permissionLocationSetting,
+                StringName.permissionLocationSettingSubtitle, () {
+              controller.openLocationSetting();
+            }),
+            Obx(() {
+              return Visibility(
+                visible: controller.permissionShowBattery,
+                child: buildPermissionItem(
+                    StringName.permissionBatteryOptimization,
+                    StringName.permissionBatteryOptimizationSubtitle, () {
+                  controller.openBatterySetting();
+                }),
+              );
+            }),
+            Obx(() {
+              return Visibility(
+                visible: controller.permissionShowBackgroundRun,
+                child: buildPermissionItem(
+                    StringName.permissionBackgroundOperation,
+                    StringName.permissionBackgroundOperationSubtitle, () {
+                  controller.openBackgroundRunSetting();
+                }),
+              );
+            }),
+            Obx(() {
+              return Visibility(
+                  visible: controller.permissionShowTip,
+                  child: buildPermissionTips());
+            })
+          ],
+        ))
+      ],
+    );
+  }
+
+  Widget buildPermissionItem(
+      String title, String subTitle, VoidCallback onTap) {
+    return Container(
+      margin: EdgeInsets.only(bottom: 12.w, left: 12.w, right: 12.w),
+      decoration: BoxDecoration(
+        color: '#FAFAFA'.color,
+        borderRadius: BorderRadius.circular(6.w),
+      ),
+      padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 22.w),
+      child: Column(
+        children: [
+          Row(
+            children: [
+              Text(
+                title,
+                style: TextStyle(
+                    fontSize: 16.sp,
+                    color: '#202020'.color,
+                    fontWeight: FontWeight.bold),
+              ),
+              Spacer(),
+              GestureDetector(
+                onTap: onTap,
+                child: Container(
+                  decoration: BoxDecoration(
+                      color: ColorName.colorPrimary,
+                      borderRadius: BorderRadius.circular(100.w)),
+                  padding:
+                      EdgeInsets.symmetric(horizontal: 15.w, vertical: 5.w),
+                  child: Text(StringName.permissionSetting,
+                      style:
+                          TextStyle(fontSize: 14.sp, color: ColorName.white)),
+                ),
+              )
+            ],
+          ),
+          SizedBox(height: 8.w),
+          Text(subTitle,
+              style: TextStyle(fontSize: 14.sp, color: '#404040'.color))
+        ],
+      ),
+    );
+  }
+
+  Widget buildPermissionTips() {
+    return Container(
+      decoration: BoxDecoration(
+        color: '#FAFAFA'.color,
+        borderRadius: BorderRadius.circular(6.w),
+      ),
+      padding: EdgeInsets.symmetric(vertical: 24.w, horizontal: 12.w),
+      margin: EdgeInsets.only(left: 12.w, right: 12.w),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          RichText(
+              text: TextSpan(
+                  style: TextStyle(fontSize: 14.sp, color: '#404040'.color),
+                  children: [
+                TextSpan(text: '温馨提示:'),
+                TextSpan(
+                    text: '请勿开启【省电模式】\n\n',
+                    style: TextStyle(fontSize: 14.sp, color: '#EC5050'.color)),
+                TextSpan(text: '在省电模式下,可能会导致轨迹异常等问题,建议您关闭省电模式')
+              ])),
+        ],
+      ),
+    );
+  }
+}

+ 21 - 2
lib/resource/string.gen.dart

@@ -194,8 +194,17 @@ class StringName {
   static final String logoutAccountContent = 'logout_account_content'.tr; // 1.删除账号所有账号信息数据和定位记录;\n2.删除并放弃账号下的会员权益;\n3.点击“确认注销”即开始注销流程不可撤回,请慎重考虑。
   static final String logoutAccount = 'logout_account'.tr; // 注销账号
   static final String accountLogoutSuccess = 'account_logout_success'.tr; // 注销成功
-  static final String recordNumber =
-      'record_number'.tr; // 备案号:皖ICP备2023011908号-2A
+  static final String recordNumber = 'record_number'.tr; // 备案号:皖ICP备2023011908号-2A
+  static final String permissionSetting = 'permission_setting'.tr; // 快速设置
+  static final String permissionLocationSetting = 'permission_location_setting'.tr; // 定位权限开启
+  static final String permissionLocationSettingSubtitle = 'permission_location_setting_subtitle'.tr; // 定位权限需要设置为本应用选择“始终允许”,才可以正常查看轨迹。
+  static final String permissionBatteryOptimization = 'permission_battery_optimization'.tr; // 电池优化白名单
+  static final String permissionBatteryOptimizationSubtitle = 'permission_battery_optimization_subtitle'.tr; // 系统可能会为了省电,关闭后台的应用,避免出现轨迹异常,需要将本应用加入保护名单。
+  static final String permissionBackgroundOperation = 'permission_background_operation'.tr; // 后台运行权限
+  static final String permissionBackgroundOperationSubtitle = 'permission_background_operation_subtitle'.tr; // 将本应用加入后台权限名单,可以一定程度保护app在后台正常运行。
+  static final String permissionSettingSuccess = 'permission_setting_success'.tr; // 设置成功
+  static final String memberFreeCodeErrorToast = 'member_free_code_error_toast'.tr; // 每位用户只能领取一次试用
+  static final String memberFreeCodeIsmember = 'member_free_code_is_member'.tr; // 您已经是会员了
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -394,6 +403,16 @@ class StringMultiSource {
       'logout_account': '注销账号',
       'account_logout_success': '注销成功',
       'record_number': '备案号:皖ICP备2023011908号-2A',
+      'permission_setting': '快速设置',
+      'permission_location_setting': '定位权限开启',
+      'permission_location_setting_subtitle': '定位权限需要设置为本应用选择“始终允许”,才可以正常查看轨迹。',
+      'permission_battery_optimization': '电池优化白名单',
+      'permission_battery_optimization_subtitle': '系统可能会为了省电,关闭后台的应用,避免出现轨迹异常,需要将本应用加入保护名单。',
+      'permission_background_operation': '后台运行权限',
+      'permission_background_operation_subtitle': '将本应用加入后台权限名单,可以一定程度保护app在后台正常运行。',
+      'permission_setting_success': '设置成功',
+      'member_free_code_error_toast': '每位用户只能领取一次试用',
+      'member_free_code_is_member': '您已经是会员了',
     },
   };
 }

+ 6 - 0
lib/router/app_pages.dart

@@ -18,6 +18,8 @@ import 'package:location/module/mine/mine_page.dart';
 import 'package:location/module/news/news_page.dart';
 import 'package:location/module/news/pending_list/news_pending_list_controller.dart';
 import 'package:location/module/news/pending_list/news_pending_list_page.dart';
+import 'package:location/module/permission/permission_setting_controller.dart';
+import 'package:location/module/permission/permission_setting_page.dart';
 import 'package:location/module/urgent_contact/add_contact/add_urgent_contact_controller.dart';
 import 'package:location/module/urgent_contact/urgent_contact_controller.dart';
 import 'package:location/module/urgent_contact/urgent_contact_page.dart';
@@ -50,6 +52,7 @@ abstract class RoutePath {
   static const urgentContact = '/urgentContact';
   static const feedback = '/feedback';
   static const about = '/about';
+  static const permissionSetting = '/permissionSetting';
 }
 
 class AppBinding extends Bindings {
@@ -70,6 +73,7 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<AddUrgentContactController>());
     lazyPut(() => getIt.get<FeedBackController>());
     lazyPut(() => getIt.get<AboutController>());
+    lazyPut(() => getIt.get<PermissionSettingController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -92,4 +96,6 @@ final generalPages = [
   GetPage(name: RoutePath.urgentContact, page: () => UrgentContactPage()),
   GetPage(name: RoutePath.feedback, page: () => FeedBackPage()),
   GetPage(name: RoutePath.about, page: () => AboutPage()),
+  GetPage(
+      name: RoutePath.permissionSetting, page: () => PermissionSettingPage()),
 ];