chore: config option editor ui

This commit is contained in:
appflowy 2022-04-04 20:47:04 +08:00
parent 271a8485b6
commit 5358203a46
27 changed files with 412 additions and 166 deletions

View File

@ -186,7 +186,8 @@
"blueColor": "Blue",
"deleteTag": "Delete tag",
"colorPannelTitle": "Colors",
"pannelTitle": "Select an option"
"pannelTitle": "Select an option or create one",
"searchOption": "Search for an option"
}
}
}

View File

@ -151,7 +151,7 @@ void _resolveGridDeps(GetIt getIt) {
(view, _) => GridBloc(view: view, service: GridService()),
);
getIt.registerFactoryParam<RowBloc, GridRowData, void>(
getIt.registerFactoryParam<RowBloc, RowData, void>(
(data, _) => RowBloc(
rowData: data,
rowlistener: RowListener(rowId: data.rowId),
@ -179,35 +179,35 @@ void _resolveGridDeps(GetIt getIt) {
),
);
getIt.registerFactoryParam<TextCellBloc, FutureCellData, void>(
getIt.registerFactoryParam<TextCellBloc, CellData, void>(
(cellData, _) => TextCellBloc(
service: CellService(),
cellData: cellData,
),
);
getIt.registerFactoryParam<SelectionCellBloc, FutureCellData, void>(
getIt.registerFactoryParam<SelectionCellBloc, CellData, void>(
(cellData, _) => SelectionCellBloc(
service: CellService(),
cellData: cellData,
),
);
getIt.registerFactoryParam<NumberCellBloc, FutureCellData, void>(
getIt.registerFactoryParam<NumberCellBloc, CellData, void>(
(cellData, _) => NumberCellBloc(
service: CellService(),
cellData: cellData,
),
);
getIt.registerFactoryParam<DateCellBloc, FutureCellData, void>(
getIt.registerFactoryParam<DateCellBloc, CellData, void>(
(cellData, _) => DateCellBloc(
service: CellService(),
cellData: cellData,
),
);
getIt.registerFactoryParam<CheckboxCellBloc, FutureCellData, void>(
getIt.registerFactoryParam<CheckboxCellBloc, CellData, void>(
(cellData, _) => CheckboxCellBloc(
service: CellService(),
cellData: cellData,

View File

@ -9,11 +9,11 @@ part 'checkbox_cell_bloc.freezed.dart';
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
final CellService service;
// final FutureCellData cellData;
// final CellData cellData;
CheckboxCellBloc({
required this.service,
required FutureCellData cellData,
required CellData cellData,
}) : super(CheckboxCellState.initial()) {
on<CheckboxCellEvent>(
(event, emit) async {

View File

@ -9,7 +9,7 @@ part 'date_cell_bloc.freezed.dart';
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
final CellService service;
final FutureCellData cellData;
final CellData cellData;
DateCellBloc({
required this.service,

View File

@ -12,7 +12,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
NumberCellBloc({
required this.service,
required FutureCellData cellData,
required CellData cellData,
}) : super(NumberCellState.initial()) {
on<NumberCellEvent>(
(event, emit) async {

View File

@ -1,5 +1,7 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -13,12 +15,17 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
SelectionCellBloc({
required this.service,
required FutureCellData cellData,
}) : super(SelectionCellState.initial()) {
required CellData cellData,
}) : super(SelectionCellState.initial(cellData)) {
on<SelectionCellEvent>(
(event, emit) async {
await event.map(
initial: (_InitialCell value) async {},
initial: (_InitialCell value) async {
_loadOptions();
},
didReceiveOptions: (_DidReceiveOptions value) {
emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions));
},
);
},
);
@ -28,19 +35,57 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
Future<void> close() async {
return super.close();
}
void _loadOptions() async {
final result = await FieldContextLoaderAdaptor(
gridId: state.cellData.gridId,
field: state.cellData.field,
).load();
result.fold(
(context) {
List<SelectOption> options = [];
switch (state.cellData.field.fieldType) {
case FieldType.MultiSelect:
options.addAll(MultiSelectTypeOption.fromBuffer(context.typeOptionData).options);
break;
case FieldType.SingleSelect:
options.addAll(SingleSelectTypeOption.fromBuffer(context.typeOptionData).options);
break;
default:
Log.error("Invalid field type, expect single select or multiple select");
break;
}
final ids = state.cellData.cell?.content.split(',');
final selectedOptions = ids?.map((id) => options.firstWhere((option) => option.id == id)).toList() ?? [];
add(SelectionCellEvent.didReceiveOptions(options, selectedOptions));
},
(err) => Log.error(err),
);
}
}
@freezed
class SelectionCellEvent with _$SelectionCellEvent {
const factory SelectionCellEvent.initial() = _InitialCell;
const factory SelectionCellEvent.didReceiveOptions(
List<SelectOption> options,
List<SelectOption> selectedOptions,
) = _DidReceiveOptions;
}
@freezed
class SelectionCellState with _$SelectionCellState {
const factory SelectionCellState() = _SelectionCellState;
// required String girdId,
// required Field field,
// required List<SelectOption> options,
const factory SelectionCellState({
required CellData cellData,
required List<SelectOption> options,
required List<SelectOption> selectedOptions,
}) = _SelectionCellState;
factory SelectionCellState.initial() => const SelectionCellState();
factory SelectionCellState.initial(CellData cellData) => SelectionCellState(
cellData: cellData,
options: [],
selectedOptions: [],
);
}

View File

@ -11,16 +11,18 @@ import 'cell_service.dart';
part 'selection_editor_bloc.freezed.dart';
class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorState> {
class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
final CellService service = CellService();
final FieldListener _listener;
SelectionEditorBloc({
SelectOptionEditorBloc({
required String gridId,
required Field field,
required List<SelectOption> options,
required List<SelectOption> selectedOptions,
}) : _listener = FieldListener(fieldId: field.id),
super(SelectionEditorState.initial(gridId, field)) {
on<SelectionEditorEvent>(
super(SelectOptionEditorState.initial(gridId, field, options, selectedOptions)) {
on<SelectOptionEditorEvent>(
(event, emit) async {
await event.map(
initial: (_Initial value) async {
@ -34,6 +36,7 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
didReceiveOptions: (_DidReceiveOptions value) {
emit(state.copyWith(options: value.options));
},
newOption: (_newOption value) {},
);
},
);
@ -48,7 +51,7 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
void _startListening() {
_listener.updateFieldNotifier.addPublishListener((result) {
result.fold(
(field) => add(SelectionEditorEvent.didReceiveFieldUpdate(field)),
(field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
@ -70,7 +73,7 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
Log.error("Invalid field type, expect single select or multiple select");
break;
}
add(SelectionEditorEvent.didReceiveOptions(options));
add(SelectOptionEditorEvent.didReceiveOptions(options));
},
(err) => Log.error(err),
);
@ -78,25 +81,33 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
}
@freezed
class SelectionEditorEvent with _$SelectionEditorEvent {
const factory SelectionEditorEvent.initial() = _Initial;
const factory SelectionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
const factory SelectionEditorEvent.didReceiveOptions(List<SelectOption> options) = _DidReceiveOptions;
class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
const factory SelectOptionEditorEvent.initial() = _Initial;
const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
const factory SelectOptionEditorEvent.didReceiveOptions(List<SelectOption> options) = _DidReceiveOptions;
const factory SelectOptionEditorEvent.newOption(String optionName) = _newOption;
}
@freezed
class SelectionEditorState with _$SelectionEditorState {
const factory SelectionEditorState({
class SelectOptionEditorState with _$SelectOptionEditorState {
const factory SelectOptionEditorState({
required String gridId,
required Field field,
required List<SelectOption> options,
}) = _SelectionEditorState;
required List<SelectOption> selectedOptions,
}) = _SelectOptionEditorState;
factory SelectionEditorState.initial(String gridId, Field field) {
return SelectionEditorState(
factory SelectOptionEditorState.initial(
String gridId,
Field field,
List<SelectOption> options,
List<SelectOption> selectedOptions,
) {
return SelectOptionEditorState(
gridId: gridId,
field: field,
options: [],
options: options,
selectedOptions: selectedOptions,
);
}
}

View File

@ -11,7 +11,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
TextCellBloc({
required this.service,
required FutureCellData cellData,
required CellData cellData,
}) : super(TextCellState.initial(cellData)) {
on<TextCellEvent>(
(event, emit) async {
@ -53,7 +53,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
@freezed
class TextCellEvent with _$TextCellEvent {
const factory TextCellEvent.initial() = _InitialCell;
const factory TextCellEvent.didReceiveCellData(GridCellData cellData) = _DidReceiveCellData;
const factory TextCellEvent.didReceiveCellData(CellData cellData) = _DidReceiveCellData;
const factory TextCellEvent.updateText(String text) = _UpdateText;
}
@ -61,10 +61,10 @@ class TextCellEvent with _$TextCellEvent {
class TextCellState with _$TextCellState {
const factory TextCellState({
required String content,
required FutureCellData cellData,
required CellData cellData,
}) = _TextCellState;
factory TextCellState.initial(FutureCellData cellData) => TextCellState(
factory TextCellState.initial(CellData cellData) => TextCellState(
content: cellData.cell?.content ?? "",
cellData: cellData,
);

View File

@ -169,14 +169,14 @@ class GridBlockRow {
});
}
class GridRowData extends Equatable {
class RowData extends Equatable {
final String gridId;
final String rowId;
final String blockId;
final List<Field> fields;
final double height;
const GridRowData({
const RowData({
required this.gridId,
required this.rowId,
required this.blockId,
@ -184,8 +184,8 @@ class GridRowData extends Equatable {
required this.height,
});
factory GridRowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
return GridRowData(
factory RowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
return RowData(
gridId: row.gridId,
rowId: row.rowId,
blockId: row.blockId,

View File

@ -13,14 +13,14 @@ import 'package:dartz/dartz.dart';
part 'row_bloc.freezed.dart';
typedef CellDataMap = LinkedHashMap<String, GridCellData>;
typedef CellDataMap = LinkedHashMap<String, CellData>;
class RowBloc extends Bloc<RowEvent, RowState> {
final RowService rowService;
final RowListener rowlistener;
final GridFieldsListener fieldListener;
RowBloc({required GridRowData rowData, required this.rowlistener})
RowBloc({required RowData rowData, required this.rowlistener})
: rowService = RowService(
gridId: rowData.gridId,
blockId: rowData.blockId,
@ -112,7 +112,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
var map = CellDataMap.new();
for (final field in state.fields) {
if (field.visibility) {
map[field.id] = GridCellData(
map[field.id] = CellData(
rowId: row.id,
gridId: rowService.gridId,
blockId: rowService.blockId,
@ -143,7 +143,7 @@ class RowState with _$RowState {
required Option<CellDataMap> cellDataMap,
}) = _RowState;
factory RowState.initial(GridRowData data) => RowState(
factory RowState.initial(RowData data) => RowState(
rowId: data.rowId,
rowHeight: data.height,
fields: data.fields,

View File

@ -29,16 +29,14 @@ class RowService {
}
}
typedef FutureCellData = GridCellData;
class GridCellData extends Equatable {
class CellData extends Equatable {
final String gridId;
final String rowId;
final String blockId;
final Field field;
final Cell? cell;
const GridCellData({
const CellData({
required this.rowId,
required this.gridId,
required this.blockId,

View File

@ -157,7 +157,7 @@ class _FlowyGridState extends State<FlowyGrid> {
(context, index) {
final blockRow = context.read<GridBloc>().state.rows[index];
final fields = context.read<GridBloc>().state.fields;
final rowData = GridRowData.fromBlockRow(blockRow, fields);
final rowData = RowData.fromBlockRow(blockRow, fields);
return GridRowWidget(data: rowData, key: ValueKey(rowData.rowId));
},
childCount: context.read<GridBloc>().state.rows.length,

View File

@ -9,8 +9,9 @@ class GridSize {
static double get leadingHeaderPadding => 30 * scale;
static double get trailHeaderPadding => 140 * scale;
static double get headerContainerPadding => 0 * scale;
static double get cellContentPadding => 10 * scale;
static double get typeOptionItemHeight => 30 * scale;
static double get cellHPadding => 10 * scale;
static double get cellVPadding => 8 * scale;
static double get typeOptionItemHeight => 32 * scale;
static double get typeOptionSeparatorHeight => 6 * scale;
//
@ -19,13 +20,13 @@ class GridSize {
vertical: GridSize.headerContainerPadding,
);
static EdgeInsets get cellContentInsets => EdgeInsets.symmetric(
horizontal: GridSize.cellContentPadding,
vertical: GridSize.cellContentPadding,
horizontal: GridSize.cellHPadding,
vertical: GridSize.cellVPadding,
);
static EdgeInsets get fieldContentInsets => EdgeInsets.symmetric(
horizontal: GridSize.cellContentPadding,
vertical: GridSize.cellContentPadding,
horizontal: GridSize.cellHPadding,
vertical: GridSize.cellVPadding,
);
static EdgeInsets get typeOptionContentInsets => const EdgeInsets.symmetric(

View File

@ -7,7 +7,7 @@ import 'number_cell.dart';
import 'selection_cell/selection_cell.dart';
import 'text_cell.dart';
Widget buildGridCell(FutureCellData cellData) {
Widget buildGridCell(CellData cellData) {
final key = ValueKey(cellData.field.id + cellData.rowId);
switch (cellData.field.fieldType) {
case FieldType.Checkbox:

View File

@ -38,7 +38,7 @@ class CellContainer extends StatelessWidget {
),
decoration: _makeBoxDecoration(context, state),
padding: GridSize.cellContentInsets,
child: Center(child: IntrinsicHeight(child: child)),
child: Center(child: child),
);
},
),

View File

@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CheckboxCell extends StatefulWidget {
final FutureCellData cellData;
final CellData cellData;
const CheckboxCell({
required this.cellData,

View File

@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DateCell extends StatefulWidget {
final FutureCellData cellData;
final CellData cellData;
const DateCell({
required this.cellData,

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NumberCell extends StatefulWidget {
final FutureCellData cellData;
final CellData cellData;
const NumberCell({
required this.cellData,

View File

@ -1,3 +1,6 @@
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';
@ -61,10 +64,19 @@ extension SelectOptionColorExtension on SelectOptionColor {
}
class SelectOptionTextField extends StatelessWidget {
final TextEditingController _controller;
final FocusNode _focusNode;
final TextEditingController _controller;
final TextfieldTagsController tagController;
final LinkedHashMap<String, SelectOption> optionMap;
final double distanceToText;
final Function(String) onNewTag;
SelectOptionTextField({
required this.optionMap,
required this.distanceToText,
required this.tagController,
required this.onNewTag,
TextEditingController? controller,
FocusNode? focusNode,
Key? key,
@ -74,26 +86,45 @@ class SelectOptionTextField extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return TextFieldTags(
textEditingController: _controller,
initialTags: ["abc", "bdf"],
textfieldTagsController: tagController,
initialTags: optionMap.keys.toList(),
focusNode: _focusNode,
textSeparators: const [' ', ','],
inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) {
return ((context, sc, tags, onTagDelegate) {
tags.retainWhere((name) => optionMap.containsKey(name) == false);
if (tags.isNotEmpty) {
assert(tags.length == 1);
onNewTag(tags.first);
}
return TextField(
controller: editController,
focusNode: focusNode,
onChanged: onChanged,
onSubmitted: onSubmitted,
onEditingComplete: () => focusNode.unfocus(),
maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
border: OutlineInputBorder(
borderSide: BorderSide(color: theme.shader3, width: 1.0),
borderRadius: Corners.s10Border,
),
isDense: true,
prefixIcon: _renderTags(tags, sc),
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,
),
),
);
});
@ -101,41 +132,26 @@ class SelectOptionTextField extends StatelessWidget {
);
}
Widget? _renderTags(List<String> tags, ScrollController sc) {
if (tags.isEmpty) {
Widget? _renderTags(ScrollController sc) {
if (optionMap.isEmpty) {
return null;
}
return SingleChildScrollView(
controller: sc,
scrollDirection: Axis.horizontal,
child: Row(children: [
Container(
decoration: BoxDecoration(
color: Color.fromARGB(255, 74, 137, 92),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6.0),
),
child: FlowyText.medium("efc", fontSize: 12),
),
Container(
decoration: BoxDecoration(
color: Color.fromARGB(255, 74, 137, 92),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6.0),
),
child: FlowyText.medium("abc", fontSize: 12),
margin: const EdgeInsets.symmetric(horizontal: 5.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
)
]),
final children = optionMap.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 SelectionBadge extends StatelessWidget {
class SelectOptionTag extends StatelessWidget {
final SelectOption option;
const SelectionBadge({required this.option, Key? key}) : super(key: key);
const SelectOptionTag({required this.option, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -143,9 +159,11 @@ class SelectionBadge extends StatelessWidget {
decoration: BoxDecoration(
color: option.color.make(context),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6.0),
borderRadius: BorderRadius.circular(8.0),
),
child: FlowyText.medium(option.name, fontSize: 12),
child: Center(child: FlowyText.medium(option.name, fontSize: 12)),
margin: const EdgeInsets.symmetric(horizontal: 3.0),
padding: const EdgeInsets.symmetric(horizontal: 6.0),
);
}
}

View File

@ -6,9 +6,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'extension.dart';
import 'selection_editor.dart';
class SingleSelectCell extends StatefulWidget {
final FutureCellData cellData;
final CellData cellData;
const SingleSelectCell({
required this.cellData,
@ -20,30 +21,28 @@ class SingleSelectCell extends StatefulWidget {
}
class _SingleSelectCellState extends State<SingleSelectCell> {
late CellFocusNode _focusNode;
late SelectionCellBloc _cellBloc;
late TextEditingController _controller;
@override
void initState() {
_cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData);
_controller = TextEditingController();
_focusNode = CellFocusNode();
_cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData)..add(const SelectionCellEvent.initial());
super.initState();
}
@override
Widget build(BuildContext context) {
_focusNode.addCallback(context, () {
Log.info(_focusNode.hasFocus);
});
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
builder: (context, state) {
return SelectOptionTextField(
focusNode: _focusNode,
controller: _controller,
final children = state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList();
return SizedBox.expand(
child: InkWell(
onTap: () {
SelectionEditor.show(context, state.cellData, state.options, state.selectedOptions);
},
child: Row(children: children),
),
);
},
),
@ -53,14 +52,13 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
@override
Future<void> dispose() async {
_cellBloc.close();
_focusNode.dispose();
super.dispose();
}
}
//----------------------------------------------------------------
class MultiSelectCell extends StatefulWidget {
final FutureCellData cellData;
final CellData cellData;
const MultiSelectCell({
required this.cellData,

View File

@ -1,6 +1,9 @@
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';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
@ -12,42 +15,81 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'extension.dart';
class SelectionEditor extends StatelessWidget {
final GridCellData cellData;
const SelectionEditor({required this.cellData, Key? key}) : super(key: key);
const double _editorPannelWidth = 300;
void show(BuildContext context) {
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
child: this,
constraints: BoxConstraints.loose(const Size(240, 200)),
),
identifier: toString(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomWithLeftAligned,
);
class SelectionEditor extends StatelessWidget {
final CellData cellData;
final List<SelectOption> options;
final List<SelectOption> selectedOptions;
const SelectionEditor({
required this.cellData,
required this.options,
required this.selectedOptions,
Key? key,
}) : super(key: key);
static String identifier() {
return (SelectionEditor).toString();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SelectionEditorBloc(gridId: cellData.gridId, field: cellData.field),
child: BlocBuilder<SelectionEditorBloc, SelectionEditorState>(
create: (context) => SelectOptionEditorBloc(
gridId: cellData.gridId,
field: cellData.field,
options: options,
selectedOptions: selectedOptions,
),
child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
builder: (context, state) {
return Column(
children: const [
_Title(),
VSpace(10),
_OptionList(),
return CustomScrollView(
shrinkWrap: true,
slivers: [
SliverToBoxAdapter(child: _TextField()),
const SliverToBoxAdapter(child: VSpace(10)),
const SliverToBoxAdapter(child: _Title()),
const SliverToBoxAdapter(child: _OptionList()),
],
);
},
),
);
}
static void show(
BuildContext context,
CellData cellData,
List<SelectOption> options,
List<SelectOption> selectedOptions,
) {
SelectionEditor.hide(context);
final editor = SelectionEditor(
cellData: cellData,
options: options,
selectedOptions: selectedOptions,
);
//
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
child: SizedBox(width: _editorPannelWidth, child: editor),
constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
),
identifier: SelectionEditor.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomWithCenterAligned,
);
}
static void hide(BuildContext context) {
FlowyOverlay.of(context).remove(identifier());
}
}
class _OptionList extends StatelessWidget {
@ -55,9 +97,9 @@ class _OptionList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SelectionEditorBloc, SelectionEditorState>(
return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
builder: (context, state) {
final cells = state.options.map((option) => _SelectionCell(option)).toList();
final cells = state.options.map((option) => _SelectOptionCell(option)).toList();
final list = ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
@ -76,40 +118,84 @@ class _OptionList extends StatelessWidget {
}
}
class _TextField extends StatelessWidget {
final TextfieldTagsController _tagController = TextfieldTagsController();
_TextField({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocConsumer<SelectOptionEditorBloc, SelectOptionEditorState>(
listener: (context, state) {},
buildWhen: (previous, current) => previous.field.id != current.field.id,
builder: (context, state) {
final optionMap = LinkedHashMap<String, SelectOption>.fromIterable(state.selectedOptions,
key: (option) => option.name, value: (option) => option);
return SizedBox(
height: 42,
child: SelectOptionTextField(
optionMap: optionMap,
distanceToText: _editorPannelWidth * 0.7,
tagController: _tagController,
onNewTag: (newTagName) {
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.newOption(newTagName));
},
),
);
},
);
}
}
class _Title extends StatelessWidget {
const _Title({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyText.medium(LocaleKeys.grid_selectOption_pannelTitle.tr(), fontSize: 12),
);
}
}
class _SelectionCell extends StatelessWidget {
final SelectOption option;
const _SelectionCell(this.option, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
// return FlowyButton(
// text: FlowyText.medium(fieldType.title(), fontSize: 12),
// hoverColor: theme.hover,
// onTap: () => onSelectField(fieldType),
// leftIcon: svgWidget(fieldType.iconName(), color: theme.iconColor),
// );
return InkWell(
onTap: () {},
child: FlowyHover(
config: HoverDisplayConfig(hoverColor: theme.hover),
builder: (_, onHover) {
return SelectionBadge(option: option);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: FlowyText.medium(
LocaleKeys.grid_selectOption_pannelTitle.tr(),
fontSize: 12,
color: theme.shader3,
),
),
);
}
}
class _SelectOptionCell extends StatelessWidget {
final SelectOption option;
const _SelectOptionCell(this.option, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: InkWell(
onTap: () {},
child: FlowyHover(
config: HoverDisplayConfig(hoverColor: theme.hover),
builder: (_, onHover) {
List<Widget> children = [
SelectOptionTag(option: option),
const Spacer(),
];
if (onHover) {
children.add(svgWidget("editor/details", color: theme.iconColor));
}
return Padding(
padding: const EdgeInsets.all(3.0),
child: Row(children: children),
);
},
),
),
);
}

View File

@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_container.dart';
class GridTextCell extends GridCell {
final FutureCellData cellData;
final CellData cellData;
const GridTextCell({
required this.cellData,
Key? key,

View File

@ -0,0 +1,44 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NumberCell extends StatefulWidget {
final CellData cellData;
const NumberCell({
required this.cellData,
Key? key,
}) : super(key: key);
@override
State<NumberCell> createState() => _NumberCellState();
}
class _NumberCellState extends State<NumberCell> {
late NumberCellBloc _cellBloc;
@override
void initState() {
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<NumberCellBloc, NumberCellState>(
builder: (context, state) {
return Container();
},
),
);
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
}

View File

@ -10,7 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
class GridRowWidget extends StatefulWidget {
final GridRowData data;
final RowData data;
const GridRowWidget({required this.data, Key? key}) : super(key: key);
@override

View File

@ -0,0 +1,44 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NumberCell extends StatefulWidget {
final CellData cellData;
const NumberCell({
required this.cellData,
Key? key,
}) : super(key: key);
@override
State<NumberCell> createState() => _NumberCellState();
}
class _NumberCellState extends State<NumberCell> {
late NumberCellBloc _cellBloc;
@override
void initState() {
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<NumberCellBloc, NumberCellState>(
builder: (context, state) {
return Container();
},
),
);
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
}

View File

@ -44,7 +44,7 @@ class FlowyText extends StatelessWidget {
softWrap: false,
textAlign: textAlign,
style: TextStyle(
color: theme.textColor,
color: color ?? theme.textColor,
fontWeight: fontWeight,
fontSize: fontSize + 2,
fontFamily: 'Mulish',

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString};
pub const DEFAULT_ROW_HEIGHT: i32 = 36;
pub const DEFAULT_ROW_HEIGHT: i32 = 42;
pub const DEFAULT_FIELD_WIDTH: i32 = 150;
#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]