import 'dart:io'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:multi_image_picker/multi_image_picker.dart'; import 'package:path_provider/path_provider.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/bean/user.dart'; import 'package:sport/pages/social/gallery_photo_view.dart'; import 'package:sport/pages/social/social_detail_page.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/router/navigator_util.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/label.dart'; import 'package:sport/widgets/space.dart'; import 'chat_page.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 ="转发帖子"; } } @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 { 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 == null? '':selectLabel.forumId, postValue, imageList, widget.post?.quoteSubjectId == '0' ? widget.post?.id : widget.post?.quoteSubjectId,widget.url,widget.hash,widget.image) ], )); if (result == true) { ToastUtil.show("发布成功"); // 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, { int currentLength, int maxLength, bool isFocused, }) { return Align(alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(left:8.0), child: Text("$currentLength/$maxLength"), )); }, 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 InkWell( onTap: () => _select(), child: ClipRRect( borderRadius: BorderRadius.circular(6), child: index >= imageList.length ? Image.asset( "lib/assets/img/bbs_icon_addimage.png", fit: BoxFit.cover, ) : AssetThumb( asset: imageList[index], quality: 50, width: 200, height: 200, ), ), ); }) : 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(), _postGameLabel(), ], ), ), ), ), ); } Widget _postWidget() { Post post = widget.post.quoteSubject ?? widget.post; 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.length > 0) 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: [ CachedNetworkImage( imageUrl: avatarList[4], width: 60.0, height: 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(); List resultList; String error; // int max = 9 - imageList.length; // if (max <= 0) { // Fluttertoast.showToast(msg: "不能再添加了~", backgroundColor: Colors.black, textColor: Colors.white, fontSize: 16.0); // return; // } try { resultList = await MultiImagePicker.pickImages( maxImages: 9, selectedAssets: imageList, materialOptions: MaterialOptions( actionBarTitle: "选择图片", allViewTitle: "选择图片", useDetailsView: true, startInAllView: true, selectionLimitReachedText: "不能选择更多了~", ), cupertinoOptions: CupertinoOptions( selectionFillColor: "#ff11ab", selectionTextColor: "#ffffff", selectionCharacter: "✓", )); } on Exception catch (e) { error = e.toString(); } // If the widget was removed from the tree while the asynchronous platform // message was in flight, we want to discard the reply rather than calling // setState to update our non-existent appearance. if (!mounted) return; if (resultList == null || resultList.isEmpty) return; setState(() { imageList = resultList; }); } 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]; print("${selectLabel.toJson()}-----------------------------------"); 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 = {}; 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(Asset(Uri.file(widget.image).toString() ,"", 0, 0)); } if (imageList != null && imageList.isNotEmpty) { Directory tempDir = await getTemporaryDirectory(); Directory directory = new Directory('${tempDir.path}/upload'); if (!directory.existsSync()) { directory.createSync(); print('文档初始化成功,文件保存路径为 ${directory.path}'); } for (var i = 0; i < imageList.length; i++) { if (_disposed) break; Asset asset = imageList[i]; if (upload.containsKey(asset)) continue; ByteData byteData = await asset.getByteData(quality: 85); File file = File('${directory.path}/${DateTime.now().millisecondsSinceEpoch}_$i.jpg'); List bytes = byteData.buffer.asUint8List().toList(); print('临时文件 ${file.path} ${bytes.length}'); file.writeAsBytesSync(bytes); _msg.value = "上传图片(${i + 1}/${imageList.length})..."; var resp = await api.mediaUp4Subject(file, srcType: "image"); photo.Image image = resp.data; file.delete(); upload[asset] = image; // 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); } if (data != null && data.code == 0) { 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)), ) ], ), ); } }