chore: show create option message on bottom

This commit is contained in:
appflowy 2022-05-06 21:07:34 +08:00
parent 228695d517
commit 18752e7af8
6 changed files with 136 additions and 28 deletions

View File

@ -181,6 +181,7 @@
"textPlaceholder": "Empty" "textPlaceholder": "Empty"
}, },
"selectOption": { "selectOption": {
"create": "Create",
"purpleColor": "Purple", "purpleColor": "Purple",
"pinkColor": "Pink", "pinkColor": "Pink",
"lightPinkColor": "Light Pink", "lightPinkColor": "Light Pink",

View File

@ -1,9 +1,13 @@
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart'; import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
import 'select_option_service.dart'; import 'select_option_service.dart';
part 'selection_editor_bloc.freezed.dart'; part 'selection_editor_bloc.freezed.dart';
@ -24,14 +28,19 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
_startListening(); _startListening();
}, },
didReceiveOptions: (_DidReceiveOptions value) { didReceiveOptions: (_DidReceiveOptions value) {
final result = _makeOptions(state.filter, value.options);
emit(state.copyWith( emit(state.copyWith(
allOptions: value.options, allOptions: value.options,
options: _makeOptions(state.filter, value.options), options: result.options,
createOption: result.createOption,
selectedOptions: value.selectedOptions, selectedOptions: value.selectedOptions,
)); ));
}, },
newOption: (_NewOption value) { newOption: (_NewOption value) {
_createOption(value.optionName); _createOption(value.optionName);
emit(state.copyWith(
filter: none(),
));
}, },
deleteOption: (_DeleteOption value) { deleteOption: (_DeleteOption value) {
_deleteOption(value.option); _deleteOption(value.option);
@ -91,16 +100,37 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
} }
void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) { void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) {
emit(state.copyWith(filter: optionName, options: _makeOptions(optionName, state.allOptions))); final _MakeOptionResult result = _makeOptions(Some(optionName), state.allOptions);
emit(state.copyWith(
filter: Some(optionName),
options: result.options,
createOption: result.createOption,
));
} }
List<SelectOption> _makeOptions(String filter, List<SelectOption> allOptions) { _MakeOptionResult _makeOptions(Option<String> filter, List<SelectOption> allOptions) {
final List<SelectOption> options = List.from(allOptions); final List<SelectOption> options = List.from(allOptions);
Option<String> createOption = filter;
filter.foldRight(null, (filter, previous) {
if (filter.isNotEmpty) { if (filter.isNotEmpty) {
options.retainWhere((option) => option.name.toLowerCase().contains(filter.toLowerCase())); options.retainWhere((option) {
final name = option.name.toLowerCase();
final lFilter = filter.toLowerCase();
if (name == lFilter) {
createOption = none();
} }
return options; return name.contains(lFilter);
});
}
});
return _MakeOptionResult(
options: options,
createOption: createOption,
);
} }
void _startListening() { void _startListening() {
@ -135,7 +165,8 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
required List<SelectOption> options, required List<SelectOption> options,
required List<SelectOption> allOptions, required List<SelectOption> allOptions,
required List<SelectOption> selectedOptions, required List<SelectOption> selectedOptions,
required String filter, required Option<String> createOption,
required Option<String> filter,
}) = _SelectOptionEditorState; }) = _SelectOptionEditorState;
factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) { factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
@ -144,7 +175,18 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
options: data?.options ?? [], options: data?.options ?? [],
allOptions: data?.options ?? [], allOptions: data?.options ?? [],
selectedOptions: data?.selectOptions ?? [], selectedOptions: data?.selectOptions ?? [],
filter: "", createOption: none(),
filter: none(),
); );
} }
} }
class _MakeOptionResult {
List<SelectOption> options;
Option<String> createOption;
_MakeOptionResult({
required this.options,
required this.createOption,
});
}

View File

@ -60,17 +60,35 @@ extension SelectOptionColorExtension on SelectOptionColor {
} }
class SelectOptionTag extends StatelessWidget { class SelectOptionTag extends StatelessWidget {
final SelectOption option; final String name;
final Color color;
final bool isSelected; final bool isSelected;
const SelectOptionTag({required this.option, this.isSelected = false, Key? key}) : super(key: key); const SelectOptionTag({
required this.name,
required this.color,
this.isSelected = false,
Key? key,
}) : super(key: key);
factory SelectOptionTag.fromSelectOption({
required BuildContext context,
required SelectOption option,
bool isSelected = false,
}) {
return SelectOptionTag(
name: option.name,
color: option.color.make(context),
isSelected: isSelected,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChoiceChip( return ChoiceChip(
pressElevation: 1, pressElevation: 1,
label: FlowyText.medium(option.name, fontSize: 12), label: FlowyText.medium(name, fontSize: 12),
selectedColor: option.color.make(context), selectedColor: color,
backgroundColor: option.color.make(context), backgroundColor: color,
labelPadding: const EdgeInsets.symmetric(horizontal: 6), labelPadding: const EdgeInsets.symmetric(horizontal: 6),
selected: true, selected: true,
onSelected: (_) {}, onSelected: (_) {},

View File

@ -150,7 +150,14 @@ class _SelectOptionCell extends StatelessWidget {
child: FlowyText.medium(cellStyle!.placeholder, fontSize: 14, color: theme.shader3), child: FlowyText.medium(cellStyle!.placeholder, fontSize: 14, color: theme.shader3),
); );
} else { } else {
final tags = selectOptions.map((option) => SelectOptionTag(option: option)).toList(); final tags = selectOptions
.map(
(option) => SelectOptionTag.fromSelectOption(
context: context,
option: option,
),
)
.toList();
child = Align( child = Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Wrap(children: tags, spacing: 4, runSpacing: 4), child: Wrap(children: tags, spacing: 4, runSpacing: 4),

View File

@ -104,9 +104,18 @@ class _OptionList extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>( return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
builder: (context, state) { builder: (context, state) {
final cells = state.options.map((option) { List<Widget> cells = [];
cells.addAll(state.options.map((option) {
return _SelectOptionCell(option, state.selectedOptions.contains(option)); return _SelectOptionCell(option, state.selectedOptions.contains(option));
}).toList(); }).toList());
state.createOption.fold(
() => null,
(createOption) {
cells.add(_CreateOptionCell(name: createOption));
},
);
final list = ListView.separated( final list = ListView.separated(
shrinkWrap: true, shrinkWrap: true,
controller: ScrollController(), controller: ScrollController(),
@ -119,7 +128,11 @@ class _OptionList extends StatelessWidget {
return cells[index]; return cells[index];
}, },
); );
return list;
return Padding(
padding: const EdgeInsets.all(3.0),
child: list,
);
}, },
); );
} }
@ -177,6 +190,30 @@ class _Title extends StatelessWidget {
} }
} }
class _CreateOptionCell extends StatelessWidget {
final String name;
const _CreateOptionCell({required this.name, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return Row(
children: [
FlowyText.medium(
LocaleKeys.grid_selectOption_create.tr(),
fontSize: 12,
color: theme.shader3,
),
const HSpace(10),
SelectOptionTag(
name: name,
color: theme.shader6,
),
],
);
}
}
class _SelectOptionCell extends StatelessWidget { class _SelectOptionCell extends StatelessWidget {
final SelectOption option; final SelectOption option;
final bool isSelected; final bool isSelected;
@ -206,7 +243,11 @@ class _SelectOptionCell extends StatelessWidget {
style: HoverStyle(hoverColor: theme.hover), style: HoverStyle(hoverColor: theme.hover),
builder: (_, onHover) { builder: (_, onHover) {
List<Widget> children = [ List<Widget> children = [
SelectOptionTag(option: option, isSelected: isSelected), SelectOptionTag(
name: option.name,
color: option.color.make(context),
isSelected: isSelected,
),
const Spacer(), const Spacer(),
]; ];
@ -223,10 +264,7 @@ class _SelectOptionCell extends StatelessWidget {
)); ));
} }
return Padding( return Row(children: children);
padding: const EdgeInsets.all(3.0),
child: Row(children: children),
);
}, },
); );
} }

View File

@ -76,7 +76,7 @@ class SelectOptionTextField extends StatelessWidget {
borderRadius: Corners.s10Border, borderRadius: Corners.s10Border,
), ),
isDense: true, isDense: true,
prefixIcon: _renderTags(sc), prefixIcon: _renderTags(context, sc),
hintText: LocaleKeys.grid_selectOption_searchOption.tr(), hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
prefixIconConstraints: BoxConstraints(maxWidth: distanceToText), prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
@ -90,12 +90,14 @@ class SelectOptionTextField extends StatelessWidget {
); );
} }
Widget? _renderTags(ScrollController sc) { Widget? _renderTags(BuildContext context, ScrollController sc) {
if (selectedOptionMap.isEmpty) { if (selectedOptionMap.isEmpty) {
return null; return null;
} }
final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList(); final children = selectedOptionMap.values
.map((option) => SelectOptionTag.fromSelectOption(context: context, option: option))
.toList();
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView( child: SingleChildScrollView(