deviceauth_view.dart 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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/widgets/click_opacity.dart';
  7. import 'package:nomo/app/widgets/ix_app_bar.dart';
  8. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  9. import 'package:pinput/pinput.dart';
  10. import '../../../../config/theme/dark_theme_colors.dart';
  11. import '../../../../config/translations/strings_enum.dart';
  12. import '../../../constants/iconfont/iconfont.dart';
  13. import '../../../widgets/info_card.dart';
  14. import '../controllers/deviceauth_controller.dart';
  15. import '../widgets/device_card.dart';
  16. class DeviceauthView extends BaseView<DeviceauthController> {
  17. const DeviceauthView({super.key});
  18. @override
  19. PreferredSizeWidget? get appBar =>
  20. IXAppBar(title: Strings.deviceAuthorization.tr);
  21. @override
  22. Widget buildContent(BuildContext context) {
  23. return Obx(() {
  24. final isPremium = controller.isPremium.value;
  25. return SingleChildScrollView(
  26. padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 20.w),
  27. child: Column(
  28. children: [
  29. if (isPremium) ...[
  30. // VIP用户界面
  31. _buildVIPUserInterface(),
  32. ] else ...[
  33. // 免费用户界面
  34. _buildFreeUserInterface(),
  35. ],
  36. ],
  37. ),
  38. );
  39. });
  40. }
  41. /// 免费用户界面 - 显示授权码和等待授权
  42. Widget _buildFreeUserInterface() {
  43. return Column(
  44. children: [
  45. // 授权码显示区域
  46. _buildAuthCodeDisplay(),
  47. // 说明卡片
  48. _buildInstructionCards(),
  49. ],
  50. );
  51. }
  52. /// VIP用户界面 - 输入授权码和管理设备
  53. Widget _buildVIPUserInterface() {
  54. return Column(
  55. children: [
  56. // 6个输入框在顶部
  57. _buildTopCodeInput(),
  58. 20.verticalSpaceFromWidth,
  59. Padding(
  60. padding: EdgeInsets.symmetric(horizontal: 8.w),
  61. child: Divider(height: 1.w, color: Get.reactiveTheme.dividerColor),
  62. ),
  63. // 设备管理区域
  64. _buildDeviceManagementSection(),
  65. // 授权步骤说明
  66. _buildAuthorizationSteps(),
  67. ],
  68. );
  69. }
  70. /// 授权码显示区域
  71. Widget _buildAuthCodeDisplay() {
  72. return Container(
  73. padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 10.w),
  74. child: Column(
  75. children: [
  76. // 6位授权码
  77. Obx(
  78. () => Text(
  79. controller.authCode.value,
  80. style: TextStyle(
  81. fontSize: 34.sp,
  82. fontWeight: FontWeight.w500,
  83. height: 1.2,
  84. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  85. letterSpacing: 10.w,
  86. ),
  87. ),
  88. ),
  89. 14.verticalSpaceFromWidth,
  90. // 倒计时和复制按钮
  91. Row(
  92. mainAxisAlignment: MainAxisAlignment.center,
  93. children: [
  94. Icon(
  95. IconFont.icon48,
  96. size: 20.w,
  97. color: Get.reactiveTheme.hintColor,
  98. ),
  99. 4.horizontalSpace,
  100. Obx(
  101. () => Text(
  102. controller.countdownText,
  103. style: TextStyle(
  104. fontSize: 12.sp,
  105. height: 1.7,
  106. color: Get.reactiveTheme.hintColor,
  107. ),
  108. ),
  109. ),
  110. 20.horizontalSpace,
  111. Container(
  112. width: 1.w,
  113. height: 20.w,
  114. color: Get.reactiveTheme.dividerColor,
  115. ),
  116. 20.horizontalSpace,
  117. ClickOpacity(
  118. onTap: () {
  119. Clipboard.setData(
  120. ClipboardData(text: controller.authCode.value),
  121. );
  122. controller.copyAuthCode();
  123. },
  124. child: Row(
  125. mainAxisSize: MainAxisSize.min,
  126. children: [
  127. Text(
  128. Strings.copy.tr,
  129. style: TextStyle(
  130. fontSize: 12.sp,
  131. height: 1.7,
  132. color: Get.reactiveTheme.hintColor,
  133. ),
  134. ),
  135. 4.horizontalSpace,
  136. Icon(
  137. IconFont.icon57,
  138. size: 20.w,
  139. color: Get.reactiveTheme.hintColor,
  140. ),
  141. ],
  142. ),
  143. ),
  144. ],
  145. ),
  146. 20.verticalSpaceFromWidth,
  147. // 提示文字
  148. Text(
  149. Strings.pleaseKeepPageOpen.tr,
  150. style: TextStyle(
  151. fontSize: 13.sp,
  152. color: DarkThemeColors.validTermColor,
  153. height: 1.4,
  154. ),
  155. ),
  156. ],
  157. ),
  158. );
  159. }
  160. /// 说明卡片
  161. Widget _buildInstructionCards() {
  162. return InfoCard(
  163. title: '',
  164. items: [
  165. InfoItem(
  166. icon: IconFont.icon11,
  167. title: Strings.authorizationCode.tr,
  168. description: Strings.authorizationCodeDesc.tr,
  169. iconColor: DarkThemeColors.shadowColor,
  170. ),
  171. InfoItem(
  172. icon: IconFont.icon23,
  173. title: Strings.shareWithPreUser.tr,
  174. description: Strings.shareWithPreUserDesc.tr,
  175. iconColor: DarkThemeColors.shadowColor,
  176. ),
  177. InfoItem(
  178. icon: IconFont.icon49,
  179. title: Strings.waitingForAuthorization.tr,
  180. description: Strings.waitingForAuthorizationDesc.tr,
  181. iconColor: DarkThemeColors.shadowColor,
  182. ),
  183. ],
  184. );
  185. }
  186. /// 顶部6个输入框
  187. Widget _buildTopCodeInput() {
  188. return Obx(
  189. () => Pinput(
  190. length: 6,
  191. defaultPinTheme: PinTheme(
  192. width: 50.w,
  193. height: 50.w,
  194. textStyle: TextStyle(
  195. fontSize: 27.sp,
  196. fontWeight: FontWeight.w600,
  197. color: Get.reactiveTheme.primaryColor,
  198. ),
  199. decoration: BoxDecoration(
  200. color: Get.reactiveTheme.highlightColor,
  201. borderRadius: BorderRadius.circular(5.r),
  202. border: Border.all(
  203. color: Get.reactiveTheme.dividerColor,
  204. width: 1.5.w,
  205. ),
  206. ),
  207. ),
  208. focusedPinTheme: PinTheme(
  209. width: 50.w,
  210. height: 50.w,
  211. textStyle: TextStyle(
  212. fontSize: 27.sp,
  213. fontWeight: FontWeight.w600,
  214. color: Get.reactiveTheme.primaryColor,
  215. ),
  216. decoration: BoxDecoration(
  217. color: Get.reactiveTheme.cardColor,
  218. borderRadius: BorderRadius.circular(5.r),
  219. border: Border.all(
  220. color: Get.reactiveTheme.primaryColor,
  221. width: 1.5.w,
  222. ),
  223. ),
  224. ),
  225. submittedPinTheme: PinTheme(
  226. width: 50.w,
  227. height: 50.w,
  228. textStyle: TextStyle(
  229. fontSize: 27.sp,
  230. fontWeight: FontWeight.w600,
  231. color: Get.reactiveTheme.primaryColor,
  232. ),
  233. decoration: BoxDecoration(
  234. color: Get.reactiveTheme.cardColor,
  235. borderRadius: BorderRadius.circular(5.r),
  236. border: Border.all(
  237. color: Get.reactiveTheme.dividerColor,
  238. width: 1.5.w,
  239. ),
  240. ),
  241. ),
  242. onChanged: (value) {
  243. controller.setInputCode(value);
  244. },
  245. onCompleted: (pin) {
  246. controller.submitAuthCode();
  247. },
  248. inputFormatters: [FilteringTextInputFormatter.digitsOnly],
  249. keyboardType: TextInputType.number,
  250. showCursor: true,
  251. cursor: Container(
  252. width: 1.w,
  253. height: 30.w,
  254. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  255. ),
  256. ),
  257. );
  258. }
  259. /// 设备管理区域
  260. Widget _buildDeviceManagementSection() {
  261. return Obx(() {
  262. final inputCodeLength = controller.inputCode.value.length;
  263. final shouldShowLoadingIcon = inputCodeLength == 6;
  264. return Container(
  265. margin: EdgeInsets.only(top: 20.w),
  266. decoration: BoxDecoration(
  267. color: Get.reactiveTheme.highlightColor,
  268. borderRadius: BorderRadius.circular(12.r),
  269. ),
  270. child: Column(
  271. crossAxisAlignment: CrossAxisAlignment.start,
  272. children: [
  273. // 设备列表
  274. ...controller.currentDevices.map((device) {
  275. return DeviceCard(
  276. device: device,
  277. onRelieve: () => controller.removeDevice(device.id),
  278. showRelieveButton: true,
  279. );
  280. }),
  281. // 默认显示等待激活的设备,只有当输入代码长度为6时显示加载图标
  282. AwaitingActivationCard(showLoadingIcon: shouldShowLoadingIcon),
  283. ],
  284. ),
  285. );
  286. });
  287. }
  288. /// 授权步骤说明
  289. Widget _buildAuthorizationSteps() {
  290. return InfoCard(
  291. title: '',
  292. items: [
  293. InfoItem(
  294. icon: IconFont.icon46,
  295. title: Strings.enterCode.tr,
  296. description: Strings.enterCodeDesc.tr,
  297. iconColor: DarkThemeColors.shadowColor,
  298. ),
  299. InfoItem(
  300. icon: IconFont.icon16,
  301. title: Strings.verifyDevice.tr,
  302. description: Strings.verifyDeviceDesc.tr,
  303. iconColor: DarkThemeColors.shadowColor,
  304. ),
  305. InfoItem(
  306. icon: IconFont.icon43,
  307. title: Strings.authorizationSuccessful.tr,
  308. description: Strings.authorizationSuccessfulDesc.tr,
  309. iconColor: DarkThemeColors.shadowColor,
  310. ),
  311. ],
  312. );
  313. }
  314. }