mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: config option editor ui
This commit is contained in:
parent
271a8485b6
commit
5358203a46
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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: [],
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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:
|
||||
|
@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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)]
|
||||
|
Loading…
Reference in New Issue
Block a user