import 'dart:io'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:images_picker/images_picker.dart'; import 'package:provider/provider.dart'; import 'package:sport/bean/forum.dart'; import 'package:sport/bean/image.dart' as photo; import 'package:sport/bean/post.dart'; import 'package:sport/pages/social/gallery_photo_view.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/services/api/inject_api.dart'; import 'package:sport/utils/toast.dart'; import 'package:sport/widgets/appbar.dart'; import 'package:sport/widgets/button_primary.dart'; import 'package:sport/widgets/dialog/alert_dialog.dart'; import 'package:sport/widgets/dialog/bindphone_dialog.dart'; import 'package:sport/widgets/space.dart'; import 'package:umeng_common_sdk/umeng_common_sdk.dart'; class PostPage extends StatefulWidget { final String id; // 论坛Id final Forum? forum; // 论坛实例 final Post? post; // 帖子 转发的情况下 final String? url; // url 转发的情况下 final String? hash; // 转发的情况下 final String? image; // 转发的情况下 final List? forums; // 主要是获取 forums 名字 分享好像拿不到这个东西... const PostPage(this.id, {this.post, this.forum, this.url, this.hash, this.image, this.forums}); @override State createState() => _PageState(); } class _PageState extends State { List imageList = []; TextEditingController? _controller; ValueNotifier _valueNotifier = ValueNotifier(""); FocusNode? _focusNode; ValueNotifier labelIndex = ValueNotifier(0); Forum? selectLabel; @override void initState() { super.initState(); _focusNode = FocusNode(); _controller = TextEditingController()..addListener(() {}); //... if (widget.post != null) { _controller?.text = "转发帖子"; _valueNotifier.value = "转发帖子"; } UmengCommonSdk.onEvent("social_new_post", {}); } @override void dispose() { super.dispose(); _controller?.dispose(); _focusNode?.dispose(); _valueNotifier.dispose(); PaintingBinding.instance?.imageCache?.clear(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( leading: buildBackButton(context), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(""), PrimaryButton( width: 65, height: 35, content: "发布", callback: () async { UmengCommonSdk.onEvent("social_new_post_click", {}); if (await showBindPhoneDialog(context) != true) { return; } // if(widget.forum != null) { // NavigatorUtil.pushAndRemoveUntil(context, (context) => SocialDetailPage(widget.forum, index: 2), RouteSettings(name: "forum")); // }else { // Navigator.of(context).pop(true); // } _focusNode?.unfocus(); String postValue = _valueNotifier.value.trim(); if (postValue == "") { ToastUtil.show("不能发布空白内容喔!"); return; } if (await showDialog( context: context, builder: (context) => CustomAlertDialog(title: '是否确认发布', ok: () => Navigator.of(context).pop(true)), ) != true) { return; } bool result = await showDialog( context: context, barrierDismissible: false, builder: (context) => SimpleDialog( children: [PostAction(selectLabel?.forumId ?? widget.id, postValue, imageList, widget.post?.quoteSubjectId == '0' ? widget.post?.id : widget.post?.quoteSubjectId, widget.url, widget.hash, widget.image)], )); if (result == true) { ToastUtil.show("发布成功"); await Future.delayed(Duration(seconds: 1)); // if(widget.forum != null) { // NavigatorUtil.pushAndRemoveUntil(context, (context) => SocialDetailPage(widget.forum, index: 2), RouteSettings(name: "forum")); // }else { Navigator.of(context).pop(true); // } } else { // ToastUtil.show("已取消发布"); } }, ) ], ), ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Form( onWillPop: () async { if (_valueNotifier.value.isNotEmpty || imageList.isNotEmpty) { bool result = await showDialog( context: context, barrierDismissible: false, builder: (context) { return CustomAlertDialog( title: '确认关闭吗?', ok: () { Navigator.of(context).pop(true); }, ); }) ?? false; return result; } return true; }, child: Column( children: [ TextFormField( focusNode: _focusNode, controller: _controller, keyboardType: TextInputType.multiline, maxLines: 8, maxLength: 500, style: TextStyle(fontSize: 16), strutStyle: StrutStyle(forceStrutHeight: true, height: 1.4), onChanged: (v) { _valueNotifier.value = v; if (_valueNotifier.value.length == 500) { ToastUtil.show("文字数量已达上限"); } }, buildCounter: ( BuildContext context, { required int currentLength, int? maxLength, required bool isFocused, }) { return Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text("$currentLength/$maxLength"), )); }, cursorColor: Theme.of(context).colorScheme.secondary, decoration: InputDecoration(hintText: widget.post == null ? '发表你的看法...' : "", border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 6), hintStyle: TextStyle(color: Color(0xff999999))), ), Space( height: 16, ), widget.post == null ? widget.url != null ? _postLink() : widget.image != null ? _postSharePoster(widget.image!) : GridView.builder( padding: EdgeInsets.zero, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: imageList.length + (imageList.length < 9 ? 1 : 0), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 12.0, mainAxisSpacing: 12.0), itemBuilder: (context, index) { return ClipRRect( borderRadius: BorderRadius.circular(6), child: index >= imageList.length ? InkWell( onTap: () { _select(); }, child: Image.asset( "lib/assets/img/bbs_icon_addimage.png", fit: BoxFit.cover, ), ) : Container( child: Stack( children: [ Image.file( File(imageList[index].path), fit: BoxFit.cover, width: 200, height: 200, ), Align( alignment: Alignment.topRight, child: GestureDetector( onTap: (){ setState(() { imageList.removeAt(index); }); }, child: Container( margin: const EdgeInsets.all(6), padding: const EdgeInsets.all(6), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.black.withOpacity(.6)), child: Image.asset("lib/assets/img/btn_close_small.png", color: Colors.white,), ), )) ], ), ), ); }) : Container( padding: EdgeInsets.all(11.0), width: double.infinity, decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.all(Radius.circular(10)), color: Theme.of(context).scaffoldBackgroundColor), child: _postWidget(), ), // if(widget.url != null) // _postLink(), Space( height: 21.0, ), Divider(), if (widget.forums?.isNotEmpty == true) _postGameLabel(), ], ), ), ), ), ); } Widget _postWidget() { Post? post = widget.post?.quoteSubject ?? widget.post; if (post == null) return Container(); double width = MediaQuery.of(context).size.width - 24 - 22; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( maxLines: 3, overflow: TextOverflow.ellipsis, text: TextSpan(style: Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 16), children: [ TextSpan(text: '${post.nickname}:', style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Theme.of(context).accentColor)), TextSpan(text: '${post.content}', style: Theme.of(context).textTheme.subtitle1!), ]), ), if (post.images?.isNotEmpty == true) GridView.count( physics: new NeverScrollableScrollPhysics(), shrinkWrap: true, padding: EdgeInsets.only(top: 15), childAspectRatio: post.images!.length == 1 ? max(16 / 10, post.images![0].getImageAspectRatio()) : 1, crossAxisSpacing: 10.0, crossAxisCount: min(3, post.images!.length), children: post.images! .asMap() .keys .take(min(3, post.images!.length)) .map((i) => GestureDetector( onTap: () => open(context, i, post.images!), child: i < 2 ? post.images!.length == 1 ? Row( mainAxisSize: MainAxisSize.min, children: [ ClipRRect( borderRadius: BorderRadius.circular(6), child: Stack( children: [ CachedNetworkImage( alignment: Alignment.centerLeft, imageUrl: post.images![i].thumbnail ?? "", fit: BoxFit.cover, width: post.images![i].getWidth(width), ), if (post.images![i].isLongImage()) Positioned( bottom: 4, right: 4, child: Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration(color: Colors.black.withOpacity(.8), borderRadius: BorderRadius.all(Radius.circular(20))), child: Text( "长图", style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Colors.white), ), ), ) ], )) ], ) : ClipRRect(borderRadius: BorderRadius.circular(6), child: CachedNetworkImage(alignment: Alignment.centerLeft, imageUrl: post.images![i].thumbnail ?? "", fit: BoxFit.cover)) : ClipRRect( borderRadius: BorderRadius.circular(6), child: Stack( fit: StackFit.expand, children: [ CachedNetworkImage( imageUrl: post.images![i].thumbnail ?? "", fit: BoxFit.cover, ), if (post.images!.length - 3 > 0) Container( color: Color(0x80000000), child: Center( child: Text( "+${post.images!.length - 3}", style: TextStyle(color: Colors.white, fontSize: 16), ), ), ) ], )))) .toList()), if (post.quoteData != null) _postLink(), ], ); } Widget _postLink() { return Container( padding: EdgeInsets.all(12.0), color: Colors.white, child: Row(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [ Icon( Icons.link, size: 60.0, ), Space( width: 5.0, ), Expanded( child: RichText( maxLines: 3, overflow: TextOverflow.ellipsis, text: TextSpan(style: Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 16), children: [ TextSpan(text: '${Provider.of(context).user.name}:', style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Theme.of(context).accentColor)), TextSpan(text: '分享了他的运动记录,快来围观吧~', style: Theme.of(context).textTheme.subtitle1!), ]), ), ), ]), ); } Widget _postSharePoster(String image) { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Container( constraints: BoxConstraints( maxWidth: 100, maxHeight: 200, ), child: ClipRRect( borderRadius: BorderRadius.circular(6), child: Image.file( File(image), fit: BoxFit.cover, )), ) ], ); } void _select() async { _focusNode?.unfocus(); int max = 9 - imageList.length; if (max <= 0) { Fluttertoast.showToast(msg: "不能再添加了~", backgroundColor: Colors.black, textColor: Colors.white, fontSize: 16.0); return; } try { List? resultList = await ImagesPicker.pick(quality: 0.8, maxSize: 1024, count: 9); if (!mounted) return; setState(() { imageList += resultList ?? []; }); } on Exception catch (e) { return; } } Widget _labelItem(String? title) { if (title == null) return Container(); return Container( padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 16.0), decoration: BoxDecoration(border: Border.all(color: Theme.of(context).accentColor), borderRadius: BorderRadius.all(Radius.circular(44.0))), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( title, style: TextStyle(color: Theme.of(context).accentColor, fontSize: 12.0), strutStyle: StrutStyle(forceStrutHeight: true), ), Space( width: 5.0, ), Image.asset( "lib/assets/img/btn_close_yellow.png", width: 7.0, height: 7.0, ) ], ), ); } Widget _postGameLabel() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ InkWell( child: selectLabel != null ? _labelItem(selectLabel?.gameName ?? "") : Container(), onTap: () { selectLabel = new Forum(); setState(() {}); }, ), InkWell( child: Container( height: 34.0, child: Row( // crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "添加运动标签", style: TextStyle(fontSize: 12.0, color: Color(0xff666666)), ), Divider( height: 2.0, ), Space( width: 4.0, ), Image.asset("lib/assets/img/btn_arrow_bottom.png") ], ), ), onTap: () async { bool flag = await showDialog( context: context, builder: (context) => CustomAlertDialog( title: "添加运动标签", isLine: true, ok: () => Navigator.of(context).pop(true), child: Container( padding: EdgeInsets.only(left: 20.0), width: double.infinity, child: ValueListenableBuilder( valueListenable: labelIndex, builder: (context, index, child) => Wrap(runSpacing: 12.0, spacing: 8.0, children: widget.forums!.asMap().entries.map((e) => _buildDrawerButtonItem(e.value, e.key, labelIndex)).toList()), ), ), )); // print("${labelIndex}=========================================="); if (flag) { selectLabel = widget.forums?[labelIndex.value]; setState(() {}); } }, ), ], ); } Widget _buildDrawerButtonItem(Forum data, int index, ValueNotifier targetIndex) { return InkWell( child: Container( decoration: BoxDecoration(color: index == targetIndex.value ? Theme.of(context).accentColor : Colors.white, borderRadius: BorderRadius.all(Radius.circular(20.0)), border: Border.all(color: index == targetIndex.value ? Colors.white : Theme.of(context).dividerTheme.color!)), padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 20.0), child: Text( data.gameName ?? "", strutStyle: StrutStyle(forceStrutHeight: true), style: TextStyle(fontSize: 14.0, color: index == targetIndex.value ? Colors.white : Color(0xff999999)), ), ), onTap: () { labelIndex.value = index; }, ); } } class PostAction extends StatefulWidget { final String forumId; final String? content; final List? imageList; final String? quoteSubjectId; final String? url; final String? hash; final String? image; const PostAction(this.forumId, this.content, this.imageList, this.quoteSubjectId, this.url, this.hash, this.image); @override State createState() => PostActionState(); } class PostActionState extends State with InjectApi { final Map upload = {}; late ValueNotifier _msg; bool _disposed = false; @override void initState() { super.initState(); _disposed = false; _msg = ValueNotifier("请稍候..."); WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { post(); }); } @override void dispose() { _disposed = true; _msg.dispose(); super.dispose(); } void post() async { List imageList = []; if (widget.imageList?.isNotEmpty == true) { imageList.addAll(widget.imageList!); } if (widget.image != null) { imageList.add(Media(size: 0, path: widget.image!)); } debugPrint("post add image ${imageList.map((e) => e.path).toList()}"); if (imageList.isNotEmpty) { for (var i = 0; i < imageList.length; i++) { if (_disposed) break; Media asset = imageList[i]; if (upload.containsKey(asset)) continue; File file = File(asset.path); _msg.value = "上传图片(${i + 1}/${imageList.length})..."; try { var resp = await api.mediaUp4Subject(file, srcType: "image"); photo.Image? image = resp.data; if (image != null) { upload[asset] = image; debugPrint("post upload image ${image.toJson()}"); } } catch (e, stack) { debugPrintStack(stackTrace: stack); ToastUtil.show("第${i + 1}张图片检测不通过,请勿使用色情、暴力、广告等的图片"); Navigator.of(context).pop(false); return; } // await Future.delayed(Duration(seconds: 3)); } } _msg.value = "发布中..."; // await Future.delayed(Duration(seconds: 3)); if (_disposed) return; var data; // 这里我也没办法知道它之前的名字叫什么吧... if (widget.url != null) { data = await api.postForum(widget.forumId, widget.content ?? "", images: upload.values.map((e) => e.id).toList().join(","), quoteSubjectId: widget.quoteSubjectId, quoteData: '{"username":{"value":"${Provider.of(context, listen: false).user.name}","from":"user#${Provider.of(context, listen: false).user.id}"},"url":{"value":"${widget.url}"},"hash":{"value":"${widget.hash}"}}'); } else { data = await api.postForum(widget.forumId, widget.content ?? "", images: upload.values.map((e) => e.id).toList().join(","), quoteSubjectId: widget.quoteSubjectId); } await Future.delayed(Duration(seconds: 1)); if (data != null && data.code == 0) { for (var i = 0; i < imageList.length; i++) { Media asset = imageList[i]; File file = File(asset.path); file.delete(); } Navigator.of(context).pop(true); } else { Navigator.of(context).pop(false); } } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( children: [ CircularProgressIndicator(), Padding( padding: const EdgeInsets.only(top: 15), child: ValueListenableBuilder(valueListenable: _msg, builder: (BuildContext context, String value, Widget? child) => Text(value)), ) ], ), ); } }