flutter 微信输入框 (第二版)

微信的聊天输入框之前实现了一个版本(flutter 微信聊天输入框_flutter 聊天输入框-CSDN博客),

但是之前实现的不太优雅。这两天重写了一遍。效果如下:

1.页面拆分

这里我们把 聊天的页面进行 拆分:Scaffold ,

bottomNavigationBar:的高度增加 body会自动的缩小。这样软键盘 已经 表情 和有 文件的发送都可以放里面! 不用关系 body的尺寸 他会自动适配

2.页面实现逻辑

首先是要监听软键盘的弹起和收回:

flutter_keyboard_visibility | Flutter Package

然后我们缓存一个键盘高度的数值:

double _keyboardMaxHeight = AppCache().keyBordHeight;

一个设置和获取 键盘高度的值:

  double get keyBordHeight {
    if (_keyBordHeight <= 0) {
      _keyBordHeight = _get<double>("keyBordHeight") ?? 0;
    }
    return _keyBordHeight;
  }

  set keyBordHeight(double height) {
    if (height > 0 && height > keyBordHeight) {
      _sharedPreferences.setDouble("keyBordHeight", height);
    }
  }

3.代码展示

---- page_chat_person.dart       #单聊页面

---- chat_bottom.dart                 #聊天框输入

---- chat_input_box.dart            #聊天时候输入框的封装

---- chat_utils.dart                     #配置和表情

3.1 page_chat_person
import 'package:flutter/material.dart';
import 'package:imspawn/model/relationship_model.dart';
import 'package:imspawn/pages/common/page_visiting_card.dart';
import 'package:imspawn/wrap/extension/extension.dart';

import '../../const/app_colors.dart';
import '../../const/app_icon.dart';
import '../../const/app_textStyle.dart';
import '../../wrap/navigator/app_navigator.dart';
import '../../wrap/navigator/page_hook.dart';
import '../../wrap/navigator/route_aware_state.dart';
import '../../wrap/widget/app_widget.dart';
import 'chat_bottom.dart';
import 'chat_utils.dart';

class PageChatPerson extends StatefulWidget {
  final ContactInfo contacts;
  const PageChatPerson({super.key, required this.contacts});

  @override
  State<PageChatPerson> createState() => _PageChatPersonState();
}

class _PageChatPersonState extends RouteAwareState<PageChatPerson>
    implements PageHook {
  final ScrollController _scrollController = ScrollController();
  final ContentController _contentController = ContentController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _scrollController.addListener(() {
      if (_scrollController.offset >
          _scrollController.position.maxScrollExtent + 50) {
        //_providerChatPersonMessages.chatMessageMore();
        print("刷新。。。。。。。。。。。。");
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    debugPrint("PageChatPerson   build");
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        backgroundColor: AppColor.colorEDEDED,
        shadowColor: AppColor.colordddddd,
        elevation: 1.cale,
        leading: AppWidget.iconBack(() {
          AppNavigator().navigateBack();
        }),
        centerTitle: true,
        title: Text(
          widget.contacts.nickName,
          style: AppTextStyle.textStyle_34_000000,
        ),
        actions: [
          Padding(
            padding: EdgeInsets.only(right: 24.cale),
            child: AppWidget.inkWellEffectNone(
              onTap: () {
                AppNavigator().navigateTo(
                  PageVisitingCard(userInfo: widget.contacts),
                );
              },
              child: Icon(
                AppIcon.dot3,
                size: 46.cale,
                color: Colors.black,
              ),
            ),
          )
        ],
      ),
      body: AppWidget.inkWellEffectNone(
        onTap: () {
          _contentController.onContentClick();
        },
        child: Container(
          color: Colors.white,
        ),
      ),
      bottomNavigationBar: ChatBottom(
        userInfoTarget: widget.contacts,
        contentController: _contentController,
      ),
    );
  }

  @override
  onHide() {
    print('PageChatPerson-------PageChatPerson onHide:${DateTime.now()}');
  }

  @override
  onShow() {
    print('PageChatPerson-------PageChatPerson onShow:${DateTime.now()}');
  }

  @override
  void dispose() {
    _scrollController.dispose();
    // _textEditingController.dispose();
    // _focusNode.dispose();
    // TODO: implement dispose
    super.dispose();
    // print('PageChatPerson-------PageChatPerson dispose:${DateTime.now()}');
  }
}
3.2 chat_bottom
import 'dart:async';

import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:imspawn/model/relationship_model.dart';
import 'package:imspawn/pages/tabbar0chat/page_chat_person.dart';
import 'package:imspawn/wrap/extension/extension.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:wechat_camera_picker/wechat_camera_picker.dart';
import '../../app/app_cache.dart';
import '../../app/app_overlay.dart';
import '../../const/app_colors.dart';
import '../../const/app_icon.dart';
import '../../const/app_textStyle.dart';
import '../../wrap/widget/app_widget.dart';
import 'chat_input_box.dart';
import 'chat_utils.dart';

class ChatBottom extends StatefulWidget {
  final ContactInfo userInfoTarget;
  final ContentController contentController;
  const ChatBottom({
    super.key,
    required this.contentController,
    required this.userInfoTarget,
  });
  @override
  State<ChatBottom> createState() => _ChatBottomState();
}

class _ChatBottomState extends State<ChatBottom> {
  final KeyboardVisibilityController _keyboardVisibilityController =
      KeyboardVisibilityController();
  late StreamSubscription _keyboardSubscription;
  final TextEditingController _textEditingController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  ChatInputEnum _chatInputEnum = ChatInputEnum.inputText;
  ChatMediaEnum _chatMediaEnum = ChatMediaEnum.none;
  bool _showContent = false;
  late OverlayEntry? _overlayEntry = null;
  late OverlayState? _overlayState = null;
  double _keyboardMaxHeight = AppCache().keyBordHeight;
  double _keyboardHeight = 0.0;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    widget.contentController.onContentClick = () {
      if (_focusNode.hasFocus) {
        FocusScope.of(context).unfocus();
      }
      setState(() {
        _showContent = false;
      });
      print('contentController.onContentClick');
    };

    _overlayState = Overlay.of(context);
    // _textEditingController.addListener(() {
    //   setState(() {});
    // });

    _keyboardSubscription =
        _keyboardVisibilityController.onChange.listen((bool visible) {
      if (mounted) {
        setState(() {
          if (visible) {
            _showContent = true;
          } else {
            if (_chatInputEnum != ChatInputEnum.inputText) {
              _showContent = false;
            }
          }
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    _keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
    if (_keyboardMaxHeight < _keyboardHeight) {
      _keyboardMaxHeight = _keyboardHeight;
      AppCache().keyBordHeight = _keyboardMaxHeight;
    }
    return Container(
      padding: EdgeInsets.symmetric(vertical: 20.cale),
      decoration: BoxDecoration(
        color: AppColor.colorF7F7F7,
        border: Border(
          top: BorderSide(width: 1.cale, color: AppColor.colordddddd),
        ),
      ),
      // height: 110.cale,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              AnimatedSwitcher(
                duration: const Duration(milliseconds: 20),
                transitionBuilder: (Widget child, Animation<double> animation) {
                  return FadeTransition(
                    opacity: animation,
                    child: child,
                  );
                },
                child: _inputWidget(),
              ),
              Expanded(
                child: _chatInputEnum == ChatInputEnum.inputText
                    ? Padding(
                        padding: EdgeInsets.symmetric(
                          horizontal: 20.cale,
                        ),
                        child: ChatInputBox(
                          style: AppTextStyle.textStyle_30_000000,
                          onEditingComplete: () {
                            print("onEditingComplete");
                          },
                          onSubmitted: (str) {
                            print("onSubmitted:$str");
                          },
                          controller: _textEditingController,
                          focusNode: _focusNode,
                        ),
                      )
                    : GestureDetector(
                        behavior: HitTestBehavior.translucent,
                        onVerticalDragStart: (DragStartDetails details) {
                          print(
                              '-------------------------->onVerticalDragStart');
                          // widget.providerChatContent.updateRecordAudioState(
                          //   RecordAudioState(
                          //       recording: true,
                          //       recordingState: 1,
                          //       noticeMessage: '松开 发送'),
                          // );
                          _showAudioRecord();
                        },
                        onVerticalDragUpdate: (DragUpdateDetails details) {
                          // print(
                          //     '-------------------------->onVerticalDragUpdate:${details.delta}');
                          // print(
                          //     '-------------------------->onVerticalDragUpdate:${details.localPosition.dy}');
                          // if (details.localPosition.dy > -150) {
                          //   widget.providerChatContent.updateRecordAudioState(
                          //     RecordAudioState(
                          //         recording: true,
                          //         recordingState: 1,
                          //         noticeMessage: '松开 发送'),
                          //   );
                          // } else {
                          //   widget.providerChatContent.updateRecordAudioState(
                          //     RecordAudioState(
                          //         recording: true,
                          //         recordingState: -1,
                          //         noticeMessage: '松开 取消'),
                          //   );
                          // }
                          _updateAudioRecord();
                        },
                        onVerticalDragEnd: (DragEndDetails details) {
                          print('-------------------------->onVerticalDragEnd');
                          _hideAudioRecord();
                          // widget.providerChatContent.updateRecordAudioState(
                          //   RecordAudioState(
                          //       recording: false,
                          //       recordingState: 1,
                          //       noticeMessage: ''),
                          // );
                        },
                        onVerticalDragCancel: () {
                          _hideAudioRecord();
                          // widget.providerChatContent.updateRecordAudioState(
                          //   RecordAudioState(
                          //       recording: false,
                          //       recordingState: 1,
                          //       noticeMessage: ''),
                          // );
                        },
                        child: Container(
                          margin: EdgeInsets.symmetric(horizontal: 20.cale),
                          decoration: BoxDecoration(
                            color: Colors.white,
                            borderRadius: BorderRadius.circular(7.cale),
                          ),
                          height: 80.cale,
                          child: Center(
                            child: Text(
                              '按住 说话',
                              style: AppTextStyle.textStyle_30_000000,
                            ),
                          ),
                        ),
                      ),
              ),
              AppWidget.inkWellEffectNone(
                onTap: () {
                  debugPrint('添加表情符号');
                  setState(() {
                    _showContent = true;
                    _chatInputEnum = ChatInputEnum.inputText;
                    _chatMediaEnum = ChatMediaEnum.mediaFace;
                    //收起小桌板
                    FocusScope.of(context).unfocus();
                  });
                },
                child: Padding(
                  padding: EdgeInsets.only(bottom: 15.cale),
                  child: Icon(
                    AppIcon.faceHappy,
                    size: 50.cale,
                    color: Colors.black,
                  ),
                ),
              ),
              AnimatedSwitcher(
                duration: const Duration(milliseconds: 50),
                transitionBuilder: (Widget child, Animation<double> animation) {
                  return ScaleTransition(
                    scale: animation,
                    alignment: Alignment.centerRight,
                    child: FadeTransition(
                      opacity: animation,
                      child: child,
                    ),
                  );
                },
                child: _chatInputEnum == ChatInputEnum.inputText &&
                        _textEditingController.value.text.isNotEmpty
                    ? AppWidget.inkWellEffectNone(
                        key: const ValueKey('发送'),
                        // onTap: () {
                        //   print('发送');
                        //   _controller.clear();
                        // },
                        onTap: _messageCharacters,
                        child: Container(
                          margin: EdgeInsets.only(
                              left: 20.cale, right: 24.cale, bottom: 10.cale),
                          padding: EdgeInsets.symmetric(
                            horizontal: 24.cale,
                            vertical: 10.cale,
                          ),
                          decoration: BoxDecoration(
                            color: AppColor.color05C160,
                            borderRadius: BorderRadius.circular(12.cale),
                          ),
                          child: Center(
                              child: Text(
                            '发送',
                            style: AppTextStyle.textStyle_30_FFFFFF,
                          )),
                        ),
                      )
                    : AppWidget.inkWellEffectNone(
                        key: const ValueKey('AppIcon.add'),
                        onTap: () {
                          debugPrint('添加附件 图片视频');
                          setState(() {
                            _showContent = true;
                            _chatInputEnum = ChatInputEnum.inputText;
                            _chatMediaEnum = ChatMediaEnum.mediaFiles;
                            //收起小桌板
                            FocusScope.of(context).unfocus();
                          });
                        },
                        child: Padding(
                          padding: EdgeInsets.only(
                              left: 10.cale, right: 20.cale, bottom: 10.cale),
                          child: Icon(
                            AppIcon.add,
                            size: 58.cale,
                            color: Colors.black,
                          ),
                        ),
                      ),
              ),
            ],
          ),
          if (_showContent)
            Container(
                width: double.infinity,
                height: _keyboardMaxHeight,
                padding: EdgeInsets.only(
                  top: 20.cale,
                ),
                // height: 240.cale,
                // height:
                //     widget.providerChatContent.keyboardHeight > 0 ? 0 : 240.cale,
                child: Container(
                    decoration: BoxDecoration(
                      border: Border(
                        top: BorderSide(
                            width: 1.cale, color: AppColor.colordddddd),
                      ),
                    ),
                    child: _chatMediaEnum == ChatMediaEnum.mediaFace
                        ? _contentFace()
                        : _contentMedia()))
        ],
      ),
    );
  }

  Widget _contentFace() {
    return Wrap(
      runAlignment: WrapAlignment.center,
      alignment: WrapAlignment.center,
      //crossAxisAlignment: WrapCrossAlignment.center,
      spacing: 30.cale,
      runSpacing: 45.cale,
      children: emojiList
          .asMap()
          .map(
            (key, value) => MapEntry(
              key,
              AppWidget.inkWellEffectNone(
                onTap: () {
                  // debugPrint(value);
                  _textEditingController.text =
                      '${_textEditingController.value.text}$value';
                },
                child: SizedBox(
                  width: 60.cale,
                  height: 60.cale,
                  child: Center(
                    child: Text(
                      value,
                      style: TextStyle(fontSize: 50.cale),
                    ),
                  ),
                ),
              ),
            ),
          )
          .values
          .toList(),
    );
  }

  Widget _contentMedia() {
    return Wrap(
      runAlignment: WrapAlignment.center,
      alignment: WrapAlignment.center,
      //crossAxisAlignment: WrapCrossAlignment.center,
      spacing: 80.cale,
      runSpacing: 80.cale,
      children: mediaOption
          .asMap()
          .map(
            (key, value) => MapEntry(
              key,
              AppWidget.inkWellEffectNone(
                onTap: () {
                  if (value['type'] == 1) {
                    _mediaSelect();
                  } else if (value['type'] == 2) {
                    _mediaTake();
                  }
                },
                child: SizedBox(
                  width: 100.cale,
                  height: 150.cale,
                  child: Column(
                    children: [
                      Container(
                        width: 100.cale,
                        height: 100.cale,
                        decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(25.cale),
                        ),
                        child: Image.asset(
                          value['icon'],
                          width: 50.cale,
                          height: 50.cale,
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(top: 16.cale),
                        child: Text(
                          value['title'],
                          style: AppTextStyle.textStyle_20_656565,
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ),
          )
          .values
          .toList(),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _focusNode.dispose();
    _textEditingController.dispose();
    _keyboardSubscription.cancel();
    super.dispose();
  }

  void _showAudioRecord() {
    print("开始录音");

    // Audio.startRecordAudio(AppUtils.generateUUid);
    // _hideAudioRecord();
    // _overlayEntry = OverlayEntry(builder: (BuildContext context) {
    //   return ChatAudioMask(recordAudioState: _recordState);
    // });
    _overlayState!.insert(_overlayEntry!);
  }

  void _updateAudioRecord() {
    final overlayEntry = _overlayEntry;
    if (overlayEntry != null) {
      overlayEntry.markNeedsBuild();
    }
  }

  void _hideAudioRecord() {
    if (_overlayEntry != null) {
      _overlayEntry?.remove();
      _overlayEntry = null;
      // if (_recordState.recording) {
      //   if (_recordState.recordingState == 1) {
      //     debugPrint("用户录音成功");
      //     _onRecordAudioDone();
      //   } else {
      //     debugPrint("用户取消录音");
      //     Audio.stopRecordAudio();
      //   }
      // }
    }
  }

  void _onRecordAudioDone() async {
    // AudioInfo? audioInfo = await Audio.stopRecordAudio();
    // if (DateTime.now().millisecondsSinceEpoch -
    //         _dateTime.millisecondsSinceEpoch <=
    //     1500) {
    //   AppOverlay.showToast("说话时间太短");
    // } else {
    //   if (audioInfo == null) {
    //     debugPrint("没有找到 录音音频文件");
    //   } else {
    //     _messageVoice(audioInfo);
    //   }
    // }
  }

  //相册选取照片
  void _mediaSelect() async {
    final List<AssetEntity>? result = await AssetPicker.pickAssets(
      context,
      pickerConfig: const AssetPickerConfig(),
    );
    if (result != null) {
      for (var asset in result) {
        if (asset.type == AssetType.image) {
          _messagePicture(asset);
        } else if (asset.type == AssetType.video) {
          _messageVideo(asset);
        }
      }
    }
  }

  //拍照
  void _mediaTake() async {
    AndroidDeviceInfo info = await AppCache().androidInfo;
    int version = int.parse(info.version.release);
    if (version >= 11) {
      PermissionStatus state = await Permission.manageExternalStorage.request();
      if (state != PermissionStatus.granted) {
        AppOverlay().showModal(
          content: "请先同意权限",
          onConfirm: () {
            openAppSettings();
          },
        );
      }
    }

    // Map<Permission, PermissionStatus> statuses = await [
    //   Permission.storage,
    //   Permission.manageExternalStorage,
    // ].request();
    //
    // if (statuses.values
    //     .toList()
    //     .where((element) => !element.isGranted)
    //     .isNotEmpty) {
    //   AppOverlay().showModal(
    //     content: "请先同意权限",
    //     onConfirm: () {
    //       openAppSettings();
    //     },
    //   );
    //   return;
    // }

    print('_mediaTake');
    final AssetEntity? asset = await CameraPicker.pickFromCamera(
      context,
      pickerConfig: const CameraPickerConfig(enableRecording: true),
    );
    if (asset != null) {
      if (asset.type == AssetType.image) {
        _messagePicture(asset);
      } else if (asset.type == AssetType.video) {
        _messageVideo(asset);
      }
    }
  }

  /// 发送消息
  void _messageCharacters() {
    // if (_controller.value.text.isNotEmpty) {
    //   AppMessage().sendMessageCharacters(
    //     chatType: IOEventEmit.chatPerson,
    //     senderId: AppCache().userInfo!.userId,
    //     target: widget.userInfoTarget,
    //     message: _controller.value.text,
    //   );
    //   _controller.clear();
    // }
  }

  void _messagePicture(AssetEntity entity) async {
    // AppMessage().sendMessagePicture(
    //   chatType: IOEventEmit.chatPerson,
    //   senderId: AppCache().userInfo!.userId,
    //   target: widget.userInfoTarget,
    //   entity: entity,
    // );
  }

  void _messageVideo(AssetEntity entity) async {
    // AppMessage().sendMessageVideo(
    //   chatType: IOEventEmit.chatPerson,
    //   senderId: AppCache().userInfo!.userId,
    //   target: widget.userInfoTarget,
    //   message: entity,
    // );
  }

  Widget _inputWidget() {
    if (_chatInputEnum == ChatInputEnum.inputAudio) {
      return AppWidget.inkWellEffectNone(
        key: const ValueKey("AppIcon.keyboard"),
        onTap: () {
          print("点击文字按钮 转向语音输入");
          setState(() {
            _chatInputEnum = ChatInputEnum.inputText;
            _focusNode.requestFocus();
          });
        },
        child: Padding(
          padding: EdgeInsets.only(left: 20.cale, bottom: 15.cale),
          child: Icon(
            AppIcon.keyboard,
            size: 50.cale,
            color: Colors.black,
          ),
        ),
      );
    } else {
      return AppWidget.inkWellEffectNone(
        key: const ValueKey("AppIcon.audio"),
        onTap: () {
          setState(() {
            debugPrint("点击音频按钮,转向问题");
            _chatInputEnum = ChatInputEnum.inputAudio;
            _showContent = false;
          });
        },
        child: Padding(
          padding: EdgeInsets.only(left: 20.cale, bottom: 15.cale),
          child: Icon(
            AppIcon.audio,
            size: 50.cale,
            color: Colors.black,
          ),
        ),
      );
    }
  }

  // void _messageVoice(AudioInfo audioInfo) {
  //   // AppMessage().sendMessageVoice(
  //   //   chatType: IOEventEmit.chatPerson,
  //   //   senderId: AppCache().userInfo!.userId,
  //   //   target: widget.userInfoTarget,
  //   //   audioInfo: audioInfo,
  //   // );
  // }
}
3.3 chat_input_box
import 'package:flutter/material.dart';
import 'package:imspawn/wrap/extension/extension.dart';

import '../../const/app_colors.dart';
import '../../const/app_textStyle.dart';

class ChatInputBox extends StatelessWidget {
  final String? hintText;
  final int? maxLength;
  final VoidCallback? onEditingComplete;
  final ValueChanged<String>? onSubmitted;
  final EdgeInsetsGeometry? contentPadding;
  final TextEditingController? controller;
  final String? errorText;
  final Widget? prefixIcon;
  final TextInputType? keyboardType;
  final BoxConstraints? prefixIconConstraints;
  final BoxDecoration? decoration;
  final TextStyle? style;
  final TextStyle? hintStyle;
  final FocusNode? focusNode;
  const ChatInputBox({
    super.key,
    this.maxLength = 200,
    this.controller,
    this.errorText,
    this.prefixIcon,
    this.prefixIconConstraints,
    this.onEditingComplete,
    this.onSubmitted,
    this.contentPadding = EdgeInsets.zero,
    this.decoration,
    this.keyboardType,
    this.style,
    this.hintStyle,
    this.focusNode,
    this.hintText,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      // height: 75.cale,
      // margin: EdgeInsets.all(5.cale),
      constraints: BoxConstraints(
        minHeight: 75.cale,
        maxHeight: 350.cale,
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(7.cale),
        color: Colors.white,
      ),
      child: TextField(
        maxLength: maxLength,
        focusNode: focusNode,
        maxLines: null,
        cursorColor: AppColor.color3BAB71,
        controller: controller,
        textAlignVertical: TextAlignVertical.center,
        keyboardType: keyboardType,
        onEditingComplete: onEditingComplete,
        onSubmitted: onSubmitted,
        style: style ?? AppTextStyle.textStyle_28_333333,
        // inputFormatters: inputFormatters,
        decoration: InputDecoration(
          focusedBorder: const OutlineInputBorder(
              borderSide: BorderSide(width: 0, color: Colors.transparent)),
          disabledBorder: const OutlineInputBorder(
              borderSide: BorderSide(width: 0, color: Colors.transparent)),
          enabledBorder: const OutlineInputBorder(
              borderSide: BorderSide(width: 0, color: Colors.transparent)),
          border: OutlineInputBorder(
            borderSide: BorderSide.none,
            borderRadius: BorderRadius.circular(7.cale),
            //borderSide: BorderSide(width: 0, color: Colors.transparent),
            // borderSide: BorderSide(width: 0, color: Colors.transparent),
          ),

          hintText: hintText,
          prefixIcon: prefixIcon,
          prefixIconConstraints: prefixIconConstraints,
          hintStyle: hintStyle ?? AppTextStyle.textStyle_28_AAAAAA,
          counterText: '', //取消文字计数器
          // border: InputBorder.none,
          isDense: true,
          errorText: errorText,
          contentPadding: EdgeInsets.symmetric(
            horizontal: 16.cale,
            vertical: 20.cale,
          ),
        ),
        // contentPadding:
        //     EdgeInsets.only(left: 16.cale, right: 16.cale, top: 20.cale),

        // errorText: "输入错误",
      ),
    );
  }
}
3.4 chat_utils
import 'dart:ui';

enum ChatInputEnum {
  inputText,
  inputAudio,
}

enum ChatMediaEnum {
  none,
  mediaFiles,
  mediaFace,
}

class ContentController {
  late VoidCallback onContentClick;
}

final List<Map> mediaOption = [
  {
    'title': '相册',
    'icon': 'assets/common/chat/ic_details_photo.webp',
    'type': 1
  },
  {
    'title': '拍照',
    'icon': 'assets/common/chat/ic_details_camera.webp',
    'type': 2
  },
  {
    'title': '视频通话',
    'icon': 'assets/common/chat/ic_details_video.webp',
    'type': 3
  },
  {
    'title': '位置',
    'icon': 'assets/common/chat/ic_details_localtion.webp',
    'type': 4
  },
  {'title': '红包', 'icon': 'assets/common/chat/ic_details_red.webp', 'type': 5},
  {
    'title': '转账',
    'icon': 'assets/common/chat/ic_details_transfer.webp',
    'type': 6
  },
  {'title': '语音输入', 'icon': 'assets/common/chat/ic_chat_voice.webp', 'type': 7},
  {
    'title': '我的收藏',
    'icon': 'assets/common/chat/ic_details_favorite.webp',
    'type': 8
  },
];

final List<String> emojiList = [
  '😀',
  '😃',
  '😄',
  '😁',
  '😆',
  '😅',
  '😂',
  '🤣',
  '☺',
  '😊',
  '😇',
  '🙂',
  '🙃',
  '😉',
  '😌',
  '😍',
  '🥰',
  '😘',
  '😗',
  '😙',
  '😚',
  '😋',
  '😛',
  '😝',
  '😜',
  '🤪',
  '🤨',
  '🧐',
  '🤓',
  '😎',
  '🤩',
  '🥳',
  '😏',
  '😒',
  '😞',
  '😔',
  '😟',
  '😕',
  '🙁',
  '☹',
];

基本上就OK,这里的思路就是 动态改变 bottomNavigationBar 的高度,聊天内容 body 会自适应高度

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/576241.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

免费预约即将截止,5月7日上海TCT亚洲3D打印展参观指南,收藏!

进入TCT亚洲展官网&#xff08;网页搜索TCT亚洲展&#xff09;&#xff0c;免费登记预约 2024年TCT亚洲展作为推动增材制造在亚洲市场的业务交流的重要平台&#xff0c;将于2024年5月7日至9日在国家会展中心&#xff08;上海&#xff09;7.1&8.1馆举办&#xff0c;与海内外…

二 SSM整合实操

SSM整合实操 一 依赖管理 数据库准备 mysql8.0.33 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIMARY KEY(emp_id) );INSERT INTO t_emp(emp_name,emp_salary) VALUE…

短视频素材有哪些?短视频素材哪一类最吸引人?

随着视频内容在全球各种媒体和平台上的普及&#xff0c;寻找能够让你的项目脱颖而出的视频素材变得尤为重要。以下视频素材网站各具特色&#xff0c;提供从自然风景到都市快照&#xff0c;从简单背景到复杂动画的多样选择。 1. 蛙学府&#xff08;中国&#xff09; 提供4K高解…

全志ARM-蜂鸣器

操作准备&#xff1a; 1.使Tab键的缩进和批量对齐为4格 在/etc/vim/vimrc 中添加一项配置 set tabstop 4; 也可以再加一行 set nu显示代码的行数 vim的设置&#xff0c;修改/etc/vim/vimrc文件&#xff0c;需要用超级用户权限 /etc/vim/vimrc set shiftwidth4 设置批量对…

VsCode一直连接不上 timed out

前言 前段时间用VsCode连接远程服务器&#xff0c;正常操作后总是连接不上&#xff0c;折磨了半个多小时&#xff0c;后面才知道原来是服务器设置的问题&#xff0c;故记录一下&#xff0c;防止后面的小伙伴也踩坑。 我使用的是阿里云服务器&#xff0c;如果是使用其他平台服务…

web(微博发布案例)

示例&#xff1a; 1、检测空白内容 2、发布内容 html: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta …

vue+element之解决upload组件上传文件失败后仍显示在列表上、自动上传、过滤、findIndex、splice、filter

MENU 前言错误案例(没有用)正确方法结束语 前言 el-upload上传失败后&#xff0c;文件仍显示在列表上。 这个pdf文件上传失败&#xff0c;仍显示在列表&#xff0c;给人错觉是上传成功&#xff0c;所以要把它去掉。 在element中&#xff0c;file-list和v-model:file-list是用于…

苹果一次性开源了8个大模型! 包含模型权重、训练日志和设置,OpenELM全面开源

不以开放性著称的苹果居然同时开源了大模型的权重、训练和评估框架&#xff0c;涵盖训练日志、多个保存点和预训练设置。同时升级计算机视觉工具包 CVNets 为 CoreNet&#xff01;支持 OpenELM&#xff01; ▲图1.由Stable Diffusion3生成。 OpenELM是Apple苹果公司最新推出的…

【产品经理修炼之道】- 如何分析一个产品

新人产品经理面试的时候&#xff0c;常被问到的一个问题是&#xff1a;如何评价一款产品。这个问题&#xff0c;我们可以从五个层级一个模型来解答&#xff0c;看你能分析到哪一层。 初级产品经理面试时&#xff0c;经常会问这样的问题&#xff1a; 1&#xff09;你是最喜欢的…

U盘格式转换GPT格式转回DOS

当前格式 fdisk /dev/sdb# 在 fdisk 提示符下&#xff0c;输入以下命令删除分区&#xff1a; d # 选择要删除的分区编号&#xff08;如 1、2 等&#xff09; w开始转换 [rootnode-24 ~]# fdisk /dev/sdbWelcome to fdisk (util-linux 2.37.4). Changes will remain in memory o…

网络安全实训Day17and18

写在前面 第17和18天都讲的sql注入&#xff0c;故合并 ​​​​​​ 网络空间安全实训-渗透测试 Web渗透 定义 针对Web站点的渗透攻击&#xff0c;以获取网站控制权限为目的 Web渗透的特点 Web技术学习门槛低&#xff0c;更容易实现 Web的普及性决定了Web渗透更容易找到目…

python项目练习-1

获取无忧书城的小说内容&#xff01; import requests # 导入请求包 from lxml import etree # 导入处理xml数据包url https://www.51shucheng.net/wangluo/douluodalu/21750.html book_num 1 # 文章页数 download_urls [] # 定义一个空列表&#xff0c;表示我们下载过小…

提升你的C编程技能:使用cURL下载Kwai视频

概述 本文将介绍如何利用C语言以及cURL库来实现Kwai视频的下载。cURL作为一个功能强大的网络传输工具&#xff0c;能够在C语言环境下轻松地实现数据的传输。我们还将探讨如何运用代理IP技术&#xff0c;提升爬虫的匿名性和效率&#xff0c;以适应Kwai视频平台的发展趋势。 正…

《欢乐钓鱼大师》攻略,钓友入坑必备!

欢迎来到《欢乐钓鱼大师》&#xff01;在这个游戏里&#xff0c;你可以尽情享受垂钓的乐趣&#xff0c;通过不断更换和升级高阶鱼竿&#xff0c;轻松地钓到各种稀有鱼类。因为许多玩家在挑战关卡时遇到了一些困难&#xff0c;所以今天我给大家带来了《欢乐钓鱼大师攻略指南》&a…

自动化机器学习流水线:基于Spring Boot与AI机器学习技术的融合探索

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

【毕设绝技】基于 SpringCloud 的在线交易平台商城的设计与实现-数据库设计(三)

毕业设计是每个大学生的困扰&#xff0c;让毕设绝技带你走出低谷迎来希望&#xff01; 基于 SpringCloud 的在线交易平台商城的设计与实现 一、数据库设计原则 在系统中&#xff0c;数据库用来保存数据。数据库设计是整个系统的根基和起点&#xff0c;也是系统开发的重要环节…

静态链接lib库使用

lib库实际上分为两种&#xff0c;一种是静态链接lib库或者叫做静态lib库&#xff0c;另一种叫做动态链接库dll库的lib导入库或称为lib导入库。这两个库是不一样的&#xff0c;很多人都分不清楚&#xff0c;很容易混淆。 第一种是静态lib&#xff0c;包含了所有的代码实现的&am…

颠覆传统:机器人与AI大模型的结合,开启智能自动化的黄金时代!

引言&#xff1a;机器人技术与大模型的结合趋势 随着科技的迅速发展&#xff0c;机器人技术与大模型的结合已经成为必然趋势。这种结合不仅仅是技术的简单叠加&#xff0c;而是一种深层次的互补与融合&#xff0c;为机器人技术的应用开辟了新的可能性。大模型&#xff0c;能够…

02_c/c++开源库ZeroMQ

1.安装 C库 libzmq sudo apt install libzmq3-dev 实例: https://zeromq.org/get-started/?languagec&librarylibzmq# 编译依赖: pkg-config --cflags --libs libzmq or cat /usr/lib/x86_64-linux-gnu/pkgconfig/libzmq.pc -isystem /usr/include/mit-krb5 -I/usr/in…

[Android]引导页

使用Kotlin Jetpack Compose创建一个左右滑动的引导页, 效果如图. 1.添加依赖项 androidx.compose.ui最新版本查询:https://maven.google.com/web/index.html com.google.accompanist:accompanist-pager最新版本查询:https://central.sonatype.com/ 确保在 build.gradle (M…