Forráskód Böngészése

[new]首页增加谈话记录以及待办事项数据显示

zk 1 éve
szülő
commit
f6cafdf2ef

BIN
assets/images/icon_back.webp


BIN
assets/images/icon_sift.webp


BIN
assets/images/icon_task_arrow.webp


BIN
assets/images/icon_task_filter_close.webp


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

@@ -25,4 +25,10 @@
     <string name="main_drawer_record_number">备案号216545885</string>
     <string name="network_error">网络异常</string>
     <string name="account">用户</string>
+    <string name="task_title">我的待办</string>
+    <string name="search_hint">搜索所有文件标题 / 内容</string>
+    <string name="task_item_desc">项</string>
+    <string name="task_item_todo">进行中</string>
+    <string name="task_item_done">已完成</string>
+    <string name="cancel">取消</string>
 </resources>

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

@@ -1,9 +1,13 @@
 import 'package:dio/dio.dart';
+import 'package:electronic_assistant/base/app_base_request.dart';
 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/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';
+import 'package:electronic_assistant/data/api/response/home_info_response.dart';
 import 'package:electronic_assistant/data/api/response/login_response.dart';
 import 'package:electronic_assistant/data/consts/constants.dart';
 import 'package:retrofit/http.dart';
@@ -23,6 +27,14 @@ abstract class AtmobApi {
 
   @POST("/project/secretary/v1/user/info/update")
   Future<BaseResponse> updateUserInfo(@Body() UserInfoUpdateRequest request);
+
+  @POST("/project/secretary/v1/home/info")
+  Future<BaseResponse<HomeInfoResponse>> homeInfo(
+      @Body() AppBaseRequest request);
+
+  @POST("/project/secretary/v1/agenda/page")
+  Future<BaseResponse<AgendaResponse>> agendaPage(
+      @Body() AgendaRequest request);
 }
 
 final atmobApi = AtmobApi(defaultDio, baseUrl: Constants.baseUrl);

+ 34 - 0
lib/data/api/request/agenda_request.dart

@@ -0,0 +1,34 @@
+import 'package:electronic_assistant/base/app_base_request.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'agenda_request.g.dart';
+
+@JsonSerializable()
+class AgendaRequest extends AppBaseRequest {
+  @JsonKey(name: 'page')
+  final int page;
+  @JsonKey(name: 'pageSize')
+  final int pageSize;
+  @JsonKey(name: 'startTime')
+  int? startTime;
+  @JsonKey(name: 'endTime')
+  int? endTime;
+  @JsonKey(name: 'completeStatus')
+  String? completeStatus;
+
+  AgendaRequest(this.page, this.pageSize,
+      {this.startTime, this.endTime, this.completeStatus});
+
+  @override
+  Map<String, dynamic> toJson() => _$AgendaRequestToJson(this);
+}
+
+enum TaskStatus {
+  ALL(''),
+  DONE('1'),
+  TODO('2');
+
+  final String value;
+
+  const TaskStatus(this.value);
+}

+ 19 - 0
lib/data/api/response/agenda_response.dart

@@ -0,0 +1,19 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../bean/agenda.dart';
+
+part 'agenda_response.g.dart';
+
+@JsonSerializable()
+class AgendaResponse {
+  @JsonKey(name: 'count')
+  int count;
+
+  @JsonKey(name: 'list')
+  List<Agenda> list;
+
+  AgendaResponse({required this.count, required this.list});
+
+  factory AgendaResponse.fromJson(Map<String, dynamic> json) =>
+      _$AgendaResponseFromJson(json);
+}

+ 20 - 0
lib/data/api/response/home_info_response.dart

@@ -0,0 +1,20 @@
+import 'package:electronic_assistant/data/bean/agenda.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../bean/talks.dart';
+
+part 'home_info_response.g.dart';
+
+@JsonSerializable()
+class HomeInfoResponse {
+  @JsonKey(name: 'talks')
+  List<TalkBean> talks;
+
+  @JsonKey(name: 'agendas')
+  List<Agenda> agendas;
+
+  HomeInfoResponse({required this.talks, required this.agendas});
+
+  factory HomeInfoResponse.fromJson(Map<String, dynamic> json) =>
+      _$HomeInfoResponseFromJson(json);
+}

+ 44 - 0
lib/data/bean/agenda.dart

@@ -0,0 +1,44 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'agenda.g.dart';
+
+@JsonSerializable()
+class Agenda {
+  @JsonKey(name: 'id')
+  int? id;
+
+  @JsonKey(name: 'talkId')
+  String? talkId;
+
+  @JsonKey(name: 'name')
+  String? name;
+
+  @JsonKey(name: 'content')
+  String? content;
+
+  @JsonKey(name: 'createTime')
+  String? createTime;
+
+  @JsonKey(name: 'updateTime')
+  String? updateTime;
+
+  @JsonKey(name: 'todo')
+  bool? todo;
+
+  @JsonKey(name: 'example')
+  bool? isExample;
+
+  bool? isDone;
+
+  Agenda(
+      {this.id,
+      this.talkId,
+      this.name,
+      this.content,
+      this.createTime,
+      this.updateTime,
+      this.todo,
+      this.isExample});
+
+  factory Agenda.fromJson(Map<String, dynamic> json) => _$AgendaFromJson(json);
+}

+ 55 - 0
lib/data/bean/talks.dart

@@ -0,0 +1,55 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'talks.g.dart';
+
+@JsonSerializable()
+class TalkBean {
+  @JsonKey(name: 'id')
+  String? id;
+
+  @JsonKey(name: 'taskId')
+  String? taskId;
+
+  @JsonKey(name: 'ssid')
+  String? ssid;
+
+  @JsonKey(name: 'audioUrl')
+  String? audioUrl;
+
+  @JsonKey(name: 'duration')
+  double? duration;
+
+  @JsonKey(name: 'characters')
+  int? characters;
+
+  @JsonKey(name: 'status')
+  int? status;
+
+  @JsonKey(name: 'title')
+  String? title;
+
+  @JsonKey(name: 'summary')
+  String? summary;
+
+  @JsonKey(name: 'createTime')
+  String? createTime;
+
+  @JsonKey(name: 'example')
+  bool? isExample;
+
+  TalkBean(
+      {this.id,
+      this.taskId,
+      this.ssid,
+      this.audioUrl,
+      this.duration,
+      this.characters,
+      this.status,
+      this.title,
+      this.summary,
+      this.createTime,
+      this.isExample});
+
+  factory TalkBean.fromJson(Map<String, dynamic> json) =>
+      _$TalkBeanFromJson(json);
+}

+ 15 - 0
lib/data/repositories/home_repository.dart

@@ -0,0 +1,15 @@
+import 'package:electronic_assistant/base/app_base_request.dart';
+
+import '../../utils/http_handler.dart';
+import '../api/atmob_api.dart';
+import '../api/response/home_info_response.dart';
+
+class HomeRepository {
+  HomeRepository._();
+
+  Future<HomeInfoResponse> homeInfo() {
+    return atmobApi.homeInfo(AppBaseRequest()).then(HttpHandler.handle(true));
+  }
+}
+
+final homeRepository = HomeRepository._();

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

@@ -0,0 +1,21 @@
+import 'package:electronic_assistant/data/api/atmob_api.dart';
+
+import '../../utils/http_handler.dart';
+import '../api/request/agenda_request.dart';
+import '../api/response/agenda_response.dart';
+
+class TaskRepository {
+  TaskRepository._();
+
+  Future<AgendaResponse> agendaPage(int page, int pageSize,
+      {int? startTime, int? endTime, TaskStatus? completeStatus}) {
+    return atmobApi
+        .agendaPage(AgendaRequest(page, pageSize,
+            startTime: startTime,
+            endTime: endTime,
+            completeStatus: completeStatus?.value))
+        .then(HttpHandler.handle(true));
+  }
+}
+
+final taskRepository = TaskRepository._();

+ 28 - 1
lib/module/home/controller.dart

@@ -1,9 +1,13 @@
 import 'package:electronic_assistant/base/base_controller.dart';
+import 'package:electronic_assistant/data/bean/talks.dart';
 import 'package:electronic_assistant/module/main/controller.dart';
 import 'package:electronic_assistant/resource/string.gen.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';
 
 class HomePageController extends BaseController {
   get isLogin => accountRepository.isLogin.value;
@@ -12,6 +16,29 @@ class HomePageController extends BaseController {
       ? accountRepository.getUserSubName(accountRepository.phone)
       : StringName.homeGoLogin.tr;
 
+  final taskList = AnimatedListController<TalkBean>();
+  final agendaList = AnimatedListController<Agenda>();
+
+  final refreshController = PullToRefreshController();
+
+  @override
+  void onReady() {
+    super.onReady();
+    refreshController.requestRefresh();
+  }
+
+  void requestHomeData() {
+    homeRepository.homeInfo().then((data) {
+      taskList.clearAll();
+      taskList.addAll(data.talks);
+      agendaList.clearAll();
+      agendaList.addAll(data.agendas,
+          duration: const Duration(milliseconds: 500));
+    }).whenComplete(() {
+      refreshController.refreshCompleted();
+    });
+  }
+
   void goTalkRecordPage() {
     Get.find<MainController>().updateIndexByPageName(StringName.mainTabFile);
   }

+ 137 - 114
lib/module/home/view.dart

@@ -1,16 +1,19 @@
 import 'package:electronic_assistant/base/base_page.dart';
+import 'package:electronic_assistant/data/bean/talks.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';
 import 'package:electronic_assistant/utils/expand.dart';
 import 'package:electronic_assistant/utils/toast_util.dart';
-import 'package:flutter/gestures.dart';
+import 'package:electronic_assistant/widget/pull_to_refresh.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 '../../data/bean/agenda.dart';
 import '../../router/app_pages.dart';
+import '../task/task_item_view.dart';
 import 'controller.dart';
 
 class HomePage extends BasePage<HomePageController> {
@@ -33,21 +36,33 @@ class HomePage extends BasePage<HomePageController> {
                 child: buildOperationBar(),
               ),
               Expanded(
-                  child: CustomScrollView(
-                slivers: [
-                  buildTalkRecordTitle(),
-                  SliverToBoxAdapter(
-                    child: Container(
-                      height: 0.3111.sw,
-                      margin: EdgeInsets.only(bottom: 15.h),
-                      child: buildTalkRecord(),
+                  child: PullToRefresh(
+                enableRefresh: true,
+                controller: controller.refreshController,
+                onRefresh: () {
+                  controller.requestHomeData();
+                },
+                child: CustomScrollView(
+                  slivers: [
+                    buildTalkRecordTitle(),
+                    SliverToBoxAdapter(
+                      child: Container(
+                        height: 0.3111.sw,
+                        margin: EdgeInsets.only(bottom: 15.h),
+                        child: buildTalkRecord(),
+                      ),
                     ),
-                  ),
-                  buildTalkTodoTitle(),
-                  SliverAnimatedList(
-                      itemBuilder: _buildTodoItem, initialItemCount: 20),
-                  buildSeeMoreView(),
-                ],
+                    buildTalkTodoTitle(),
+                    SliverAnimatedList(
+                        key: controller.agendaList.listKey,
+                        itemBuilder: (context, index, animation) {
+                          return _buildInsertTodoItem(context, index, animation,
+                              controller.agendaList.items[index]);
+                        },
+                        initialItemCount: controller.agendaList.length),
+                    buildSeeMoreView(),
+                  ],
+                ),
               ))
             ],
           ),
@@ -66,23 +81,24 @@ class HomePage extends BasePage<HomePageController> {
       child: Container(
         alignment: Alignment.center,
         padding: const EdgeInsets.only(top: 12, bottom: 36).w,
-        child: RichText(
-          text: TextSpan(
-            text: StringName.homeTalkTodo1.tr,
-            style:
-                TextStyle(color: ColorName.secondaryTextColor, fontSize: 12.sp),
-            children: <TextSpan>[
-              TextSpan(
-                  text: StringName.homeTalkTodo2.tr,
-                  style:
-                      TextStyle(color: ColorName.colorPrimary, fontSize: 12.sp),
-                  recognizer: TapGestureRecognizer()
-                    ..onTap = () {
-                      ToastUtil.showToast('点击了全部');
-                    }),
-            ],
-          ),
-        ),
+
+        // child: RichText(
+        //   text: TextSpan(
+        //     text: StringName.homeTalkTodo1.tr,
+        //     style:
+        //         TextStyle(color: ColorName.secondaryTextColor, fontSize: 12.sp),
+        //     children: <TextSpan>[
+        //       TextSpan(
+        //           text: StringName.homeTalkTodo2.tr,
+        //           style:
+        //               TextStyle(color: ColorName.colorPrimary, fontSize: 12.sp),
+        //           recognizer: TapGestureRecognizer()
+        //             ..onTap = () {
+        //               ToastUtil.showToast('点击了全部');
+        //             }),
+        //     ],
+        //   ),
+        // ),
       ),
     );
   }
@@ -92,9 +108,10 @@ class HomePage extends BasePage<HomePageController> {
         child: Column(
       children: [
         SizedBox(height: 9.w),
-        buildTitle(StringName.homeTalkTodoTitle.tr, () {
-          ToastUtil.showToast('待办事项 查看全部');
-        }),
+        // buildTitle(StringName.homeTalkTodoTitle.tr, () {
+        //   Get.toNamed(RoutePath.task);
+        // }),
+        buildTitle(StringName.homeTalkTodoTitle.tr, null),
         SizedBox(height: 12.w)
       ],
     ));
@@ -215,7 +232,13 @@ class HomePage extends BasePage<HomePageController> {
         slivers: [
           SliverToBoxAdapter(child: SizedBox(width: 12.w)),
           buildGoRecordView(),
-          SliverAnimatedList(itemBuilder: _buildTalkItem, initialItemCount: 10)
+          SliverAnimatedList(
+              key: controller.taskList.listKey,
+              itemBuilder: (context, index, animation) {
+                return _buildInsertTalkItem(context, index, animation,
+                    controller.taskList.items[index]);
+              },
+              initialItemCount: controller.taskList.length),
         ],
       ),
     );
@@ -280,50 +303,15 @@ class HomePage extends BasePage<HomePageController> {
     ));
   }
 
-  Widget _buildTodoItem(
-      BuildContext context, int index, Animation<double> animation) {
-    return Container(
-      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 17).w,
-      margin: const EdgeInsets.only(left: 12, right: 12, bottom: 8).w,
-      decoration: BoxDecoration(
-        color: Colors.white,
-        borderRadius: BorderRadius.circular(8),
-      ),
-      child: Row(
-        crossAxisAlignment: CrossAxisAlignment.center,
-        children: [
-          SizedBox(
-              width: 20.w,
-              height: 20.w,
-              child: Assets.images.iconAgentChecked.image()),
-          SizedBox(width: 8.w),
-          Text('今天要完成某项任务',
-              style: TextStyle(
-                  fontSize: 15.sp,
-                  fontWeight: FontWeight.bold,
-                  color: ColorName.primaryTextColor)),
-          const Spacer(),
-          Container(
-            decoration: BoxDecoration(
-              gradient: LinearGradient(
-                colors: ['#9075FF'.toColor(), '#4366FF'.toColor()],
-                stops: const [0.3, 1.0],
-              ),
-              borderRadius: BorderRadius.circular(6),
-            ),
-            padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4).w,
-            child: Text(
-              StringName.homeTalkThinking.tr,
-              style: TextStyle(fontSize: 13.sp, color: ColorName.white),
-            ),
-          )
-        ],
-      ),
+  Widget _buildInsertTalkItem(BuildContext context, int index,
+      Animation<double> animation, TalkBean item) {
+    return FadeTransition(
+      opacity: animation,
+      child: _buildTalkView(item),
     );
   }
 
-  Widget _buildTalkItem(
-      BuildContext context, int index, Animation<double> animation) {
+  Widget _buildTalkView(TalkBean item) {
     return Container(
         width: 258.w,
         margin: EdgeInsets.only(right: 8.w),
@@ -340,19 +328,24 @@ class HomePage extends BasePage<HomePageController> {
           children: [
             Row(
               children: [
-                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),
-                    )),
+                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('与吴总张总沟通活动策划',
+                Text(item.title.orEmpty,
+                    maxLines: 1,
+                    overflow: TextOverflow.ellipsis,
                     style: TextStyle(
                         fontSize: 15.sp,
                         color: ColorName.colorPrimary,
@@ -361,23 +354,24 @@ class HomePage extends BasePage<HomePageController> {
             ),
             SizedBox(height: 5.h),
             Text(
-                style: TextStyle(
-                    fontSize: 12.sp, color: ColorName.secondaryTextColor),
-                overflow: TextOverflow.ellipsis,
-                maxLines: 2,
-                '这个群,现在由我管理。目的是把你们训练成一个个社会高薪人士,从本周起,已经个个社会高薪人士,从本周起,已经个个社会高薪人士,从本周起,已经'),
+              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('1m12s',
+                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('2024-04-15 10:04',
+                Text(item.createTime.orEmpty,
                     style: TextStyle(
                         fontSize: 12.sp, color: ColorName.tertiaryTextColor))
               ],
@@ -386,7 +380,33 @@ class HomePage extends BasePage<HomePageController> {
         ));
   }
 
-  Widget buildTitle(String titleName, VoidCallback onTap) {
+  Widget _buildInsertTodoItem(BuildContext context, int index,
+      Animation<double> animation, Agenda item) {
+    HomePageController controller = Get.find();
+    return SlideTransition(
+        position: animation.drive(
+          Tween<Offset>(
+            begin: const Offset(0, 0.1),
+            end: Offset.zero,
+          ).chain(CurveTween(curve: Curves.easeInOut)),
+        ),
+        child: FadeTransition(
+          opacity: animation,
+          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) {
+    return SizeTransition(sizeFactor: animation, child: taskItemView(item));
+  }
+
+  Widget buildTitle(String titleName, VoidCallback? onTap) {
     return Padding(
       padding: const EdgeInsets.symmetric(horizontal: 12).w,
       child: Row(
@@ -398,23 +418,26 @@ class HomePage extends BasePage<HomePageController> {
                   fontSize: 17.sp,
                   color: ColorName.primaryTextColor)),
           const Spacer(),
-          GestureDetector(
-            onTap: onTap,
-            child: Padding(
-              padding: const EdgeInsets.symmetric(vertical: 6).w,
-              child: Row(
-                crossAxisAlignment: CrossAxisAlignment.center,
-                children: [
-                  Text(
-                    StringName.homeTalkSeeAll.tr,
-                    style: TextStyle(
-                        fontSize: 13.sp, color: ColorName.secondaryTextColor),
-                  ),
-                  SizedBox(
-                      width: 16.w,
-                      height: 16.w,
-                      child: Assets.images.iconHomeTalkArrow.image()),
-                ],
+          Visibility(
+            visible: onTap == null ? false : true,
+            child: GestureDetector(
+              onTap: onTap,
+              child: Padding(
+                padding: const EdgeInsets.symmetric(vertical: 6).w,
+                child: Row(
+                  crossAxisAlignment: CrossAxisAlignment.center,
+                  children: [
+                    Text(
+                      StringName.homeTalkSeeAll.tr,
+                      style: TextStyle(
+                          fontSize: 13.sp, color: ColorName.secondaryTextColor),
+                    ),
+                    SizedBox(
+                        width: 16.w,
+                        height: 16.w,
+                        child: Assets.images.iconHomeTalkArrow.image()),
+                  ],
+                ),
               ),
             ),
           )

+ 1 - 1
lib/module/main/view.dart

@@ -115,7 +115,7 @@ class MainTabPage extends BasePage<MainController> {
         child: Column(
           mainAxisSize: MainAxisSize.min,
           children: <Widget>[
-            Image.asset(imagePath, width: 24.w, height: 24.w),
+            Expanded(child: Image.asset(imagePath)),
             Text(
               tabBean.title.tr,
               style: style,

+ 10 - 0
lib/module/task/controller.dart

@@ -0,0 +1,10 @@
+import 'package:electronic_assistant/base/base_controller.dart';
+import 'package:electronic_assistant/data/repositories/task_repository.dart';
+
+class TaskController extends BaseController {
+  String get filterTxt => '展示近两周待办';
+
+  refreshTodoTaskList() {
+    // taskRepository.agendaPage(page, pageSize)
+  }
+}

+ 48 - 0
lib/module/task/search/task_search.dart

@@ -0,0 +1,48 @@
+import 'package:electronic_assistant/base/base_page.dart';
+import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:get/get_core/src/get_main.dart';
+
+import '../../../resource/assets.gen.dart';
+import '../../../resource/colors.gen.dart';
+
+class TaskSearchPage extends BasePage {
+  const TaskSearchPage({super.key});
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Column(
+      children: [
+        Row(
+          children: [
+            Expanded(
+                child: Padding(
+              padding: EdgeInsets.only(left: 12.w),
+              child: CupertinoSearchTextField(
+                placeholder: StringName.searchHint.tr,
+                prefixIcon:
+                    ImageIcon(Assets.images.iconSearch.provider(), size: 20.w),
+                backgroundColor: const Color(0xFFF6F6F6),
+                style: TextStyle(
+                    fontSize: 14.w, color: ColorName.primaryTextColor),
+                placeholderStyle:
+                    TextStyle(fontSize: 14.w, color: const Color(0xFFAFAFAF)),
+              ),
+            )),
+            TextButton(
+                onPressed: () {
+                  Get.back();
+                },
+                child: Text(StringName.cancel.tr,
+                    style: TextStyle(
+                        fontSize: 14.w, color: ColorName.secondaryTextColor))),
+          ],
+        ),
+      ],
+    );
+  }
+}

+ 76 - 0
lib/module/task/task_item_view.dart

@@ -0,0 +1,76 @@
+import 'package:electronic_assistant/data/bean/agenda.dart';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import '../../resource/assets.gen.dart';
+import '../../resource/colors.gen.dart';
+import '../../resource/string.gen.dart';
+
+Widget taskItemView(Agenda item,
+    {VoidCallback? onCheckClick, VoidCallback? onThinkingClick}) {
+  return Container(
+    padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 17).w,
+    margin: const EdgeInsets.only(left: 12, right: 12, bottom: 8).w,
+    decoration: BoxDecoration(
+      color: Colors.white,
+      borderRadius: BorderRadius.circular(8),
+    ),
+    child: Row(
+      crossAxisAlignment: CrossAxisAlignment.center,
+      children: [
+        GestureDetector(
+          onTap: onCheckClick,
+          child: SizedBox(
+              width: 20.w,
+              height: 20.w,
+              child: item.isDone.isTrue
+                  ? Assets.images.iconAgentChecked.image()
+                  : Assets.images.iconAgentUnCheck.image()),
+        ),
+        SizedBox(width: 3.w),
+        Visibility(
+          visible: item.isExample.isTrue,
+          child: Container(
+            decoration: BoxDecoration(
+              color: ColorName.colorPrimary.withOpacity(0.2),
+              borderRadius: BorderRadius.circular(4),
+            ),
+            padding: const EdgeInsets.symmetric(horizontal: 6).w,
+            child: Text(
+              StringName.homeTalkExample.tr,
+              style: TextStyle(fontSize: 12.sp, color: ColorName.colorPrimary),
+            ),
+          ),
+        ),
+        SizedBox(width: 5.w),
+        Expanded(
+          child: Padding(
+            padding: const EdgeInsets.only(right: 12).w,
+            child: Text(item.content ?? '',
+                maxLines: 1,
+                overflow: TextOverflow.ellipsis,
+                style: TextStyle(
+                    fontSize: 15.sp,
+                    fontWeight: FontWeight.bold,
+                    color: ColorName.primaryTextColor)),
+          ),
+        ),
+        Container(
+          decoration: BoxDecoration(
+            gradient: LinearGradient(
+              colors: ['#9075FF'.toColor(), '#4366FF'.toColor()],
+              stops: const [0.3, 1.0],
+            ),
+            borderRadius: BorderRadius.circular(6),
+          ),
+          padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4).w,
+          child: Text(
+            StringName.homeTalkThinking.tr,
+            style: TextStyle(fontSize: 13.sp, color: ColorName.white),
+          ),
+        )
+      ],
+    ),
+  );
+}

+ 200 - 0
lib/module/task/view.dart

@@ -0,0 +1,200 @@
+import 'package:electronic_assistant/base/base_page.dart';
+import 'package:electronic_assistant/module/task/task_item_view.dart';
+import 'package:electronic_assistant/resource/colors.gen.dart';
+import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:electronic_assistant/router/app_pages.dart';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+
+import '../../resource/assets.gen.dart';
+import 'controller.dart';
+
+class TaskPage extends BasePage<TaskController> {
+  const TaskPage({super.key});
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        buildBgBox(),
+        buildTopGradient(),
+        Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            AppBar(
+              systemOverlayStyle: SystemUiOverlayStyle.dark,
+              backgroundColor: Colors.transparent,
+              leading: IconButton(
+                icon: SizedBox(
+                    width: 24.w,
+                    height: 24.w,
+                    child: Assets.images.iconBack.image()),
+                // Custom icon
+                onPressed: () {
+                  Get.back();
+                },
+              ),
+              title: Text(StringName.taskTitle.tr,
+                  style: TextStyle(
+                      fontSize: 17.sp,
+                      fontWeight: FontWeight.bold,
+                      color: ColorName.primaryTextColor)),
+              centerTitle: true,
+            ),
+            Row(
+              children: [
+                Expanded(
+                    child: Padding(
+                  padding: EdgeInsets.only(left: 12.w),
+                  child: CupertinoSearchTextField(
+                    enabled: false,
+                    onTap: () {
+                      Get.toNamed(RoutePath.taskSearch);
+                    },
+                    placeholder: StringName.searchHint.tr,
+                    prefixIcon: ImageIcon(Assets.images.iconSearch.provider(),
+                        size: 20.w),
+                    backgroundColor: Colors.white,
+                    style: TextStyle(
+                        fontSize: 14.w, color: ColorName.primaryTextColor),
+                    placeholderStyle: TextStyle(
+                        fontSize: 14.w, color: const Color(0xFFAFAFAF)),
+                  ),
+                )),
+                GestureDetector(
+                  onTap: () {},
+                  child: Column(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    children: [
+                      Container(
+                          margin: EdgeInsets.symmetric(horizontal: 16.w),
+                          width: 20.w,
+                          height: 20.w,
+                          child: Assets.images.iconSift.image()),
+                      // Replace with your image asset
+                      SizedBox(height: 2.h),
+                      // Add some space between the image and text
+                      Text(
+                        '筛选',
+                        style: TextStyle(
+                            fontSize: 10.sp, color: ColorName.primaryTextColor),
+                      ),
+                    ],
+                  ),
+                )
+              ],
+            ),
+            SizedBox(height: 12.h),
+            Container(
+              margin: EdgeInsets.only(left: 12.w),
+              padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
+              decoration: BoxDecoration(
+                  color: const Color(0xFFE9E9E9),
+                  borderRadius: BorderRadius.circular(8.w)),
+              child: IntrinsicWidth(
+                child: Row(
+                  children: [
+                    Text(controller.filterTxt,
+                        style: TextStyle(
+                            fontSize: 13.sp,
+                            color: ColorName.secondaryTextColor)),
+                    SizedBox(width: 6.w),
+                    Opacity(
+                        opacity: 0.7,
+                        child: ImageIcon(
+                            Assets.images.iconTaskFilterClose.provider(),
+                            size: 16.w))
+                  ],
+                ),
+              ),
+            ),
+            SizedBox(width: 7.h),
+            Expanded(
+                child: CustomScrollView(slivers: [
+              SliverToBoxAdapter(
+                child: taskGroupItem(StringName.taskItemTodo.tr, 3, false),
+              ),
+              SliverAnimatedList(
+                  itemBuilder: _buildTodoItem, initialItemCount: 4),
+              SliverToBoxAdapter(
+                child: taskGroupItem(StringName.taskItemDone.tr, 16, false),
+              ),
+              SliverAnimatedList(
+                  itemBuilder: _buildDoneItem, initialItemCount: 10),
+            ]))
+          ],
+        )
+      ],
+    );
+  }
+
+  Widget _buildTodoItem(
+      BuildContext context, int index, Animation<double> animation) {
+    return Container();
+  }
+
+  Widget _buildDoneItem(
+      BuildContext context, int index, Animation<double> animation) {
+    return Container();
+  }
+
+  Container buildTopGradient() {
+    return Container(
+        width: 1.sw,
+        height: 112.h,
+        decoration: BoxDecoration(
+          gradient: LinearGradient(
+            colors: ['#E8EBFF'.toColor(), '#00E8EBFF'.toColor()],
+            begin: Alignment.topCenter,
+            end: Alignment.bottomCenter,
+            stops: const [0.3, 1.0],
+          ),
+        ) // 其他子小部件
+        );
+  }
+
+  DecoratedBox buildBgBox() {
+    return DecoratedBox(
+        decoration: BoxDecoration(color: '#F6F6F6'.toColor()),
+        child: Container(
+          height: double.infinity,
+        ));
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  Widget taskGroupItem(String groupName, int count, bool isExpanded) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 12.w),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          Text(groupName,
+              style: TextStyle(
+                  fontSize: 14.sp, color: ColorName.secondaryTextColor)),
+          //占位填充
+          const Spacer(),
+          Row(
+            children: [
+              Text('$count${StringName.taskItemDesc.tr}',
+                  style: TextStyle(
+                      fontSize: 12.sp, color: ColorName.secondaryTextColor)),
+              SizedBox(width: 4.w),
+              SizedBox(
+                  width: 16.w,
+                  height: 16.h,
+                  child: Assets.images.iconTaskArrow.image())
+            ],
+          )
+        ],
+      ),
+    );
+  }
+}

+ 10 - 0
lib/router/app_pages.dart

@@ -1,4 +1,6 @@
 import 'package:electronic_assistant/module/main/controller.dart';
+import 'package:electronic_assistant/module/task/search/task_search.dart';
+import 'package:electronic_assistant/module/task/view.dart';
 import 'package:get/get.dart';
 
 import '../module/chat/controller.dart';
@@ -10,6 +12,7 @@ import '../module/login/controller.dart';
 import '../module/login/view.dart';
 import '../module/main/view.dart';
 import '../module/splash/view.dart';
+import '../module/task/controller.dart';
 
 abstract class AppPage {
   static final pages = <GetPage>[
@@ -28,6 +31,10 @@ abstract class RoutePath {
 
   static const fileSearch = '/fileSearch';
 
+  static const task = '/task';
+
+  static const taskSearch = '/taskSearch';
+
   static const chat = '/chat';
 }
 
@@ -37,6 +44,7 @@ class AppBinding extends Bindings {
     lazyPut(() => MainController());
     lazyPut(() => HomePageController());
     lazyPut(() => LoginController());
+    lazyPut(() => TaskController());
     lazyPut(() => ChatController());
   }
 
@@ -52,4 +60,6 @@ final generalPages = [
   GetPage(name: RoutePath.files, page: () => const FilesPage()),
   GetPage(name: RoutePath.fileSearch, page: () => const FileSearchPage()),
   GetPage(name: RoutePath.chat, page: () => const ChatPage()),
+  GetPage(name: RoutePath.task, page: () => const TaskPage()),
+  GetPage(name: RoutePath.taskSearch, page: () => const TaskSearchPage()),
 ];

+ 107 - 0
lib/utils/animated_list_controller.dart

@@ -0,0 +1,107 @@
+import 'package:flutter/widgets.dart';
+
+class AnimatedListController<T> {
+  final List<T> items = [];
+  final GlobalKey<SliverAnimatedListState> listKey =
+      GlobalKey<SliverAnimatedListState>();
+
+  set items(List<T> items) {
+    this.items = items;
+  }
+
+  void add(T item,
+      {Duration duration = const Duration(milliseconds: 300), int? index}) {
+    if (index != null) {
+      items.insert(index, item);
+      listKey.currentState?.insertItem(index, duration: duration);
+    } else {
+      items.add(item);
+      listKey.currentState?.insertItem(items.length - 1, duration: duration);
+    }
+  }
+
+  void addAtIndex(int index, List<T> newItems,
+      {Duration duration = const Duration(milliseconds: 300)}) {
+    for (var i = 0; i < newItems.length; i++) {
+      items.insert(index + i, newItems[i]);
+      listKey.currentState?.insertItem(index + i, duration: duration);
+    }
+  }
+
+  void addAll(List<T> newItems,
+      {Duration duration = const Duration(milliseconds: 300)}) {
+    for (var i = 0; i < newItems.length; i++) {
+      items.add(newItems[i]);
+      listKey.currentState?.insertItem(items.length - 1, duration: duration);
+    }
+  }
+
+  void update(int index, T item) {
+    items[index] = item;
+    listKey.currentState?.insertItem(index);
+  }
+
+  void remove(
+      int index, Widget Function(BuildContext, Animation<double>, T) buildItem,
+      {Duration duration = const Duration(milliseconds: 300)}) {
+    final removedItem = items.removeAt(index);
+    listKey.currentState?.removeItem(
+      index,
+      (context, animation) => buildItem(context, animation, removedItem),
+      duration: duration,
+    );
+  }
+
+  void removeAtIndex(int index, int count,
+      Widget Function(BuildContext, Animation<double>, T) buildItem,
+      {Duration duration = const Duration(milliseconds: 300)}) {
+    for (var i = 0; i < count; i++) {
+      final removedItem = items.removeAt(index);
+      listKey.currentState?.removeItem(
+        index,
+        (context, animation) => buildItem(context, animation, removedItem),
+        duration: duration,
+      );
+    }
+  }
+
+  void removeAll(Widget Function(BuildContext, Animation<double>, T) buildItem,
+      {Duration duration = const Duration(milliseconds: 300)}) {
+    for (var i = items.length - 1; i >= 0; i--) {
+      final removedItem = items.removeAt(i);
+      listKey.currentState?.removeItem(
+        i,
+        (context, animation) => buildItem(context, animation, removedItem),
+        duration: duration,
+      );
+    }
+  }
+
+  /// *不带动画****
+  void clearAt(int index) {
+    items.removeAt(index);
+    listKey.currentState
+        ?.removeItem(index, (context, animation) => Container());
+  }
+
+  void clearItem(int index) {
+    items.removeAt(index);
+    listKey.currentState
+        ?.removeItem(index, (context, animation) => Container());
+  }
+
+  void clearAll() {
+    items.clear();
+    listKey.currentState?.removeAllItems((context, animation) => Container());
+  }
+
+  /// *不带动画****
+
+  int get length {
+    return items.length;
+  }
+
+  T get(int index) {
+    return items[index];
+  }
+}

+ 29 - 0
lib/utils/expand.dart

@@ -6,3 +6,32 @@ extension HexColor on String {
     return Color(int.parse('FF$hexCode', radix: 16));
   }
 }
+
+extension BoolExtension on bool? {
+  bool get isTrue => this == true;
+
+  bool get isFalse => this == false;
+}
+
+extension StringExtension on String? {
+  String get orEmpty => this ?? '';
+}
+
+extension DurationExtension on double? {
+  String toFormattedDuration() {
+    if (this == null) return '';
+    int totalSeconds = (this!).round();
+    if (totalSeconds < 60) {
+      return '${totalSeconds}s';
+    } else if (totalSeconds < 3600) {
+      int minutes = totalSeconds ~/ 60;
+      int seconds = totalSeconds % 60;
+      return '${minutes}m${seconds}s';
+    } else {
+      int hours = totalSeconds ~/ 3600;
+      int minutes = (totalSeconds % 3600) ~/ 60;
+      int seconds = totalSeconds % 60;
+      return '${hours}h${minutes}m${seconds}s';
+    }
+  }
+}

+ 0 - 30
test/widget_test.dart

@@ -1,30 +0,0 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility in the flutter_test package. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:electronic_assistant/main.dart';
-
-void main() {
-  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
-    // Build our app and trigger a frame.
-    await tester.pumpWidget(const MyApp());
-
-    // Verify that our counter starts at 0.
-    expect(find.text('0'), findsOneWidget);
-    expect(find.text('1'), findsNothing);
-
-    // Tap the '+' icon and trigger a frame.
-    await tester.tap(find.byIcon(Icons.add));
-    await tester.pump();
-
-    // Verify that our counter has incremented.
-    expect(find.text('0'), findsNothing);
-    expect(find.text('1'), findsOneWidget);
-  });
-}