Explorar o código

[new]谈话记录增加长按重命名以及删除功能

zk hai 1 ano
pai
achega
41ac34c5d4

BIN=BIN
assets/images/icon_rename_clear_txt.webp


BIN=BIN
assets/images/icon_rename_close.webp


BIN=BIN
assets/images/icon_talk_delete.webp


+ 11 - 0
assets/string/base/string.xml

@@ -31,4 +31,15 @@
     <string name="task_item_todo">进行中</string>
     <string name="task_item_done">已完成</string>
     <string name="cancel">取消</string>
+    <string name="sure">确认</string>
+    <string name="talk_rename">重命名</string>
+    <string name="talk_delete">删除</string>
+    <string name="talk_rename_title">重命名标题</string>
+    <string name="talk_rename_title_hint">请输入谈话记录标题</string>
+    <string name="talk_rename_success">修改成功</string>
+    <string name="talk_rename_fail">修改失败</string>
+    <string name="talk_rename_delete_prompt">是否删除“%s”模板?该谈话包含您的待办事项,将一并删除。
+    </string>
+    <string name="talk_delete_success">删除成功</string>
+    <string name="talk_delete_fail">删除失败</string>
 </resources>

+ 8 - 0
lib/data/api/atmob_api.dart

@@ -4,6 +4,8 @@ import 'package:electronic_assistant/base/base_response.dart';
 import 'package:electronic_assistant/data/api/network_module.dart';
 import 'package:electronic_assistant/data/api/request/agenda_request.dart';
 import 'package:electronic_assistant/data/api/request/login_request.dart';
+import 'package:electronic_assistant/data/api/request/talk_delete_request.dart';
+import 'package:electronic_assistant/data/api/request/talk_rename_request.dart';
 import 'package:electronic_assistant/data/api/request/user_info_update_request.dart';
 import 'package:electronic_assistant/data/api/request/verification_code_request.dart';
 import 'package:electronic_assistant/data/api/response/agenda_response.dart';
@@ -35,6 +37,12 @@ abstract class AtmobApi {
   @POST("/project/secretary/v1/agenda/page")
   Future<BaseResponse<AgendaResponse>> agendaPage(
       @Body() AgendaRequest request);
+
+  @POST("/project/secretary/v1/talk/update")
+  Future<BaseResponse> talkRename(@Body() TalkRenameRequest request);
+
+  @POST("/project/secretary/v1/talk/delete")
+  Future<BaseResponse> talkDelete(@Body() TalkDeleteRequest request);
 }
 
 final atmobApi = AtmobApi(defaultDio, baseUrl: Constants.baseUrl);

+ 14 - 0
lib/data/api/request/talk_delete_request.dart

@@ -0,0 +1,14 @@
+import 'package:electronic_assistant/base/app_base_request.dart';
+
+class TalkDeleteRequest extends AppBaseRequest {
+  String? id;
+
+  TalkDeleteRequest(this.id);
+
+  @override
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> data = super.toJson();
+    data['id'] = id;
+    return data;
+  }
+}

+ 18 - 0
lib/data/api/request/talk_rename_request.dart

@@ -0,0 +1,18 @@
+import 'package:electronic_assistant/base/app_base_request.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'talk_rename_request.g.dart';
+
+@JsonSerializable()
+class TalkRenameRequest extends AppBaseRequest {
+  @JsonKey(name: "id")
+  String? id;
+
+  @JsonKey(name: "title")
+  String? title;
+
+  TalkRenameRequest(this.id, this.title);
+
+  @override
+  Map<String, dynamic> toJson() => _$TalkRenameRequestToJson(this);
+}

+ 14 - 0
lib/data/repositories/task_repository.dart

@@ -1,7 +1,9 @@
 import 'package:electronic_assistant/data/api/atmob_api.dart';
+import 'package:electronic_assistant/data/api/request/talk_delete_request.dart';
 
 import '../../utils/http_handler.dart';
 import '../api/request/agenda_request.dart';
+import '../api/request/talk_rename_request.dart';
 import '../api/response/agenda_response.dart';
 
 class TaskRepository {
@@ -16,6 +18,18 @@ class TaskRepository {
             completeStatus: completeStatus?.value))
         .then(HttpHandler.handle(true));
   }
+
+  Future<void> talkRename(String? id, String? title) {
+    return atmobApi
+        .talkRename(TalkRenameRequest(id, title))
+        .then(HttpHandler.handle(true));
+  }
+
+  Future<void> talkDelete(String? id) {
+    return atmobApi
+        .talkDelete(TalkDeleteRequest(id))
+        .then(HttpHandler.handle(true));
+  }
 }
 
 final taskRepository = TaskRepository._();

+ 163 - 0
lib/dialog/rename_dialog.dart

@@ -0,0 +1,163 @@
+import 'package:electronic_assistant/resource/assets.gen.dart';
+import 'package:electronic_assistant/resource/colors.gen.dart';
+import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:electronic_assistant/utils/toast_util.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:get/get.dart';
+
+typedef RenameBuilder = void Function(String? content);
+
+void reNameDialog(String title, String? content,
+    {String? hintTxt, int? maxLength, RenameBuilder? returnBuilder}) {
+  final controller = TextEditingController();
+  controller.text = content ?? "";
+  final contentObs = content.obs;
+  controller.addListener(() {
+    contentObs.value = controller.text;
+  });
+  SmartDialog.show(
+      builder: (_) {
+        return IntrinsicHeight(
+          child: Container(
+            padding: EdgeInsets.all(16.w),
+            width: 336.w,
+            decoration: BoxDecoration(
+              color: Colors.white,
+              borderRadius: BorderRadius.circular(12),
+            ),
+            child: Column(
+              children: [
+                Row(
+                  crossAxisAlignment: CrossAxisAlignment.center,
+                  children: [
+                    Text(
+                      title,
+                      style: TextStyle(
+                          fontWeight: FontWeight.bold,
+                          fontSize: 15.sp,
+                          color: ColorName.primaryTextColor),
+                    ),
+                    const Spacer(),
+                    GestureDetector(
+                      onTap: () {
+                        SmartDialog.dismiss();
+                      },
+                      child: SizedBox(
+                        width: 28.w,
+                        height: 28.w,
+                        child: Assets.images.iconRenameClose.image(),
+                      ),
+                    )
+                  ],
+                ),
+                SizedBox(height: 24.h),
+                Container(
+                  decoration: BoxDecoration(
+                    color: "#F0F0F0".toColor(),
+                    borderRadius: BorderRadius.circular(8),
+                  ),
+                  padding: EdgeInsets.symmetric(horizontal: 14.w),
+                  height: 51.h,
+                  child: Row(
+                    children: [
+                      Expanded(
+                        child: TextField(
+                          maxLength: maxLength,
+                          style: TextStyle(
+                              fontSize: 16.sp,
+                              color: ColorName.primaryTextColor),
+                          controller: controller,
+                          maxLines: 1,
+                          decoration: InputDecoration(
+                            counterText: '',
+                            hintText: hintTxt,
+                            hintStyle: TextStyle(
+                                fontSize: 16.sp,
+                                color: ColorName.tertiaryTextColor),
+                            border: InputBorder.none,
+                            fillColor: Colors.transparent,
+                          ),
+                        ),
+                      ),
+                      Obx(() {
+                        return Visibility(
+                          visible: contentObs.value!.isNotEmpty,
+                          child: GestureDetector(
+                            onTap: () {
+                              controller.clear();
+                            },
+                            child: SizedBox(
+                              width: 20.w,
+                              height: 20.w,
+                              child: Assets.images.iconRenameClearTxt.image(),
+                            ),
+                          ),
+                        );
+                      })
+                    ],
+                  ),
+                ),
+                SizedBox(height: 28.h),
+                Row(
+                  children: [
+                    Expanded(
+                        child: GestureDetector(
+                      onTap: () {
+                        SmartDialog.dismiss();
+                      },
+                      child: Container(
+                        padding: EdgeInsets.symmetric(vertical: 12.h),
+                        decoration: BoxDecoration(
+                          color: '#F0F0F0'.toColor(),
+                          borderRadius: BorderRadius.circular(8),
+                        ),
+                        child: Center(
+                          child: Text(
+                            StringName.cancel.tr,
+                            style: TextStyle(
+                                fontSize: 16.sp,
+                                color: ColorName.secondaryTextColor),
+                          ),
+                        ),
+                      ),
+                    )),
+                    SizedBox(width: 12.w),
+                    Expanded(
+                        child: GestureDetector(
+                      onTap: () {
+                        if (contentObs.value!.isEmpty) {
+                          ToastUtil.showToast(hintTxt);
+                          return;
+                        }
+                        returnBuilder?.call(contentObs.value);
+                        SmartDialog.dismiss();
+                      },
+                      child: Container(
+                        padding: EdgeInsets.symmetric(vertical: 12.h),
+                        decoration: BoxDecoration(
+                          color: ColorName.colorPrimary,
+                          borderRadius: BorderRadius.circular(8),
+                        ),
+                        child: Center(
+                          child: Text(
+                            StringName.sure.tr,
+                            style: TextStyle(
+                                fontSize: 16.sp, color: ColorName.white),
+                          ),
+                        ),
+                      ),
+                    )),
+                  ],
+                ),
+                SizedBox(height: 8.h),
+              ],
+            ),
+          ),
+        );
+      },
+      clickMaskDismiss: false);
+}

+ 93 - 0
lib/dialog/talk_delete_dialog.dart

@@ -0,0 +1,93 @@
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:get/get.dart';
+
+import '../resource/colors.gen.dart';
+import '../resource/string.gen.dart';
+
+typedef TalkDeleteBuilder = void Function();
+
+void talkDeleteDialog(String? talkId, String? talkTitle,
+    {TalkDeleteBuilder? returnBuilder}) {
+  SmartDialog.show(
+      builder: (_) {
+        return IntrinsicHeight(
+          child: Container(
+            padding: EdgeInsets.all(16.w),
+            width: 280.w,
+            decoration: BoxDecoration(
+              color: Colors.white,
+              borderRadius: BorderRadius.circular(12),
+            ),
+            child: Column(
+              children: [
+                SizedBox(height: 24.h),
+                Text(
+                  StringName.talkRenameDeletePrompt.tr
+                      .replacePlaceholders([talkTitle]),
+                  style: TextStyle(
+                      fontSize: 15.sp,
+                      color: ColorName.primaryTextColor,
+                      fontWeight: FontWeight.bold),
+                ),
+                SizedBox(height: 35.h),
+                Row(
+                  children: [
+                    Expanded(
+                        child: GestureDetector(
+                      onTap: () {
+                        SmartDialog.dismiss();
+                      },
+                      child: Container(
+                        padding: EdgeInsets.symmetric(vertical: 8.h),
+                        decoration: BoxDecoration(
+                          color: '#F0F0F0'.toColor(),
+                          borderRadius: BorderRadius.circular(8),
+                        ),
+                        child: Center(
+                          child: Text(
+                            StringName.cancel.tr,
+                            style: TextStyle(
+                                fontSize: 16.sp,
+                                color: ColorName.secondaryTextColor),
+                          ),
+                        ),
+                      ),
+                    )),
+                    SizedBox(width: 12.w),
+                    Expanded(
+                        child: GestureDetector(
+                      onTap: () {
+                        SmartDialog.dismiss();
+                        if (returnBuilder != null) {
+                          returnBuilder.call();
+                        }
+                      },
+                      child: Container(
+                        padding: EdgeInsets.symmetric(vertical: 8.h),
+                        decoration: BoxDecoration(
+                          color: ColorName.colorPrimary,
+                          borderRadius: BorderRadius.circular(8),
+                        ),
+                        child: Center(
+                          child: Text(
+                            StringName.sure.tr,
+                            style: TextStyle(
+                                fontSize: 16.sp, color: ColorName.white),
+                          ),
+                        ),
+                      ),
+                    )),
+                  ],
+                ),
+                SizedBox(height: 4.h),
+              ],
+            ),
+          ),
+        );
+      },
+      clickMaskDismiss: false);
+}

+ 25 - 0
lib/module/home/controller.dart

@@ -1,13 +1,17 @@
 import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:electronic_assistant/data/bean/talks.dart';
+import 'package:electronic_assistant/data/repositories/task_repository.dart';
+import 'package:electronic_assistant/module/home/view.dart';
 import 'package:electronic_assistant/module/main/controller.dart';
 import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:electronic_assistant/utils/toast_util.dart';
 import 'package:electronic_assistant/widget/pull_to_refresh.dart';
 import 'package:get/get.dart';
 import '../../data/bean/agenda.dart';
 import '../../data/repositories/account_repository.dart';
 import '../../data/repositories/home_repository.dart';
 import '../../utils/animated_list_controller.dart';
+import '../../utils/error_handler.dart';
 
 class HomePageController extends BaseController {
   get isLogin => accountRepository.isLogin.value;
@@ -39,6 +43,19 @@ class HomePageController extends BaseController {
     });
   }
 
+  void requestName(String? newName, TalkBean bean) {
+    taskRepository.talkRename(bean.id, newName).then((data) {
+      bean.title = newName;
+      int index = taskList.indexOf(bean);
+      if (index != -1) {
+        taskList.update(index, bean);
+        ToastUtil.showToast(StringName.talkRenameSuccess.tr);
+      }
+    }).catchError((error) {
+      ErrorHandler.toastError(error, message: StringName.talkRenameFail.tr);
+    });
+  }
+
   void goTalkRecordPage() {
     Get.find<MainController>().updateIndexByPageName(StringName.mainTabFile);
   }
@@ -46,4 +63,12 @@ class HomePageController extends BaseController {
   void showLoginDrawer() {
     Get.find<MainController>().openDrawer();
   }
+
+  void requestDelete(TalkBean item) {
+    taskRepository.talkDelete(item.id).then((data) {
+      requestHomeData();
+    }).catchError((error) {
+      ErrorHandler.toastError(error, message: StringName.talkDeleteFail.tr);
+    });
+  }
 }

+ 107 - 79
lib/module/home/view.dart

@@ -1,5 +1,8 @@
 import 'package:electronic_assistant/base/base_page.dart';
 import 'package:electronic_assistant/data/bean/talks.dart';
+import 'package:electronic_assistant/dialog/rename_dialog.dart';
+import 'package:electronic_assistant/dialog/talk_delete_dialog.dart';
+import 'package:electronic_assistant/popup/talk_popup.dart';
 import 'package:electronic_assistant/resource/assets.gen.dart';
 import 'package:electronic_assistant/resource/colors.gen.dart';
 import 'package:electronic_assistant/resource/string.gen.dart';
@@ -307,77 +310,88 @@ class HomePage extends BasePage<HomePageController> {
       Animation<double> animation, TalkBean item) {
     return FadeTransition(
       opacity: animation,
-      child: _buildTalkView(item),
+      child: _buildTalkView(item, onLongPressStart: (details) {
+        showTalkPopup(details.globalPosition, Alignment.bottomRight,
+            onRename: () {
+          showRenameTalkDialog(item);
+        }, onDelete: () {
+          showDeleteTalkDialog(item);
+        });
+      }),
     );
   }
 
-  Widget _buildTalkView(TalkBean item) {
-    return Container(
-        width: 258.w,
-        margin: EdgeInsets.only(right: 8.w),
-        decoration: BoxDecoration(
-          color: Colors.white,
-          border: Border.all(color: '#F0F0F0'.toColor(), width: 1),
-          borderRadius: BorderRadius.circular(8),
-        ),
-        height: double.infinity,
-        padding: EdgeInsets.symmetric(horizontal: 12.w),
-        child: Column(
-          mainAxisAlignment: MainAxisAlignment.center,
-          crossAxisAlignment: CrossAxisAlignment.start,
-          children: [
-            Row(
-              children: [
-                Visibility(
-                  visible: item.isExample.isTrue,
-                  child: Container(
-                      padding: const EdgeInsets.symmetric(horizontal: 6).w,
-                      decoration: BoxDecoration(
-                        color: '#DFE4FC'.toColor(),
-                        borderRadius: BorderRadius.circular(4),
-                      ),
-                      child: Text(
-                        StringName.homeTalkExample.tr,
-                        style: TextStyle(
-                            fontSize: 12.sp, color: ColorName.colorPrimary),
-                      )),
-                ),
-                SizedBox(width: 6.w),
-                Text(item.title.orEmpty,
-                    maxLines: 1,
-                    overflow: TextOverflow.ellipsis,
-                    style: TextStyle(
-                        fontSize: 15.sp,
-                        color: ColorName.colorPrimary,
-                        fontWeight: FontWeight.bold))
-              ],
-            ),
-            SizedBox(height: 5.h),
-            Text(
-              item.summary.orEmpty,
-              style: TextStyle(
-                  fontSize: 12.sp, color: ColorName.secondaryTextColor),
-              overflow: TextOverflow.ellipsis,
-              maxLines: 2,
-            ),
-            SizedBox(height: 8.h),
-            Row(
-              crossAxisAlignment: CrossAxisAlignment.center,
-              children: [
-                Text(item.duration.toFormattedDuration(),
-                    style: TextStyle(
-                        fontSize: 12.sp, color: ColorName.tertiaryTextColor)),
-                SizedBox(width: 6.w),
-                Container(
-                    width: 1, height: 9, color: ColorName.tertiaryTextColor),
-                SizedBox(width: 6.w),
-                Text(item.createTime.orEmpty,
-                    style: TextStyle(
-                        fontSize: 12.sp, color: ColorName.tertiaryTextColor))
-              ],
-            )
-          ],
-        ));
+  Widget _buildTalkView(TalkBean item,
+      {GestureLongPressStartCallback? onLongPressStart}) {
+    return GestureDetector(
+      onLongPressStart: onLongPressStart,
+      child: Container(
+          width: 258.w,
+          margin: EdgeInsets.only(right: 8.w),
+          decoration: BoxDecoration(
+            color: Colors.white,
+            border: Border.all(color: '#F0F0F0'.toColor(), width: 1),
+            borderRadius: BorderRadius.circular(8),
+          ),
+          height: double.infinity,
+          padding: EdgeInsets.symmetric(horizontal: 12.w),
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Row(
+                children: [
+                  Visibility(
+                    visible: item.isExample.isTrue,
+                    child: Container(
+                        padding: const EdgeInsets.symmetric(horizontal: 6).w,
+                        decoration: BoxDecoration(
+                          color: '#DFE4FC'.toColor(),
+                          borderRadius: BorderRadius.circular(4),
+                        ),
+                        child: Text(
+                          StringName.homeTalkExample.tr,
+                          style: TextStyle(
+                              fontSize: 12.sp, color: ColorName.colorPrimary),
+                        )),
+                  ),
+                  SizedBox(width: 6.w),
+                  Text(item.title.orEmpty,
+                      maxLines: 1,
+                      overflow: TextOverflow.ellipsis,
+                      style: TextStyle(
+                          fontSize: 15.sp,
+                          color: ColorName.colorPrimary,
+                          fontWeight: FontWeight.bold))
+                ],
+              ),
+              SizedBox(height: 5.h),
+              Text(
+                item.summary.orEmpty,
+                style: TextStyle(
+                    fontSize: 12.sp, color: ColorName.secondaryTextColor),
+                overflow: TextOverflow.ellipsis,
+                maxLines: 2,
+              ),
+              SizedBox(height: 8.h),
+              Row(
+                crossAxisAlignment: CrossAxisAlignment.center,
+                children: [
+                  Text(item.duration.toFormattedDuration(),
+                      style: TextStyle(
+                          fontSize: 12.sp, color: ColorName.tertiaryTextColor)),
+                  SizedBox(width: 6.w),
+                  Container(
+                      width: 1, height: 9, color: ColorName.tertiaryTextColor),
+                  SizedBox(width: 6.w),
+                  Text(item.createTime.orEmpty,
+                      style: TextStyle(
+                          fontSize: 12.sp, color: ColorName.tertiaryTextColor))
+                ],
+              )
+            ],
+          )),
+    );
   }
 
   Widget _buildInsertTodoItem(BuildContext context, int index,
@@ -385,17 +399,21 @@ class HomePage extends BasePage<HomePageController> {
     HomePageController controller = Get.find();
     return FadeTransition(
       opacity: animation,
-      child: taskItemView(item, onCheckClick: () {
-        controller.agendaList.remove(
-            index,
-            (context, animation, item) =>
-                _buildRemoveTodoItem(context, index, animation, item));
-      }),
+      child: taskItemView(
+        item,
+        onCheckClick: () {
+          controller.agendaList.remove(
+              index,
+              (context, animation, item) =>
+                  _buildRemoveTodoItem(context, index, animation, item));
+        },
+      ),
     );
   }
 
   Widget _buildRemoveTodoItem(BuildContext context, int index,
       Animation<double> animation, Agenda item) {
+    item.isDone = true;
     return SizeTransition(sizeFactor: animation, child: taskItemView(item));
   }
 
@@ -439,10 +457,10 @@ class HomePage extends BasePage<HomePageController> {
     );
   }
 
-  attachDialog(BuildContext? context, Alignment alignment) {
+  void showUnfinishedRecordPopup() {
     SmartDialog.showAttach(
-      targetContext: context,
-      alignment: alignment,
+      targetContext: todoTargetContext,
+      alignment: Alignment.bottomRight,
       animationType: SmartAnimationType.fade,
       clickMaskDismiss: true,
       maskColor: Colors.transparent,
@@ -468,7 +486,17 @@ class HomePage extends BasePage<HomePageController> {
     );
   }
 
-  void showUnfinishedRecordPopup() {
-    attachDialog(todoTargetContext, Alignment.bottomRight);
+  void showRenameTalkDialog(TalkBean item) {
+    reNameDialog(StringName.talkRenameTitle.tr, item.title,
+        hintTxt: StringName.talkRenameTitleHint.tr,
+        maxLength: 15, returnBuilder: (newName) {
+      controller.requestName(newName, item);
+    });
+  }
+
+  void showDeleteTalkDialog(TalkBean item) {
+    talkDeleteDialog(item.id, item.title, returnBuilder: () {
+      controller.requestDelete(item);
+    });
   }
 }

+ 107 - 0
lib/popup/talk_popup.dart

@@ -0,0 +1,107 @@
+import 'dart:ui';
+
+import 'package:electronic_assistant/resource/colors.gen.dart';
+import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:get/get.dart';
+
+import '../resource/assets.gen.dart';
+
+void showTalkPopup(Offset offset, Alignment alignment,
+    {VoidCallback? onRename, VoidCallback? onDelete}) {
+  SmartDialog.showAttach(
+    targetContext: null,
+    targetBuilder: (targetOffset, targetSize) {
+      return offset;
+    },
+    animationType: SmartAnimationType.fade,
+    usePenetrate: true,
+    clickMaskDismiss: true,
+    alignment: alignment,
+    maskColor: Colors.transparent,
+    builder: (_) {
+      return Container(
+        width: 128.w,
+        decoration: BoxDecoration(
+          color: Colors.white,
+          border: Border.all(color: '#D8D8D8'.toColor(), width: 1), // 边框
+          borderRadius: BorderRadius.circular(8), // 圆角
+          boxShadow: [
+            BoxShadow(
+              color: Colors.black.withOpacity(0.1), // 阴影颜色
+              spreadRadius: 2, // 阴影扩散半径
+              blurRadius: 6, // 阴影模糊半径
+              offset: const Offset(0, 3), // 阴影偏移量
+            ),
+          ],
+        ),
+        child: Column(
+          children: [
+            _createNormalItem(StringName.talkRename.tr, onItemClick: () {
+              SmartDialog.dismiss();
+              onRename?.call();
+            }),
+            Divider(color: "#F6F6F6".toColor(), height: 1),
+            _buildDeleteItem(onDelete),
+          ],
+        ),
+      );
+    },
+  );
+}
+
+GestureDetector _buildDeleteItem(VoidCallback? onDelete) {
+  return GestureDetector(
+    onTap: () {
+      SmartDialog.dismiss();
+      onDelete?.call();
+    },
+    child: Container(
+      color: Colors.transparent,
+      padding: EdgeInsets.symmetric(horizontal: _itemPadding),
+      height: _itemHeight,
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          Text(
+            StringName.talkDelete.tr,
+            style: TextStyle(color: '#F5574E'.toColor(), fontSize: 14.sp),
+          ),
+          const Spacer(),
+          SizedBox(
+              width: 20.w,
+              height: 20.w,
+              child: Assets.images.iconTalkDelete.image())
+        ],
+      ),
+    ),
+  );
+}
+
+Widget _createNormalItem(String title, {VoidCallback? onItemClick}) {
+  return GestureDetector(
+    onTap: onItemClick,
+    child: Container(
+      color: Colors.transparent,
+      padding: EdgeInsets.symmetric(horizontal: _itemPadding),
+      height: _itemHeight,
+      child: Align(
+        alignment: Alignment.centerLeft,
+        child: Text(
+          StringName.talkRename.tr,
+          style: TextStyle(
+            fontSize: 14.sp,
+            color: ColorName.primaryTextColor,
+          ),
+        ),
+      ),
+    ),
+  );
+}
+
+final _itemHeight = 52.h;
+final _itemPadding = 14.w;

+ 20 - 7
lib/utils/animated_list_controller.dart

@@ -37,8 +37,22 @@ class AnimatedListController<T> {
   }
 
   void update(int index, T item) {
-    items[index] = item;
-    listKey.currentState?.insertItem(index);
+    clearAt(index);
+    add(item, index: index);
+  }
+
+  void removeItem(
+      T item, Widget Function(BuildContext, Animation<double>, T) buildItem,
+      {Duration duration = const Duration(milliseconds: 300)}) {
+    final index = items.indexOf(item);
+    if (index != -1) {
+      items.removeAt(index);
+      listKey.currentState?.removeItem(
+        index,
+        (context, animation) => buildItem(context, animation, item),
+        duration: duration,
+      );
+    }
   }
 
   void remove(
@@ -84,11 +98,6 @@ class AnimatedListController<T> {
         ?.removeItem(index, (context, animation) => Container());
   }
 
-  void clearItem(int index) {
-    items.removeAt(index);
-    listKey.currentState
-        ?.removeItem(index, (context, animation) => Container());
-  }
 
   void clearAll() {
     items.clear();
@@ -104,4 +113,8 @@ class AnimatedListController<T> {
   T get(int index) {
     return items[index];
   }
+
+  int indexOf(T item) {
+    return items.indexOf(item);
+  }
 }

+ 14 - 0
lib/utils/expand.dart

@@ -34,4 +34,18 @@ extension DurationExtension on double? {
       return '${hours}h${minutes}m${seconds}s';
     }
   }
+}
+
+extension StringExtensions on String {
+  String replacePlaceholders(List<dynamic> replacements) {
+    var result = this;
+    for (var replacement in replacements) {
+      if (replacement is String) {
+        result = result.replaceFirst('%s', replacement);
+      } else if (replacement is int) {
+        result = result.replaceFirst('%d', replacement.toString());
+      }
+    }
+    return result;
+  }
 }

+ 1 - 1
lib/utils/toast_util.dart

@@ -5,7 +5,7 @@ class ToastUtil {
 
   static void showToast(String? msg,
       {Duration? displayTime,
-      SmartToastType? displayType = SmartToastType.onlyRefresh}) {
+      SmartToastType? displayType = SmartToastType.normal}) {
     if (msg != null) {
       SmartDialog.showToast(msg,
           displayType: displayType, displayTime: displayTime);