account_view.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. import 'package:flutter_svg/flutter_svg.dart';
  5. import 'package:get/get.dart';
  6. import 'package:nomo/app/base/base_view.dart';
  7. import 'package:nomo/app/widgets/click_opacity.dart';
  8. import 'package:nomo/app/widgets/ix_app_bar.dart';
  9. import 'package:nomo/app/widgets/submit_btn.dart';
  10. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  11. import '../../../../config/theme/dark_theme_colors.dart';
  12. import '../../../../config/translations/strings_enum.dart';
  13. import '../../../../utils/device_manager.dart';
  14. import '../../../constants/enums.dart';
  15. import '../../../data/sp/ix_sp.dart';
  16. import '../../../components/ix_snackbar.dart';
  17. import '../../../constants/assets.dart';
  18. import '../../../constants/iconfont/iconfont.dart';
  19. import '../../../dialog/all_dialog.dart';
  20. import '../../../routes/app_pages.dart';
  21. import '../../../widgets/ix_image.dart';
  22. import '../controllers/account_controller.dart';
  23. class AccountView extends BaseView<AccountController> {
  24. const AccountView({super.key});
  25. @override
  26. PreferredSizeWidget? get appBar => IXAppBar(title: Strings.account.tr);
  27. @override
  28. Widget buildContent(BuildContext context) {
  29. return Obx(() {
  30. return SingleChildScrollView(
  31. padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 10.w),
  32. child: Column(
  33. crossAxisAlignment: CrossAxisAlignment.start,
  34. children: [
  35. // Account 信息卡片
  36. // _buildAccountCard(isPremium),
  37. _buildAccountSection(),
  38. // Security Section
  39. if (!controller.apiController.isGuest)
  40. _buildSectionHeader(Strings.securitySection.tr),
  41. if (!controller.apiController.isGuest) _buildSecuritySection(),
  42. 20.verticalSpaceFromWidth,
  43. // 底部按钮
  44. _buildBottomButtons(),
  45. 20.verticalSpaceFromWidth,
  46. ],
  47. ),
  48. );
  49. });
  50. }
  51. /// 构建账户信息卡片
  52. Widget _buildAccountCard(bool isPremium) {
  53. return Container(
  54. decoration: BoxDecoration(
  55. color: Get.reactiveTheme.highlightColor,
  56. borderRadius: BorderRadius.circular(12.r),
  57. ),
  58. child: Column(
  59. children: [
  60. // Account 条目
  61. _buildAccountItem(isPremium),
  62. _buildDivider(),
  63. // UID 条目
  64. _buildUIDItem(),
  65. _buildDivider(),
  66. // Premium 功能列表
  67. _buildPremiumFeatures(isPremium),
  68. ],
  69. ),
  70. );
  71. }
  72. /// Account 条目
  73. Widget _buildAccountItem(bool isPremium) {
  74. return Container(
  75. height: 56.w,
  76. padding: EdgeInsets.symmetric(horizontal: 16.w),
  77. child: Row(
  78. children: [
  79. // 图标
  80. Container(
  81. width: 30.w,
  82. height: 30.w,
  83. decoration: BoxDecoration(
  84. color: Get.reactiveTheme.shadowColor,
  85. borderRadius: BorderRadius.circular(8.r),
  86. ),
  87. child: Icon(IconFont.icon29, size: 20.w, color: Colors.white),
  88. ),
  89. 10.horizontalSpace,
  90. // 标题
  91. Expanded(
  92. child: Text(
  93. Strings.account.tr,
  94. style: TextStyle(
  95. fontSize: 14.sp,
  96. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  97. fontWeight: FontWeight.w400,
  98. ),
  99. ),
  100. ),
  101. // 徽章
  102. IXImage(
  103. source: isPremium ? Assets.premium : Assets.free,
  104. width: isPremium ? 92.w : 64.w,
  105. height: 28.w,
  106. sourceType: ImageSourceType.asset,
  107. ),
  108. ],
  109. ),
  110. );
  111. }
  112. /// UID 条目
  113. Widget _buildUIDItem() {
  114. return ClickOpacity(
  115. onTap: () {
  116. Clipboard.setData(ClipboardData(text: controller.uid));
  117. Get.snackbar(Strings.copied.tr, 'UID ${Strings.copied.tr}');
  118. },
  119. child: Container(
  120. height: 56.w,
  121. padding: EdgeInsets.symmetric(horizontal: 16.w),
  122. child: Row(
  123. children: [
  124. // 图标
  125. Container(
  126. width: 30.w,
  127. height: 30.w,
  128. decoration: BoxDecoration(
  129. color: Get.reactiveTheme.shadowColor,
  130. borderRadius: BorderRadius.circular(8.r),
  131. ),
  132. child: Icon(IconFont.icon14, size: 20.w, color: Colors.white),
  133. ),
  134. 10.horizontalSpace,
  135. // UID
  136. Expanded(
  137. child: Text(
  138. controller.uid,
  139. style: TextStyle(
  140. fontSize: 14.sp,
  141. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  142. fontWeight: FontWeight.w400,
  143. ),
  144. ),
  145. ),
  146. // 复制图标
  147. Icon(
  148. IconFont.icon57,
  149. size: 20.w,
  150. color: Get.reactiveTheme.hintColor,
  151. ),
  152. ],
  153. ),
  154. ),
  155. );
  156. }
  157. Widget _buildPremiumFeatures(bool isPremium) {
  158. return Padding(
  159. padding: EdgeInsets.symmetric(horizontal: 14.w),
  160. child: Column(
  161. children: [
  162. _buildFeatureItem(IconFont.icon60, Strings.unlockAllFreeLocations.tr),
  163. _buildFeatureItem(IconFont.icon61, Strings.unlockSmartMode.tr),
  164. _buildFeatureItem(IconFont.icon62, Strings.unlockMultiHopMode.tr),
  165. _buildFeatureItem(
  166. IconFont.icon63,
  167. Strings.premiumCanShareXDevices.tr,
  168. ),
  169. _buildFeatureItem(
  170. IconFont.icon64,
  171. Strings.ownYourOwnPrivateServer.tr,
  172. ),
  173. _buildFeatureItem(IconFont.icon65, Strings.closeAds.tr),
  174. ],
  175. ),
  176. );
  177. }
  178. Widget _buildFeatureItem(IconData icon, String title) {
  179. return SizedBox(
  180. height: 44.w,
  181. child: Row(
  182. children: [
  183. Icon(icon, color: DarkThemeColors.subscriptionColor, size: 24.w),
  184. 12.horizontalSpace,
  185. Expanded(
  186. child: Text(
  187. title,
  188. style: TextStyle(
  189. fontSize: 13.sp,
  190. color: Get.reactiveTheme.hintColor,
  191. ),
  192. ),
  193. ),
  194. Container(
  195. width: 20.w,
  196. height: 20.w,
  197. decoration: BoxDecoration(
  198. shape: BoxShape.circle,
  199. color: DarkThemeColors.subscriptionSelectColor,
  200. ),
  201. child: Icon(Icons.check, color: Colors.white, size: 12.w),
  202. ),
  203. ],
  204. ),
  205. );
  206. }
  207. /// 底部按钮
  208. Widget _buildBottomButtons() {
  209. return Column(
  210. children: [
  211. if (controller.apiController.isPremium) ...[
  212. if (controller.apiController.isGuest) ...[
  213. SubmitButton(
  214. text: Strings.changeSubscription.tr,
  215. bgColor: Get.reactiveTheme.highlightColor,
  216. textColor: DarkThemeColors.subscriptionColor,
  217. onPressed: () {
  218. controller.toSubscription();
  219. },
  220. ),
  221. ] else ...[
  222. _buildSecondaryButton(
  223. text: Strings.changeSubscription.tr,
  224. icon: IconFont.icon23,
  225. onTap: () {
  226. controller.toSubscription();
  227. },
  228. ),
  229. ],
  230. ] else ...[
  231. // Upgrade to Premium 按钮
  232. _buildSecondaryButton(
  233. text: Strings.upgradeToPremium.tr,
  234. icon: IconFont.icon23,
  235. onTap: () {
  236. controller.toSubscription();
  237. },
  238. ),
  239. ],
  240. // 绑定邮箱 按钮
  241. if (controller.apiController.isGuest &&
  242. controller.apiController.isPremium) ...[
  243. 20.verticalSpaceFromWidth,
  244. _buildSecondaryButton(
  245. text: Strings.bindEmailMemberBenefits.tr,
  246. icon: IconFont.icon23,
  247. onTap: () {
  248. // TODO: 绑定邮箱
  249. AllDialog.showBindEmailMemberBenefits();
  250. },
  251. ),
  252. 10.verticalSpaceFromWidth,
  253. // 提示文字
  254. Text(
  255. Strings.bindingAccountEmailProtectsPreRights.tr,
  256. textAlign: TextAlign.center,
  257. style: TextStyle(
  258. fontSize: 12.sp,
  259. color: Get.reactiveTheme.hintColor,
  260. ),
  261. ),
  262. ],
  263. ],
  264. );
  265. }
  266. /// 次要按钮(黑色边框)
  267. Widget _buildSecondaryButton({
  268. required String text,
  269. required IconData icon,
  270. required VoidCallback onTap,
  271. }) {
  272. return ClickOpacity(
  273. onTap: onTap,
  274. child: Container(
  275. height: 52.w,
  276. decoration: BoxDecoration(
  277. border: Border.all(color: Get.reactiveTheme.dividerColor, width: 1.w),
  278. borderRadius: BorderRadius.circular(12.r),
  279. ),
  280. child: Row(
  281. mainAxisAlignment: MainAxisAlignment.center,
  282. children: [
  283. Text(
  284. text,
  285. style: TextStyle(
  286. fontSize: 16.sp,
  287. color: DarkThemeColors.subscriptionColor,
  288. fontWeight: FontWeight.w400,
  289. ),
  290. ),
  291. SizedBox(width: 8.w),
  292. Icon(icon, size: 20.w, color: DarkThemeColors.subscriptionColor),
  293. ],
  294. ),
  295. ),
  296. );
  297. }
  298. /// 构建分割线
  299. Widget _buildDivider() {
  300. return Divider(height: 1.w, color: Get.reactiveTheme.dividerColor);
  301. }
  302. /// 构建分组标题
  303. Widget _buildSectionHeader(String title) {
  304. return Padding(
  305. padding: EdgeInsets.symmetric(vertical: 10.w),
  306. child: Text(
  307. title,
  308. style: TextStyle(
  309. fontSize: 16.sp,
  310. color: Get.reactiveTheme.hintColor,
  311. fontWeight: FontWeight.w500,
  312. ),
  313. ),
  314. );
  315. }
  316. /// Account 分组
  317. Widget _buildAccountSection() {
  318. return Obx(() {
  319. return Container(
  320. decoration: BoxDecoration(
  321. color: Get.reactiveTheme.highlightColor,
  322. borderRadius: BorderRadius.circular(12.r),
  323. ),
  324. child: Column(
  325. children: [
  326. _buildSettingItem(
  327. icon: IconFont.icon29,
  328. iconColor: Get.reactiveTheme.shadowColor,
  329. title: _getUserAccount().isNotEmpty
  330. ? _getUserAccount()
  331. : Strings.account.tr,
  332. trailing: Row(
  333. mainAxisSize: MainAxisSize.min,
  334. children: [
  335. IXImage(
  336. source: controller.apiController.userLevel == 3
  337. ? Assets.premium
  338. : controller.apiController.userLevel == 9999
  339. ? Assets.test
  340. : Assets.free,
  341. width: controller.apiController.userLevel == 3
  342. ? 92.w
  343. : 64.w,
  344. height: 28.w,
  345. sourceType: ImageSourceType.asset,
  346. ),
  347. ],
  348. ),
  349. onTap: () {
  350. Get.toNamed(Routes.ACCOUNT);
  351. },
  352. ),
  353. _buildDivider(),
  354. _buildSettingItem(
  355. icon: IconFont.icon14,
  356. iconColor: Get.reactiveTheme.shadowColor,
  357. title:
  358. 'UID ${DeviceManager.getCacheDeviceId().length > 12 ? '${DeviceManager.getCacheDeviceId().substring(0, 6)}***${DeviceManager.getCacheDeviceId().substring(DeviceManager.getCacheDeviceId().length - 6)}' : DeviceManager.getCacheDeviceId()}',
  359. showInfo: true,
  360. trailing: ClickOpacity(
  361. onTap: () {
  362. Clipboard.setData(
  363. ClipboardData(text: DeviceManager.getCacheDeviceId()),
  364. );
  365. IXSnackBar.showIXSnackBar(
  366. title: Strings.copied.tr,
  367. message: Strings.copied.tr,
  368. );
  369. },
  370. child: Icon(
  371. IconFont.icon57,
  372. size: 20.w,
  373. color: Get.reactiveTheme.hintColor,
  374. ),
  375. ),
  376. onTap: () {},
  377. onInfoTap: () {
  378. AllDialog.showUidInfo();
  379. },
  380. ),
  381. _buildDivider(),
  382. // 根据用户类型显示不同的时间信息
  383. if (controller.apiController.isPremium) ...[
  384. // _buildSettingItem(
  385. // icon: IconFont.icon23,
  386. // iconColor: Get.reactiveTheme.shadowColor,
  387. // title: Strings.myPreCode.tr,
  388. // trailing: Row(
  389. // mainAxisSize: MainAxisSize.min,
  390. // children: [
  391. // Text(
  392. // '123***ADZ',
  393. // style: TextStyle(
  394. // fontSize: 13.sp,
  395. // color: Get.reactiveTheme.hintColor,
  396. // ),
  397. // ),
  398. // SizedBox(width: 4.w),
  399. // Icon(
  400. // IconFont.icon02,
  401. // size: 20.w,
  402. // color: Get.reactiveTheme.hintColor,
  403. // ),
  404. // ],
  405. // ),
  406. // onTap: () {
  407. // // TODO: 跳转到Pre Code页面
  408. // Get.toNamed(Routes.PRECODE);
  409. // },
  410. // ),
  411. // _buildDivider(),
  412. _buildSettingItem(
  413. icon: IconFont.icon30,
  414. iconColor: Get.reactiveTheme.shadowColor,
  415. title: Strings.validTerm.tr,
  416. trailing: Text(
  417. controller.apiController.validTermText,
  418. style: TextStyle(
  419. fontSize: 13.sp,
  420. color: Get.reactiveTheme.primaryColor,
  421. fontWeight: FontWeight.w500,
  422. ),
  423. ),
  424. onTap: () {
  425. // TODO: 跳转到有效期详情页面
  426. },
  427. ),
  428. ] else ...[
  429. _buildSettingItem(
  430. icon: IconFont.icon30,
  431. iconColor: Get.reactiveTheme.shadowColor,
  432. title: Strings.freeTime.tr,
  433. trailing: Text(
  434. '${controller.apiController.remainTimeFormatted} / Days',
  435. style: TextStyle(
  436. fontSize: 14.sp,
  437. color: const Color(0xFFFFCC00),
  438. fontWeight: FontWeight.w500,
  439. ),
  440. ),
  441. ),
  442. ],
  443. // _buildDivider(),
  444. // _buildSettingItem(
  445. // icon: IconFont.icon31,
  446. // iconColor: Get.reactiveTheme.shadowColor,
  447. // title: Strings.deviceAuthorization.tr,
  448. // trailing: Row(
  449. // mainAxisSize: MainAxisSize.min,
  450. // children: [
  451. // Text(
  452. // isPremium ? '1/4' : '0/1',
  453. // style: TextStyle(
  454. // fontSize: 13.sp,
  455. // color: Get.reactiveTheme.hintColor,
  456. // ),
  457. // ),
  458. // SizedBox(width: 4.w),
  459. // Icon(
  460. // IconFont.icon02,
  461. // size: 20.w,
  462. // color: Get.reactiveTheme.hintColor,
  463. // ),
  464. // ],
  465. // ),
  466. // onTap: () {
  467. // Get.toNamed(Routes.DEVICEAUTH);
  468. // },
  469. // ),
  470. ],
  471. ),
  472. );
  473. });
  474. }
  475. /// Security 分组
  476. Widget _buildSecuritySection() {
  477. return Container(
  478. decoration: BoxDecoration(
  479. color: Get.reactiveTheme.highlightColor,
  480. borderRadius: BorderRadius.circular(12.r),
  481. ),
  482. child: Column(
  483. children: [
  484. _buildSettingItem(
  485. icon: IconFont.icon11,
  486. iconColor: DarkThemeColors.settingSecurityLinearGradientStartColor,
  487. iconGradient: LinearGradient(
  488. colors: [
  489. DarkThemeColors.settingSecurityLinearGradientStartColor,
  490. DarkThemeColors.settingSecurityLinearGradientEndColor,
  491. ],
  492. begin: Alignment.topCenter,
  493. end: Alignment.bottomCenter,
  494. ),
  495. title: Strings.changePassword.tr,
  496. onTap: () {
  497. // TODO: 跳转到忘记密码页面
  498. Get.toNamed(Routes.FORGOTPWD);
  499. },
  500. ),
  501. _buildDivider(),
  502. _buildSettingItem(
  503. icon: IconFont.icon40,
  504. iconColor: 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.deleteAccount.tr,
  514. onTap: () {
  515. AllDialog.showDeleteAccountConfirm(() {
  516. // 退出登录
  517. controller.handleDeleteAccount();
  518. });
  519. },
  520. ),
  521. _buildDivider(),
  522. _buildSettingItem(
  523. icon: IconFont.icon66,
  524. iconColor: DarkThemeColors.settingSecurityLinearGradientStartColor,
  525. iconGradient: LinearGradient(
  526. colors: [
  527. DarkThemeColors.settingSecurityLinearGradientStartColor,
  528. DarkThemeColors.settingSecurityLinearGradientEndColor,
  529. ],
  530. begin: Alignment.topCenter,
  531. end: Alignment.bottomCenter,
  532. ),
  533. title: Strings.logout.tr,
  534. titleColor: const Color(0xFFEF0000),
  535. onTap: () {
  536. AllDialog.showLogoutConfirm(() {
  537. // 退出登录
  538. controller.handleLogout();
  539. });
  540. },
  541. ),
  542. ],
  543. ),
  544. );
  545. }
  546. /// 构建设置项
  547. Widget _buildSettingItem({
  548. IconData? icon,
  549. String? svgPath,
  550. required Color iconColor,
  551. Gradient? iconGradient,
  552. required String title,
  553. Color? titleColor,
  554. bool showInfo = false,
  555. Widget? trailing,
  556. VoidCallback? onTap,
  557. VoidCallback? onInfoTap,
  558. }) {
  559. // 确保至少提供了 icon 或 svgPath 之一
  560. assert(
  561. icon != null || svgPath != null,
  562. 'Must provide either icon or svgPath',
  563. );
  564. return ClickOpacity(
  565. onTap: onTap,
  566. child: Container(
  567. height: 56.w,
  568. padding: EdgeInsets.symmetric(horizontal: 14.w),
  569. child: Row(
  570. children: [
  571. // 图标
  572. Container(
  573. width: 30.w,
  574. height: 30.w,
  575. decoration: BoxDecoration(
  576. gradient: iconGradient,
  577. color: iconGradient == null ? iconColor : null,
  578. borderRadius: BorderRadius.circular(8.r),
  579. ),
  580. child: svgPath != null
  581. ? Padding(
  582. padding: EdgeInsets.all(5.w),
  583. child: SvgPicture.asset(
  584. svgPath,
  585. width: 20.w,
  586. height: 20.w,
  587. colorFilter: const ColorFilter.mode(
  588. Colors.white,
  589. BlendMode.srcIn,
  590. ),
  591. ),
  592. )
  593. : Icon(icon!, size: 20.w, color: Colors.white),
  594. ),
  595. 10.horizontalSpace,
  596. // 标题
  597. Expanded(
  598. child: Row(
  599. children: [
  600. Text(
  601. title,
  602. style: TextStyle(
  603. fontSize: 14.sp,
  604. color:
  605. titleColor ??
  606. Get.reactiveTheme.textTheme.bodyLarge!.color,
  607. fontWeight: FontWeight.w500,
  608. ),
  609. ),
  610. 4.horizontalSpace,
  611. if (showInfo)
  612. ClickOpacity(
  613. onTap: onInfoTap,
  614. child: Icon(
  615. IconFont.icon59,
  616. size: 20.w,
  617. color: Colors.white,
  618. ),
  619. ),
  620. ],
  621. ),
  622. ),
  623. // 右侧内容
  624. if (trailing != null) trailing,
  625. ],
  626. ),
  627. ),
  628. );
  629. }
  630. /// 获取用户账号显示文本
  631. String _getUserAccount() {
  632. final user = IXSP.getUser();
  633. if (user == null) return '';
  634. if (user.memberLevel == MemberLevel.normal.level) {
  635. return user.account?.username ?? '';
  636. }
  637. return '';
  638. }
  639. }