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