account_view.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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. ? controller.apiController.remainTimeSeconds > 0
  338. ? Assets.premium
  339. : Assets.premiumExpired
  340. : controller.apiController.userLevel == 9999
  341. ? Assets.test
  342. : Assets.free,
  343. width: controller.apiController.userLevel == 3
  344. ? 92.w
  345. : 64.w,
  346. height: 28.w,
  347. sourceType: ImageSourceType.asset,
  348. ),
  349. ],
  350. ),
  351. onTap: () {
  352. Get.toNamed(Routes.ACCOUNT);
  353. },
  354. ),
  355. _buildDivider(),
  356. _buildSettingItem(
  357. icon: IconFont.icon14,
  358. iconColor: Get.reactiveTheme.shadowColor,
  359. title:
  360. 'UID ${DeviceManager.getCacheDeviceId().length > 12 ? '${DeviceManager.getCacheDeviceId().substring(0, 6)}***${DeviceManager.getCacheDeviceId().substring(DeviceManager.getCacheDeviceId().length - 6)}' : DeviceManager.getCacheDeviceId()}',
  361. showInfo: true,
  362. trailing: ClickOpacity(
  363. onTap: () {
  364. Clipboard.setData(
  365. ClipboardData(text: DeviceManager.getCacheDeviceId()),
  366. );
  367. IXSnackBar.showIXSnackBar(
  368. title: Strings.copied.tr,
  369. message: Strings.copied.tr,
  370. );
  371. },
  372. child: Icon(
  373. IconFont.icon57,
  374. size: 20.w,
  375. color: Get.reactiveTheme.hintColor,
  376. ),
  377. ),
  378. onTap: () {},
  379. onInfoTap: () {
  380. AllDialog.showUidInfo();
  381. },
  382. ),
  383. _buildDivider(),
  384. // 根据用户类型显示不同的时间信息
  385. if (controller.apiController.isPremium) ...[
  386. // _buildSettingItem(
  387. // icon: IconFont.icon23,
  388. // iconColor: Get.reactiveTheme.shadowColor,
  389. // title: Strings.myPreCode.tr,
  390. // trailing: Row(
  391. // mainAxisSize: MainAxisSize.min,
  392. // children: [
  393. // Text(
  394. // '123***ADZ',
  395. // style: TextStyle(
  396. // fontSize: 13.sp,
  397. // color: Get.reactiveTheme.hintColor,
  398. // ),
  399. // ),
  400. // SizedBox(width: 4.w),
  401. // Icon(
  402. // IconFont.icon02,
  403. // size: 20.w,
  404. // color: Get.reactiveTheme.hintColor,
  405. // ),
  406. // ],
  407. // ),
  408. // onTap: () {
  409. // // TODO: 跳转到Pre Code页面
  410. // Get.toNamed(Routes.PRECODE);
  411. // },
  412. // ),
  413. // _buildDivider(),
  414. _buildSettingItem(
  415. icon: IconFont.icon30,
  416. iconColor: Get.reactiveTheme.shadowColor,
  417. title: Strings.validTerm.tr,
  418. trailing: Text(
  419. controller.apiController.remainTimeSeconds > 0
  420. ? controller.apiController.validTermText
  421. : Strings.expired.tr,
  422. style: TextStyle(
  423. fontSize: 13.sp,
  424. color: Get.reactiveTheme.primaryColor,
  425. fontWeight: FontWeight.w500,
  426. ),
  427. ),
  428. onTap: () {
  429. // TODO: 跳转到有效期详情页面
  430. },
  431. ),
  432. ] else ...[
  433. _buildSettingItem(
  434. icon: IconFont.icon30,
  435. iconColor: Get.reactiveTheme.shadowColor,
  436. title: Strings.freeTime.tr,
  437. trailing: Text(
  438. '${controller.apiController.remainTimeFormatted} / Days',
  439. style: TextStyle(
  440. fontSize: 14.sp,
  441. color: const Color(0xFFFFCC00),
  442. fontWeight: FontWeight.w500,
  443. ),
  444. ),
  445. ),
  446. ],
  447. // _buildDivider(),
  448. // _buildSettingItem(
  449. // icon: IconFont.icon31,
  450. // iconColor: Get.reactiveTheme.shadowColor,
  451. // title: Strings.deviceAuthorization.tr,
  452. // trailing: Row(
  453. // mainAxisSize: MainAxisSize.min,
  454. // children: [
  455. // Text(
  456. // isPremium ? '1/4' : '0/1',
  457. // style: TextStyle(
  458. // fontSize: 13.sp,
  459. // color: Get.reactiveTheme.hintColor,
  460. // ),
  461. // ),
  462. // SizedBox(width: 4.w),
  463. // Icon(
  464. // IconFont.icon02,
  465. // size: 20.w,
  466. // color: Get.reactiveTheme.hintColor,
  467. // ),
  468. // ],
  469. // ),
  470. // onTap: () {
  471. // Get.toNamed(Routes.DEVICEAUTH);
  472. // },
  473. // ),
  474. ],
  475. ),
  476. );
  477. });
  478. }
  479. /// Security 分组
  480. Widget _buildSecuritySection() {
  481. return Container(
  482. decoration: BoxDecoration(
  483. color: Get.reactiveTheme.highlightColor,
  484. borderRadius: BorderRadius.circular(12.r),
  485. ),
  486. child: Column(
  487. children: [
  488. _buildSettingItem(
  489. icon: IconFont.icon11,
  490. iconColor: DarkThemeColors.settingSecurityLinearGradientStartColor,
  491. iconGradient: LinearGradient(
  492. colors: [
  493. DarkThemeColors.settingSecurityLinearGradientStartColor,
  494. DarkThemeColors.settingSecurityLinearGradientEndColor,
  495. ],
  496. begin: Alignment.topCenter,
  497. end: Alignment.bottomCenter,
  498. ),
  499. title: Strings.changePassword.tr,
  500. onTap: () {
  501. // TODO: 跳转到忘记密码页面
  502. Get.toNamed(Routes.FORGOTPWD);
  503. },
  504. ),
  505. _buildDivider(),
  506. _buildSettingItem(
  507. icon: IconFont.icon40,
  508. iconColor: DarkThemeColors.settingSecurityLinearGradientStartColor,
  509. iconGradient: LinearGradient(
  510. colors: [
  511. DarkThemeColors.settingSecurityLinearGradientStartColor,
  512. DarkThemeColors.settingSecurityLinearGradientEndColor,
  513. ],
  514. begin: Alignment.topCenter,
  515. end: Alignment.bottomCenter,
  516. ),
  517. title: Strings.deleteAccount.tr,
  518. onTap: () {
  519. AllDialog.showDeleteAccountConfirm(() {
  520. // 退出登录
  521. controller.handleDeleteAccount();
  522. });
  523. },
  524. ),
  525. _buildDivider(),
  526. _buildSettingItem(
  527. icon: IconFont.icon66,
  528. iconColor: DarkThemeColors.settingSecurityLinearGradientStartColor,
  529. iconGradient: LinearGradient(
  530. colors: [
  531. DarkThemeColors.settingSecurityLinearGradientStartColor,
  532. DarkThemeColors.settingSecurityLinearGradientEndColor,
  533. ],
  534. begin: Alignment.topCenter,
  535. end: Alignment.bottomCenter,
  536. ),
  537. title: Strings.logout.tr,
  538. titleColor: const Color(0xFFEF0000),
  539. onTap: () {
  540. AllDialog.showLogoutConfirm(() {
  541. // 退出登录
  542. controller.handleLogout();
  543. });
  544. },
  545. ),
  546. ],
  547. ),
  548. );
  549. }
  550. /// 构建设置项
  551. Widget _buildSettingItem({
  552. IconData? icon,
  553. String? svgPath,
  554. required Color iconColor,
  555. Gradient? iconGradient,
  556. required String title,
  557. Color? titleColor,
  558. bool showInfo = false,
  559. Widget? trailing,
  560. VoidCallback? onTap,
  561. VoidCallback? onInfoTap,
  562. }) {
  563. // 确保至少提供了 icon 或 svgPath 之一
  564. assert(
  565. icon != null || svgPath != null,
  566. 'Must provide either icon or svgPath',
  567. );
  568. return ClickOpacity(
  569. onTap: onTap,
  570. child: Container(
  571. height: 56.w,
  572. padding: EdgeInsets.symmetric(horizontal: 14.w),
  573. child: Row(
  574. children: [
  575. // 图标
  576. Container(
  577. width: 30.w,
  578. height: 30.w,
  579. decoration: BoxDecoration(
  580. gradient: iconGradient,
  581. color: iconGradient == null ? iconColor : null,
  582. borderRadius: BorderRadius.circular(8.r),
  583. ),
  584. child: svgPath != null
  585. ? Padding(
  586. padding: EdgeInsets.all(5.w),
  587. child: SvgPicture.asset(
  588. svgPath,
  589. width: 20.w,
  590. height: 20.w,
  591. colorFilter: const ColorFilter.mode(
  592. Colors.white,
  593. BlendMode.srcIn,
  594. ),
  595. ),
  596. )
  597. : Icon(icon!, size: 20.w, color: Colors.white),
  598. ),
  599. 10.horizontalSpace,
  600. // 标题
  601. Expanded(
  602. child: Row(
  603. children: [
  604. Text(
  605. title,
  606. style: TextStyle(
  607. fontSize: 14.sp,
  608. color:
  609. titleColor ??
  610. Get.reactiveTheme.textTheme.bodyLarge!.color,
  611. fontWeight: FontWeight.w500,
  612. ),
  613. ),
  614. 4.horizontalSpace,
  615. if (showInfo)
  616. ClickOpacity(
  617. onTap: onInfoTap,
  618. child: Icon(
  619. IconFont.icon59,
  620. size: 20.w,
  621. color: Colors.white,
  622. ),
  623. ),
  624. ],
  625. ),
  626. ),
  627. // 右侧内容
  628. if (trailing != null) trailing,
  629. ],
  630. ),
  631. ),
  632. );
  633. }
  634. /// 获取用户账号显示文本
  635. String _getUserAccount() {
  636. final user = IXSP.getUser();
  637. if (user == null) return '';
  638. if (user.memberLevel == MemberLevel.normal.level) {
  639. return user.account?.username ?? '';
  640. }
  641. return '';
  642. }
  643. }