setting_view.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. import 'dart:io';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:flutter_screenutil/flutter_screenutil.dart';
  6. import 'package:flutter_svg/flutter_svg.dart';
  7. import 'package:get/get.dart';
  8. import 'package:nomo/app/base/base_view.dart';
  9. import 'package:nomo/app/constants/assets.dart';
  10. import 'package:nomo/app/dialog/all_dialog.dart';
  11. import 'package:nomo/app/widgets/click_opacity.dart';
  12. import 'package:nomo/app/widgets/ix_image.dart';
  13. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  14. import 'package:nomo/utils/device_manager.dart';
  15. import '../../../../config/theme/dark_theme_colors.dart';
  16. import '../../../../config/translations/localization_service.dart';
  17. import '../../../../config/translations/strings_enum.dart';
  18. import '../../../../utils/system_helper.dart';
  19. import '../../../components/ix_snackbar.dart';
  20. import '../../../constants/iconfont/iconfont.dart';
  21. import '../../../routes/app_pages.dart';
  22. import '../../../widgets/ix_app_bar.dart';
  23. import '../controllers/setting_controller.dart';
  24. class SettingView extends BaseView<SettingController> {
  25. const SettingView({super.key});
  26. @override
  27. PreferredSizeWidget? get appBar => IXAppBar(title: Strings.settings.tr);
  28. @override
  29. Widget buildContent(BuildContext context) {
  30. return CustomScrollView(
  31. slivers: [
  32. // Account Section
  33. _buildSectionHeader(Strings.account.tr),
  34. _buildLoginSection(),
  35. _buildAccountSection(),
  36. // Network Section
  37. _buildSectionHeader(Strings.networkSection.tr),
  38. _buildNetworkSection(),
  39. // APP Section
  40. _buildSectionHeader('APP'),
  41. _buildAppSection(),
  42. // Security Section
  43. _buildSectionHeader(Strings.securitySection.tr),
  44. _buildSecuritySection(),
  45. // 底部间距
  46. SliverSafeArea(sliver: SliverToBoxAdapter(child: 0.verticalSpace)),
  47. ],
  48. );
  49. }
  50. /// 构建分组标题
  51. Widget _buildSectionHeader(String title) {
  52. return SliverToBoxAdapter(
  53. child: Padding(
  54. padding: EdgeInsets.fromLTRB(14.w, 10.w, 14.w, 10.w),
  55. child: Text(
  56. title,
  57. style: TextStyle(
  58. fontSize: 16.sp,
  59. color: Get.reactiveTheme.hintColor,
  60. fontWeight: FontWeight.w500,
  61. ),
  62. ),
  63. ),
  64. );
  65. }
  66. /// 构建登录分组
  67. Widget _buildLoginSection() {
  68. return SliverToBoxAdapter(
  69. child: Container(
  70. margin: EdgeInsets.only(left: 14.w, right: 14.w, bottom: 10.w),
  71. decoration: BoxDecoration(
  72. color: Get.reactiveTheme.highlightColor,
  73. borderRadius: BorderRadius.circular(12.r),
  74. ),
  75. child: _buildSettingItem(
  76. icon: IconFont.icon37,
  77. iconColor: Get.reactiveTheme.shadowColor,
  78. title: Strings.login.tr,
  79. trailing: Icon(
  80. IconFont.icon02,
  81. size: 20.w,
  82. color: Get.reactiveTheme.hintColor,
  83. ),
  84. onTap: () {
  85. Get.toNamed(Routes.LOGIN);
  86. },
  87. ),
  88. ),
  89. );
  90. }
  91. /// Account 分组
  92. Widget _buildAccountSection() {
  93. return SliverToBoxAdapter(
  94. child: Obx(() {
  95. final isPremium = controller.isPremium.value;
  96. return Container(
  97. margin: EdgeInsets.symmetric(horizontal: 14.w),
  98. decoration: BoxDecoration(
  99. color: Get.reactiveTheme.highlightColor,
  100. borderRadius: BorderRadius.circular(12.r),
  101. ),
  102. child: Column(
  103. children: [
  104. _buildSettingItem(
  105. icon: IconFont.icon29,
  106. iconColor: Get.reactiveTheme.shadowColor,
  107. title: Strings.account.tr,
  108. trailing: IXImage(
  109. source: isPremium ? Assets.premium : Assets.free,
  110. width: isPremium ? 92.w : 64.w,
  111. height: 28.w,
  112. sourceType: ImageSourceType.asset,
  113. ),
  114. onTap: () {
  115. Get.toNamed(Routes.ACCOUNT);
  116. },
  117. ),
  118. _buildDivider(),
  119. _buildSettingItem(
  120. icon: IconFont.icon14,
  121. iconColor: Get.reactiveTheme.shadowColor,
  122. title:
  123. 'UID ${DeviceManager.getCacheDeviceId().length > 12 ? '${DeviceManager.getCacheDeviceId().substring(0, 6)}***${DeviceManager.getCacheDeviceId().substring(DeviceManager.getCacheDeviceId().length - 6)}' : DeviceManager.getCacheDeviceId()}',
  124. showInfo: true,
  125. trailing: ClickOpacity(
  126. onTap: () {
  127. Clipboard.setData(
  128. ClipboardData(text: DeviceManager.getCacheDeviceId()),
  129. );
  130. IXSnackBar.showIXSnackBar(
  131. title: Strings.copied.tr,
  132. message: Strings.copied.tr,
  133. );
  134. },
  135. child: Icon(
  136. IconFont.icon57,
  137. size: 20.w,
  138. color: Get.reactiveTheme.hintColor,
  139. ),
  140. ),
  141. onTap: () {},
  142. onInfoTap: () {
  143. AllDialog.showUidInfo();
  144. },
  145. ),
  146. _buildDivider(),
  147. // 根据用户类型显示不同的时间信息
  148. if (isPremium) ...[
  149. _buildSettingItem(
  150. icon: IconFont.icon23,
  151. iconColor: Get.reactiveTheme.shadowColor,
  152. title: Strings.myPreCode.tr,
  153. trailing: Row(
  154. mainAxisSize: MainAxisSize.min,
  155. children: [
  156. Text(
  157. '123***ADZ',
  158. style: TextStyle(
  159. fontSize: 13.sp,
  160. color: Get.reactiveTheme.hintColor,
  161. ),
  162. ),
  163. SizedBox(width: 4.w),
  164. Icon(
  165. IconFont.icon02,
  166. size: 20.w,
  167. color: Get.reactiveTheme.hintColor,
  168. ),
  169. ],
  170. ),
  171. onTap: () {
  172. // TODO: 跳转到Pre Code页面
  173. Get.toNamed(Routes.PRECODE);
  174. },
  175. ),
  176. _buildDivider(),
  177. _buildSettingItem(
  178. icon: IconFont.icon30,
  179. iconColor: Get.reactiveTheme.shadowColor,
  180. title: Strings.validTerm.tr,
  181. trailing: Text(
  182. 'Year / 2026-12-12',
  183. style: TextStyle(
  184. fontSize: 13.sp,
  185. color: Get.reactiveTheme.primaryColor,
  186. fontWeight: FontWeight.w500,
  187. ),
  188. ),
  189. onTap: () {
  190. // TODO: 跳转到有效期详情页面
  191. },
  192. ),
  193. ] else ...[
  194. _buildSettingItem(
  195. icon: IconFont.icon30,
  196. iconColor: Get.reactiveTheme.shadowColor,
  197. title: Strings.freeTime.tr,
  198. trailing: Text(
  199. '01:60:59 / Days',
  200. style: TextStyle(
  201. fontSize: 14.sp,
  202. color: const Color(0xFFFF9500),
  203. fontWeight: FontWeight.w500,
  204. ),
  205. ),
  206. ),
  207. ],
  208. _buildDivider(),
  209. _buildSettingItem(
  210. icon: IconFont.icon31,
  211. iconColor: Get.reactiveTheme.shadowColor,
  212. title: Strings.deviceAuthorization.tr,
  213. trailing: Row(
  214. mainAxisSize: MainAxisSize.min,
  215. children: [
  216. Text(
  217. isPremium ? '1/4' : '0/1',
  218. style: TextStyle(
  219. fontSize: 13.sp,
  220. color: Get.reactiveTheme.hintColor,
  221. ),
  222. ),
  223. SizedBox(width: 4.w),
  224. Icon(
  225. IconFont.icon02,
  226. size: 20.w,
  227. color: Get.reactiveTheme.hintColor,
  228. ),
  229. ],
  230. ),
  231. onTap: () {
  232. Get.toNamed(Routes.DEVICEAUTH);
  233. },
  234. ),
  235. ],
  236. ),
  237. );
  238. }),
  239. );
  240. }
  241. /// Network 分组
  242. Widget _buildNetworkSection() {
  243. return SliverToBoxAdapter(
  244. child: Container(
  245. margin: EdgeInsets.symmetric(horizontal: 14.w),
  246. decoration: BoxDecoration(
  247. color: Get.reactiveTheme.highlightColor,
  248. borderRadius: BorderRadius.circular(12.r),
  249. ),
  250. child: Column(
  251. children: [
  252. _buildSettingItem(
  253. icon: IconFont.icon34,
  254. iconColor: Get.reactiveTheme.primaryColor,
  255. title: Strings.routingMode.tr,
  256. trailing: Icon(
  257. IconFont.icon02,
  258. size: 20.w,
  259. color: Get.reactiveTheme.hintColor,
  260. ),
  261. onTap: () {
  262. // TODO: 跳转到路由模式页面
  263. Get.toNamed(Routes.ROUTINGMODE);
  264. },
  265. ),
  266. _buildDivider(),
  267. if (Platform.isAndroid) ...[
  268. _buildSettingItem(
  269. icon: IconFont.icon32,
  270. iconColor: Get.reactiveTheme.primaryColor,
  271. title: Strings.splitTunneling.tr,
  272. trailing: Icon(
  273. IconFont.icon02,
  274. size: 20.w,
  275. color: Get.reactiveTheme.hintColor,
  276. ),
  277. onTap: () {
  278. // TODO: 跳转到分流隧道页面
  279. Get.toNamed(Routes.SPLITTUNNELING);
  280. },
  281. ),
  282. _buildDivider(),
  283. ],
  284. _buildSettingItem(
  285. icon: IconFont.icon33,
  286. iconColor: Get.reactiveTheme.primaryColor,
  287. title: Strings.autoReconnect.tr,
  288. trailing: Obx(
  289. () => CupertinoSwitch(
  290. value: controller.autoReconnect.value,
  291. onChanged: (value) {
  292. controller.autoReconnect.value = value;
  293. },
  294. activeTrackColor: Get.reactiveTheme.shadowColor,
  295. thumbColor: Colors.white,
  296. inactiveThumbColor: Colors.white,
  297. inactiveTrackColor: Colors.grey,
  298. ),
  299. ),
  300. ),
  301. _buildDivider(),
  302. _buildSettingItem(
  303. icon: IconFont.icon35,
  304. iconColor: Get.reactiveTheme.primaryColor,
  305. title: Strings.restoreDefault.tr,
  306. trailing: Icon(
  307. IconFont.icon02,
  308. size: 20.w,
  309. color: Get.reactiveTheme.hintColor,
  310. ),
  311. onTap: () {
  312. // TODO: 恢复默认设置
  313. },
  314. ),
  315. ],
  316. ),
  317. ),
  318. );
  319. }
  320. /// APP 分组
  321. Widget _buildAppSection() {
  322. return SliverToBoxAdapter(
  323. child: Container(
  324. margin: EdgeInsets.symmetric(horizontal: 14.w),
  325. decoration: BoxDecoration(
  326. color: Get.reactiveTheme.highlightColor,
  327. borderRadius: BorderRadius.circular(12.r),
  328. ),
  329. child: Column(
  330. children: [
  331. _buildSettingItem(
  332. icon: IconFont.icon36,
  333. iconColor: DarkThemeColors.settingAppLinearGradientStartColor,
  334. iconGradient: LinearGradient(
  335. colors: [
  336. DarkThemeColors.settingAppLinearGradientStartColor,
  337. DarkThemeColors.settingAppLinearGradientEndColor,
  338. ],
  339. begin: Alignment.topCenter,
  340. end: Alignment.bottomCenter,
  341. ),
  342. title: Strings.language.tr,
  343. trailing: Row(
  344. mainAxisSize: MainAxisSize.min,
  345. children: [
  346. Text(
  347. LocalizationService.getGlobalLanguageTitle(),
  348. style: TextStyle(
  349. fontSize: 13.sp,
  350. color: Get.reactiveTheme.hintColor,
  351. ),
  352. ),
  353. 8.horizontalSpace,
  354. Icon(
  355. IconFont.icon02,
  356. size: 20.w,
  357. color: Get.reactiveTheme.hintColor,
  358. ),
  359. ],
  360. ),
  361. onTap: () {
  362. // TODO: 跳转到语言选择页面
  363. Get.toNamed(Routes.LANGUAGE);
  364. },
  365. ),
  366. _buildDivider(),
  367. _buildSettingItem(
  368. icon: IconFont.icon37,
  369. iconColor: DarkThemeColors.settingAppLinearGradientStartColor,
  370. iconGradient: LinearGradient(
  371. colors: [
  372. DarkThemeColors.settingAppLinearGradientStartColor,
  373. DarkThemeColors.settingAppLinearGradientEndColor,
  374. ],
  375. begin: Alignment.topCenter,
  376. end: Alignment.bottomCenter,
  377. ),
  378. title: Strings.feedback.tr,
  379. trailing: Icon(
  380. IconFont.icon02,
  381. size: 20.w,
  382. color: Get.reactiveTheme.hintColor,
  383. ),
  384. onTap: () {
  385. // TODO: 跳转到反馈页面
  386. Get.toNamed(Routes.FEEDBACK);
  387. },
  388. ),
  389. _buildDivider(),
  390. _buildSettingItem(
  391. icon: IconFont.icon38,
  392. iconColor: DarkThemeColors.settingAppLinearGradientStartColor,
  393. iconGradient: LinearGradient(
  394. colors: [
  395. DarkThemeColors.settingAppLinearGradientStartColor,
  396. DarkThemeColors.settingAppLinearGradientEndColor,
  397. ],
  398. begin: Alignment.topCenter,
  399. end: Alignment.bottomCenter,
  400. ),
  401. title: Strings.privacyPolicy.tr,
  402. trailing: Icon(
  403. IconFont.icon02,
  404. size: 20.w,
  405. color: Get.reactiveTheme.hintColor,
  406. ),
  407. onTap: () {
  408. // TODO: 跳转到隐私政策页面
  409. SystemHelper.openPrivacyTerms();
  410. },
  411. ),
  412. _buildDivider(),
  413. _buildSettingItem(
  414. icon: IconFont.icon38,
  415. iconColor: DarkThemeColors.settingAppLinearGradientStartColor,
  416. iconGradient: LinearGradient(
  417. colors: [
  418. DarkThemeColors.settingAppLinearGradientStartColor,
  419. DarkThemeColors.settingAppLinearGradientEndColor,
  420. ],
  421. begin: Alignment.topCenter,
  422. end: Alignment.bottomCenter,
  423. ),
  424. title: Strings.termsOfService.tr,
  425. trailing: Icon(
  426. IconFont.icon02,
  427. size: 20.w,
  428. color: Get.reactiveTheme.hintColor,
  429. ),
  430. onTap: () {
  431. // TODO: 跳转到服务条款页面
  432. SystemHelper.openTermsOfService();
  433. },
  434. ),
  435. _buildDivider(),
  436. _buildSettingItem(
  437. svgPath: Assets.pushNotifications,
  438. iconColor: DarkThemeColors.settingAppLinearGradientStartColor,
  439. iconGradient: LinearGradient(
  440. colors: [
  441. DarkThemeColors.settingAppLinearGradientStartColor,
  442. DarkThemeColors.settingAppLinearGradientEndColor,
  443. ],
  444. begin: Alignment.topCenter,
  445. end: Alignment.bottomCenter,
  446. ),
  447. title: Strings.pushNotifications.tr,
  448. trailing: Obx(
  449. () => CupertinoSwitch(
  450. value: controller.pushNotifications.value,
  451. onChanged: (value) {
  452. controller.showNotificationConfigPage();
  453. },
  454. activeTrackColor: Get.reactiveTheme.shadowColor,
  455. thumbColor: Colors.white,
  456. inactiveThumbColor: Colors.white,
  457. inactiveTrackColor: Colors.grey,
  458. ),
  459. ),
  460. onTap: () {
  461. controller.showNotificationConfigPage();
  462. },
  463. ),
  464. _buildDivider(),
  465. _buildSettingItem(
  466. icon: IconFont.icon39,
  467. iconColor: DarkThemeColors.settingAppLinearGradientStartColor,
  468. iconGradient: LinearGradient(
  469. colors: [
  470. DarkThemeColors.settingAppLinearGradientStartColor,
  471. DarkThemeColors.settingAppLinearGradientEndColor,
  472. ],
  473. begin: Alignment.topCenter,
  474. end: Alignment.bottomCenter,
  475. ),
  476. title: Strings.version.tr,
  477. trailing: Text(
  478. 'V1.0.0',
  479. style: TextStyle(
  480. fontSize: 13.sp,
  481. color: Get.reactiveTheme.hintColor,
  482. ),
  483. ),
  484. ),
  485. ],
  486. ),
  487. ),
  488. );
  489. }
  490. /// Security 分组
  491. Widget _buildSecuritySection() {
  492. return SliverToBoxAdapter(
  493. child: Container(
  494. margin: EdgeInsets.symmetric(horizontal: 14.w),
  495. decoration: BoxDecoration(
  496. color: Get.reactiveTheme.highlightColor,
  497. borderRadius: BorderRadius.circular(12.r),
  498. ),
  499. child: Column(
  500. children: [
  501. _buildSettingItem(
  502. icon: IconFont.icon11,
  503. iconColor:
  504. DarkThemeColors.settingSecurityLinearGradientStartColor,
  505. iconGradient: LinearGradient(
  506. colors: [
  507. DarkThemeColors.settingSecurityLinearGradientStartColor,
  508. DarkThemeColors.settingSecurityLinearGradientEndColor,
  509. ],
  510. begin: Alignment.topCenter,
  511. end: Alignment.bottomCenter,
  512. ),
  513. title: Strings.changePassword.tr,
  514. onTap: () {
  515. // TODO: 跳转到忘记密码页面
  516. Get.toNamed(Routes.FORGOTPWD);
  517. },
  518. ),
  519. _buildDivider(),
  520. _buildSettingItem(
  521. icon: IconFont.icon40,
  522. iconColor:
  523. DarkThemeColors.settingSecurityLinearGradientStartColor,
  524. iconGradient: LinearGradient(
  525. colors: [
  526. DarkThemeColors.settingSecurityLinearGradientStartColor,
  527. DarkThemeColors.settingSecurityLinearGradientEndColor,
  528. ],
  529. begin: Alignment.topCenter,
  530. end: Alignment.bottomCenter,
  531. ),
  532. title: Strings.deleteAccount.tr,
  533. onTap: () {
  534. AllDialog.showDeleteAccountConfirm(() {
  535. // 退出登录
  536. controller.handleDeleteAccount();
  537. });
  538. },
  539. ),
  540. _buildDivider(),
  541. _buildSettingItem(
  542. icon: IconFont.icon66,
  543. iconColor:
  544. DarkThemeColors.settingSecurityLinearGradientStartColor,
  545. iconGradient: LinearGradient(
  546. colors: [
  547. DarkThemeColors.settingSecurityLinearGradientStartColor,
  548. DarkThemeColors.settingSecurityLinearGradientEndColor,
  549. ],
  550. begin: Alignment.topCenter,
  551. end: Alignment.bottomCenter,
  552. ),
  553. title: Strings.logout.tr,
  554. titleColor: const Color(0xFFEF0000),
  555. onTap: () {
  556. AllDialog.showLogoutConfirm(() {
  557. // 退出登录
  558. controller.handleLogout();
  559. });
  560. },
  561. ),
  562. ],
  563. ),
  564. ),
  565. );
  566. }
  567. /// 构建设置项
  568. Widget _buildSettingItem({
  569. IconData? icon,
  570. String? svgPath,
  571. required Color iconColor,
  572. Gradient? iconGradient,
  573. required String title,
  574. Color? titleColor,
  575. bool showInfo = false,
  576. Widget? trailing,
  577. VoidCallback? onTap,
  578. VoidCallback? onInfoTap,
  579. }) {
  580. // 确保至少提供了 icon 或 svgPath 之一
  581. assert(
  582. icon != null || svgPath != null,
  583. 'Must provide either icon or svgPath',
  584. );
  585. return ClickOpacity(
  586. onTap: onTap,
  587. child: Container(
  588. height: 56.w,
  589. padding: EdgeInsets.symmetric(horizontal: 14.w),
  590. child: Row(
  591. children: [
  592. // 图标
  593. Container(
  594. width: 30.w,
  595. height: 30.w,
  596. decoration: BoxDecoration(
  597. gradient: iconGradient,
  598. color: iconGradient == null ? iconColor : null,
  599. borderRadius: BorderRadius.circular(8.r),
  600. ),
  601. child: svgPath != null
  602. ? Padding(
  603. padding: EdgeInsets.all(5.w),
  604. child: SvgPicture.asset(
  605. svgPath,
  606. width: 20.w,
  607. height: 20.w,
  608. colorFilter: const ColorFilter.mode(
  609. Colors.white,
  610. BlendMode.srcIn,
  611. ),
  612. ),
  613. )
  614. : Icon(icon!, size: 20.w, color: Colors.white),
  615. ),
  616. 10.horizontalSpace,
  617. // 标题
  618. Expanded(
  619. child: Row(
  620. children: [
  621. Text(
  622. title,
  623. style: TextStyle(
  624. fontSize: 14.sp,
  625. color:
  626. titleColor ??
  627. Get.reactiveTheme.textTheme.bodyLarge!.color,
  628. fontWeight: FontWeight.w500,
  629. ),
  630. ),
  631. 4.horizontalSpace,
  632. if (showInfo)
  633. ClickOpacity(
  634. onTap: onInfoTap,
  635. child: Icon(
  636. IconFont.icon59,
  637. size: 20.w,
  638. color: Colors.white,
  639. ),
  640. ),
  641. ],
  642. ),
  643. ),
  644. // 右侧内容
  645. if (trailing != null) trailing,
  646. ],
  647. ),
  648. ),
  649. );
  650. }
  651. /// 构建分割线
  652. Widget _buildDivider() {
  653. return Padding(
  654. padding: EdgeInsets.only(left: 60.w),
  655. child: Divider(
  656. height: 1,
  657. color: Get.reactiveTheme.dividerColor.withOpacity(0.3),
  658. ),
  659. );
  660. }
  661. }