mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: show create option message on bottom
This commit is contained in:
parent
228695d517
commit
18752e7af8
@ -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",
|
||||||
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -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: (_) {},
|
||||||
|
@ -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),
|
||||||
|
@ -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),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user