share.dart 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:typed_data';
  5. import 'package:app_settings/app_settings.dart';
  6. import 'package:dio/dio.dart';
  7. import 'package:fast/constants.dart';
  8. import 'package:fast/model/model.dart';
  9. import 'package:fast/utils/api.dart';
  10. import 'package:fast/utils/global.dart';
  11. import 'package:fast/utils/http_utils.dart';
  12. import 'package:fast/utils/size_fit.dart';
  13. import 'package:fast/view/component/toast.dart';
  14. import 'package:flutter/foundation.dart';
  15. import 'package:flutter/material.dart';
  16. import 'package:flutter/services.dart';
  17. import 'package:fluwx/fluwx.dart';
  18. import 'package:image_gallery_saver/image_gallery_saver.dart';
  19. import 'package:permission_handler/permission_handler.dart';
  20. import 'package:screenshot/screenshot.dart';
  21. import 'alert_widget.dart';
  22. import 'challenge_result.dart';
  23. import 'loading.dart';
  24. // import 'package:widget_to_image/widget_to_image.dart';
  25. // ignore: must_be_immutable
  26. class Share extends StatefulWidget {
  27. String type;
  28. FastResultBean fastResultBean = FastResultBean();
  29. Share({Key? key, required this.type, FastResultBean? resultBean})
  30. : super(key: key) {
  31. if (resultBean != null) {
  32. fastResultBean = resultBean;
  33. }
  34. }
  35. @override
  36. State<Share> createState() => _ShareState();
  37. }
  38. const baseUrl = 'https://api.fast.dev.liveplus.fun/static/image/';
  39. class _ShareState extends State<Share> {
  40. Uint8List? byteData;
  41. Uint8List? qrcodeData;
  42. ScreenshotController screenshotController = ScreenshotController();
  43. @override
  44. void initState() {
  45. getQRCode();
  46. super.initState();
  47. }
  48. Future getQRCode() async {
  49. Dio dio = Dio();
  50. // 注意:这里使用bytes
  51. dio.options.responseType = ResponseType.bytes;
  52. // 如果headers有东西,则添加
  53. Map<String, dynamic> headers = {
  54. 'Authorization': 'Bearer ${Global().token}'
  55. };
  56. dio.options.headers = headers;
  57. try {
  58. String platform = Platform.isIOS?'IOS':'ANDROID';
  59. String path = Api.qrcode+'client='+platform;
  60. Response response = await dio.get(path);
  61. final Uint8List bytes = consolidateHttpClientResponseBytes(response.data);
  62. setState(() {
  63. qrcodeData = bytes;
  64. });
  65. Timer(const Duration(seconds: 2), () {
  66. loadData();
  67. });
  68. } catch (e) {
  69. return await null;
  70. }
  71. // var data = await HttpUtils.get(Api.qrcode);
  72. // if (data != null) {
  73. // String aa = data as String;
  74. // Uint8List bytes = encode(aa);
  75. // setState(() {
  76. // qrcodeData = bytes;
  77. // });
  78. // }
  79. // Timer(const Duration(seconds: 2), () {
  80. // loadData();
  81. // });
  82. }
  83. consolidateHttpClientResponseBytes(List<int> data) {
  84. // response.contentLength is not trustworthy when GZIP is involved
  85. // or other cases where an intermediate transformer has been applied
  86. // to the stream.
  87. final List<List<int>> chunks = <List<int>>[];
  88. int contentLength = 0;
  89. chunks.add(data);
  90. contentLength += data.length;
  91. final Uint8List bytes = Uint8List(contentLength);
  92. int offset = 0;
  93. for (List<int> chunk in chunks) {
  94. bytes.setRange(offset, offset + chunk.length, chunk);
  95. offset += chunk.length;
  96. }
  97. return bytes;
  98. }
  99. Uint8List encode(String s) {
  100. var encodedString = utf8.encode(s);
  101. var encodedLength = encodedString.length;
  102. var data = ByteData(encodedLength + 4);
  103. data.setUint32(0, encodedLength, Endian.big);
  104. var bytes = data.buffer.asUint8List();
  105. bytes.setRange(4, encodedLength + 4, encodedString);
  106. return bytes;
  107. }
  108. void loadData() {
  109. screenshotController.capture().then((value) {
  110. setState(() {
  111. byteData = value as Uint8List;
  112. });
  113. });
  114. }
  115. // Future loadData() async{
  116. // ByteData data = await WidgetToImage.widgetToImage(detail());
  117. // setState(() {
  118. // byteData=data;
  119. // });
  120. // }
  121. Future share(int type) async {
  122. if (byteData == null) {
  123. return;
  124. }
  125. if (type == 2) {
  126. var status = await Permission.storage.request().isGranted;
  127. if (Platform.isIOS) {
  128. status = await Permission.photos.request().isGranted;
  129. var perStatus = await Permission.photos.status;
  130. if (perStatus.isLimited) {
  131. status = true;
  132. }
  133. }
  134. if (status == true) {
  135. var result = await ImageGallerySaver.saveImage(byteData!);
  136. //toast 保存成功
  137. Navigator.of(context).pop();
  138. showDialog(
  139. context: context,
  140. barrierDismissible: false,
  141. barrierColor: Colors.transparent,
  142. builder: (BuildContext context) {
  143. return Toast(
  144. title: '分享图已保存',
  145. content: const SizedBox(
  146. width: 0,
  147. ),
  148. );
  149. });
  150. } else {
  151. //处理拒绝的情况
  152. showDialog(
  153. context: context,
  154. barrierDismissible: false,
  155. barrierColor: const Color(0xF2000D1F),
  156. builder: (BuildContext context) {
  157. return AlertWidget(
  158. title: '为了确保您的完整体验\n请开启fast16cc使用你的相册访问权限',
  159. confirm: '去开启',
  160. confirmCallback: () {
  161. AppSettings.openNotificationSettings();
  162. Navigator.of(context).pop();
  163. });
  164. });
  165. }
  166. return;
  167. }
  168. bool isSuccess = await registerWxApi(
  169. appId: 'wxa8557217acf4f532',
  170. universalLink: 'https://api.fast.liveplus.fun/');
  171. if (isSuccess) {
  172. weChatResponseEventHandler.listen((event) {
  173. if (event is WeChatPaymentResponse) {
  174. } else if (event is WeChatAuthResponse) {
  175. print(event.code);
  176. } else if (event is WeChatShareResponse) {
  177. print(event.toString());
  178. }
  179. });
  180. }
  181. shareToWeChat(
  182. // WeChatShareTextModel("hello world")
  183. WeChatShareImageModel(WeChatImage.binary(byteData!),
  184. title: '风靡全球的16/8间歇性断食法\n高效又好玩,等你来挑战~',
  185. scene: type == 0 ? WeChatScene.SESSION : WeChatScene.TIMELINE));
  186. }
  187. @override
  188. Widget build(BuildContext context) {
  189. SizeFit.initialize(context);
  190. return Material(
  191. child: Stack(
  192. children: [
  193. Opacity(
  194. opacity: 0.01,
  195. child: Screenshot(
  196. child: widget.type == 'me' ? detail() : challenge(),
  197. controller: screenshotController),
  198. ),
  199. Container(
  200. width: 375,
  201. height: 750,
  202. color: kBgColor,
  203. ),
  204. Opacity(
  205. opacity: 1,
  206. child: Container(
  207. color: kBgColor,
  208. alignment: Alignment.center,
  209. child: Stack(
  210. children: [
  211. Container(
  212. margin: EdgeInsets.only(top: 204.px),
  213. width: 335.px,
  214. height: 302.px,
  215. decoration: BoxDecoration(
  216. color: const Color(0xFF313F52),
  217. borderRadius:
  218. BorderRadius.all(Radius.circular(32.px))),
  219. child: Stack(
  220. children: [
  221. Positioned(
  222. top: 12.px,
  223. right: 12.px,
  224. child: GestureDetector(
  225. onTap: () {
  226. Navigator.of(context).pop();
  227. },
  228. child: Container(
  229. alignment: Alignment.center,
  230. decoration: BoxDecoration(
  231. color: const Color(0x1A000000),
  232. borderRadius: BorderRadius.all(
  233. Radius.circular(18.px))),
  234. width: 36.px,
  235. height: 36.px,
  236. child: Image.asset(
  237. 'assets/images/close.png',
  238. width: 20.px,
  239. height: 20.px,
  240. ),
  241. ),
  242. )),
  243. Positioned(
  244. left: 0.0,
  245. right: 0.0,
  246. bottom: 32.px,
  247. child: Opacity(
  248. opacity: byteData == null ? 0.4 : 1.0,
  249. child: Row(
  250. mainAxisAlignment: MainAxisAlignment.center,
  251. crossAxisAlignment:
  252. CrossAxisAlignment.center,
  253. children: [
  254. GestureDetector(
  255. onTap: () {
  256. share(0);
  257. },
  258. child: Column(
  259. children: [
  260. Image.asset(
  261. 'assets/images/share_friend.png',
  262. width: 60.px,
  263. height: 60.px,
  264. ),
  265. SizedBox(
  266. height: 10.px,
  267. ),
  268. Text(
  269. '发送给好友',
  270. style: TextStyle(
  271. color: Colors.white,
  272. fontSize: 14.px),
  273. )
  274. ],
  275. ),
  276. ),
  277. SizedBox(
  278. width: 20.px,
  279. ),
  280. GestureDetector(
  281. onTap: () {
  282. share(1);
  283. },
  284. child: Column(
  285. children: [
  286. Image.asset(
  287. 'assets/images/share_timeline.png',
  288. width: 60.px,
  289. height: 60.px,
  290. ),
  291. SizedBox(
  292. height: 10.px,
  293. ),
  294. Text(
  295. '分享到朋友圈',
  296. style: TextStyle(
  297. color: Colors.white,
  298. fontSize: 14.px),
  299. )
  300. ],
  301. ),
  302. ),
  303. SizedBox(
  304. width: 20.px,
  305. ),
  306. GestureDetector(
  307. onTap: () {
  308. share(2);
  309. },
  310. child: Column(
  311. children: [
  312. Image.asset(
  313. 'assets/images/share_save.png',
  314. width: 60.px,
  315. height: 60.px,
  316. ),
  317. SizedBox(
  318. height: 10.px,
  319. ),
  320. Text(
  321. '保存到相册',
  322. style: TextStyle(
  323. color: Colors.white,
  324. fontSize: 14.px),
  325. )
  326. ],
  327. ),
  328. )
  329. ],
  330. )),
  331. )
  332. ],
  333. ),
  334. ),
  335. Container(
  336. margin: EdgeInsets.only(left: 78.px),
  337. width: 180.px,
  338. height: 360.px,
  339. decoration: BoxDecoration(
  340. color: const Color(0xFF313F52),
  341. boxShadow: [
  342. BoxShadow(
  343. offset: Offset(0.px, 8.px),
  344. blurRadius: 16.px,
  345. color: const Color(0x66000000))
  346. ]),
  347. child: previewContent(),
  348. )
  349. ],
  350. )))
  351. ],
  352. ),
  353. );
  354. }
  355. Widget previewContent() {
  356. // return Container(width: 180.px,height: 360.px,color: Colors.pink,);
  357. if (byteData != null) {
  358. return Image.memory(
  359. byteData!,
  360. width: 180.px,
  361. height: 360.px,
  362. fit: BoxFit.cover,
  363. );
  364. }
  365. return Container(
  366. width: 180.px,
  367. height: 360.px,
  368. alignment: Alignment.center,
  369. margin: EdgeInsets.only(bottom: 20.px),
  370. child: Loading(
  371. showBackground: false,
  372. ));
  373. }
  374. challenge() {
  375. var size = MediaQuery.of(context).size;
  376. double rate = 1.0;
  377. if (size.height / size.width > 2.0) {
  378. if (size.width < 375.px) {
  379. rate = size.width / 375.px * 0.95;
  380. }
  381. } else {
  382. if (size.height < 750.px) {
  383. rate = size.height / 750.px * 0.95;
  384. }
  385. }
  386. return Container(
  387. width: 375.px * rate,
  388. height: 750.px * rate,
  389. color: Colors.transparent,
  390. child: Stack(alignment: AlignmentDirectional.topStart, children: [
  391. Image.network(
  392. '${baseUrl}share_${widget.fastResultBean.days}.png',
  393. width: 375.px * rate,
  394. height: 750.px * rate,
  395. fit: BoxFit.cover,
  396. ),
  397. Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
  398. SizedBox(
  399. width: 375.px * rate,
  400. height: 42.px * rate,
  401. ),
  402. Container(
  403. width: 80.px * rate,
  404. height: 80.px * rate,
  405. decoration: BoxDecoration(boxShadow: [
  406. BoxShadow(
  407. offset: Offset(0, 6.px * rate),
  408. blurRadius: 12.px * rate,
  409. color: const Color(0x80000D1F))
  410. ]),
  411. child: ClipOval(
  412. child: Image.network(
  413. Global().userBean!.avatar!,
  414. width: 80.px * rate,
  415. height: 80.px * rate,
  416. fit: BoxFit.cover,
  417. ),
  418. ),
  419. ),
  420. SizedBox(
  421. height: 11.px * rate,
  422. ),
  423. Text(
  424. Global().userBean!.nickname!,
  425. style: TextStyle(
  426. color: Colors.white,
  427. fontSize: 20.px * rate,
  428. height: 1.0,
  429. fontWeight: FontWeight.bold),
  430. ),
  431. Container(
  432. width: 324.px * rate,
  433. height: 260.px * rate,
  434. margin: EdgeInsets.only(top: 94.px * rate),
  435. decoration: BoxDecoration(
  436. color: const Color(0xCC000D1F),
  437. border: Border.all(color: kThemeColor, width: 1),
  438. borderRadius:
  439. BorderRadius.all(Radius.circular(34.px * rate)),
  440. image: const DecorationImage(
  441. image: AssetImage('assets/images/statistic.png'),
  442. alignment: Alignment.topRight,
  443. scale: 2.9)),
  444. child: Statistic(
  445. resultBean: widget.fastResultBean,
  446. rate: rate,
  447. ))
  448. ]),
  449. if (qrcodeData != null)
  450. Positioned(
  451. left: 245.px * rate,
  452. bottom: 106.px * rate,
  453. child: Image.memory(
  454. qrcodeData!,
  455. width: 100.px * rate,
  456. height: 100.px * rate,
  457. )),
  458. Positioned(
  459. left: 30.px * rate,
  460. bottom: 102.px * rate,
  461. child: Column(
  462. mainAxisAlignment: MainAxisAlignment.start,
  463. crossAxisAlignment: CrossAxisAlignment.start,
  464. children: [
  465. SizedBox(
  466. height: 5.px * rate,
  467. ),
  468. Text(
  469. Global().userBean!.inviteCode!,
  470. style: TextStyle(
  471. color: kThemeColor,
  472. fontSize: 36.px * rate,
  473. fontFamily: 'Exo2',
  474. fontWeight: FontWeight.w600),
  475. ),
  476. SizedBox(height: 2.px,),
  477. Text(
  478. '使用我的邀请码加入',
  479. style: TextStyle(
  480. color: Colors.white,
  481. fontSize: 14.px * rate,
  482. fontWeight: FontWeight.bold),
  483. ),
  484. Row(
  485. children: [
  486. Text(
  487. '立即获得逆龄石',
  488. style: TextStyle(
  489. color: Colors.white,
  490. fontSize: 20.px * rate,
  491. fontWeight: FontWeight.bold),
  492. ),
  493. SizedBox(
  494. width: 4.px * rate,
  495. ),
  496. Text(
  497. '+3',
  498. style: TextStyle(
  499. color: const Color(0x99FFFFFF),
  500. fontSize: 20.px * rate,
  501. fontFamily: 'Exo2',
  502. fontWeight: FontWeight.w600),
  503. ),
  504. SizedBox(
  505. width: 4.px * rate,
  506. ),
  507. Image.asset(
  508. 'assets/images/stone.png',
  509. width: 24.px * rate,
  510. height: 24.px * rate,
  511. )
  512. ],
  513. )
  514. ],
  515. )),
  516. ]));
  517. }
  518. detail() {
  519. var size = MediaQuery.of(context).size;
  520. double rate = 1.0;
  521. if (size.height / size.width > 2.0) {
  522. if (size.width < 375.px) {
  523. rate = size.width / 375.px * 0.95;
  524. }
  525. } else {
  526. if (size.height < 750.px) {
  527. rate = size.height / 750.px * 0.95;
  528. }
  529. }
  530. return Container(
  531. width: rate * 375.px,
  532. height: rate * 750.px,
  533. color: Colors.transparent,
  534. child: Stack(
  535. alignment: AlignmentDirectional.topStart,
  536. children: [
  537. Image.network(
  538. '${baseUrl}share_bg.png',
  539. width: rate * 375.px,
  540. height: rate * 750.px,
  541. fit: BoxFit.cover,
  542. ),
  543. Column(
  544. crossAxisAlignment: CrossAxisAlignment.center,
  545. children: [
  546. SizedBox(
  547. width: rate * 375.px,
  548. height: rate * 46.px,
  549. ),
  550. Container(
  551. width: rate * 80.px,
  552. height: rate * 80.px,
  553. decoration: BoxDecoration(boxShadow: [
  554. BoxShadow(
  555. offset: Offset(0, rate * 6.px),
  556. blurRadius: rate * 12.px,
  557. color: const Color(0x80000D1F))
  558. ]),
  559. child: ClipOval(
  560. child: Image.network(
  561. Global().userBean!.avatar!,
  562. width: rate * 80.px,
  563. height: rate * 80.px,
  564. fit: BoxFit.cover,
  565. ),
  566. ),
  567. ),
  568. SizedBox(
  569. height: rate * 16.px,
  570. ),
  571. Text(
  572. Global().userBean!.nickname!,
  573. style: TextStyle(
  574. color: Colors.white,
  575. fontSize: rate * 20.px,
  576. fontWeight: FontWeight.bold),
  577. ),
  578. Row(
  579. mainAxisAlignment: MainAxisAlignment.center,
  580. children: [
  581. Container(
  582. margin: EdgeInsets.only(top: rate * 8.px),
  583. padding: EdgeInsets.only(
  584. left: rate * 9.px, right: rate * 9.px),
  585. height: rate * 24.px,
  586. alignment: Alignment.center,
  587. decoration: BoxDecoration(
  588. border: Border.all(
  589. color: const Color(0x80FFFFFF),
  590. width: 1,
  591. ),
  592. borderRadius:
  593. BorderRadius.all(Radius.circular(rate * 12.px)),
  594. color: const Color(0x33000D1F)),
  595. child: Row(
  596. mainAxisAlignment: MainAxisAlignment.center,
  597. children: [
  598. Container(
  599. margin:EdgeInsets.only(bottom: 1.px),
  600. child: const Text(
  601. '逆龄石',
  602. style: TextStyle(color: Colors.white, fontSize: 12),
  603. ),),
  604. SizedBox(
  605. width: rate * 4.px,
  606. ),
  607. Image.asset(
  608. 'assets/images/stone.png',
  609. width: rate * 16.px,
  610. height: rate * 16.px,
  611. ),
  612. SizedBox(
  613. width: rate * 4.px,
  614. ),
  615. Container(
  616. margin:EdgeInsets.only(bottom: 2.px),
  617. child:Text(
  618. '×${Global().userBean!.rjvBalance}',
  619. style: TextStyle(
  620. color: kThemeColor,
  621. fontSize: rate * 14.px,
  622. fontFamily: 'Exo2',
  623. fontWeight: FontWeight.w600),
  624. ))
  625. ],
  626. ),
  627. )
  628. ],
  629. ),
  630. ],
  631. ),
  632. if (qrcodeData != null)
  633. Positioned(
  634. left: rate * 245.px,
  635. bottom: rate * 106.px,
  636. child: Image.memory(
  637. qrcodeData!,
  638. width: rate * 100.px,
  639. height: rate * 100.px,
  640. )),
  641. Positioned(
  642. left: rate * 2.px,
  643. top: rate * 244.px,
  644. child: Text(
  645. Global().userBean!.nickname!,
  646. style: TextStyle(
  647. color: const Color(0xFF000D1F),
  648. fontSize: rate * 10.px,
  649. fontWeight: FontWeight.bold),
  650. )),
  651. Positioned(
  652. left: rate * 106.px,
  653. top: rate * 297.px,
  654. child: ClipOval(
  655. child: Image.network(
  656. Global().userBean!.avatar!,
  657. width: rate * 32.px,
  658. height: rate * 32.px,
  659. fit: BoxFit.cover,
  660. ),
  661. )),
  662. Positioned(
  663. left: rate * 143.px,
  664. top: rate * 298.px,
  665. child: Text(
  666. Global().userBean!.nickname!,
  667. style: TextStyle(
  668. color: Colors.white,
  669. fontSize: rate * 10.px,
  670. fontWeight: FontWeight.bold),
  671. )),
  672. Positioned(
  673. left: rate * 30.px,
  674. bottom: rate * 102.px,
  675. child: Column(
  676. mainAxisAlignment: MainAxisAlignment.start,
  677. crossAxisAlignment: CrossAxisAlignment.start,
  678. children: [
  679. SizedBox(
  680. height: rate * 5.px,
  681. ),
  682. Text(
  683. Global().userBean!.inviteCode!,
  684. style: TextStyle(
  685. color: kThemeColor,
  686. fontSize: rate * 36.px,
  687. fontFamily: 'Exo2',
  688. fontWeight: FontWeight.w600),
  689. ),
  690. SizedBox(
  691. height: rate * 2.px,
  692. ),
  693. Text(
  694. '使用我的邀请码加入',
  695. style: TextStyle(
  696. color: Colors.white,
  697. fontSize: rate * 14.px,
  698. fontWeight: FontWeight.bold),
  699. ),
  700. Row(
  701. children: [
  702. Text(
  703. '立即获得逆龄石',
  704. style: TextStyle(
  705. color: Colors.white,
  706. fontSize: rate * 20.px,
  707. fontWeight: FontWeight.bold),
  708. ),
  709. SizedBox(
  710. width: rate * 4.px,
  711. ),
  712. Text(
  713. '+3',
  714. style: TextStyle(
  715. color: const Color(0x99FFFFFF),
  716. fontSize: rate * 20.px,
  717. fontFamily: 'Exo2',
  718. fontWeight: FontWeight.w600),
  719. ),
  720. SizedBox(
  721. width: rate * 4.px,
  722. ),
  723. Image.asset(
  724. 'assets/images/stone.png',
  725. width: rate * 24.px,
  726. height: rate * 24.px,
  727. )
  728. ],
  729. )
  730. ],
  731. )),
  732. ],
  733. ));
  734. }
  735. }