fix: add new select option tag from textfield

This commit is contained in:
appflowy 2022-04-09 20:45:33 +08:00
parent ce85050114
commit ed10ebac7a
8 changed files with 174 additions and 181 deletions

View File

@ -110,7 +110,7 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
void _loadOptions() async {
_delayOperation?.cancel();
_delayOperation = Timer(
const Duration(milliseconds: 300),
const Duration(milliseconds: 1),
() async {
final result = await _selectOptionService.getOpitonContext(
gridId: state.gridId,
@ -119,10 +119,14 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
);
result.fold(
(selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
)),
(selectOptionContext) {
if (!isClosed) {
add(SelectOptionEditorEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
));
}
},
(err) => Log.error(err),
);
},

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
@ -6,7 +7,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:equatable/equatable.dart';
import 'grid_block_service.dart';
import 'field/grid_listenr.dart';
import 'grid_service.dart';
@ -154,46 +154,3 @@ class GridLoadingState with _$GridLoadingState {
const factory GridLoadingState.loading() = _Loading;
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
}
class GridBlockRow {
final String gridId;
final String rowId;
final String blockId;
final double height;
const GridBlockRow({
required this.gridId,
required this.rowId,
required this.blockId,
required this.height,
});
}
class RowData extends Equatable {
final String gridId;
final String rowId;
final String blockId;
final List<Field> fields;
final double height;
const RowData({
required this.gridId,
required this.rowId,
required this.blockId,
required this.fields,
required this.height,
});
factory RowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
return RowData(
gridId: row.gridId,
rowId: row.rowId,
blockId: row.blockId,
fields: fields,
height: row.height,
);
}
@override
List<Object> get props => [rowId, fields];
}

View File

@ -1,5 +1,4 @@
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
@ -41,3 +40,34 @@ class CellData with _$CellData {
Cell? cell,
}) = _CellData;
}
@freezed
class RowData with _$RowData {
const factory RowData({
required String gridId,
required String rowId,
required String blockId,
required List<Field> fields,
required double height,
}) = _RowData;
factory RowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
return RowData(
gridId: row.gridId,
rowId: row.rowId,
blockId: row.blockId,
fields: fields,
height: row.height,
);
}
}
@freezed
class GridBlockRow with _$GridBlockRow {
const factory GridBlockRow({
required String gridId,
required String rowId,
required String blockId,
required double height,
}) = _GridBlockRow;
}

View File

@ -1,5 +1,6 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';

View File

@ -1,6 +1,3 @@
import 'dart:collection';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
@ -8,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textfield_tags/textfield_tags.dart';
extension SelectOptionColorExtension on SelectOptionColor {
Color make(BuildContext context) {
@ -63,98 +59,6 @@ extension SelectOptionColorExtension on SelectOptionColor {
}
}
class SelectOptionTextField extends StatelessWidget {
final FocusNode _focusNode;
final TextEditingController _controller;
final TextfieldTagsController tagController;
final List<SelectOption> options;
final LinkedHashMap<String, SelectOption> selectedOptionMap;
final double distanceToText;
final Function(String) onNewTag;
SelectOptionTextField({
required this.options,
required this.selectedOptionMap,
required this.distanceToText,
required this.tagController,
required this.onNewTag,
TextEditingController? controller,
FocusNode? focusNode,
Key? key,
}) : _controller = controller ?? TextEditingController(),
_focusNode = focusNode ?? FocusNode(),
super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return TextFieldTags(
textEditingController: _controller,
textfieldTagsController: tagController,
initialTags: selectedOptionMap.keys.toList(),
focusNode: _focusNode,
textSeparators: const [' ', ','],
inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) {
return ((context, sc, tags, onTagDelegate) {
tags.retainWhere((name) {
return options.where((option) => option.name == name).isEmpty;
});
if (tags.isNotEmpty) {
assert(tags.length == 1);
onNewTag(tags.first);
}
return TextField(
autofocus: true,
controller: editController,
focusNode: focusNode,
onChanged: onChanged,
onSubmitted: onSubmitted,
maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: theme.main1, width: 1.0),
borderRadius: Corners.s10Border,
),
isDense: true,
prefixIcon: _renderTags(sc),
hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: theme.main1,
width: 1.0,
),
borderRadius: Corners.s10Border,
),
),
);
});
},
);
}
Widget? _renderTags(ScrollController sc) {
if (selectedOptionMap.isEmpty) {
return null;
}
final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList();
return Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
controller: sc,
scrollDirection: Axis.horizontal,
child: Row(children: children),
),
);
}
}
class SelectOptionTag extends StatelessWidget {
final SelectOption option;
final bool isSelected;

View File

@ -1,5 +1,4 @@
import 'dart:collection';
import 'package:app_flowy/workspace/application/grid/cell_bloc/selection_editor_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
@ -21,6 +20,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'extension.dart';
import 'text_field.dart';
const double _editorPannelWidth = 300;
@ -135,8 +135,7 @@ class _TextField extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocConsumer<SelectOptionEditorBloc, SelectOptionEditorState>(
listener: (context, state) {},
return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
builder: (context, state) {
final optionMap = LinkedHashMap<String, SelectOption>.fromIterable(state.selectedOptions,
key: (option) => option.name, value: (option) => option);

View File

@ -0,0 +1,101 @@
import 'dart:collection';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'extension.dart';
class SelectOptionTextField extends StatelessWidget {
final FocusNode _focusNode;
final TextEditingController _controller;
final TextfieldTagsController tagController;
final List<SelectOption> options;
final LinkedHashMap<String, SelectOption> selectedOptionMap;
final double distanceToText;
final Function(String) onNewTag;
SelectOptionTextField({
required this.options,
required this.selectedOptionMap,
required this.distanceToText,
required this.tagController,
required this.onNewTag,
TextEditingController? controller,
FocusNode? focusNode,
Key? key,
}) : _controller = controller ?? TextEditingController(),
_focusNode = focusNode ?? FocusNode(),
super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return TextFieldTags(
textEditingController: _controller,
textfieldTagsController: tagController,
initialTags: selectedOptionMap.keys.toList(),
focusNode: _focusNode,
textSeparators: const [' ', ','],
inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) {
return ((context, sc, tags, onTagDelegate) {
return TextField(
autofocus: true,
controller: editController,
focusNode: focusNode,
onChanged: onChanged,
onSubmitted: (text) {
if (onSubmitted != null) {
onSubmitted(text);
}
if (text.isNotEmpty) {
onNewTag(text);
}
},
maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: theme.main1, width: 1.0),
borderRadius: Corners.s10Border,
),
isDense: true,
prefixIcon: _renderTags(sc),
hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: theme.main1, width: 1.0),
borderRadius: Corners.s10Border,
),
),
);
});
},
);
}
Widget? _renderTags(ScrollController sc) {
if (selectedOptionMap.isEmpty) {
return null;
}
final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList();
return Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
controller: sc,
scrollDirection: Axis.horizontal,
child: Row(children: children),
),
);
}
}

View File

@ -16,9 +16,28 @@ use std::str::FromStr;
pub const SELECTION_IDS_SEPARATOR: &str = ",";
pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
fn insert_option(&mut self, new_option: SelectOption);
fn delete_option(&mut self, delete_option: SelectOption);
fn insert_option(&mut self, new_option: SelectOption) {
let options = self.mut_options();
if let Some(index) = options
.iter()
.position(|option| option.id == new_option.id || option.name == new_option.name)
{
options.remove(index);
options.insert(index, new_option);
} else {
options.insert(0, new_option);
}
}
fn delete_option(&mut self, delete_option: SelectOption) {
let options = self.mut_options();
if let Some(index) = options.iter().position(|option| option.id == delete_option.id) {
options.remove(index);
}
}
fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext;
fn mut_options(&mut self) -> &mut Vec<SelectOption>;
}
// Single select
@ -33,21 +52,6 @@ pub struct SingleSelectTypeOption {
impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
impl SelectOptionOperation for SingleSelectTypeOption {
fn insert_option(&mut self, new_option: SelectOption) {
if let Some(index) = self.options.iter().position(|option| option.id == new_option.id) {
self.options.remove(index);
self.options.insert(index, new_option);
} else {
self.options.insert(0, new_option);
}
}
fn delete_option(&mut self, delete_option: SelectOption) {
if let Some(index) = self.options.iter().position(|option| option.id == delete_option.id) {
self.options.remove(index);
}
}
fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext {
let select_options = make_select_context_from(cell_meta, &self.options);
SelectOptionContext {
@ -55,6 +59,10 @@ impl SelectOptionOperation for SingleSelectTypeOption {
select_options,
}
}
fn mut_options(&mut self) -> &mut Vec<SelectOption> {
&mut self.options
}
}
impl CellDataOperation for SingleSelectTypeOption {
@ -139,21 +147,6 @@ impl MultiSelectTypeOption {
}
impl SelectOptionOperation for MultiSelectTypeOption {
fn insert_option(&mut self, new_option: SelectOption) {
if let Some(index) = self.options.iter().position(|option| option.id == new_option.id) {
self.options.remove(index);
self.options.insert(index, new_option);
} else {
self.options.insert(0, new_option);
}
}
fn delete_option(&mut self, delete_option: SelectOption) {
if let Some(index) = self.options.iter().position(|option| option.id == delete_option.id) {
self.options.remove(index);
}
}
fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext {
let select_options = make_select_context_from(cell_meta, &self.options);
SelectOptionContext {
@ -161,6 +154,10 @@ impl SelectOptionOperation for MultiSelectTypeOption {
select_options,
}
}
fn mut_options(&mut self) -> &mut Vec<SelectOption> {
&mut self.options
}
}
impl CellDataOperation for MultiSelectTypeOption {