import 'dart:convert'; import 'dart:html'; import 'dart:typed_data'; import 'package:avoid_keyboard/avoid_keyboard.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; import 'package:link/view/component/link_btn.dart'; import 'package:link/view/component/toast.dart'; import 'package:link/view/component/web_image.dart'; import 'package:link/view/preview.dart'; import 'package:web_smooth_scroll/web_smooth_scroll.dart'; import '../constants.dart'; import '../utils/api.dart'; import '../utils/http_utils.dart'; import '../utils/size_fit.dart'; import '../utils/storage.dart'; import 'component/link_step.dart'; import 'component/top_container.dart'; import 'package:dio/dio.dart' as adio; import 'package:dio/dio.dart'; import 'component/upload_file.dart'; class AddLink extends StatelessWidget { const AddLink({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Material( color: kBgColor, child: TopContainer(child: AddLinkContent())); } } class AddLinkContent extends StatefulWidget { const AddLinkContent({Key? key}) : super(key: key); @override State createState() => _AddLinkContentState(); } class _AddLinkContentState extends State { TextEditingController controller = TextEditingController(); TextEditingController titleController = TextEditingController(); TextEditingController descController = TextEditingController(); FileUploadInputElement uploadInput = FileUploadInputElement(); bool isLink = true; bool showError = false; Uint8List? chooseImage; String strPicUrl = ''; bool isFirstEnter = true; var social; late List types; late ScrollController scrollController; @override void initState() { scrollController = ScrollController(); Map data = Get.parameters; social = jsonDecode(data['social']); types = social['types']; if (types[0] == 'LINK') { isLink = true; } else { isLink = false; } isFirstEnter = data['add_more'] != '1'; loadCache(); saveCache(); super.initState(); } @override dispose() { clearCache(); controller.dispose(); titleController.dispose(); descController.dispose(); super.dispose(); } loadCache() { var json = isFirstEnter ? StorageUtil().getJSON('tempGuideLink1') : StorageUtil().getJSON('tempLink'); if (json != null) { setState(() { isLink = json['type'] == 'LINK'; controller.text = json['link'].toString(); titleController.text = json['title'].toString(); descController.text = json['desc'].toString(); }); } } Future chooseFile() async { uploadInput.accept = '.png,.jpg,.jpeg'; uploadInput.multiple = false; uploadInput.click(); uploadInput.onChange.listen((e) { final files = uploadInput.files; if (files?.length == 1) { FileReader reader = FileReader(); reader.onLoadEnd.listen((event) { setState(() { chooseImage = reader.result as Uint8List; }); uploadImg(); }); reader.readAsArrayBuffer(files![0]); } }); } Future uploadImg() async { // qrCode.scanRgbaBytes(chooseImage!, 3000, 3000); // if (qrCode.location == null){ // print('aaaa'); // } // else { // print('bbb'); // } // final emvdecode = EMVMPM Map data = await HttpUtils.get(Api.ossFormUpload, params: {'type': 'QRCODE', 'file_ext': 'png'}); Dio dio = Dio(); Map map = data['fields']; map['file'] = await adio.MultipartFile.fromBytes(chooseImage!, filename: 'avatar.png'); adio.FormData formData = adio.FormData.fromMap(map); adio.Response response = await dio.post(data['upload_url'], data: formData); setState(() { strPicUrl = data['view_url']; }); saveCache(); } checkData() { if (isLink && controller.text.isEmpty) { Toast().showInfoText('请粘贴链接地址后再提交\n或选择“稍后添加”', context: context); setState(() { showError = true; }); return; } if (!isLink && chooseImage == null) { Toast().showInfoText('请上传二维码后再提交\n或选择“稍后添加”', context: context); setState(() { showError = true; }); return; } if (isLink) { List rules = []; if (social['url_rules'] != null) { rules = social['url_rules']; } bool isFind = false; for (int i = 0; i < rules.length; i++) { var rule = rules[i]; if (rule['type'] == 'CONTAIN' && controller.text.contains(rule['rule'])) { isFind = true; } } if (rules.isEmpty) { isFind = true; } if (isFind == false) { Toast().showCustomHud(commitAlert(), context: context); return; } } commit(); } commitAlert() { return Container( alignment: Alignment.center, padding: EdgeInsets.only(top: 32.px, bottom: 32.px), child: Column( children: [ Image.asset( 'assets/images/toast_tip.png', width: 48.px, height: 48.px, ), SizedBox( height: 12.px, ), Text( '粘贴的链接地址\n与所选平台不匹配!\n建议返回修改', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 16.px, fontWeight: FontWeight.bold), ), SizedBox( height: 16.px, ), AlertButton( title: '返回修改', isCancel: false, width: 220.px, height: 40.px, callback: () { Toast().hideHud(); }), SizedBox( height: 24.px, ), AlertButton( title: '忽略并提交', isCancel: true, width: 220.px, height: 40.px, callback: () { Toast().hideHud(); commit(); }), ], ), ); } Future commit() async { Toast().showHud(context: context); if (isLink) { var data2 = await HttpUtils.post(Api.userSocials, data: { 'user_url': controller.text, 'description': descController.text, 'social_id': social['id'], 'social_name': social['id'].length == 0 ? social['name'] : '', 'type': 'LINK', 'user_nick': titleController.text }); } else { var data2 = await HttpUtils.post(Api.userSocials, data: { 'user_url': strPicUrl, 'description': descController.text, 'social_id': social['id'], 'social_name': social['id'].length == 0 ? social['name'] : '', 'type': 'QR', 'user_nick': titleController.text }); } Toast().hideHud(); clearCache(); var user = StorageUtil().getJSON('userInfo'); Get.offAllNamed('/', parameters: {'u': user['id']}); // Get.toNamed('/', parameters: {'u': user['id']}); } saveCache() { var data = { "social": social, "type": isLink ? 'LINK' : 'QR', "link": controller.text, "title": titleController.text, "desc": descController.text }; StorageUtil().setJSON(isFirstEnter ? 'tempGuideLink1' : 'tempLink', data); } clearCache() { StorageUtil().remove('tempLink'); StorageUtil().remove('tempGuideLink1'); } intro() { Color tapColor = kBtnColor; if (isLink) { if (controller.text.isNotEmpty) { tapColor = const Color(0xFFA5A5AD); } } else { if (chooseImage != null) { tapColor = const Color(0xFFA5A5AD); } } return Container( color: showError ? const Color(0xFF002FFF) : Colors.transparent, padding: EdgeInsets.only( left: 20.px, right: 20.px, top: showError ? 12.px : 0, bottom: showError ? 12.px : 0), child: Column( children: [ Row( children: [ Container( width: 12.px, height: 12.px, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.px), border: Border.all(color: kThemeColor, width: 1.px)), child: Text( '1', style: TextStyle( color: kThemeColor, fontFamily: 'Link1', fontWeight: FontWeight.w800, fontSize: 8.px), ), ), SizedBox( width: 3.px, ), Text( '去『已选平台-我』找到「', style: TextStyle(color: const Color(0xFFA5A5AD), fontSize: 12.px), ), Text( isLink ? '分享-复制链接' : '我的二维码', style: TextStyle( color: const Color(0xFFA5A5AD), fontSize: 12.px, fontWeight: FontWeight.bold), ), Text( isLink ? '」' : '」保存', style: TextStyle(color: const Color(0xFFA5A5AD), fontSize: 12.px), ), ], ), SizedBox( height: 5.px, ), Row( children: [ Container( width: 12.px, height: 12.px, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.px), border: Border.all(color: kThemeColor, width: 1.px)), child: Text( '2', style: TextStyle( color: kThemeColor, fontFamily: 'Link1', fontWeight: FontWeight.w800, fontSize: 8.px), ), ), SizedBox( width: 3.px, ), Text( '回到此处', style: TextStyle(color: const Color(0xFFA5A5AD), fontSize: 12.px), ), SizedBox( width: 3.px, ), if (!isLink && chooseImage == null) UploadFile( child: Text( '上传二维码', style: TextStyle( color: tapColor, fontSize: 12.px, fontWeight: FontWeight.bold), ), callback: (Uint8List file) { setState(() { chooseImage = file; showError = false; }); uploadImg(); }, ), if (isLink || (chooseImage != null)) GestureDetector( onTap: () { if (isLink) { if (controller.text.isEmpty) { getClipData(); } } else { if (chooseImage == null) { chooseFile(); } } }, child: Text( isLink ? '粘贴链接' : '上传二维码', style: TextStyle( color: tapColor, fontSize: 12.px, fontWeight: FontWeight.bold), ), ) ], ), ], ), ); } switchWarn() { return Container( alignment: Alignment.center, padding: EdgeInsets.only(top: 32.px, bottom: 32.px), child: Column( children: [ SvgPicture.asset( 'assets/icons/question.svg', width: 48.px, height: 48.px, ), SizedBox( height: 12.px, ), Text( isLink ? '切换选择「二维码」\n将使现有「链接」不被展示\n确定要切换吗?' : '切换选择「链接」\n将使现有「二维码」不被展示\n确定要切换吗?', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 16.px, fontWeight: FontWeight.bold), ), SizedBox( height: 16.px, ), AlertButton( title: '确定', isCancel: false, width: 220.px, height: 40.px, callback: () { Toast().hideHud(); setState(() { showError = false; if (isLink) { // strLink = ''; isLink = false; } else { // chooseImage = null; isLink = true; } }); }), SizedBox( height: 24.px, ), AlertButton( title: '再想想', isCancel: true, width: 220.px, height: 40.px, callback: () { Toast().hideHud(); }), ], ), ); } switchType() { if (isLink && controller.text.isNotEmpty) { Toast().showCustomHud(switchWarn(), context: context); return; } if (!isLink && chooseImage != null) { Toast().showCustomHud(switchWarn(), context: context); return; } setState(() { showError = false; isLink = !isLink; }); // clearCache(); } preview() { Toast().showText('长按图片识别或保存', context: context); showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, useSafeArea: false, builder: (BuildContext context) { return Preview( memory: chooseImage, ); }); } Future getClipData() async { await Clipboard.getData(Clipboard.kTextPlain).then((value) { if (value != null && value.text != null) { setState(() { controller.text = value.text!; // strLink = value.text!; // controller.text = strLink; }); } }); } content() { return Column( children: [ SizedBox( height: 32.px, ), if (isFirstEnter) Row(mainAxisAlignment: MainAxisAlignment.center, children: [ LinkStep(step: -1, content: '', hasShadow: false), Container( width: 60.px, height: 8.px, transform: Matrix4.translationValues(-1, 0, 0), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ Color(0xFF74747A), Color(0xFF74747A), kThemeColor ])), ), Container( transform: Matrix4.translationValues(-2, 0, 0), child: LinkStep(step: 2, content: '添加我的第 1 个链接', hasShadow: true), ) ]), if (isFirstEnter) SizedBox( height: 36.px, ), Container( width: 343.px, padding: EdgeInsets.all(20.px), margin: EdgeInsets.only(bottom: 20.px), decoration: BoxDecoration( color: const Color(0xFF2C2C2E), borderRadius: BorderRadius.circular(24.px)), child: GestureDetector( onTap: () { Get.toNamed('/choose_social', parameters: {'update': '1'}) ?.then((value) { if (value == null) { return; } setState(() { social = value; types = value['types']; if (types[0] == 'LINK') { isLink = true; } else { isLink = false; } }); }); }, child: Row( children: [ Expanded( child: Text( '已选平台', style: TextStyle( color: kThemeColor, fontSize: 16.px, fontWeight: FontWeight.bold), )), social['logo'].length == 0 ? ClipRRect( borderRadius: BorderRadius.circular(6.px), child: SvgPicture.asset( 'assets/icons/other_link.svg', width: 24.px, height: 24.px, )) : WebImage( url: social['logo'], width: 24.px, height: 24.px, borderRadius: 6.px, ), SizedBox( width: 8.px, ), Text( social['name'], style: TextStyle( color: const Color(0xFFA5A5AD), fontSize: 14.px), ), Image.asset( 'assets/images/arrow_right.png', width: 20.px, height: 20.px, ) ], ), )), Container( width: 343.px, padding: EdgeInsets.only(top: 20.px, bottom: 20.px), margin: EdgeInsets.only(bottom: 20.px), decoration: BoxDecoration( color: const Color(0xFF2C2C2E), borderRadius: BorderRadius.circular(24.px)), child: Column( children: [ Row( children: [ SizedBox( width: 20.px, ), Expanded( child: Text( '链接方式', style: TextStyle( color: kThemeColor, fontSize: 16.px, fontWeight: FontWeight.bold), )), Container( height: 28.px, // width: 98.px, decoration: BoxDecoration( color: const Color(0xFF131314), borderRadius: BorderRadius.circular(14.px)), child: Row( children: [ Container( padding: EdgeInsets.only(left: 10.px, right: 10.px), height: 28.px, decoration: BoxDecoration( color: kThemeColor, borderRadius: BorderRadius.circular(14.px)), child: Row( children: [ SvgPicture.asset( isLink ? 'assets/icons/link.svg' : 'assets/icons/qrcode.svg', width: 16.px, height: 16.px, color: const Color(0xFF131314), ), SizedBox( width: 2.px, ), Text( isLink ? '链接' : '二维码', style: TextStyle( color: const Color(0xFF131314), fontSize: 12.px), ) ], ), ), if (types.length > 1) GestureDetector( onTap: () { switchType(); }, child: Container( alignment: Alignment.center, width: 36.px, height: 28.px, child: SvgPicture.asset( isLink ? 'assets/icons/qrcode.svg' : 'assets/icons/link.svg', width: 16.px, height: 16.px, color: const Color(0xFF74747A), ), ), ) ], ), ), SizedBox( width: 20.px, ) ], ), SizedBox( height: 8.px, ), intro(), // SizedBox( // height: 28.px, // ), SizedBox( height: 16.px, ), if (isLink) Row( children: [ SizedBox( width: 20.px, ), Expanded( child: TextField( controller: controller, cursorColor: kBtnColor, onChanged: (value) { // setState(() { // strLink = value; // showError = false; // // keyword = value; // }); saveCache(); }, style: TextStyle( color: Colors.white, fontSize: 14.px, fontFamily: 'Link1'), decoration: InputDecoration( border: InputBorder.none, hintText: '请输入链接', hintStyle: TextStyle( fontSize: 14.px, color: const Color(0xFF444447)), counterText: "", ), ), ), if (controller.text.isEmpty) GestureDetector( onTap: () { getClipData(); }, child: SvgPicture.asset( 'assets/icons/copy_link.svg', width: 24.px, height: 24.px, color: kBtnColor, ), ), SizedBox( width: 20.px, ), ], ), if (!isLink) Stack( children: [ DottedBorder( color: const Color(0xFF444447), borderType: BorderType.RRect, radius: Radius.circular(16.px), child: ClipRRect( borderRadius: BorderRadius.circular(16.px), child: Container( width: 303.px, alignment: Alignment.center, padding: EdgeInsets.only(top: 12.px, bottom: 12.px), child: chooseImage != null ? GestureDetector( onTap: () { preview(); }, child: Image.memory( chooseImage!, height: 120.px, ), ) : UploadFile( // width: 120.px, // height: 120.px, child: SvgPicture.asset( 'assets/icons/upload_qrcode.svg', width: 120.px, height: 120.px, ), callback: (Uint8List file) { setState(() { chooseImage = file; showError = false; }); uploadImg(); }, ), ), )), if (chooseImage != null) Positioned( right: 12.px, top: 12.px, child: UploadFile( // width: 68.px, // height: 28.px, child: Container( width: 68.px, height: 28.px, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(14.px), color: const Color(0xBF131314)), child: Text( '重新上传', style: TextStyle( color: kBtnColor, fontSize: 12.px), )), callback: (Uint8List file) { setState(() { chooseImage = file; showError = false; }); uploadImg(); }, )) // GestureDetector( // onTap: () { // chooseFile(); // }, // child: Container( // width: 68.px, // height: 28.px, // alignment: Alignment.center, // decoration: BoxDecoration( // borderRadius: BorderRadius.circular(14.px), // color: const Color(0xBF131314)), // child: Text( // '重新上传', // style: TextStyle( // color: kBtnColor, fontSize: 12.px), // )), // )) ], ), ], ), ), if ((isLink && controller.text.isNotEmpty) || (!isLink && chooseImage != null)) Container( width: 343.px, padding: EdgeInsets.all(20.px), margin: EdgeInsets.only(bottom: 20.px), decoration: BoxDecoration( color: const Color(0xFF2C2C2E), borderRadius: BorderRadius.circular(24.px)), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '标题', style: TextStyle( color: kThemeColor, fontWeight: FontWeight.bold, fontSize: 16.px, height: 1.75), ), SizedBox( height: 8.px, ), SelectableText( '填写在我该平台上的用户名,不填将展示平台名称,如:', style: TextStyle( color: const Color(0xFFA5A5AD), fontSize: 12.px, height: 1.5), ), SizedBox( height: 16.px, ), TextField( cursorColor: kBtnColor, controller: titleController, onChanged: (value) { saveCache(); }, style: TextStyle(color: Colors.white, fontSize: 14.px), decoration: InputDecoration( border: InputBorder.none, hintText: social['url_example'], hintStyle: TextStyle( fontSize: 14.px, color: const Color(0xFF444447)), counterText: "", ), ), Container( height: 1.px, color: const Color(0xFF444447), margin: EdgeInsets.only(top: 18.px, bottom: 18.px), ), Text( '描述', style: TextStyle( color: kThemeColor, fontWeight: FontWeight.bold, fontSize: 16.px, height: 1.75), ), SizedBox( height: 8.px, ), Text( '对以上内容的补充描述,如:', style: TextStyle( color: const Color(0xFFA5A5AD), fontSize: 12.px, height: 1.5), ), SizedBox( height: 16.px, ), TextField( cursorColor: kBtnColor, controller: descController, onChanged: (value) { saveCache(); }, style: TextStyle(color: Colors.white, fontSize: 14.px), decoration: InputDecoration( border: InputBorder.none, hintText: social['description_example'], hintStyle: TextStyle( fontSize: 14.px, color: const Color(0xFF444447)), counterText: "", ), ), ], )), SizedBox( height: 162.px, ) ], ); } @override Widget build(BuildContext context) { SizeFit.initialize(context); return Stack(children: [ Positioned( left: 0, top: 0, right: 0, height: MediaQuery.of(context).size.height, // bottom: 0, child: Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom), child: SingleChildScrollView( // physics: const NeverScrollableScrollPhysics(), controller: scrollController, child: AvoidKeyboard(child: content()), ))), Positioned( left: 0, right: 0, bottom: 0, child: Container( height: isFirstEnter ? 162.px : 140.px, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0x00131314), Color(0xFF131314), Color(0xFF131314), ])), child: Column( children: [ SizedBox( height: 36.px, ), LinkButton( title: '完成', disable: false, isBlack: false, callback: () { checkData(); }), SizedBox( height: 30.px, ), if (isFirstEnter) GestureDetector( child: Text( '稍后添加', style: TextStyle( color: const Color(0xFF74747A), fontSize: 14.px), ), ) ], ), )) ]); } }