setting_view.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. import 'package:get/get.dart';
  5. import 'package:nomo/app/base/base_view.dart';
  6. import 'package:nomo/app/dialog/all_dialog.dart';
  7. import 'package:nomo/app/widgets/click_opacity.dart';
  8. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  9. import '../../../../utils/system_helper.dart';
  10. import '../../../routes/app_pages.dart';
  11. import '../../../widgets/ix_app_bar.dart';
  12. import '../controllers/setting_controller.dart';
  13. class SettingView extends BaseView<SettingController> {
  14. const SettingView({super.key});
  15. @override
  16. Widget buildContent(BuildContext context) {
  17. return Column(
  18. children: [
  19. IXAppBar(title: 'Settings'),
  20. Expanded(
  21. child: CustomScrollView(
  22. slivers: [
  23. // Account Section
  24. _buildSectionHeader('Account'),
  25. _buildAccountSection(),
  26. // Network Section
  27. _buildSectionHeader('Network'),
  28. _buildNetworkSection(),
  29. // APP Section
  30. _buildSectionHeader('APP'),
  31. _buildAppSection(),
  32. // Security Section
  33. _buildSectionHeader('Security'),
  34. _buildSecuritySection(),
  35. // 底部间距
  36. SliverToBoxAdapter(child: 20.verticalSpaceFromWidth),
  37. ],
  38. ),
  39. ),
  40. ],
  41. );
  42. }
  43. /// 构建分组标题
  44. Widget _buildSectionHeader(String title) {
  45. return SliverToBoxAdapter(
  46. child: Padding(
  47. padding: EdgeInsets.fromLTRB(28.w, 20.w, 28.w, 12.w),
  48. child: Text(
  49. title,
  50. style: TextStyle(
  51. fontSize: 14.sp,
  52. color: Get.reactiveTheme.hintColor,
  53. fontWeight: FontWeight.w500,
  54. ),
  55. ),
  56. ),
  57. );
  58. }
  59. /// Account 分组
  60. Widget _buildAccountSection() {
  61. return SliverToBoxAdapter(
  62. child: Obx(() {
  63. final isPremium = controller.isPremium.value;
  64. return Container(
  65. margin: EdgeInsets.symmetric(horizontal: 14.w),
  66. decoration: BoxDecoration(
  67. color: Get.reactiveTheme.highlightColor,
  68. borderRadius: BorderRadius.circular(16.r),
  69. ),
  70. child: Column(
  71. children: [
  72. _buildSettingItem(
  73. icon: Icons.account_circle,
  74. iconColor: const Color(0xFF00A8E8),
  75. title: 'Account',
  76. trailing: _buildBadge(
  77. isPremium ? 'Premium' : 'Free',
  78. isPremium: isPremium,
  79. ),
  80. onTap: () {
  81. Get.toNamed(Routes.ACCOUNT);
  82. },
  83. ),
  84. _buildDivider(),
  85. _buildSettingItem(
  86. icon: Icons.badge_outlined,
  87. iconColor: const Color(0xFF00A8E8),
  88. title: 'UID 123-456-789-101',
  89. trailing: Icon(
  90. Icons.copy,
  91. size: 20.w,
  92. color: Get.reactiveTheme.hintColor,
  93. ),
  94. onTap: () {
  95. Clipboard.setData(
  96. const ClipboardData(text: '123-456-789-101'),
  97. );
  98. Get.snackbar('已复制', 'UID 已复制到剪贴板');
  99. },
  100. ),
  101. _buildDivider(),
  102. // 根据用户类型显示不同的时间信息
  103. if (isPremium) ...[
  104. _buildSettingItem(
  105. icon: Icons.workspace_premium,
  106. iconColor: const Color(0xFF00A8E8),
  107. title: 'My Pre Code',
  108. trailing: Row(
  109. mainAxisSize: MainAxisSize.min,
  110. children: [
  111. Text(
  112. '123***ADZ',
  113. style: TextStyle(
  114. fontSize: 14.sp,
  115. color: Get.reactiveTheme.hintColor,
  116. ),
  117. ),
  118. SizedBox(width: 4.w),
  119. Icon(
  120. Icons.arrow_forward_ios,
  121. size: 16.w,
  122. color: Get.reactiveTheme.hintColor,
  123. ),
  124. ],
  125. ),
  126. onTap: () {
  127. // TODO: 跳转到Pre Code页面
  128. Get.toNamed(Routes.PRECODE);
  129. },
  130. ),
  131. _buildDivider(),
  132. _buildSettingItem(
  133. icon: Icons.event,
  134. iconColor: const Color(0xFF00A8E8),
  135. title: 'Valid Term',
  136. trailing: Row(
  137. mainAxisSize: MainAxisSize.min,
  138. children: [
  139. Text(
  140. 'Year / 2026-12-12',
  141. style: TextStyle(
  142. fontSize: 14.sp,
  143. color: const Color(0xFF00A8E8),
  144. fontWeight: FontWeight.w500,
  145. ),
  146. ),
  147. SizedBox(width: 4.w),
  148. Icon(
  149. Icons.arrow_forward_ios,
  150. size: 16.w,
  151. color: Get.reactiveTheme.hintColor,
  152. ),
  153. ],
  154. ),
  155. onTap: () {
  156. // TODO: 跳转到有效期详情页面
  157. },
  158. ),
  159. ] else ...[
  160. _buildSettingItem(
  161. icon: Icons.access_time,
  162. iconColor: const Color(0xFF00A8E8),
  163. title: 'Free Time',
  164. trailing: Text(
  165. '01:60:59 / Days',
  166. style: TextStyle(
  167. fontSize: 14.sp,
  168. color: const Color(0xFFFF9500),
  169. fontWeight: FontWeight.w500,
  170. ),
  171. ),
  172. ),
  173. ],
  174. _buildDivider(),
  175. _buildSettingItem(
  176. icon: Icons.phone_iphone,
  177. iconColor: const Color(0xFF00A8E8),
  178. title: 'Device Authorization',
  179. trailing: Row(
  180. mainAxisSize: MainAxisSize.min,
  181. children: [
  182. Text(
  183. isPremium ? '1/4' : '0/1',
  184. style: TextStyle(
  185. fontSize: 14.sp,
  186. color: Get.reactiveTheme.hintColor,
  187. ),
  188. ),
  189. SizedBox(width: 4.w),
  190. Icon(
  191. Icons.arrow_forward_ios,
  192. size: 16.w,
  193. color: Get.reactiveTheme.hintColor,
  194. ),
  195. ],
  196. ),
  197. onTap: () {
  198. Get.toNamed(Routes.DEVICEAUTH);
  199. },
  200. ),
  201. ],
  202. ),
  203. );
  204. }),
  205. );
  206. }
  207. /// Network 分组
  208. Widget _buildNetworkSection() {
  209. return SliverToBoxAdapter(
  210. child: Container(
  211. margin: EdgeInsets.symmetric(horizontal: 14.w),
  212. decoration: BoxDecoration(
  213. color: Get.reactiveTheme.highlightColor,
  214. borderRadius: BorderRadius.circular(16.r),
  215. ),
  216. child: Column(
  217. children: [
  218. _buildSettingItem(
  219. icon: Icons.router,
  220. iconColor: const Color(0xFF00A8E8),
  221. title: 'Routing Mode',
  222. trailing: Icon(
  223. Icons.arrow_forward_ios,
  224. size: 16.w,
  225. color: Get.reactiveTheme.hintColor,
  226. ),
  227. onTap: () {
  228. // TODO: 跳转到路由模式页面
  229. Get.toNamed(Routes.ROUTINGMODE);
  230. },
  231. ),
  232. _buildDivider(),
  233. _buildSettingItem(
  234. icon: Icons.share,
  235. iconColor: const Color(0xFF00A8E8),
  236. title: 'Split Tunneling',
  237. trailing: Icon(
  238. Icons.arrow_forward_ios,
  239. size: 16.w,
  240. color: Get.reactiveTheme.hintColor,
  241. ),
  242. onTap: () {
  243. // TODO: 跳转到分流隧道页面
  244. Get.toNamed(Routes.SPLITTUNNELING);
  245. },
  246. ),
  247. _buildDivider(),
  248. _buildSettingItem(
  249. icon: Icons.flash_on,
  250. iconColor: const Color(0xFF00A8E8),
  251. title: 'Auto Reconnect',
  252. trailing: Obx(
  253. () => Switch(
  254. value: controller.autoReconnect.value,
  255. onChanged: (value) {
  256. controller.autoReconnect.value = value;
  257. },
  258. activeColor: const Color(0xFF00D9FF),
  259. ),
  260. ),
  261. ),
  262. _buildDivider(),
  263. _buildSettingItem(
  264. icon: Icons.restart_alt,
  265. iconColor: const Color(0xFF00A8E8),
  266. title: 'Restore Default',
  267. trailing: Icon(
  268. Icons.arrow_forward_ios,
  269. size: 16.w,
  270. color: Get.reactiveTheme.hintColor,
  271. ),
  272. onTap: () {
  273. // TODO: 恢复默认设置
  274. },
  275. ),
  276. ],
  277. ),
  278. ),
  279. );
  280. }
  281. /// APP 分组
  282. Widget _buildAppSection() {
  283. return SliverToBoxAdapter(
  284. child: Container(
  285. margin: EdgeInsets.symmetric(horizontal: 14.w),
  286. decoration: BoxDecoration(
  287. color: Get.reactiveTheme.highlightColor,
  288. borderRadius: BorderRadius.circular(16.r),
  289. ),
  290. child: Column(
  291. children: [
  292. _buildSettingItem(
  293. icon: Icons.language,
  294. iconColor: const Color(0xFFFF9500),
  295. title: 'Language',
  296. trailing: Row(
  297. mainAxisSize: MainAxisSize.min,
  298. children: [
  299. Text(
  300. 'English',
  301. style: TextStyle(
  302. fontSize: 14.sp,
  303. color: Get.reactiveTheme.hintColor,
  304. ),
  305. ),
  306. 8.horizontalSpace,
  307. Icon(
  308. Icons.arrow_forward_ios,
  309. size: 16.w,
  310. color: Get.reactiveTheme.hintColor,
  311. ),
  312. ],
  313. ),
  314. onTap: () {
  315. // TODO: 跳转到语言选择页面
  316. Get.toNamed(Routes.LANGUAGE);
  317. },
  318. ),
  319. _buildDivider(),
  320. _buildSettingItem(
  321. icon: Icons.email,
  322. iconColor: const Color(0xFFFF9500),
  323. title: 'Feedback',
  324. trailing: Icon(
  325. Icons.arrow_forward_ios,
  326. size: 16.w,
  327. color: Get.reactiveTheme.hintColor,
  328. ),
  329. onTap: () {
  330. // TODO: 跳转到反馈页面
  331. Get.toNamed(Routes.FEEDBACK);
  332. },
  333. ),
  334. _buildDivider(),
  335. _buildSettingItem(
  336. icon: Icons.policy,
  337. iconColor: const Color(0xFFFF9500),
  338. title: 'Privacy Policy',
  339. trailing: Icon(
  340. Icons.arrow_forward_ios,
  341. size: 16.w,
  342. color: Get.reactiveTheme.hintColor,
  343. ),
  344. onTap: () {
  345. // TODO: 跳转到隐私政策页面
  346. SystemHelper.openPrivacyTerms();
  347. },
  348. ),
  349. _buildDivider(),
  350. _buildSettingItem(
  351. icon: Icons.description,
  352. iconColor: const Color(0xFFFF9500),
  353. title: 'Terms of Service',
  354. trailing: Icon(
  355. Icons.arrow_forward_ios,
  356. size: 16.w,
  357. color: Get.reactiveTheme.hintColor,
  358. ),
  359. onTap: () {
  360. // TODO: 跳转到服务条款页面
  361. SystemHelper.openTermsOfService();
  362. },
  363. ),
  364. _buildDivider(),
  365. _buildSettingItem(
  366. icon: Icons.apps,
  367. iconColor: const Color(0xFFFF9500),
  368. title: 'Version',
  369. trailing: Text(
  370. 'V1.0.0',
  371. style: TextStyle(
  372. fontSize: 14.sp,
  373. color: Get.reactiveTheme.hintColor,
  374. ),
  375. ),
  376. ),
  377. ],
  378. ),
  379. ),
  380. );
  381. }
  382. /// Security 分组
  383. Widget _buildSecuritySection() {
  384. return SliverToBoxAdapter(
  385. child: Container(
  386. margin: EdgeInsets.symmetric(horizontal: 14.w),
  387. decoration: BoxDecoration(
  388. color: Get.reactiveTheme.highlightColor,
  389. borderRadius: BorderRadius.circular(16.r),
  390. ),
  391. child: Column(
  392. children: [
  393. _buildSettingItem(
  394. icon: Icons.person_remove,
  395. iconColor: const Color(0xFFFF3B30),
  396. title: 'Delete Account',
  397. onTap: () {
  398. // TODO: 删除账户
  399. },
  400. ),
  401. _buildDivider(),
  402. _buildSettingItem(
  403. icon: Icons.logout,
  404. iconColor: const Color(0xFFFF3B30),
  405. title: 'logout',
  406. titleColor: const Color(0xFFFF3B30),
  407. onTap: () {
  408. // TODO: 退出登录
  409. AllDialog.showLogoutConfirm();
  410. },
  411. ),
  412. ],
  413. ),
  414. ),
  415. );
  416. }
  417. /// 构建设置项
  418. Widget _buildSettingItem({
  419. required IconData icon,
  420. required Color iconColor,
  421. required String title,
  422. Color? titleColor,
  423. Widget? trailing,
  424. VoidCallback? onTap,
  425. }) {
  426. return ClickOpacity(
  427. onTap: onTap,
  428. child: Container(
  429. height: 56.w,
  430. padding: EdgeInsets.symmetric(horizontal: 16.w),
  431. child: Row(
  432. children: [
  433. // 图标
  434. Container(
  435. width: 32.w,
  436. height: 32.w,
  437. decoration: BoxDecoration(
  438. color: iconColor,
  439. borderRadius: BorderRadius.circular(8.r),
  440. ),
  441. child: Icon(icon, size: 20.w, color: Colors.white),
  442. ),
  443. SizedBox(width: 12.w),
  444. // 标题
  445. Expanded(
  446. child: Text(
  447. title,
  448. style: TextStyle(
  449. fontSize: 16.sp,
  450. color:
  451. titleColor ??
  452. Get.reactiveTheme.textTheme.bodyLarge!.color,
  453. fontWeight: FontWeight.w400,
  454. ),
  455. ),
  456. ),
  457. // 右侧内容
  458. if (trailing != null) trailing,
  459. ],
  460. ),
  461. ),
  462. );
  463. }
  464. /// 构建分割线
  465. Widget _buildDivider() {
  466. return Padding(
  467. padding: EdgeInsets.only(left: 60.w),
  468. child: Divider(
  469. height: 1,
  470. color: Get.reactiveTheme.dividerColor.withOpacity(0.3),
  471. ),
  472. );
  473. }
  474. /// 构建徽章
  475. Widget _buildBadge(String text, {bool isPremium = false}) {
  476. return Container(
  477. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.w),
  478. decoration: BoxDecoration(
  479. color: isPremium
  480. ? const Color(0xFFFF9500).withOpacity(0.2)
  481. : Get.reactiveTheme.hintColor.withOpacity(0.2),
  482. borderRadius: BorderRadius.circular(12.r),
  483. ),
  484. child: Row(
  485. mainAxisSize: MainAxisSize.min,
  486. children: [
  487. Icon(
  488. isPremium ? Icons.workspace_premium : Icons.diamond,
  489. size: 14.w,
  490. color: isPremium
  491. ? const Color(0xFFFF9500)
  492. : Get.reactiveTheme.hintColor,
  493. ),
  494. SizedBox(width: 4.w),
  495. Text(
  496. text,
  497. style: TextStyle(
  498. fontSize: 12.sp,
  499. color: isPremium
  500. ? const Color(0xFFFF9500)
  501. : Get.reactiveTheme.hintColor,
  502. fontWeight: FontWeight.w500,
  503. ),
  504. ),
  505. ],
  506. ),
  507. );
  508. }
  509. }