account_view.dart 22 KB

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