123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- import 'dart:async';
- import 'dart:convert';
- import 'dart:io';
- import 'dart:math';
- import 'package:flutter/cupertino.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/scheduler.dart';
- import 'package:flutter/services.dart';
- import 'package:image_picker/image_picker.dart';
- import 'package:multi_image_picker/multi_image_picker.dart';
- import 'package:path_provider/path_provider.dart';
- import 'package:sport/bean/message.dart';
- import 'package:sport/db/message_db.dart';
- import 'package:sport/pages/social/chat_page.dart';
- import 'package:sport/services/api/inject_api.dart';
- import 'package:sport/utils/toast.dart';
- import 'package:sport/widgets/button_primary.dart';
- import 'package:sport/widgets/decoration.dart';
- import 'package:sport/widgets/space.dart';
- import '../application.dart';
- import 'dialog/request_dialog.dart';
- class MenuBar extends StatefulWidget {
- final String inputField;
- final Function closeMenuCallBack;
- final Widget messageList;
- final Function scrollToBottom;
- final MenuIdentity menuIdentity;
- final Function sendCallBack;
- final GlobalKey globalkey;
- MenuBar(
- this.messageList, {
- @required this.menuIdentity,
- this.inputField,
- this.closeMenuCallBack,
- this.scrollToBottom,
- this.sendCallBack,
- this.globalkey,
- });
- @override
- State<StatefulWidget> createState() {
- // TODO: implement createState
- return _MenuBarState();
- }
- }
- class _MenuBarState extends State<MenuBar>
- with WidgetsBindingObserver, InjectApi {
- GlobalKey _myKey = new GlobalKey(); // 用来定位Message位置
- List<Asset> imageList = []; // 选图片的列表
- File _image; // 拍照后的图片路径
- String _textFieldValue = ""; // TextField的文本
- // TextField的 controller
- TextEditingController _controller;
- FocusNode _focusNode = new FocusNode(); // TextField 的 focus
- double keyBoardHeight = 270.0; // 初始化下面menu的高度 后续会动态调整后 优化
- bool isFirst = true; // flag 优化menu高度的操作
- String emojiJson; // 读取的JSON
- int isShowMenuBottomIndex = 0; // 用数字去优化if else 的判断
- double height;
- Timer _timer;
- bool showInput = true;
- @override
- void didUpdateWidget(MenuBar oldWidget) {
- super.didUpdateWidget(oldWidget);
- widget.scrollToBottom();
- }
- void initState() {
- super.initState();
- initEmoji();
- _controller = TextEditingController();
- // _focusNode.addListener(() {
- // if (_focusNode.hasFocus) {
- //// setState(() {
- //// isShowExtMenu = false;
- //// });
- // }
- // });
- }
- // 直接进来就请求了 不要 搞这些骚的....
- void initEmoji() async {
- String json = await DefaultAssetBundle.of(context)
- .loadString("lib/assets/json/emoji_list.json");
- setState(() {
- emojiJson = json;
- });
- }
- //页面销毁
- @override
- void dispose() {
- super.dispose();
- //释放
- _focusNode.dispose();
- _timer?.cancel();
- }
- // 这里 插库 + 渲染更新...
- Future add(MessageInstance message) async {
- // 聊天的时候 是没有返回 curId的 本地存储的时候 就得自己构造一个? 或者是不存?
- await MessageDB().insert(new MessageItem(
- message: message, status: 0, userId: message.toUser.id, curId: 0));
- var list = await MessageDB().findHasUserId(message.toUser.id);
- if (list.length == 0) {
- await MessageDB()
- .insertUser(new UserTableInfo(userId: message.toUser.id, isTop: 0));
- }
- // view 上setstate 渲染的...
- widget.sendCallBack(message); // 添加完 在 callBack...
- }
- _postFeedBackpostFeedBack(String content, {int typeId = 0}) async {
- typeId == null ? typeId = 0 : typeId = typeId;
- await api.postFeedback(typeId, content,
- extra: await Application.getDeviceInfo());
- }
- Widget extMenuItem(
- String text,
- String url,
- int index,
- ) {
- String error;
- void getPicture() async {
- try {
- // 这里是拿到想要发的图片
- List<Asset> resultList = await MultiImagePicker.pickImages(
- maxImages: 9,
- selectedAssets: imageList,
- materialOptions: MaterialOptions(
- actionBarTitle: "选择图片",
- allViewTitle: "选择图片",
- useDetailsView: true,
- startInAllView: true,
- selectionLimitReachedText: "不能选择更多了~",
- ),
- cupertinoOptions: CupertinoOptions(
- selectionFillColor: "#ff11ab",
- selectionTextColor: "#ffffff",
- selectionCharacter: "✓",
- ));
- List<String> urls = [];
- if (resultList != null) {
- Directory tempDir = await getTemporaryDirectory();
- Directory directory = new Directory('${tempDir.path}/upload');
- if (!directory.existsSync()) {
- directory.createSync();
- print('文档初始化成功,文件保存路径为 ${directory.path}');
- }
- for (var i = 0; i < resultList.length; i++) {
- Asset asset = resultList[i];
- ByteData byteData = await asset.getByteData(quality: 85);
- File file = File(
- '${directory.path}/${DateTime.now().millisecondsSinceEpoch}_$i.jpg');
- List<int> bytes = byteData.buffer.asUint8List().toList();
- print('临时文件 ${file.path} ${bytes.length}');
- file.writeAsBytesSync(bytes);
- String url = (await api.postChatUpload(file)).data["url"];
- urls.add(url);
- file.delete();
- }
- }
- if (widget.menuIdentity.menuScene == "chat") {
- if (urls.length > 0) {
- for (int i = 0; i < urls.length; i++) {
- MessageInstance message = (await api.postChatSend(
- widget.menuIdentity.userId,
- "image",
- '{"url":"${urls[i]}"}'))
- .data;
- add(message);
- }
- }
- }
- } on Exception catch (e) {
- error = e.toString();
- }
- }
- void getPhoto() async {
- try {
- // 拍完直接发...
- final pickedFile = await new ImagePicker()
- .getImage(source: ImageSource.camera, imageQuality: 70);
- // 如果是聊天 直接发 ...
- if (widget.menuIdentity.menuScene == "chat") {
- await request(context, () async {
- // 先上传
- var data = (await api.postChatUpload(File(pickedFile.path))).data;
- print(data['url']);
- // print("${data}-----------------------------");
- MessageInstance message = (await api.postChatSend(
- widget.menuIdentity.userId,
- "image",
- '{ "url":"${data['url']}" }'))
- .data;
- add(message);
- });
- }
- } on Exception catch (e) {
- error = e.toString();
- }
- }
- List<Function> menuOperation = [getPicture, getPhoto];
- return InkWell(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: <Widget>[
- Container(
- padding: EdgeInsets.fromLTRB(14.0, 16.0, 14.0, 14.0),
- child: Image.asset("lib/assets/img/$url.png"),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(10.0),
- color: Color(0xfff1f1f1),
- ),
- ),
- SizedBox(
- height: 5.0,
- ),
- Text("$text")
- ],
- ),
- onTap: () {
- menuOperation[index]();
- },
- );
- }
- Widget _emoJiList() {
- if (emojiJson != null && emojiJson.length != 0) {
- List<dynamic> data = json.decode(emojiJson);
- return Container(
- height: keyBoardHeight,
- padding: EdgeInsets.only(left: 5, top: 5, right: 5, bottom: 5),
- decoration: BoxDecoration(
- color: Colors.white,
- border:
- Border(top: BorderSide(width: 1.0, color: Color(0xFFDCDCDC)))),
- child: GridView.custom(
- padding: EdgeInsets.all(3),
- shrinkWrap: true,
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 6,
- mainAxisSpacing: 0.5,
- crossAxisSpacing: 6.0,
- ),
- childrenDelegate: SliverChildBuilderDelegate(
- (context, index) {
- return GestureDetector(
- onTap: () {
- String intPutString = _controller.text +
- String.fromCharCode(data[index]["unicode"]);
- var content = intPutString;
- _controller.value = TextEditingValue(
- // 设置内容
- text: content,
- // 保持光标在最后
- selection: TextSelection.fromPosition(TextPosition(
- affinity: TextAffinity.downstream,
- offset: content.length)));
- // 主要是 onchange 没有办法 加上 表情 ...
- setState(() {});
- },
- child: Center(
- child: Text(
- String.fromCharCode(data[index]["unicode"]),
- style: TextStyle(fontSize: 33),
- ),
- ),
- );
- },
- childCount: data.length,
- ),
- ),
- );
- }
- return Container();
- }
- Widget menuBottom() {
- // 这里的软键盘动画还是有点问题....
- List<Widget> list = [
- Container(),
- SizedBox(
- height: MediaQuery.of(context).viewInsets.bottom,
- ),
- Container(
- height: keyBoardHeight,
- padding: EdgeInsets.only(left: 24.0, right: 24.0),
- decoration: BoxDecoration(
- color: Colors.white,
- border: Border(
- top: BorderSide(width: 1.0, color: Color(0xFFDCDCDC)))),
- child: GridView(
- shrinkWrap: true,
- padding: EdgeInsets.only(top: 24.0),
- physics: NeverScrollableScrollPhysics(),
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 4, //横轴三个子widget
- ),
- children: <Widget>[
- extMenuItem("图片", "bbs_icon_picture", 0),
- extMenuItem("拍照", "bbs_icon_photo", 1),
- ])),
- _emoJiList(),
- ];
- return list[isShowMenuBottomIndex];
- }
- Widget build(BuildContext context) {
- // 优化 输入键盘高度 跟 菜单栏高度的操作
- if (isFirst && MediaQuery.of(context).viewInsets.bottom != 0.0) {
- setState(() {
- keyBoardHeight = MediaQuery.of(context).viewInsets.bottom;
- isFirst = false;
- });
- }
- return Column(
- children: <Widget>[
- Expanded(
- child: Column(
- children: <Widget>[
- Flexible(
- child: GestureDetector(
- onTap: () {
- setState(() {
- isShowMenuBottomIndex = 0;
- });
- _focusNode.unfocus();
- },
- child: widget.messageList,
- )),
- ],
- ),
- ),
- if (showInput)
- // Column(
- // children: <Widget>[
- //// Container(
- //// color: Colors.white,
- //// padding: EdgeInsets.only(top: 10.0,left: 10.0,right: 10.0,bottom: 10.0),
- //// child:
- //// ),
- // ,
- // ],
- // ),
- Container(
- padding: const EdgeInsets.symmetric(
- vertical: 8.0, horizontal: 12.0),
- decoration: shadowTop(),
- child: Row(
- children: <Widget>[
- GestureDetector(
- onTap: () async {
- if (_focusNode.hasFocus) {
- await SystemChannels.textInput
- .invokeMethod('TextInput.hide');
- setState(() {
- isShowMenuBottomIndex = 2;
- });
- } else {
- setState(() {
- isShowMenuBottomIndex = 2;
- });
- }
- },
- child: Padding(
- // padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
- child: Image.asset(
- "lib/assets/img/bbs_icon_addmore.png"),
- padding: EdgeInsets.only(right: 12.0),
- ),
- ),
- GestureDetector(
- onTap: () {
- setState(() {
- isShowMenuBottomIndex = 3;
- FocusScope.of(context).requestFocus(_focusNode);
- if (_focusNode.hasFocus) {
- SystemChannels.textInput
- .invokeMethod('TextInput.hide');
- }
- });
- },
- child: Padding(
- // padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
- child: Image.asset(
- "lib/assets/img/bbs_icon_expression.png"),
- padding: EdgeInsets.only(right: 12.0),
- ),
- ),
- Expanded(
- child: TextField(
- controller: _controller,
- focusNode: _focusNode,
- keyboardType: TextInputType.multiline,
- style: TextStyle(
- fontSize: 16.0,
- ),
- strutStyle: StrutStyle(forceStrutHeight: true, height: 1.4),
- minLines: 1,
- maxLines: 3,
- // maxLength: 200,
- onChanged: (value) {
- setState(() {
- // _textFieldValue = value;
- });
- },
- onTap: () {
- setState(() {
- isShowMenuBottomIndex = 1;
- });
- widget.scrollToBottom();
- },
- decoration: InputDecoration(
- filled: true,
- fillColor: Color(0xfff1f1f1),
- counterText: "",
- hintText: "${widget.inputField}",
- contentPadding:
- EdgeInsets.symmetric(horizontal: 10.0, vertical: 0.0),
- border: OutlineInputBorder(
- borderSide:
- BorderSide(color: Color(0xfff1f1f1), width: 0.5),
- borderRadius: BorderRadius.all(Radius.circular(44.0)),
- ),
- focusedBorder: OutlineInputBorder(
- borderSide:
- BorderSide(color: Color(0xfff1f1f1), width: 1.0),
- borderRadius: BorderRadius.all(Radius.circular(10.0)),
- ),
- enabledBorder: OutlineInputBorder(
- borderSide:
- BorderSide(color: Color(0xfff1f1f1), width: 0.5),
- borderRadius: BorderRadius.all(Radius.circular(10.0)),
- ),
- ),
- ),
- ),
- Space(
- width: 5.0,
- ),
- PrimaryButton(
- width: 75,
- height: 35.0,
- content: "发送",
- callback: () async {
- if (_controller.text.length <= 0) {
- ToastUtil.show("请输入正确的内容");
- return;
- }
- if (widget.menuIdentity.menuScene == "chat") {
- MessageInstance message = (await api.postChatSend(
- widget.menuIdentity.userId,
- "text",
- '{"text":"${_controller.text}"}'))
- .data;
- await add(
- message); // await 是等待的标志 我等待完 在做后面的init 的事?
- _controller.text = "";
- }
- if (widget.menuIdentity.menuScene == "feedback") {
- await _postFeedBackpostFeedBack(_textFieldValue);
- }
- // 这里可能传不了 callback 所有需要的值都在menu bar里面 只能通过传 不同的 场景 进行 不同的操作
- },
- shadow: _controller.text.length <= 0 ? false : true,
- buttonColor:
- _controller.text == "" ? Color(0xffd2d2d2) : null,
- ),
- ],
- )),
- // 底部的骚操作
- menuBottom(),
- ],
- );
- }
- }
- // 只是用来封装 scrollToBottom
- class GetMenuController {
- ScrollController scrollMenuController = new ScrollController();
- // 暂时不用 这个 scrollToBottom ...
- void scrollToBottom(BuildContext context, GlobalKey key, bool first) {
- scrollMenuController.addListener(() {
- print("[offSet:]${scrollMenuController.offset}----------------------");
- });
- // 页面一屏的高度
- double pageHeight = MediaQuery.of(context).size.height;
- // 头部的高度...
- double appBarAndHeight = MediaQuery.of(context).padding.top + 56;
- // 尾部的高度...
- double bottomHeight = 160;
- // scrollView 的 高度 ...
- double scrollHeight = key.currentContext.size.height;
- print("[scrollHeight]:$scrollHeight----------------------------");
- double position = scrollMenuController?.position?.maxScrollExtent;
- double currentOffset = scrollMenuController.offset;
- double getScrollTop(bool first) {
- if (first) {
- return scrollHeight;
- } else {
- // 不需要滚动的状态... 当前 所处的位置 需不需要 加上 bottomHeight...
- if (currentOffset < bottomHeight) {
- return position + appBarAndHeight + bottomHeight;
- } else {
- return position + appBarAndHeight;
- }
- }
- }
- if (scrollMenuController.hasClients) {
- Future.delayed(Duration(milliseconds: 30), () {
- scrollMenuController?.jumpTo(getScrollTop(first));
- });
- }
- }
- // 后续修改为用这个...
- void scroll() {
- // SchedulerBinding.instance.addPostFrameCallback((_) {
- ////here the sublist is already build
- // scrollMenuController
- // .jumpTo(scrollMenuController.position.maxScrollExtent);
- // print(
- // "${scrollMenuController.position.minScrollExtent} - ${scrollMenuController.position.maxScrollExtent}");
- // });
- }
- }
- // 自己内部传的 bean 用来鉴别 是什么 场景使用的 menuBar
- class MenuIdentity {
- String menuScene; // 当前的MenuBar 所属的场景在哪 比如 聊天 chat / 社区 social / 反馈 feedBack 等
- int userId; // Chat的时候需要的userId
- MenuIdentity({this.menuScene, this.userId});
- MenuIdentity.fromJson(Map<String, dynamic> json) {
- menuScene = json['menuScene'];
- userId = json['userId'];
- }
- Map<String, dynamic> toJson() {
- final Map<String, dynamic> data = new Map<String, dynamic>();
- data['menuScene'] = this.menuScene;
- data['userId'] = this.userId;
- return data;
- }
- }
|