add-multi-filter

为Flutter插件视图添加多条件过滤功能(MultiFilterBar),支持标签、关键词、日期、优先级、复选框等多种过滤类型。替代传统的FilterDialog,提供更直观的两层级交互UI。适用场景:(1) 列表视图需要多维度筛选,(2) 数据量较大需要快速过滤,(3) 需要实时显示过滤条件的应用

$ Installer

git clone https://github.com/hunmer/Memento /tmp/Memento && cp -r /tmp/Memento/.claude/skills/add-multi-filter ~/.claude/skills/Memento

// tip: Run this command in your terminal to install the skill


name: add-multi-filter description: 为Flutter插件视图添加多条件过滤功能(MultiFilterBar),支持标签、关键词、日期、优先级、复选框等多种过滤类型。替代传统的FilterDialog,提供更直观的两层级交互UI。适用场景:(1) 列表视图需要多维度筛选,(2) 数据量较大需要快速过滤,(3) 需要实时显示过滤条件的应用

Add Multi-Filter

为Flutter插件视图添加强大的多条件过滤功能,使用 SuperCupertinoNavigationWrapper 的 MultiFilterBar 组件。

Usage

# 基础用法 - 自动检测过滤需求
/add-multi-filter <view-file>

# 指定过滤条件类型
/add-multi-filter <view-file> --filters tags,priority,date,status

# 指定控制器变量名
/add-multi-filter <view-file> --controller _controller

Examples:

# 为待办事项插件添加过滤
/add-multi-filter lib/plugins/todo/views/todo_main_view.dart

# 为日记插件添加标签和日期过滤
/add-multi-filter lib/plugins/diary/views/diary_list_view.dart --filters tags,dateRange

# 自定义控制器名称
/add-multi-filter lib/plugins/activity/views/activity_list_view.dart --controller _activityController

Arguments

  • <view-file>: 目标 Flutter 视图文件路径(包含 SuperCupertinoNavigationWrapper)
  • --filters <types>: 过滤条件类型列表(逗号分隔)
    • 可选值:tags, keyword, priority, date, dateRange, status, custom
    • 默认:自动检测(基于控制器方法)
  • --controller <name>: 控制器变量名(默认:自动检测)

Workflow

1. Analyze Target File

读取目标文件并识别:

  • SuperCupertinoNavigationWrapper 位置
  • 控制器变量名(如 _plugin.taskController
  • 现有的过滤/排序功能
  • 可用的数据(标签列表、优先级枚举等)

2. Add Required Imports

在文件顶部添加导入:

import 'package:Memento/widgets/super_cupertino_navigation_wrapper/index.dart';

3. Create Filter Items Method

创建 _buildFilterItems() 方法,根据检测到的数据结构构建过滤条件:

/// 构建过滤条件列表
List<FilterItem> _buildFilterItems() {
  // 获取动态数据
  final availableTags = _controller.getAllTags();

  return [
    // 1. 标签多选过滤(如果有标签)
    if (availableTags.isNotEmpty)
      FilterItem(
        id: 'tags',
        title: 'plugin_tags'.tr,
        type: FilterType.tagsMultiple,
        builder: (context, currentValue, onChanged) {
          return FilterBuilders.buildTagsFilter(
            context: context,
            currentValue: currentValue,
            onChanged: onChanged,
            availableTags: availableTags,
          );
        },
        getBadge: FilterBuilders.tagsBadge,
      ),

    // 2. 优先级过滤(如果有优先级枚举)
    FilterItem(
      id: 'priority',
      title: 'plugin_priority'.tr,
      type: FilterType.custom,
      builder: (context, currentValue, onChanged) {
        return FilterBuilders.buildPriorityFilter<PriorityEnum>(
          context: context,
          currentValue: currentValue,
          onChanged: onChanged,
          priorityLabels: {
            PriorityEnum.low: 'plugin_low'.tr,
            PriorityEnum.medium: 'plugin_medium'.tr,
            PriorityEnum.high: 'plugin_high'.tr,
          },
          priorityColors: const {
            PriorityEnum.low: Colors.green,
            PriorityEnum.medium: Colors.orange,
            PriorityEnum.high: Colors.red,
          },
        );
      },
      getBadge: (value) => FilterBuilders.priorityBadge(
        value,
        {
          PriorityEnum.low: 'plugin_low'.tr,
          PriorityEnum.medium: 'plugin_medium'.tr,
          PriorityEnum.high: 'plugin_high'.tr,
        },
      ),
    ),

    // 3. 日期范围过滤
    FilterItem(
      id: 'dateRange',
      title: 'plugin_dateRange'.tr,
      type: FilterType.dateRange,
      builder: (context, currentValue, onChanged) {
        return FilterBuilders.buildDateRangeFilter(
          context: context,
          currentValue: currentValue,
          onChanged: onChanged,
        );
      },
      getBadge: FilterBuilders.dateRangeBadge,
    ),

    // 4. 状态复选框过滤
    FilterItem(
      id: 'status',
      title: 'plugin_status'.tr,
      type: FilterType.checkbox,
      builder: (context, currentValue, onChanged) {
        return FilterBuilders.buildCheckboxFilter(
          context: context,
          currentValue: currentValue,
          onChanged: onChanged,
          options: {
            'option1': 'plugin_option1'.tr,
            'option2': 'plugin_option2'.tr,
          },
        );
      },
      getBadge: FilterBuilders.checkboxBadge,
      initialValue: const {
        'option1': true,
        'option2': true,
      },
    ),
  ];
}

4. Create Filter Handler Method

创建 _applyMultiFilters() 方法处理过滤变更:

/// 应用多条件过滤
void _applyMultiFilters(Map<String, dynamic> filters) {
  // 构建过滤参数
  final filterParams = <String, dynamic>{};

  // 标签过滤
  if (filters['tags'] != null && (filters['tags'] as List).isNotEmpty) {
    filterParams['tags'] = filters['tags'];
  }

  // 优先级过滤
  if (filters['priority'] != null) {
    filterParams['priority'] = filters['priority'];
  }

  // 日期范围过滤
  if (filters['dateRange'] != null) {
    final range = filters['dateRange'] as DateTimeRange;
    filterParams['startDate'] = range.start;
    filterParams['endDate'] = range.end;
  }

  // 状态过滤
  if (filters['status'] != null) {
    final status = filters['status'] as Map<String, bool>;
    filterParams['option1'] = status['option1'] ?? true;
    filterParams['option2'] = status['option2'] ?? true;
  }

  // 应用过滤
  if (filterParams.isEmpty) {
    _controller.clearFilter();
  } else {
    _controller.applyFilter(filterParams);
  }

  setState(() {});
}

5. Update SuperCupertinoNavigationWrapper

在 SuperCupertinoNavigationWrapper 中添加多条件过滤配置:

SuperCupertinoNavigationWrapper(
  // ... 现有配置 ...

  // 启用多条件过滤
  enableMultiFilter: true,
  multiFilterItems: _buildFilterItems(),
  multiFilterBarHeight: 50,
  onMultiFilterChanged: _applyMultiFilters,

  // ... 其他配置 ...
)

6. Remove Old Filter Dialog (Optional)

如果存在旧的 FilterDialog:

  1. 移除 FilterDialog 导入
  2. 删除 _showFilterDialog() 方法
  3. 移除 actions 中的 filter 按钮:
// 移除这个按钮
IconButton(
  icon: const Icon(Icons.filter_alt),
  onPressed: _showFilterDialog,  // ❌ 删除
),

7. Add Internationalization Strings

在插件的国际化文件中添加必要的字符串:

中文 (zh):

'plugin_tags': '标签',
'plugin_priority': '优先级',
'plugin_low': '低',
'plugin_medium': '中',
'plugin_high': '高',
'plugin_dateRange': '日期范围',
'plugin_status': '状态',
'plugin_option1': '选项1',
'plugin_option2': '选项2',

英文 (en):

'plugin_tags': 'Tags',
'plugin_priority': 'Priority',
'plugin_low': 'Low',
'plugin_medium': 'Medium',
'plugin_high': 'High',
'plugin_dateRange': 'Date Range',
'plugin_status': 'Status',
'plugin_option1': 'Option 1',
'plugin_option2': 'Option 2',

Filter Types Guide

Available Filter Types

类型FilterType构建器方法Badge方法返回值
标签多选tagsMultiplebuildTagsFiltertagsBadgeList<String>
标签单选tagsSinglebuildTagFiltertagBadgeString?
关键词inputbuildKeywordFilterkeywordBadgeString
优先级custombuildPriorityFilter<T>priorityBadgeT?
日期范围dateRangebuildDateRangeFilterdateRangeBadgeDateTimeRange?
单日期datebuildDateFilterdateBadgeDateTime?
复选框checkboxbuildCheckboxFiltercheckboxBadgeMap<String, bool>
自定义custom自定义 widget自定义函数任意类型

Detection Patterns

Tags Filter

查找方法:

  • getAllTags(), getTags()
  • 返回类型:List<String>Set<String>

Priority Filter

查找枚举:

  • 名称包含 Priority
  • 值:low, medium, high

Date Filter

查找方法:

  • applyFilter() 接受 startDate, endDate 参数
  • DateTime 类型的字段

Status Filter

查找方法:

  • applyFilter() 接受 showCompleted, showIncomplete 等布尔参数

Controller Requirements

控制器需要实现以下方法:

class YourController extends ChangeNotifier {
  // 必需:应用过滤
  void applyFilter(Map<String, dynamic> filter) {
    // 实现过滤逻辑
  }

  // 必需:清除过滤
  void clearFilter() {
    // 清除所有过滤条件
  }

  // 可选:获取标签列表
  List<String> getAllTags() {
    return _items.expand((item) => item.tags).toSet().toList();
  }

  // 可选:获取过滤后的数据
  List<YourModel> get filteredItems {
    return _applyFilterLogic(_items);
  }
}

Example: Complete Implementation

以下是一个完整的示例(基于 todo 插件):

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Memento/widgets/super_cupertino_navigation_wrapper.dart';
import 'package:Memento/widgets/super_cupertino_navigation_wrapper/index.dart';
import 'package:Memento/plugins/todo/models/task.dart';
import 'package:Memento/plugins/todo/todo_plugin.dart';

class TodoListView extends StatefulWidget {
  const TodoListView({super.key});

  @override
  State<TodoListView> createState() => _TodoListViewState();
}

class _TodoListViewState extends State<TodoListView> {
  late TodoPlugin _plugin;

  @override
  void initState() {
    super.initState();
    _plugin = TodoPlugin.instance;
  }

  /// 构建过滤条件列表
  List<FilterItem> _buildFilterItems() {
    final availableTags = _plugin.taskController.getAllTags();

    return [
      // 标签多选
      if (availableTags.isNotEmpty)
        FilterItem(
          id: 'tags',
          title: 'todo_tags'.tr,
          type: FilterType.tagsMultiple,
          builder: (context, currentValue, onChanged) {
            return FilterBuilders.buildTagsFilter(
              context: context,
              currentValue: currentValue,
              onChanged: onChanged,
              availableTags: availableTags,
            );
          },
          getBadge: FilterBuilders.tagsBadge,
        ),

      // 优先级
      FilterItem(
        id: 'priority',
        title: 'todo_priority'.tr,
        type: FilterType.custom,
        builder: (context, currentValue, onChanged) {
          return FilterBuilders.buildPriorityFilter<TaskPriority>(
            context: context,
            currentValue: currentValue,
            onChanged: onChanged,
            priorityLabels: {
              TaskPriority.low: 'todo_low'.tr,
              TaskPriority.medium: 'todo_medium'.tr,
              TaskPriority.high: 'todo_high'.tr,
            },
            priorityColors: const {
              TaskPriority.low: Colors.green,
              TaskPriority.medium: Colors.orange,
              TaskPriority.high: Colors.red,
            },
          );
        },
        getBadge: (value) => FilterBuilders.priorityBadge(
          value,
          {
            TaskPriority.low: 'todo_low'.tr,
            TaskPriority.medium: 'todo_medium'.tr,
            TaskPriority.high: 'todo_high'.tr,
          },
        ),
      ),

      // 日期范围
      FilterItem(
        id: 'dateRange',
        title: 'todo_dateRange'.tr,
        type: FilterType.dateRange,
        builder: (context, currentValue, onChanged) {
          return FilterBuilders.buildDateRangeFilter(
            context: context,
            currentValue: currentValue,
            onChanged: onChanged,
          );
        },
        getBadge: FilterBuilders.dateRangeBadge,
      ),

      // 完成状态
      FilterItem(
        id: 'status',
        title: 'todo_status'.tr,
        type: FilterType.checkbox,
        builder: (context, currentValue, onChanged) {
          return FilterBuilders.buildCheckboxFilter(
            context: context,
            currentValue: currentValue,
            onChanged: onChanged,
            options: {
              'showCompleted': 'todo_showCompleted'.tr,
              'showIncomplete': 'todo_showIncomplete'.tr,
            },
          );
        },
        getBadge: FilterBuilders.checkboxBadge,
        initialValue: const {
          'showCompleted': true,
          'showIncomplete': true,
        },
      ),
    ];
  }

  /// 应用多条件过滤
  void _applyMultiFilters(Map<String, dynamic> filters) {
    final filterParams = <String, dynamic>{};

    if (filters['tags'] != null && (filters['tags'] as List).isNotEmpty) {
      filterParams['tags'] = filters['tags'];
    }

    if (filters['priority'] != null) {
      filterParams['priority'] = filters['priority'];
    }

    if (filters['dateRange'] != null) {
      final range = filters['dateRange'] as DateTimeRange;
      filterParams['startDate'] = range.start;
      filterParams['endDate'] = range.end;
    }

    if (filters['status'] != null) {
      final status = filters['status'] as Map<String, bool>;
      filterParams['showCompleted'] = status['showCompleted'] ?? true;
      filterParams['showIncomplete'] = status['showIncomplete'] ?? true;
    }

    if (filterParams.isEmpty) {
      _plugin.taskController.clearFilter();
    } else {
      _plugin.taskController.applyFilter(filterParams);
    }

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return SuperCupertinoNavigationWrapper(
      title: Text('todo_tasks'.tr),
      largeTitle: 'todo_tasks'.tr,

      // 启用多条件过滤
      enableMultiFilter: true,
      multiFilterItems: _buildFilterItems(),
      multiFilterBarHeight: 50,
      onMultiFilterChanged: _applyMultiFilters,

      body: AnimatedBuilder(
        animation: _plugin.taskController,
        builder: (context, _) {
          return TaskListWidget(
            tasks: _plugin.taskController.filteredTasks,
          );
        },
      ),
    );
  }
}

Best Practices

1. Filter Count

建议不超过 4-5 个过滤条件,过多会影响用户体验。

2. Dynamic Data

对于动态数据(如标签列表),使用条件渲染:

if (availableTags.isNotEmpty)
  FilterItem(
    id: 'tags',
    // ...
  ),

3. Initial Values

为常用过滤条件设置合理的初始值:

FilterItem(
  id: 'status',
  // ...
  initialValue: const {
    'showCompleted': true,
    'showIncomplete': true,
  },
),

4. Performance

onMultiFilterChanged 中使用防抖处理(如果过滤操作很耗时):

Timer? _filterDebounce;

void _applyMultiFilters(Map<String, dynamic> filters) {
  _filterDebounce?.cancel();
  _filterDebounce = Timer(const Duration(milliseconds: 300), () {
    // 执行过滤逻辑
  });
}

5. Internationalization

所有用户可见的文本都应该使用国际化:

title: 'plugin_filterName'.tr,

Testing Checklist

完成后验证:

  • flutter analyze 无错误
  • 所有过滤条件都能正常工作
  • Badge 正确显示过滤状态
  • 清空按钮能清除所有过滤
  • 搜索模式下过滤栏正确隐藏
  • 组合多个过滤条件测试
  • 国际化字符串完整

Troubleshooting

过滤不生效

检查控制器的 applyFilter() 方法是否正确实现。

Badge 不显示

确保 getBadge 函数返回非空字符串。

标签列表为空

检查 getAllTags() 方法是否正确返回数据。

日期选择器异常

确保导入了 Material 组件:

import 'package:flutter/material.dart';

Notes

  • 使用中文注释与现有代码库保持一致
  • 优先使用 FilterBuilders 提供的构建器方法
  • 自定义过滤器时注意类型安全
  • 使用 flutter analyze 验证代码
  • 参考 USAGE_EXAMPLE.md 获取更多示例