deviceauth_view.dart 10.0 KB

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