|
|
@@ -0,0 +1,221 @@
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
|
+import 'package:keyboard/resource/colors.gen.dart';
|
|
|
+
|
|
|
+import '../../../../resource/assets.gen.dart';
|
|
|
+import '../../../../widget/gradient_text.dart';
|
|
|
+
|
|
|
+/// 选择新的Ai模型时回调
|
|
|
+typedef OnChooseReplyModeCallback = void Function(String newMode);
|
|
|
+
|
|
|
+/// 回复模式选择,Popup锚点弹窗
|
|
|
+class ReplyModeSelectPopup {
|
|
|
+ /// 用于标识弹窗的唯一性
|
|
|
+ static const _tag = "ReplyModeSelectPopup";
|
|
|
+
|
|
|
+ /// 弹窗是否显示中
|
|
|
+ static bool _isShowing = false;
|
|
|
+
|
|
|
+ /// 弹窗是否显示中
|
|
|
+ static bool isShowing() {
|
|
|
+ return _isShowing;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 显示
|
|
|
+ /// [anchorBtnKey] 锚点按钮的GlobalKey
|
|
|
+ /// [modeList] 模型列表
|
|
|
+ /// [currentMode] 当前使用的回复模式
|
|
|
+ /// [onChooseReplyModeCallback] 选择新的Ai模型时回调
|
|
|
+ static void show(
|
|
|
+ GlobalKey anchorBtnKey,
|
|
|
+ BuildContext context, {
|
|
|
+ required List<String> modeList,
|
|
|
+ required String currentMode,
|
|
|
+ required OnChooseReplyModeCallback onChooseReplyModeCallback,
|
|
|
+ }) {
|
|
|
+ if (_isShowing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ double popupWidth = 145.w;
|
|
|
+
|
|
|
+ SmartDialog.showAttach(
|
|
|
+ // 绑定唯一tag
|
|
|
+ tag: _tag,
|
|
|
+ // 绑定锚点的BuildContext,才能确定弹窗的显示位置
|
|
|
+ targetContext: context,
|
|
|
+ // 在锚点的顶部显示
|
|
|
+ alignment: _calculateBestAlignment(anchorBtnKey, context),
|
|
|
+ // 使用动画
|
|
|
+ useAnimation: true,
|
|
|
+ // 动画类型
|
|
|
+ animationType: SmartAnimationType.fade,
|
|
|
+ // 是否允许事件穿透
|
|
|
+ usePenetrate: true,
|
|
|
+ // 监听弹窗关闭
|
|
|
+ onDismiss: () {
|
|
|
+ _isShowing = false;
|
|
|
+ },
|
|
|
+ // 位置偏移配置
|
|
|
+ targetBuilder: (Offset targetOffset, Size targetSize) {
|
|
|
+ // 获取锚点信息
|
|
|
+ final anchorRenderBox =
|
|
|
+ anchorBtnKey.currentContext!.findRenderObject() as RenderBox;
|
|
|
+ final anchorPosition = anchorRenderBox.localToGlobal(Offset.zero);
|
|
|
+ final anchorSize = anchorRenderBox.size;
|
|
|
+
|
|
|
+ // 计算X轴的偏移量,距离屏幕左边有一段距离
|
|
|
+ double leftOffset = 6.w;
|
|
|
+ double popupX =
|
|
|
+ anchorPosition.dx +
|
|
|
+ (anchorSize.width / 2) -
|
|
|
+ (popupWidth / 2) +
|
|
|
+ leftOffset;
|
|
|
+
|
|
|
+ // 计算Y轴的偏移量(默认是在按钮的上边显示,UI设计要求盖着按钮,所以需要增加偏移量)
|
|
|
+ double bottomOffset = 46.h;
|
|
|
+ double popupY = anchorPosition.dy + bottomOffset;
|
|
|
+
|
|
|
+ return Offset(popupX, popupY);
|
|
|
+ },
|
|
|
+ // 构建弹窗内容
|
|
|
+ builder: (_) {
|
|
|
+ return Container(
|
|
|
+ // 固定弹窗的宽度
|
|
|
+ width: popupWidth,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: ColorName.white,
|
|
|
+ border: Border.all(
|
|
|
+ color: ColorName.bgAiModelSwitchBorderBtn,
|
|
|
+ width: 1.0.w,
|
|
|
+ ),
|
|
|
+ borderRadius: BorderRadius.all(Radius.circular(16.r)),
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ // 包裹内容
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ SizedBox(height: 12.h),
|
|
|
+ // Ai模型列表
|
|
|
+ Flexible(
|
|
|
+ child: ListView.builder(
|
|
|
+ // ListView包裹内容
|
|
|
+ shrinkWrap: true,
|
|
|
+ physics: const ClampingScrollPhysics(),
|
|
|
+ // 去除默认padding
|
|
|
+ padding: EdgeInsets.zero,
|
|
|
+ itemCount: modeList.length,
|
|
|
+ itemBuilder: (BuildContext context, int index) {
|
|
|
+ String itemData = modeList[index];
|
|
|
+ return _buildListItem(
|
|
|
+ index,
|
|
|
+ itemData,
|
|
|
+ currentMode == itemData,
|
|
|
+ onChooseReplyModeCallback,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ // 当前选中项
|
|
|
+ _buildCurrentSelectItem(currentMode),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ // 设置为正在显示中
|
|
|
+ _isShowing = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 计算对齐方式
|
|
|
+ static Alignment _calculateBestAlignment(
|
|
|
+ GlobalKey anchorBtnKey,
|
|
|
+ BuildContext context,
|
|
|
+ ) {
|
|
|
+ final renderBox =
|
|
|
+ anchorBtnKey.currentContext?.findRenderObject() as RenderBox;
|
|
|
+ final position = renderBox.localToGlobal(Offset.zero);
|
|
|
+ final screenHeight = MediaQuery.of(context).size.height;
|
|
|
+
|
|
|
+ return position.dy > screenHeight / 2
|
|
|
+ ? Alignment.topCenter
|
|
|
+ : Alignment.bottomCenter;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 列表项
|
|
|
+ static Widget _buildListItem(
|
|
|
+ int index,
|
|
|
+ String itemData,
|
|
|
+ bool isSelected,
|
|
|
+ OnChooseReplyModeCallback onChooseReplyModeCallback,
|
|
|
+ ) {
|
|
|
+ return GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ onChooseReplyModeCallback(itemData);
|
|
|
+ SmartDialog.dismiss();
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ child: Container(
|
|
|
+ width: 128.w,
|
|
|
+ margin: EdgeInsets.only(left: 6.w, right: 6.w, bottom: 10.h),
|
|
|
+ padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 35.w),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ borderRadius: BorderRadius.circular(7.r),
|
|
|
+ color: isSelected ? ColorName.bgAiModelSelected : ColorName.white,
|
|
|
+ ),
|
|
|
+ child: Text(
|
|
|
+ itemData,
|
|
|
+ style: TextStyle(
|
|
|
+ color: ColorName.black80,
|
|
|
+ fontSize: 14.sp,
|
|
|
+ fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 当前选中项
|
|
|
+ static Widget _buildCurrentSelectItem(String currentSelect) {
|
|
|
+ return GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ dismiss();
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 14.w),
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ // 文字
|
|
|
+ GradientText(
|
|
|
+ colors: [
|
|
|
+ ColorName.aiModelSwitchBtnColor1,
|
|
|
+ ColorName.aiModelSwitchBtnColor2,
|
|
|
+ ],
|
|
|
+ child: Text(
|
|
|
+ currentSelect,
|
|
|
+ style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ SizedBox(width: 8.w),
|
|
|
+ // 箭头
|
|
|
+ Assets.images.iconModeSwitchArrow.image(width: 15.w, height: 15.w),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 隐藏
|
|
|
+ static void dismiss() {
|
|
|
+ if (!_isShowing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SmartDialog.dismiss(tag: _tag);
|
|
|
+ }
|
|
|
+}
|