account_view.dart 20 KB

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