diff --git a/lib/apis/api.dart b/lib/apis/api.dart index 34e9423..8cfc206 100644 --- a/lib/apis/api.dart +++ b/lib/apis/api.dart @@ -15,7 +15,13 @@ class Api { ); } - /// 获取部门信息 + /// 获取分页角色列表 + static Future> getRoles(Map params) { + return DioService.dio.get(Urls.roles, + queryParameters: {'page': 1, 'pageSize': 10, ...params}); + } + + /// 获取部门树 static Future getDepts() { return DioService.dio.get(Urls.depts); } diff --git a/lib/app_theme.dart b/lib/app_theme.dart index 5413d6f..bdc2388 100644 --- a/lib/app_theme.dart +++ b/lib/app_theme.dart @@ -117,6 +117,10 @@ final theme = ThemeData( color: AppTheme.primaryColor, fontSize: ScreenAdaper.height(30), ), + hintStyle: TextStyle( + color: AppTheme.grey, + fontSize: ScreenAdaper.height(25), + ), fillColor: AppTheme.inputFillColor, filled: true, labelStyle: TextStyle( diff --git a/lib/constants/global_url.dart b/lib/constants/global_url.dart index dc124c2..7226408 100644 --- a/lib/constants/global_url.dart +++ b/lib/constants/global_url.dart @@ -17,4 +17,5 @@ class Urls { static String accountMenus = 'account/menus'; static String userInfo = 'system/users'; static String depts = 'system/depts'; + static String roles = 'system/roles'; } diff --git a/lib/models/role.model.dart b/lib/models/role.model.dart index b66c95e..9b0188d 100644 --- a/lib/models/role.model.dart +++ b/lib/models/role.model.dart @@ -13,7 +13,7 @@ class RoleModel { final int? id; final DateTime? createdAt; final DateTime? updatedAt; - final String? name; + final String name; final String? value; final String? remark; final int? status; diff --git a/lib/screens/hr_manage/components/dept_picker.dart b/lib/screens/hr_manage/components/dept_picker.dart index 84fef8e..82183d6 100644 --- a/lib/screens/hr_manage/components/dept_picker.dart +++ b/lib/screens/hr_manage/components/dept_picker.dart @@ -42,7 +42,6 @@ class DeptPicker extends StatelessWidget { } Future>> getData() async { - await Future.delayed(const Duration(milliseconds: 500)); try { final res = await Api.getDepts(); if (res.data != null) { diff --git a/lib/screens/hr_manage/components/edit_userinfo.dart b/lib/screens/hr_manage/components/edit_userinfo.dart index 5977bae..016f372 100644 --- a/lib/screens/hr_manage/components/edit_userinfo.dart +++ b/lib/screens/hr_manage/components/edit_userinfo.dart @@ -5,12 +5,16 @@ import 'package:get/get.dart'; import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; +import 'package:sk_base_mobile/models/role.model.dart'; import 'package:sk_base_mobile/models/user_info.model.dart'; import 'package:sk_base_mobile/screens/hr_manage/components/dept_picker.dart'; +import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart'; import 'package:sk_base_mobile/widgets/core/sk_cascade_picker.dart'; import 'package:sk_base_mobile/widgets/core/sk_dialog_header.dart'; +import 'package:sk_base_mobile/widgets/core/sk_multi_picker.dart'; +import 'package:sk_base_mobile/widgets/core/sk_multi_picker_dialog.dart'; import 'package:sk_base_mobile/widgets/core/sk_text_input.dart'; import 'package:sk_base_mobile/widgets/gradient_button.dart'; @@ -51,6 +55,7 @@ class EditUserInfo extends StatelessWidget { SkTextInput( isDense: true, textController: controller.nameEditController, + customLabel: true, labelText: '姓名', ), SizedBox( @@ -60,6 +65,7 @@ class EditUserInfo extends StatelessWidget { /// 部门 SkTextInput( isDense: true, + customLabel: true, keyboardType: TextInputType.none, textController: controller.deptEditController, labelText: '所属部门', @@ -74,81 +80,48 @@ class EditUserInfo extends StatelessWidget { ), /// 角色 - SkTextInput( + SkMultiPickerDropdown( isDense: true, + customLabel: true, keyboardType: TextInputType.none, - textController: TextEditingController(), + textController: controller.roleEditController, labelText: '角色', + hint: '选择角色', onTap: (_) async { - Get.bottomSheet(Container( - height: ScreenAdaper.height(400), - decoration: - const BoxDecoration(color: AppTheme.nearlyWhite), - child: Column(children: [ - Container( - padding: EdgeInsets.symmetric( - vertical: ScreenAdaper.height(20), - horizontal: ScreenAdaper.width(20)), - child: Row( - children: [ - Text( - '取消', - style: TextStyle( - fontSize: ScreenAdaper.height(30)), - ), - const Spacer(), - Text( - '确定', - style: TextStyle( - fontSize: ScreenAdaper.height(30), - color: AppTheme.primaryColor), - ) - ], - ), - ), - Expanded( - child: SingleChildScrollView( - child: ListBody( - children: [ - '角色1', - '角色2', - '角色3', - '角色4', - '角色5', - '角色6', - '角色7', - '角色8', - '角色9' - ].map((String text) { - return Obx( - () => CheckboxListTile( - contentPadding: EdgeInsets.symmetric( - vertical: ScreenAdaper.height(0), - horizontal: ScreenAdaper.width(20)), - title: Text(text), - value: - controller.selectedDepts.contains(text), - onChanged: (bool? value) { - if (value == true) { - controller.selectedDepts.add(text); - } else { - controller.selectedDepts.remove(text); - } - }, - ), - ); - }).toList(), - ), - )) - ]), - )); + Get.bottomSheet(SkMutiPickerDialog( + onSelected: (List selectedData) { + controller.roleEditController.text; + }, getData: () async { + try { + final res = + await Api.getRoles({'page': 1, 'pageSize': 30}); + if (res.data != null) { + List> result = + res.data!.items.map>((e) { + RoleModel data = RoleModel.fromJson(e); + return PickerItem( + label: data.name, + value: data.id!, + subLabel: data.remark, + checked: false); + }).toList(); + return result; + } + return []; + } catch (e) { + LoggerUtil().error(e); + return []; + } + })).then( + (value) => Get.delete()); }), SizedBox( height: ScreenAdaper.height(defaultPadding), ), SkTextInput( isDense: true, - textController: TextEditingController(), + textController: controller.nickNameEditController, + customLabel: true, labelText: '登录用户名', ), SizedBox( @@ -156,9 +129,10 @@ class EditUserInfo extends StatelessWidget { ), SkTextInput( isDense: true, + customLabel: true, textController: TextEditingController(), labelText: '手机号', - ) + ), ]), ), )), @@ -271,8 +245,9 @@ class EditUserInfoController extends GetxController { EditUserInfoController(this.userId); final nameEditController = TextEditingController(); final deptEditController = TextEditingController(); + final roleEditController = TextEditingController(); + final nickNameEditController = TextEditingController(); final userInfo = Rxn(); - RxList selectedDepts = RxList([]); @override onReady() { diff --git a/lib/widgets/core/sk_base_field.dart b/lib/widgets/core/sk_base_field.dart new file mode 100644 index 0000000..4f238f8 --- /dev/null +++ b/lib/widgets/core/sk_base_field.dart @@ -0,0 +1,43 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +abstract class SkBaseFieldWidget extends StatelessWidget { + FocusNode focusNode = FocusNode(); + late SkBaseFieldController baseFieldController; + final bool customLabel; + final String? labelText; + + SkBaseFieldWidget({ + super.key, + this.customLabel = false, + this.labelText, + autoFocus = false, + }) { + baseFieldController = Get.put( + SkBaseFieldController(autoFocus: autoFocus, focusNode: focusNode), + tag: '${labelText ?? Random().nextInt(1000)}', + ); + } +} + +class SkBaseFieldController extends GetxController { + RxBool isFocus = false.obs; + final bool autoFocus; + FocusNode? focusNode; + SkBaseFieldController({required this.autoFocus, this.focusNode}); + + @override + void onReady() { + if (autoFocus) { + WidgetsBinding.instance.addPostFrameCallback((_) { + focusNode?.requestFocus(); + }); + } + focusNode?.addListener(() { + isFocus.value = focusNode?.hasFocus ?? false; + }); + super.onReady(); + } +} diff --git a/lib/widgets/core/sk_form_item.dart b/lib/widgets/core/sk_form_item.dart new file mode 100644 index 0000000..dc217d1 --- /dev/null +++ b/lib/widgets/core/sk_form_item.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_base_field.dart'; + +class SkFormItem extends StatelessWidget { + final Widget child; + final bool customLabel; + final bool isRequired; + final String? labelText; + final SkBaseFieldController controller; + const SkFormItem( + {super.key, + required this.child, + required this.customLabel, + required this.controller, + this.labelText, + this.isRequired = false}); + + @override + Widget build(BuildContext context) { + if (customLabel) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (isRequired) + Text( + "*", + style: TextStyle( + color: Colors.red, fontSize: ScreenAdaper.height(25)), + ), + Obx(() => Text( + labelText ?? '', + style: TextStyle( + fontSize: ScreenAdaper.height(25), + color: controller.isFocus.value + ? AppTheme.primaryColor + : AppTheme.nearlyBlack), + )), + ]), + SizedBox( + height: ScreenAdaper.height(5), + ), + child + ], + ); + } else { + return child; + } + } +} diff --git a/lib/widgets/core/sk_multi_picker.dart b/lib/widgets/core/sk_multi_picker.dart new file mode 100644 index 0000000..eea2f0b --- /dev/null +++ b/lib/widgets/core/sk_multi_picker.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_base_field.dart'; +import 'package:sk_base_mobile/widgets/core/sk_form_item.dart'; +import 'package:sk_base_mobile/widgets/core/sk_tag.dart'; +import 'package:multi_dropdown/multiselect_dropdown.dart'; + +class SkMultiPickerDropdown extends SkBaseFieldWidget { + final TextEditingController textController; + final Function(FocusNode)? onTap; + final bool isRequired; + final String? labelText; + final String? hint; + final bool isTextArea; + final bool isDense; + final Function(String)? onTapOutside; + final Function(String)? onChanged; + final String? Function(String?)? validator; + final EdgeInsetsGeometry? contentPadding; + final ValueChanged? onFieldSubmitted; + final Icon? prefix; + final Widget? suffixIcon; + final InputBorder? border; + final FloatingLabelBehavior? floatingLabelBehavior; + final TextInputType? keyboardType; + SkMultiPickerDropdown( + {super.key, + required this.textController, + super.customLabel = false, + super.autoFocus = false, + this.onTap, + this.hint, + this.onFieldSubmitted, + this.isRequired = false, + this.onTapOutside, + this.keyboardType, + this.labelText, + this.prefix, + this.suffixIcon, + this.onChanged, + this.border, + this.floatingLabelBehavior = FloatingLabelBehavior.always, + this.isTextArea = false, + this.contentPadding, + this.isDense = false, + this.validator}); + + @override + Widget build(BuildContext context) { + return SkFormItem( + child: MultiSelectDropDown( + focusNode: focusNode, + onOptionSelected: (List selectedOptions) {}, + options: >[ + ValueItem(label: 'Option 1', value: 1), + ValueItem(label: 'Option 2', value: 2), + ValueItem(label: 'Option 3', value: 3), + ValueItem(label: 'Option 4', value: 4), + ValueItem(label: 'Option 5', value: 5), + ValueItem(label: 'Option 6', value: 6), + ], + borderColor: AppTheme.nearlyBlack, + fieldBackgroundColor: AppTheme.inputFillColor, + borderWidth: 1, + borderRadius: ScreenAdaper.sp(15), + focusedBorderWidth: 2, + focusedBorderColor: AppTheme.primaryColorLight, + hint: '请选择', + hintStyle: Theme.of(Get.context!).inputDecorationTheme.hintStyle, + hintPadding: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)), + selectionType: SelectionType.multi, + chipConfig: const ChipConfig(wrapType: WrapType.scroll), + optionTextStyle: const TextStyle(fontSize: 16), + selectedOptionIcon: const Icon(Icons.check_circle), + ), + customLabel: customLabel, + labelText: labelText, + isRequired: isRequired, + controller: baseFieldController); + + // return DropdownButtonFormField( + // focusNode: focusNode, + // onTap: () { + // if (widget.onTap != null) { + // widget.onTap!(focusNode); + // } + // }, + // icon: Icon(Icons.arrow_drop_down), + // selectedItemBuilder: (context) => [ + // Container( + // width: ScreenAdaper.width(200), + // child: SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: Row(mainAxisSize: MainAxisSize.min, children: [ + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // SkTag(text: '角色', color: AppTheme.primaryColor), + // ]), + // ), + // ) + // ], + // autovalidateMode: AutovalidateMode.onUserInteraction, + // decoration: InputDecoration( + // prefixIcon: widget.prefix, + // errorStyle: const TextStyle(fontSize: 0, height: 0.01), + // contentPadding: widget.contentPadding, + // isDense: widget.isDense, + // border: widget.border, + // floatingLabelBehavior: widget.floatingLabelBehavior, + // label: widget.labelText != null + // ? Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisSize: MainAxisSize.min, + // children: [ + // if (widget.isRequired) + // Text( + // "*", + // style: TextStyle( + // color: Colors.red, + // fontSize: ScreenAdaper.height(30)), + // ), + // Text( + // widget.labelText!, + // style: TextStyle(fontSize: ScreenAdaper.height(30)), + // ), + // ]) + // : null, + // focusedBorder: OutlineInputBorder( + // borderSide: + // const BorderSide(color: AppTheme.primaryColorLight, width: 2), + // borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), + // hintText: widget.hint ?? '请输入', + // ), + // items: [], + // onChanged: (Object? value) {}, + // ); + } +} diff --git a/lib/widgets/core/sk_multi_picker_dialog.dart b/lib/widgets/core/sk_multi_picker_dialog.dart new file mode 100644 index 0000000..16327f2 --- /dev/null +++ b/lib/widgets/core/sk_multi_picker_dialog.dart @@ -0,0 +1,136 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/util/snack_bar.util.dart'; +import 'package:sk_base_mobile/widgets/loading_indicator.dart'; + +class SkMutiPickerDialog extends StatelessWidget { + final SkMutiPickerDialogController controller; + Function(List data)? onSelected; + SkMutiPickerDialog( + {super.key, + required Future>> Function() getData, + this.onSelected}) + : controller = Get.put(SkMutiPickerDialogController(getData: getData)); + + @override + Widget build(BuildContext context) { + return Container( + height: ScreenAdaper.height(400), + decoration: const BoxDecoration(color: AppTheme.nearlyWhite), + child: Column(children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(20), + horizontal: ScreenAdaper.width(20)), + child: Row( + children: [ + Text( + '取消', + style: TextStyle(fontSize: ScreenAdaper.height(30)), + ), + const Spacer(), + GestureDetector( + onTap: () { + if (onSelected != null) { + final selectedData = controller.pickData + .where((element) => element.checked) + .toList(); + if (selectedData.isEmpty) { + SnackBarUtil().warning('请最少选择一个“员工”角色作为基础角色'); + return; + } + onSelected!(selectedData); + } + }, + child: Text( + '确定', + style: TextStyle( + fontSize: ScreenAdaper.height(30), + color: AppTheme.primaryColor), + ), + ) + ], + ), + ), + Expanded( + child: Obx( + () => controller.loading.value + ? const LoadingIndicator( + common: true, + ) + : SingleChildScrollView( + child: ListBody( + children: controller.pickData.mapIndexed(( + int index, + PickerItem item, + ) { + return CheckboxListTile( + fillColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.selected)) { + return AppTheme.primaryColor; + } + return null; + }), + dense: true, + visualDensity: VisualDensity.compact, + contentPadding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(0), + horizontal: ScreenAdaper.width(20)), + title: Text(item.label, + style: TextStyle(fontSize: ScreenAdaper.height(25))), + subtitle: item.subLabel == null + ? null + : Text( + item.subLabel!, + style: TextStyle( + fontSize: ScreenAdaper.height(20), + color: AppTheme.grey), + ), + value: controller.pickData[index].checked, + onChanged: (bool? value) { + PickerItem item = controller.pickData[index]; + item.checked = value ?? false; + controller.pickData[index] = item; + }, + ); + }).toList(), + )), + )) + ]), + ); + } +} + +class SkMutiPickerDialogController extends GetxController { + RxList> pickData = RxList([]); + Future>> Function() getData; + SkMutiPickerDialogController({required this.getData}); + RxBool loading = false.obs; + @override + void onReady() { + init(); + super.onReady(); + } + + Future init() async { + loading.value = true; + pickData.assignAll(await getData()); + loading.value = false; + } +} + +class PickerItem { + final String label; + final String? subLabel; + final T value; + bool checked; + PickerItem( + {required this.label, + required this.value, + required this.checked, + this.subLabel}); +} diff --git a/lib/widgets/core/sk_text_input.dart b/lib/widgets/core/sk_text_input.dart index 6756354..ede6bd6 100644 --- a/lib/widgets/core/sk_text_input.dart +++ b/lib/widgets/core/sk_text_input.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_base_field.dart'; +import 'package:sk_base_mobile/widgets/core/sk_form_item.dart'; -class SkTextInput extends StatefulWidget { +class SkTextInput extends SkBaseFieldWidget { final TextEditingController textController; final Function(FocusNode)? onTap; final bool isRequired; - final String? labelText; + final String? hint; final bool isTextArea; final bool isDense; @@ -14,15 +15,19 @@ class SkTextInput extends StatefulWidget { final Function(String)? onChanged; final String? Function(String?)? validator; final EdgeInsetsGeometry? contentPadding; - final bool autoFocus; + final ValueChanged? onFieldSubmitted; final Icon? prefix; final Widget? suffixIcon; final InputBorder? border; final FloatingLabelBehavior? floatingLabelBehavior; final TextInputType? keyboardType; - const SkTextInput( + + SkTextInput( {super.key, + super.customLabel = false, + super.autoFocus = false, + super.labelText, required this.textController, this.onTap, this.hint, @@ -30,76 +35,57 @@ class SkTextInput extends StatefulWidget { this.isRequired = false, this.onTapOutside, this.keyboardType, - this.labelText, this.prefix, this.suffixIcon, this.onChanged, this.border, this.floatingLabelBehavior = FloatingLabelBehavior.always, this.isTextArea = false, - this.autoFocus = false, this.contentPadding, this.isDense = false, this.validator}); - @override - State createState() => _SkTextInputState(); -} - -class _SkTextInputState extends State { - late FocusNode focusNode = FocusNode(); - - @override - void initState() { - super.initState(); - if (widget.autoFocus) { - WidgetsBinding.instance.addPostFrameCallback((_) { - focusNode.requestFocus(); - }); - } - } - @override Widget build(BuildContext context) { - return TextFormField( + Widget field = TextFormField( focusNode: focusNode, - controller: widget.textController, + controller: textController, onChanged: (String value) { - if (widget.onChanged != null) { - widget.onChanged!(value); + if (onChanged != null) { + onChanged!(value); } }, onTapOutside: (event) { - if (widget.onTapOutside != null) { - widget.onTapOutside!(widget.textController.text); + if (onTapOutside != null) { + onTapOutside!(textController.text); FocusScope.of(context).unfocus(); } }, - maxLines: widget.isTextArea ? 2 : 1, // 添加这行代码 + maxLines: isTextArea ? 2 : 1, // 添加这行代码 onTap: () { - if (widget.onTap != null) { - widget.onTap!(focusNode); + if (onTap != null) { + onTap!(focusNode); } }, - keyboardType: widget.keyboardType, - onFieldSubmitted: widget.onFieldSubmitted, + keyboardType: keyboardType, + onFieldSubmitted: onFieldSubmitted, autovalidateMode: AutovalidateMode.onUserInteraction, - validator: widget.validator, + validator: validator, decoration: InputDecoration( - prefixIcon: widget.prefix, - suffixIcon: widget.suffixIcon, + prefixIcon: prefix, + suffixIcon: suffixIcon, errorStyle: const TextStyle(fontSize: 0, height: 0.01), - contentPadding: widget.contentPadding, - isDense: widget.isDense, - border: widget.border, - floatingLabelBehavior: widget.floatingLabelBehavior, - label: widget.labelText != null + contentPadding: contentPadding, + isDense: isDense, + border: border, + floatingLabelBehavior: floatingLabelBehavior, + label: labelText != null && !customLabel ? Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - if (widget.isRequired) + if (isRequired) Text( "*", style: TextStyle( @@ -107,16 +93,28 @@ class _SkTextInputState extends State { fontSize: ScreenAdaper.height(30)), ), Text( - widget.labelText!, + labelText!, style: TextStyle(fontSize: ScreenAdaper.height(30)), ), ]) : null, - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: AppTheme.primaryColorLight, width: 2), - borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), - hintText: widget.hint ?? '请输入', + hintText: hint ?? '请输入', ), ); + return SkFormItem( + controller: baseFieldController, + customLabel: customLabel, + labelText: labelText, + isRequired: isRequired, + child: field, + ); } } + +// class SkTextInputController extends GetxController { +// RxBool isFocus = false.obs; +// @override +// void onReady() { +// super.onReady(); +// } +// } diff --git a/pubspec.lock b/pubspec.lock index a180c01..8efef62 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -608,6 +608,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + multi_dropdown: + dependency: "direct main" + description: + name: multi_dropdown + sha256: b63ff339fcc875d667f8688c8ef62853545b580dd2b6fe78b73339783268afd8 + url: "https://pub.dev" + source: hosted + version: "2.1.4" octo_image: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e6be3f6..30c4af3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: math_expressions: ^2.4.0 install_plugin: ^2.1.0 url_launcher: ^6.2.5 + multi_dropdown: ^2.1.4 dev_dependencies: flutter_test: sdk: flutter