mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #843 from AppFlowy-IO/feat/board_card
Board: Support display text/number/url/single-select/muti-select/date card style
This commit is contained in:
commit
6b0becd9ca
@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@ -20,6 +21,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final BoardDataController _dataController;
|
||||
late final AFBoardDataController boardDataController;
|
||||
|
||||
GridFieldCache get fieldCache => _dataController.fieldCache;
|
||||
String get gridId => _dataController.gridId;
|
||||
|
||||
BoardBloc({required ViewPB view})
|
||||
: _dataController = BoardDataController(view: view),
|
||||
super(BoardState.initial(view.id)) {
|
||||
@ -57,6 +61,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
didReceiveGroups: (List<GroupPB> groups) {
|
||||
emit(state.copyWith(groups: groups));
|
||||
},
|
||||
didReceiveRows: (List<RowInfo> rowInfos) {
|
||||
emit(state.copyWith(rowInfos: rowInfos));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -68,7 +75,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
GridRowCache? getRowCache(String blockId, String rowId) {
|
||||
GridRowCache? getRowCache(String blockId) {
|
||||
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
||||
return blockCache?.rowCache;
|
||||
}
|
||||
@ -92,24 +99,29 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
|
||||
boardDataController.addColumns(columns);
|
||||
},
|
||||
onRowsChanged: (List<RowInfo> rowInfos, RowChangeReason reason) {
|
||||
add(BoardEvent.didReceiveRows(rowInfos));
|
||||
},
|
||||
onError: (err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<BoardColumnItem> _buildRows(List<RowPB> rows) {
|
||||
return rows.map((row) {
|
||||
final rowInfo = RowInfo(
|
||||
gridId: _dataController.gridId,
|
||||
blockId: row.blockId,
|
||||
id: row.id,
|
||||
fields: _dataController.fieldCache.unmodifiableFields,
|
||||
height: row.height.toDouble(),
|
||||
rawRow: row,
|
||||
);
|
||||
return BoardColumnItem(row: rowInfo);
|
||||
List<AFColumnItem> _buildRows(List<RowPB> rows) {
|
||||
final items = rows.map((row) {
|
||||
// final rowInfo = RowInfo(
|
||||
// gridId: _dataController.gridId,
|
||||
// blockId: row.blockId,
|
||||
// id: row.id,
|
||||
// fields: _dataController.fieldCache.unmodifiableFields,
|
||||
// height: row.height.toDouble(),
|
||||
// rawRow: row,
|
||||
// );
|
||||
return BoardColumnItem(row: row);
|
||||
}).toList();
|
||||
|
||||
return <AFColumnItem>[...items];
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||
@ -131,6 +143,8 @@ class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.createRow() = _CreateRow;
|
||||
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
||||
_DidReceiveGroup;
|
||||
const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
|
||||
_DidReceiveRows;
|
||||
const factory BoardEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
) = _DidReceiveGridUpdate;
|
||||
@ -186,10 +200,15 @@ class GridFieldEquatable extends Equatable {
|
||||
}
|
||||
|
||||
class BoardColumnItem extends AFColumnItem {
|
||||
final RowInfo row;
|
||||
final RowPB row;
|
||||
|
||||
BoardColumnItem({required this.row});
|
||||
|
||||
@override
|
||||
String get id => row.id;
|
||||
}
|
||||
|
||||
class CreateCardItem extends AFColumnItem {
|
||||
@override
|
||||
String get id => '$CreateCardItem';
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'dart:async';
|
||||
@ -12,6 +14,10 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||
typedef OnGridChanged = void Function(GridPB);
|
||||
typedef OnGroupChanged = void Function(List<GroupPB>);
|
||||
typedef OnRowsChanged = void Function(
|
||||
List<RowInfo> rowInfos,
|
||||
RowChangeReason,
|
||||
);
|
||||
typedef OnError = void Function(FlowyError);
|
||||
|
||||
class BoardDataController {
|
||||
@ -21,17 +27,25 @@ class BoardDataController {
|
||||
|
||||
// key: the block id
|
||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||
UnmodifiableMapView<String, GridBlockCache> get blocks =>
|
||||
UnmodifiableMapView(_blocks);
|
||||
LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
|
||||
|
||||
OnFieldsChanged? _onFieldsChanged;
|
||||
OnGridChanged? _onGridChanged;
|
||||
OnGroupChanged? _onGroupChanged;
|
||||
OnRowsChanged? _onRowsChanged;
|
||||
OnError? _onError;
|
||||
|
||||
List<RowInfo> get rowInfos {
|
||||
final List<RowInfo> rows = [];
|
||||
for (var block in _blocks.values) {
|
||||
rows.addAll(block.rows);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
BoardDataController({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.identity(),
|
||||
_blocks = LinkedHashMap.new(),
|
||||
_gridFFIService = GridService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id);
|
||||
|
||||
@ -39,11 +53,13 @@ class BoardDataController {
|
||||
OnGridChanged? onGridChanged,
|
||||
OnFieldsChanged? onFieldsChanged,
|
||||
OnGroupChanged? onGroupChanged,
|
||||
OnRowsChanged? onRowsChanged,
|
||||
OnError? onError,
|
||||
}) {
|
||||
_onGridChanged = onGridChanged;
|
||||
_onFieldsChanged = onFieldsChanged;
|
||||
_onGroupChanged = onGroupChanged;
|
||||
_onRowsChanged = onRowsChanged;
|
||||
_onError = onError;
|
||||
|
||||
fieldCache.addListener(onFields: (fields) {
|
||||
@ -57,6 +73,7 @@ class BoardDataController {
|
||||
() => result.fold(
|
||||
(grid) async {
|
||||
_onGridChanged?.call(grid);
|
||||
_initialBlocks(grid.blocks);
|
||||
return await _loadFields(grid).then((result) {
|
||||
return result.fold(
|
||||
(l) {
|
||||
@ -72,8 +89,8 @@ class BoardDataController {
|
||||
);
|
||||
}
|
||||
|
||||
void createRow() {
|
||||
_gridFFIService.createRow();
|
||||
Future<Either<RowPB, FlowyError>> createRow() {
|
||||
return _gridFFIService.createRow();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
@ -85,6 +102,29 @@ class BoardDataController {
|
||||
}
|
||||
}
|
||||
|
||||
void _initialBlocks(List<BlockPB> blocks) {
|
||||
for (final block in blocks) {
|
||||
if (_blocks[block.id] != null) {
|
||||
Log.warn("Initial duplicate block's cache: ${block.id}");
|
||||
return;
|
||||
}
|
||||
|
||||
final cache = GridBlockCache(
|
||||
gridId: gridId,
|
||||
block: block,
|
||||
fieldCache: fieldCache,
|
||||
);
|
||||
|
||||
cache.addListener(
|
||||
onChangeReason: (reason) {
|
||||
_onRowsChanged?.call(rowInfos, reason);
|
||||
},
|
||||
);
|
||||
|
||||
_blocks[block.id] = cache;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
||||
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
||||
return Future(
|
||||
|
@ -0,0 +1,71 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardCheckboxCellBloc
|
||||
extends Bloc<BoardCheckboxCellEvent, BoardCheckboxCellState> {
|
||||
final GridCheckboxCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardCheckboxCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardCheckboxCellState.initial(cellController)) {
|
||||
on<BoardCheckboxCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(isSelected: _isSelected(cellData)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardCheckboxCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent {
|
||||
const factory BoardCheckboxCellEvent.initial() = _InitialCell;
|
||||
const factory BoardCheckboxCellEvent.didReceiveCellUpdate(
|
||||
String cellContent) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellState with _$BoardCheckboxCellState {
|
||||
const factory BoardCheckboxCellState({
|
||||
required bool isSelected,
|
||||
}) = _CheckboxCellState;
|
||||
|
||||
factory BoardCheckboxCellState.initial(GridCellController context) {
|
||||
return BoardCheckboxCellState(
|
||||
isSelected: _isSelected(context.getCellData()));
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSelected(String? cellData) {
|
||||
return cellData == "Yes";
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
part 'board_date_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
|
||||
final GridDateCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
BoardDateCellBloc({required this.cellController})
|
||||
: super(BoardDateCellState.initial(cellController)) {
|
||||
on<BoardDateCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () => _startListening(),
|
||||
didReceiveCellUpdate: (DateCellDataPB? cellData) {
|
||||
emit(state.copyWith(
|
||||
data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||
},
|
||||
didReceiveFieldUpdate: (FieldPB value) =>
|
||||
emit(state.copyWith(field: value)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((data) {
|
||||
if (!isClosed) {
|
||||
add(BoardDateCellEvent.didReceiveCellUpdate(data));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardDateCellEvent with _$BoardDateCellEvent {
|
||||
const factory BoardDateCellEvent.initial() = _InitialCell;
|
||||
const factory BoardDateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory BoardDateCellEvent.didReceiveFieldUpdate(FieldPB field) =
|
||||
_DidReceiveFieldUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardDateCellState with _$BoardDateCellState {
|
||||
const factory BoardDateCellState({
|
||||
required DateCellDataPB? data,
|
||||
required String dateStr,
|
||||
required FieldPB field,
|
||||
}) = _BoardDateCellState;
|
||||
|
||||
factory BoardDateCellState.initial(GridDateCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
|
||||
return BoardDateCellState(
|
||||
field: context.field,
|
||||
data: cellData,
|
||||
dateStr: _dateStrFromCellData(cellData),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
||||
String dateStr = "";
|
||||
if (cellData != null) {
|
||||
dateStr = cellData.date + " " + cellData.time;
|
||||
}
|
||||
return dateStr;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_number_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardNumberCellBloc
|
||||
extends Bloc<BoardNumberCellEvent, BoardNumberCellState> {
|
||||
final GridNumberCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardNumberCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardNumberCellState.initial(cellController)) {
|
||||
on<BoardNumberCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (content) {
|
||||
emit(state.copyWith(content: content));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardNumberCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardNumberCellEvent with _$BoardNumberCellEvent {
|
||||
const factory BoardNumberCellEvent.initial() = _InitialCell;
|
||||
const factory BoardNumberCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardNumberCellState with _$BoardNumberCellState {
|
||||
const factory BoardNumberCellState({
|
||||
required String content,
|
||||
}) = _BoardNumberCellState;
|
||||
|
||||
factory BoardNumberCellState.initial(GridCellController context) =>
|
||||
BoardNumberCellState(
|
||||
content: context.getCellData() ?? "",
|
||||
);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_url_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
|
||||
final GridURLCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardURLCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardURLCellState.initial(cellController)) {
|
||||
on<BoardURLCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
));
|
||||
},
|
||||
updateURL: (String url) {
|
||||
cellController.saveCellData(url, deduplicate: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(BoardURLCellEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellEvent with _$BoardURLCellEvent {
|
||||
const factory BoardURLCellEvent.initial() = _InitialCell;
|
||||
const factory BoardURLCellEvent.updateURL(String url) = _UpdateURL;
|
||||
const factory BoardURLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellState with _$BoardURLCellState {
|
||||
const factory BoardURLCellState({
|
||||
required String content,
|
||||
required String url,
|
||||
}) = _BoardURLCellState;
|
||||
|
||||
factory BoardURLCellState.initial(GridURLCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
return BoardURLCellState(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'card_data_controller.dart';
|
||||
|
||||
part 'card_bloc.freezed.dart';
|
||||
|
||||
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
final RowFFIService _rowService;
|
||||
final CardDataController _dataController;
|
||||
|
||||
BoardCardBloc({
|
||||
required String gridId,
|
||||
required CardDataController dataController,
|
||||
}) : _rowService = RowFFIService(
|
||||
gridId: gridId,
|
||||
blockId: dataController.rowPB.blockId,
|
||||
rowId: dataController.rowPB.id,
|
||||
),
|
||||
_dataController = dataController,
|
||||
super(BoardCardState.initial(
|
||||
dataController.rowPB, dataController.loadData())) {
|
||||
on<BoardCardEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialRow value) async {
|
||||
await _startListening();
|
||||
},
|
||||
createRow: (_CreateRow value) {
|
||||
_rowService.createRow();
|
||||
},
|
||||
didReceiveCells: (_DidReceiveCells value) async {
|
||||
final cells = value.gridCellMap.values
|
||||
.map((e) => GridCellEquatable(e.field))
|
||||
.toList();
|
||||
emit(state.copyWith(
|
||||
gridCellMap: value.gridCellMap,
|
||||
cells: UnmodifiableListView(cells),
|
||||
changeReason: value.reason,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_dataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_dataController.addListener(
|
||||
onRowChanged: (cells, reason) {
|
||||
if (!isClosed) {
|
||||
add(BoardCardEvent.didReceiveCells(cells, reason));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCardEvent with _$BoardCardEvent {
|
||||
const factory BoardCardEvent.initial() = _InitialRow;
|
||||
const factory BoardCardEvent.createRow() = _CreateRow;
|
||||
const factory BoardCardEvent.didReceiveCells(
|
||||
GridCellMap gridCellMap, RowChangeReason reason) = _DidReceiveCells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCardState with _$BoardCardState {
|
||||
const factory BoardCardState({
|
||||
required RowPB rowPB,
|
||||
required GridCellMap gridCellMap,
|
||||
required UnmodifiableListView<GridCellEquatable> cells,
|
||||
RowChangeReason? changeReason,
|
||||
}) = _BoardCardState;
|
||||
|
||||
factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) =>
|
||||
BoardCardState(
|
||||
rowPB: rowPB,
|
||||
gridCellMap: cellDataMap,
|
||||
cells: UnmodifiableListView(
|
||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class GridCellEquatable extends Equatable {
|
||||
final FieldPB _field;
|
||||
|
||||
const GridCellEquatable(FieldPB field) : _field = field;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
_field.id,
|
||||
_field.fieldType,
|
||||
_field.visibility,
|
||||
_field.width,
|
||||
];
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import 'package:app_flowy/plugins/board/presentation/card/card_cell_builder.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
typedef OnCardChanged = void Function(GridCellMap, RowChangeReason);
|
||||
|
||||
class CardDataController extends BoardCellBuilderDelegate {
|
||||
final RowPB rowPB;
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridRowCache _rowCache;
|
||||
final List<VoidCallback> _onCardChangedListeners = [];
|
||||
|
||||
CardDataController({
|
||||
required this.rowPB,
|
||||
required GridFieldCache fieldCache,
|
||||
required GridRowCache rowCache,
|
||||
}) : _fieldCache = fieldCache,
|
||||
_rowCache = rowCache;
|
||||
|
||||
GridCellMap loadData() {
|
||||
return _rowCache.loadGridCells(rowPB.id);
|
||||
}
|
||||
|
||||
void addListener({OnCardChanged? onRowChanged}) {
|
||||
_onCardChangedListeners.add(_rowCache.addListener(
|
||||
rowId: rowPB.id,
|
||||
onCellUpdated: onRowChanged,
|
||||
));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final fn in _onCardChangedListeners) {
|
||||
_rowCache.removeRowListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellFieldNotifier buildFieldNotifier() {
|
||||
return GridCellFieldNotifier(
|
||||
notifier: GridCellFieldNotifierImpl(_fieldCache));
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellCache get cellCache => _rowCache.cellCache;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
// ignore_for_file: unused_field
|
||||
|
||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
@ -7,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../application/board_bloc.dart';
|
||||
import 'card/card.dart';
|
||||
import 'card/card_cell_builder.dart';
|
||||
|
||||
class BoardPage extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
@ -49,12 +51,14 @@ class BoardContent extends StatelessWidget {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
child: AFBoard(
|
||||
key: UniqueKey(),
|
||||
scrollController: ScrollController(),
|
||||
dataController: context.read<BoardBloc>().boardDataController,
|
||||
headerBuilder: _buildHeader,
|
||||
footBuilder: _buildFooter,
|
||||
cardBuilder: _buildCard,
|
||||
cardBuilder: (_, data) => _buildCard(context, data),
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
config: AFBoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
@ -87,10 +91,29 @@ class BoardContent extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
||||
final rowInfo = (item as BoardColumnItem).row;
|
||||
final rowPB = (item as BoardColumnItem).row;
|
||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(item));
|
||||
|
||||
final fieldCache = context.read<BoardBloc>().fieldCache;
|
||||
final gridId = context.read<BoardBloc>().gridId;
|
||||
final cardController = CardDataController(
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
rowPB: rowPB,
|
||||
);
|
||||
|
||||
final cellBuilder = BoardCellBuilder(cardController);
|
||||
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
child: BoardCard(rowInfo: rowInfo),
|
||||
child: BoardCard(
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: cardController,
|
||||
gridId: gridId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_checkbox_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardCheckboxCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardCheckboxCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardCheckboxCell> createState() => _BoardCheckboxCellState();
|
||||
}
|
||||
|
||||
class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
late BoardCheckboxCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCheckboxCellController;
|
||||
_cellBloc = BoardCheckboxCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardCheckboxCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>(
|
||||
builder: (context, state) {
|
||||
final icon = state.isSelected
|
||||
? svgWidget('editor/editor_check')
|
||||
: svgWidget('editor/editor_uncheck');
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyIconButton(
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: icon,
|
||||
width: 20,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardDateCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardDateCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardDateCell> createState() => _BoardDateCellState();
|
||||
}
|
||||
|
||||
class _BoardDateCellState extends State<BoardDateCell> {
|
||||
late BoardDateCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridDateCellController;
|
||||
|
||||
_cellBloc = BoardDateCellBloc(cellController: cellController)
|
||||
..add(const BoardDateCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.dateStr.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_number_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardNumberCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardNumberCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardNumberCell> createState() => _BoardNumberCellState();
|
||||
}
|
||||
|
||||
class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
late BoardNumberCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridNumberCellController;
|
||||
|
||||
_cellBloc = BoardNumberCellBloc(cellController: cellController)
|
||||
..add(const BoardNumberCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -34,9 +34,15 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||
builder: (context, state) {
|
||||
return SelectOptionWrap(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellControllerBuilder: widget.cellControllerBuilder,
|
||||
final children = state.selectedOptions
|
||||
.map((option) => SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
))
|
||||
.toList();
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Wrap(children: children, spacing: 4, runSpacing: 2),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -32,10 +32,17 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: FlowyText.medium(state.content),
|
||||
);
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -0,0 +1,64 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_url_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardUrlCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardUrlCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardUrlCell> createState() => _BoardUrlCellState();
|
||||
}
|
||||
|
||||
class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
late BoardURLCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||
_cellBloc = BoardURLCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardURLCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
||||
builder: (context, state) {
|
||||
final richText = RichText(
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
text: state.content,
|
||||
style: TextStyle(
|
||||
color: theme.main2,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: richText,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,13 +1,85 @@
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
|
||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'card_cell_builder.dart';
|
||||
import 'card_container.dart';
|
||||
|
||||
class BoardCard extends StatelessWidget {
|
||||
final RowInfo rowInfo;
|
||||
class BoardCard extends StatefulWidget {
|
||||
final String gridId;
|
||||
final CardDataController dataController;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
|
||||
const BoardCard({required this.rowInfo, Key? key}) : super(key: key);
|
||||
const BoardCard({
|
||||
required this.gridId,
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardCard> createState() => _BoardCardState();
|
||||
}
|
||||
|
||||
class _BoardCardState extends State<BoardCard> {
|
||||
late BoardCardBloc _cardBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cardBloc = BoardCardBloc(
|
||||
gridId: widget.gridId,
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox(height: 20, child: Text('1234'));
|
||||
return BlocProvider.value(
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
builder: (context, state) {
|
||||
return BoardCardContainer(
|
||||
accessoryBuilder: (context) {
|
||||
return [const _CardMoreOption()];
|
||||
},
|
||||
child: Column(
|
||||
children: _makeCells(context, state.gridCellMap),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _makeCells(BuildContext context, GridCellMap cellMap) {
|
||||
return cellMap.values.map(
|
||||
(cellId) {
|
||||
final child = widget.cellBuilder.buildCell(cellId);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class _CardMoreOption extends StatelessWidget with CardAccessory {
|
||||
const _CardMoreOption({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return svgWidget('home/details', color: context.read<AppTheme>().iconColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap(BuildContext context) {
|
||||
Log.debug('show options');
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'board_checkbox_cell.dart';
|
||||
import 'board_date_cell.dart';
|
||||
import 'board_number_cell.dart';
|
||||
import 'board_select_option_cell.dart';
|
||||
import 'board_text_cell.dart';
|
||||
import 'board_url_cell.dart';
|
||||
|
||||
abstract class BoardCellBuilderDelegate
|
||||
extends GridCellControllerBuilderDelegate {
|
||||
GridCellCache get cellCache;
|
||||
}
|
||||
|
||||
class BoardCellBuilder {
|
||||
final BoardCellBuilderDelegate delegate;
|
||||
|
||||
BoardCellBuilder(this.delegate);
|
||||
|
||||
Widget buildCell(GridCellIdentifier cellId) {
|
||||
final cellControllerBuilder = GridCellControllerBuilder(
|
||||
delegate: delegate,
|
||||
cellId: cellId,
|
||||
cellCache: delegate.cellCache,
|
||||
);
|
||||
|
||||
final key = cellId.key();
|
||||
switch (cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return BoardCheckboxCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return BoardDateCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return BoardSelectOptionCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return BoardSelectOptionCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return BoardNumberCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return BoardTextCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.URL:
|
||||
return BoardUrlCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class BoardCardContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final CardAccessoryBuilder? accessoryBuilder;
|
||||
const BoardCardContainer({
|
||||
required this.child,
|
||||
this.accessoryBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => _CardContainerNotifier(),
|
||||
child: Consumer<_CardContainerNotifier>(
|
||||
builder: (context, notifier, _) {
|
||||
Widget container = Center(child: child);
|
||||
if (accessoryBuilder != null) {
|
||||
final accessories = accessoryBuilder!(context);
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _CardEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: container,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CardAccessory implements Widget {
|
||||
void onTap(BuildContext context);
|
||||
}
|
||||
|
||||
typedef CardAccessoryBuilder = List<CardAccessory> Function(
|
||||
BuildContext buildContext,
|
||||
);
|
||||
|
||||
class CardAccessoryContainer extends StatelessWidget {
|
||||
final List<CardAccessory> accessories;
|
||||
const CardAccessoryContainer({required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final children = accessories.map((accessory) {
|
||||
final hover = FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: theme.hover,
|
||||
backgroundColor: theme.surface,
|
||||
),
|
||||
builder: (_, onHover) => Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: accessory,
|
||||
),
|
||||
);
|
||||
return GestureDetector(
|
||||
child: hover,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => accessory.onTap(context),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Wrap(children: children, spacing: 6);
|
||||
}
|
||||
}
|
||||
|
||||
class _CardEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<CardAccessory> accessories;
|
||||
const _CardEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<_CardContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
if (onEnter) {
|
||||
children.add(CardAccessoryContainer(accessories: accessories)
|
||||
.positioned(right: 0));
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) =>
|
||||
Provider.of<_CardContainerNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<_CardContainerNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: IntrinsicHeight(
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: children,
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CardContainerNotifier extends ChangeNotifier {
|
||||
bool _onEnter = false;
|
||||
|
||||
_CardContainerNotifier();
|
||||
|
||||
set onEnter(bool value) {
|
||||
if (_onEnter != value) {
|
||||
_onEnter = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
part of 'cell_service.dart';
|
||||
|
||||
typedef GridCellController = IGridCellController<String, String>;
|
||||
typedef GridCheckboxCellController = IGridCellController<String, String>;
|
||||
typedef GridNumberCellController = IGridCellController<String, String>;
|
||||
typedef GridSelectOptionCellController
|
||||
= IGridCellController<SelectOptionCellDataPB, String>;
|
||||
typedef GridDateCellController
|
||||
@ -58,7 +60,7 @@ class GridCellControllerBuilder {
|
||||
parser: StringCellDataParser(),
|
||||
reloadOnFieldChanged: true,
|
||||
);
|
||||
return GridCellController(
|
||||
return GridNumberCellController(
|
||||
cellId: _cellId,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: cellDataLoader,
|
||||
@ -127,7 +129,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
final GridCellDataLoader<T> _cellDataLoader;
|
||||
final IGridCellDataPersistence<D> _cellDataPersistence;
|
||||
|
||||
late final CellListener _cellListener;
|
||||
CellListener? _cellListener;
|
||||
ValueNotifier<T?>? _cellDataNotifier;
|
||||
|
||||
bool isListening = false;
|
||||
@ -186,7 +188,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
/// For example:
|
||||
/// user input: 12
|
||||
/// cell display: $12
|
||||
_cellListener.start(onCellChanged: (result) {
|
||||
_cellListener?.start(onCellChanged: (result) {
|
||||
result.fold(
|
||||
(_) => _loadData(),
|
||||
(err) => Log.error(err),
|
||||
@ -289,7 +291,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
return;
|
||||
}
|
||||
_isDispose = true;
|
||||
_cellListener.stop();
|
||||
_cellListener?.stop();
|
||||
_loadDataOperation?.cancel();
|
||||
_saveDataOperation?.cancel();
|
||||
_cellDataNotifier = null;
|
||||
|
@ -6,7 +6,7 @@ import 'cell_service/cell_service.dart';
|
||||
part 'checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
final GridCellController cellController;
|
||||
final GridCheckboxCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
CheckboxCellBloc({
|
||||
|
@ -8,7 +8,7 @@ import 'cell_service/cell_service.dart';
|
||||
part 'number_cell_bloc.freezed.dart';
|
||||
|
||||
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
final GridCellController cellController;
|
||||
final GridNumberCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
NumberCellBloc({
|
||||
|
@ -46,7 +46,7 @@ class GridDataController {
|
||||
|
||||
GridDataController({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.identity(),
|
||||
_blocks = LinkedHashMap.new(),
|
||||
_gridFFIService = GridService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id);
|
||||
|
||||
|
@ -12,10 +12,10 @@ part 'row_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class RowActionSheetBloc
|
||||
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
||||
final RowService _rowService;
|
||||
final RowFFIService _rowService;
|
||||
|
||||
RowActionSheetBloc({required RowInfo rowData})
|
||||
: _rowService = RowService(
|
||||
: _rowService = RowFFIService(
|
||||
gridId: rowData.gridId,
|
||||
blockId: rowData.blockId,
|
||||
rowId: rowData.id,
|
||||
|
@ -12,13 +12,13 @@ import 'row_service.dart';
|
||||
part 'row_bloc.freezed.dart';
|
||||
|
||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
final RowService _rowService;
|
||||
final RowFFIService _rowService;
|
||||
final GridRowDataController _dataController;
|
||||
|
||||
RowBloc({
|
||||
required RowInfo rowInfo,
|
||||
required GridRowDataController dataController,
|
||||
}) : _rowService = RowService(
|
||||
}) : _rowService = RowFFIService(
|
||||
gridId: rowInfo.gridId,
|
||||
blockId: rowInfo.blockId,
|
||||
rowId: rowInfo.id,
|
||||
@ -35,13 +35,12 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
_rowService.createRow();
|
||||
},
|
||||
didReceiveCells: (_DidReceiveCells value) async {
|
||||
final fields = value.gridCellMap.values
|
||||
final cells = value.gridCellMap.values
|
||||
.map((e) => GridCellEquatable(e.field))
|
||||
.toList();
|
||||
final snapshots = UnmodifiableListView(fields);
|
||||
emit(state.copyWith(
|
||||
gridCellMap: value.gridCellMap,
|
||||
snapshots: snapshots,
|
||||
cells: UnmodifiableListView(cells),
|
||||
changeReason: value.reason,
|
||||
));
|
||||
},
|
||||
@ -80,7 +79,7 @@ class RowState with _$RowState {
|
||||
const factory RowState({
|
||||
required RowInfo rowInfo,
|
||||
required GridCellMap gridCellMap,
|
||||
required UnmodifiableListView<GridCellEquatable> snapshots,
|
||||
required UnmodifiableListView<GridCellEquatable> cells,
|
||||
RowChangeReason? changeReason,
|
||||
}) = _RowState;
|
||||
|
||||
@ -88,8 +87,9 @@ class RowState with _$RowState {
|
||||
RowState(
|
||||
rowInfo: rowInfo,
|
||||
gridCellMap: cellDataMap,
|
||||
snapshots: UnmodifiableListView(
|
||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()),
|
||||
cells: UnmodifiableListView(
|
||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,6 @@ class GridRowDataController extends GridCellBuilderDelegate {
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridRowCache _rowCache;
|
||||
|
||||
GridFieldCache get fieldCache => _fieldCache;
|
||||
|
||||
GridRowCache get rowCache => _rowCache;
|
||||
|
||||
GridRowDataController({
|
||||
required this.rowInfo,
|
||||
required GridFieldCache fieldCache,
|
||||
@ -49,5 +45,5 @@ class GridRowDataController extends GridCellBuilderDelegate {
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellCache get cellCache => rowCache.cellCache;
|
||||
GridCellCache get cellCache => _rowCache.cellCache;
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
|
||||
class RowService {
|
||||
class RowFFIService {
|
||||
final String gridId;
|
||||
final String blockId;
|
||||
final String rowId;
|
||||
|
||||
RowService(
|
||||
RowFFIService(
|
||||
{required this.gridId, required this.blockId, required this.rowId});
|
||||
|
||||
Future<Either<RowPB, FlowyError>> createRow() {
|
||||
|
@ -8,6 +8,8 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class GridCellAccessoryBuildContext {
|
||||
final BuildContext anchorContext;
|
||||
final bool isCellEditing;
|
||||
@ -57,18 +59,6 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
||||
bool enable() => !isCellEditing;
|
||||
}
|
||||
|
||||
typedef AccessoryBuilder = List<GridCellAccessory> Function(
|
||||
GridCellAccessoryBuildContext buildContext);
|
||||
|
||||
abstract class CellAccessory extends Widget {
|
||||
const CellAccessory({Key? key}) : super(key: key);
|
||||
|
||||
// The hover will show if the isHover's value is true
|
||||
ValueNotifier<bool>? get onAccessoryHover;
|
||||
|
||||
AccessoryBuilder? get accessoryBuilder;
|
||||
}
|
||||
|
||||
class AccessoryHover extends StatefulWidget {
|
||||
final CellAccessory child;
|
||||
final EdgeInsets contentPadding;
|
||||
|
@ -94,6 +94,18 @@ abstract class CellEditable {
|
||||
ValueNotifier<bool> get onCellEditing;
|
||||
}
|
||||
|
||||
typedef AccessoryBuilder = List<GridCellAccessory> Function(
|
||||
GridCellAccessoryBuildContext buildContext);
|
||||
|
||||
abstract class CellAccessory extends Widget {
|
||||
const CellAccessory({Key? key}) : super(key: key);
|
||||
|
||||
// The hover will show if the isHover's value is true
|
||||
ValueNotifier<bool>? get onAccessoryHover;
|
||||
|
||||
AccessoryBuilder? get accessoryBuilder;
|
||||
}
|
||||
|
||||
abstract class GridCellWidget extends StatefulWidget
|
||||
implements CellAccessory, CellEditable, CellShortcuts {
|
||||
GridCellWidget({Key? key}) : super(key: key) {
|
||||
|
@ -25,24 +25,28 @@ class CellContainer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProxyProvider<RegionStateNotifier,
|
||||
CellContainerNotifier>(
|
||||
create: (_) => CellContainerNotifier(child),
|
||||
_CellContainerNotifier>(
|
||||
create: (_) => _CellContainerNotifier(child),
|
||||
update: (_, rowStateNotifier, cellStateNotifier) =>
|
||||
cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
|
||||
child: Selector<CellContainerNotifier, bool>(
|
||||
child: Selector<_CellContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.isFocus,
|
||||
builder: (context, isFocus, _) {
|
||||
Widget container = Center(child: GridCellShortcuts(child: child));
|
||||
|
||||
if (accessoryBuilder != null) {
|
||||
final accessories = accessoryBuilder!(GridCellAccessoryBuildContext(
|
||||
anchorContext: context,
|
||||
isCellEditing: isFocus,
|
||||
));
|
||||
final accessories = accessoryBuilder!(
|
||||
GridCellAccessoryBuildContext(
|
||||
anchorContext: context,
|
||||
isCellEditing: isFocus,
|
||||
),
|
||||
);
|
||||
|
||||
if (accessories.isNotEmpty) {
|
||||
container =
|
||||
CellEnterRegion(child: container, accessories: accessories);
|
||||
container = _GridCellEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,16 +78,16 @@ class CellContainer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class CellEnterRegion extends StatelessWidget {
|
||||
class _GridCellEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<GridCellAccessory> accessories;
|
||||
const CellEnterRegion(
|
||||
const _GridCellEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<CellContainerNotifier, bool>(
|
||||
return Selector<_CellContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
@ -95,10 +99,10 @@ class CellEnterRegion extends StatelessWidget {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) =>
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
@ -111,13 +115,13 @@ class CellEnterRegion extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class CellContainerNotifier extends ChangeNotifier {
|
||||
class _CellContainerNotifier extends ChangeNotifier {
|
||||
final CellEditable cellEditable;
|
||||
VoidCallback? _onCellFocusListener;
|
||||
bool _isFocus = false;
|
||||
bool _onEnter = false;
|
||||
|
||||
CellContainerNotifier(this.cellEditable) {
|
||||
_CellContainerNotifier(this.cellEditable) {
|
||||
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
||||
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ class _CheckboxCellState extends GridCellState<GridCheckboxCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCheckboxCellController;
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellController)
|
||||
..add(const CheckboxCellEvent.initial());
|
||||
super.initState();
|
||||
|
@ -73,7 +73,7 @@ class SelectOptionTag extends StatelessWidget {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
factory SelectOptionTag.fromSelectOption({
|
||||
factory SelectOptionTag.fromOption({
|
||||
required BuildContext context,
|
||||
required SelectOptionPB option,
|
||||
VoidCallback? onSelected,
|
||||
@ -91,7 +91,8 @@ class SelectOptionTag extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ChoiceChip(
|
||||
pressElevation: 1,
|
||||
label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||
label:
|
||||
FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||
selectedColor: color,
|
||||
backgroundColor: color,
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
@ -133,7 +134,7 @@ class SelectOptionTagCell extends StatelessWidget {
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
flex: 2,
|
||||
child: SelectOptionTag.fromSelectOption(
|
||||
child: SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
onSelected: () => onSelected(option),
|
||||
|
@ -153,21 +153,25 @@ class SelectOptionWrap extends StatelessWidget {
|
||||
if (selectOptions.isEmpty && cellStyle != null) {
|
||||
child = Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.medium(cellStyle!.placeholder,
|
||||
fontSize: 14, color: theme.shader3),
|
||||
child: FlowyText.medium(
|
||||
cellStyle!.placeholder,
|
||||
fontSize: 14,
|
||||
color: theme.shader3,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final tags = selectOptions
|
||||
.map(
|
||||
(option) => SelectOptionTag.fromSelectOption(
|
||||
context: context,
|
||||
option: option,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
child = Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Wrap(children: tags, spacing: 4, runSpacing: 2),
|
||||
child: Wrap(
|
||||
children: selectOptions
|
||||
.map((option) => SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
))
|
||||
.toList(),
|
||||
spacing: 4,
|
||||
runSpacing: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -176,15 +180,14 @@ class SelectOptionWrap extends StatelessWidget {
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
child,
|
||||
InkWell(
|
||||
onTap: () {
|
||||
onFocus?.call(true);
|
||||
final cellContext =
|
||||
cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
SelectOptionCellEditor.show(
|
||||
context, cellContext, () => onFocus?.call(false));
|
||||
},
|
||||
),
|
||||
InkWell(onTap: () {
|
||||
onFocus?.call(true);
|
||||
SelectOptionCellEditor.show(
|
||||
context,
|
||||
cellControllerBuilder.build() as GridSelectOptionCellController,
|
||||
() => onFocus?.call(false),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -49,7 +49,8 @@ class SelectOptionTextField extends StatelessWidget {
|
||||
initialTags: selectedOptionMap.keys.toList(),
|
||||
focusNode: _focusNode,
|
||||
textSeparators: const [' ', ','],
|
||||
inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) {
|
||||
inputfieldBuilder: (BuildContext context, editController, focusNode,
|
||||
error, onChanged, onSubmitted) {
|
||||
return ((context, sc, tags, onTagDelegate) {
|
||||
return TextField(
|
||||
autofocus: true,
|
||||
@ -99,7 +100,8 @@ class SelectOptionTextField extends StatelessWidget {
|
||||
}
|
||||
|
||||
final children = selectedOptionMap.values
|
||||
.map((option) => SelectOptionTag.fromSelectOption(context: context, option: option))
|
||||
.map((option) =>
|
||||
SelectOptionTag.fromOption(context: context, option: option))
|
||||
.toList();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
@ -90,9 +90,9 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext =
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||
_cellBloc = URLCellBloc(cellController: cellContext);
|
||||
_cellBloc = URLCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const URLCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ class RowContent extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RowBloc, RowState>(
|
||||
buildWhen: (previous, current) =>
|
||||
!listEquals(previous.snapshots, current.snapshots),
|
||||
!listEquals(previous.cells, current.cells),
|
||||
builder: (context, state) {
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
@ -181,28 +181,27 @@ class RowContent extends StatelessWidget {
|
||||
return gridCellMap.values.map(
|
||||
(cellId) {
|
||||
final GridCellWidget child = builder.build(cellId);
|
||||
accessoryBuilder(GridCellAccessoryBuildContext buildContext) {
|
||||
final builder = child.accessoryBuilder;
|
||||
List<GridCellAccessory> accessories = [];
|
||||
if (cellId.field.isPrimary) {
|
||||
accessories.add(PrimaryCellAccessory(
|
||||
onTapCallback: onExpand,
|
||||
isCellEditing: buildContext.isCellEditing,
|
||||
));
|
||||
}
|
||||
|
||||
if (builder != null) {
|
||||
accessories.addAll(builder(buildContext));
|
||||
}
|
||||
return accessories;
|
||||
}
|
||||
|
||||
return CellContainer(
|
||||
width: cellId.field.width.toDouble(),
|
||||
child: child,
|
||||
rowStateNotifier:
|
||||
Provider.of<RegionStateNotifier>(context, listen: false),
|
||||
accessoryBuilder: accessoryBuilder,
|
||||
accessoryBuilder: (buildContext) {
|
||||
final builder = child.accessoryBuilder;
|
||||
List<GridCellAccessory> accessories = [];
|
||||
if (cellId.field.isPrimary) {
|
||||
accessories.add(PrimaryCellAccessory(
|
||||
onTapCallback: onExpand,
|
||||
isCellEditing: buildContext.isCellEditing,
|
||||
));
|
||||
}
|
||||
|
||||
if (builder != null) {
|
||||
accessories.addAll(builder(buildContext));
|
||||
}
|
||||
return accessories;
|
||||
},
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
@ -23,18 +23,19 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final column1 = AFBoardColumnData(id: "To Do", items: [
|
||||
List<AFColumnItem> a = [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
// RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 4"),
|
||||
]);
|
||||
final column2 = AFBoardColumnData(id: "In Progress", items: [
|
||||
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 6"),
|
||||
];
|
||||
final column1 = AFBoardColumnData(id: "To Do", items: a);
|
||||
final column2 = AFBoardColumnData(id: "In Progress", items: <AFColumnItem>[
|
||||
// RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
// TextItem("Card 6"),
|
||||
]);
|
||||
|
||||
final column3 = AFBoardColumnData(id: "Done", items: []);
|
||||
final column3 = AFBoardColumnData(id: "Done", items: <AFColumnItem>[]);
|
||||
|
||||
boardDataController.addColumn(column1);
|
||||
boardDataController.addColumn(column2);
|
||||
|
@ -46,6 +46,8 @@ class AFBoard extends StatelessWidget {
|
||||
///
|
||||
final BoardPhantomController phantomController;
|
||||
|
||||
final ScrollController? scrollController;
|
||||
|
||||
final AFBoardConfig config;
|
||||
|
||||
AFBoard({
|
||||
@ -54,6 +56,7 @@ class AFBoard extends StatelessWidget {
|
||||
this.background,
|
||||
this.footBuilder,
|
||||
this.headerBuilder,
|
||||
this.scrollController,
|
||||
this.columnConstraints = const BoxConstraints(maxWidth: 200),
|
||||
this.config = const AFBoardConfig(),
|
||||
Key? key,
|
||||
@ -69,6 +72,7 @@ class AFBoard extends StatelessWidget {
|
||||
return BoardContent(
|
||||
config: config,
|
||||
dataController: dataController,
|
||||
scrollController: scrollController,
|
||||
background: background,
|
||||
delegate: phantomController,
|
||||
columnConstraints: columnConstraints,
|
||||
@ -202,7 +206,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
return ChangeNotifierProvider.value(
|
||||
key: ValueKey(columnData.id),
|
||||
value: widget.dataController.columnController(columnData.id),
|
||||
child: Consumer<BoardColumnDataController>(
|
||||
child: Consumer<AFBoardColumnDataController>(
|
||||
builder: (context, value, child) {
|
||||
return ConstrainedBox(
|
||||
constraints: widget.columnConstraints,
|
||||
|
@ -12,7 +12,7 @@ abstract class AFColumnItem extends ReoderFlexItem {
|
||||
String toString() => id;
|
||||
}
|
||||
|
||||
/// [BoardColumnDataController] is used to handle the [AFBoardColumnData].
|
||||
/// [AFBoardColumnDataController] is used to handle the [AFBoardColumnData].
|
||||
/// * Remove an item by calling [removeAt] method.
|
||||
/// * Move item to another position by calling [move] method.
|
||||
/// * Insert item to index by calling [insert] method
|
||||
@ -20,10 +20,10 @@ abstract class AFColumnItem extends ReoderFlexItem {
|
||||
///
|
||||
/// All there operations will notify listeners by default.
|
||||
///
|
||||
class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
final AFBoardColumnData columnData;
|
||||
|
||||
BoardColumnDataController({
|
||||
AFBoardColumnDataController({
|
||||
required this.columnData,
|
||||
});
|
||||
|
||||
@ -42,7 +42,8 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
AFColumnItem removeAt(int index, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
|
||||
Log.debug('[$BoardColumnDataController] $columnData remove item at $index');
|
||||
Log.debug(
|
||||
'[$AFBoardColumnDataController] $columnData remove item at $index');
|
||||
final item = columnData._items.removeAt(index);
|
||||
if (notify) {
|
||||
notifyListeners();
|
||||
@ -64,7 +65,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
return false;
|
||||
}
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
||||
'[$AFBoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
||||
final item = columnData._items.removeAt(fromIndex);
|
||||
columnData._items.insert(toIndex, item);
|
||||
notifyListeners();
|
||||
@ -78,7 +79,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
bool insert(int index, AFColumnItem item, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData insert $item at $index');
|
||||
'[$AFBoardColumnDataController] $columnData insert $item at $index');
|
||||
|
||||
if (columnData._items.length > index) {
|
||||
columnData._items.insert(index, item);
|
||||
@ -100,12 +101,12 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
void replace(int index, AFColumnItem newItem) {
|
||||
if (columnData._items.isEmpty) {
|
||||
columnData._items.add(newItem);
|
||||
Log.debug('[$BoardColumnDataController] $columnData add $newItem');
|
||||
Log.debug('[$AFBoardColumnDataController] $columnData add $newItem');
|
||||
} else {
|
||||
final removedItem = columnData._items.removeAt(index);
|
||||
columnData._items.insert(index, newItem);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
||||
'[$AFBoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
@ -35,7 +35,7 @@ class AFBoardDataController extends ChangeNotifier
|
||||
List<String> get columnIds =>
|
||||
_columnDatas.map((columnData) => columnData.id).toList();
|
||||
|
||||
final LinkedHashMap<String, BoardColumnDataController> _columnControllers =
|
||||
final LinkedHashMap<String, AFBoardColumnDataController> _columnControllers =
|
||||
LinkedHashMap();
|
||||
|
||||
AFBoardDataController({
|
||||
@ -47,7 +47,7 @@ class AFBoardDataController extends ChangeNotifier
|
||||
void addColumn(AFBoardColumnData columnData, {bool notify = true}) {
|
||||
if (_columnControllers[columnData.id] != null) return;
|
||||
|
||||
final controller = BoardColumnDataController(columnData: columnData);
|
||||
final controller = AFBoardColumnDataController(columnData: columnData);
|
||||
_columnDatas.add(columnData);
|
||||
_columnControllers[columnData.id] = controller;
|
||||
if (notify) notifyListeners();
|
||||
@ -84,11 +84,11 @@ class AFBoardDataController extends ChangeNotifier
|
||||
if (columnIds.isNotEmpty && notify) notifyListeners();
|
||||
}
|
||||
|
||||
BoardColumnDataController columnController(String columnId) {
|
||||
AFBoardColumnDataController columnController(String columnId) {
|
||||
return _columnControllers[columnId]!;
|
||||
}
|
||||
|
||||
BoardColumnDataController? getColumnController(String columnId) {
|
||||
AFBoardColumnDataController? getColumnController(String columnId) {
|
||||
final columnController = _columnControllers[columnId];
|
||||
if (columnController == null) {
|
||||
Log.warn('Column:[$columnId] \'s controller is not exist');
|
||||
@ -153,7 +153,7 @@ class AFBoardDataController extends ChangeNotifier
|
||||
}
|
||||
|
||||
@override
|
||||
BoardColumnDataController? controller(String columnId) {
|
||||
AFBoardColumnDataController? controller(String columnId) {
|
||||
return _columnControllers[columnId];
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import '../reorder_flex/drag_target_inteceptor.dart';
|
||||
import 'phantom_state.dart';
|
||||
|
||||
abstract class BoardPhantomControllerDelegate {
|
||||
BoardColumnDataController? controller(String columnId);
|
||||
AFBoardColumnDataController? controller(String columnId);
|
||||
|
||||
bool removePhantom(String columnId);
|
||||
|
||||
|
@ -150,7 +150,7 @@ impl FolderTest {
|
||||
// assert_eq!(json, expected_json);
|
||||
// }
|
||||
FolderScript::AssertWorkspace(workspace) => {
|
||||
assert_eq!(self.workspace, workspace);
|
||||
assert_eq!(self.workspace, workspace, "Workspace not equal");
|
||||
}
|
||||
FolderScript::ReadWorkspace(workspace_id) => {
|
||||
let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap();
|
||||
@ -166,7 +166,7 @@ impl FolderTest {
|
||||
// assert_eq!(json, expected_json);
|
||||
// }
|
||||
FolderScript::AssertApp(app) => {
|
||||
assert_eq!(self.app, app);
|
||||
assert_eq!(self.app, app, "App not equal");
|
||||
}
|
||||
FolderScript::ReadApp(app_id) => {
|
||||
let app = read_app(sdk, &app_id).await;
|
||||
@ -184,7 +184,7 @@ impl FolderTest {
|
||||
self.view = view;
|
||||
}
|
||||
FolderScript::AssertView(view) => {
|
||||
assert_eq!(self.view, view);
|
||||
assert_eq!(self.view, view, "View not equal");
|
||||
}
|
||||
FolderScript::ReadView(view_id) => {
|
||||
let view = read_view(sdk, &view_id).await;
|
||||
@ -215,7 +215,7 @@ impl FolderTest {
|
||||
}
|
||||
FolderScript::AssertRevisionState { rev_id, state } => {
|
||||
let record = cache.get(rev_id).await.unwrap();
|
||||
assert_eq!(record.state, state);
|
||||
assert_eq!(record.state, state, "Revision state is not match");
|
||||
if let RevisionState::Ack = state {
|
||||
// There is a defer action that writes the revisions to disk, so we wait here.
|
||||
// Make sure everything is written.
|
||||
@ -235,7 +235,7 @@ impl FolderTest {
|
||||
.unwrap_or_else(|| panic!("Expected Next revision is {}, but receive None", rev_id.unwrap()));
|
||||
let mut notify = rev_manager.ack_notify();
|
||||
let _ = notify.recv().await;
|
||||
assert_eq!(next_revision.rev_id, rev_id.unwrap());
|
||||
assert_eq!(next_revision.rev_id, rev_id.unwrap(), "Revision id not match");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOptionPB);
|
||||
impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
|
||||
impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB);
|
||||
impl MultiSelectTypeOptionBuilder {
|
||||
pub fn option(mut self, opt: SelectOptionPB) -> Self {
|
||||
pub fn add_option(mut self, opt: SelectOptionPB) -> Self {
|
||||
self.0.options.push(opt);
|
||||
self
|
||||
}
|
||||
@ -127,9 +127,9 @@ mod tests {
|
||||
let facebook_option = SelectOptionPB::new("Facebook");
|
||||
let twitter_option = SelectOptionPB::new("Twitter");
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(google_option.clone())
|
||||
.option(facebook_option.clone())
|
||||
.option(twitter_option);
|
||||
.add_option(google_option.clone())
|
||||
.add_option(facebook_option.clone())
|
||||
.add_option(twitter_option);
|
||||
|
||||
let field_rev = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
|
@ -156,8 +156,8 @@ mod tests {
|
||||
let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
|
||||
let cell_data_changeset = SelectOptionCellChangeset::from_insert(&ids).to_str();
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(google_option.clone())
|
||||
.option(facebook_option.clone());
|
||||
.add_option(google_option.clone())
|
||||
.add_option(facebook_option.clone());
|
||||
let multi_select_field_rev = FieldBuilder::new(multi_select).build();
|
||||
let multi_type_option = MultiSelectTypeOptionPB::from(&multi_select_field_rev);
|
||||
let cell_data = multi_type_option
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::services::cell::apply_cell_data_changeset;
|
||||
use crate::services::field::SelectOptionCellChangeset;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use crate::services::field::{DateCellChangesetPB, SelectOptionCellChangeset};
|
||||
use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
|
||||
use indexmap::IndexMap;
|
||||
use std::collections::HashMap;
|
||||
@ -35,17 +34,33 @@ impl<'a> RowRevisionBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
|
||||
pub fn insert_cell(&mut self, field_id: &str, data: String) {
|
||||
match self.field_rev_map.get(&field_id.to_owned()) {
|
||||
None => {
|
||||
let msg = format!("Can't find the field with id: {}", field_id);
|
||||
Err(FlowyError::internal().context(msg))
|
||||
tracing::warn!("Can't find the field with id: {}", field_id);
|
||||
}
|
||||
Some(field_rev) => {
|
||||
let data = apply_cell_data_changeset(data, None, field_rev)?;
|
||||
let data = apply_cell_data_changeset(data, None, field_rev).unwrap();
|
||||
let cell = CellRevision::new(data);
|
||||
self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_date_cell(&mut self, field_id: &str, timestamp: i64) {
|
||||
match self.field_rev_map.get(&field_id.to_owned()) {
|
||||
None => {
|
||||
tracing::warn!("Invalid field_id: {}", field_id);
|
||||
}
|
||||
Some(field_rev) => {
|
||||
let cell_data = serde_json::to_string(&DateCellChangesetPB {
|
||||
date: Some(timestamp.to_string()),
|
||||
time: None,
|
||||
})
|
||||
.unwrap();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
|
||||
let cell = CellRevision::new(data);
|
||||
self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use crate::services::field::*;
|
||||
use crate::services::row::RowRevisionBuilder;
|
||||
use flowy_grid_data_model::revision::BuildGridContext;
|
||||
use flowy_sync::client_grid::GridBuilder;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
pub fn make_default_grid() -> BuildGridContext {
|
||||
let mut grid_builder = GridBuilder::new();
|
||||
@ -40,24 +41,94 @@ pub fn make_default_board() -> BuildGridContext {
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
let text_field_id = text_field.id.clone();
|
||||
grid_builder.add_field(text_field);
|
||||
|
||||
// date
|
||||
let date_type_option = DateTypeOptionBuilder::default();
|
||||
let date_field = FieldBuilder::new(date_type_option)
|
||||
.name("Date")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let date_field_id = date_field.id.clone();
|
||||
let timestamp = timestamp();
|
||||
grid_builder.add_field(date_field);
|
||||
|
||||
// single select
|
||||
let in_progress_option = SelectOptionPB::new("In progress");
|
||||
let not_started_option = SelectOptionPB::new("Not started");
|
||||
let done_option = SelectOptionPB::new("Done");
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
let single_select_type_option = SingleSelectTypeOptionBuilder::default()
|
||||
.add_option(not_started_option.clone())
|
||||
.add_option(in_progress_option)
|
||||
.add_option(done_option);
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
let single_select_field = FieldBuilder::new(single_select_type_option)
|
||||
.name("Status")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let single_select_field_id = single_select_field.id.clone();
|
||||
grid_builder.add_field(single_select_field);
|
||||
|
||||
// MultiSelect
|
||||
let apple_option = SelectOptionPB::new("Apple");
|
||||
let banana_option = SelectOptionPB::new("Banana");
|
||||
let pear_option = SelectOptionPB::new("Pear");
|
||||
let multi_select_type_option = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(banana_option.clone())
|
||||
.add_option(apple_option.clone())
|
||||
.add_option(pear_option.clone());
|
||||
let multi_select_field = FieldBuilder::new(multi_select_type_option)
|
||||
.name("Fruit")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let multi_select_field_id = multi_select_field.id.clone();
|
||||
grid_builder.add_field(multi_select_field);
|
||||
|
||||
// Number
|
||||
let number_type_option = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number_type_option)
|
||||
.name("Price")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let number_field_id = number_field.id.clone();
|
||||
grid_builder.add_field(number_field);
|
||||
|
||||
// Checkbox
|
||||
let checkbox_type_option = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox_type_option).name("Reimbursement").build();
|
||||
let checkbox_field_id = checkbox_field.id.clone();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
|
||||
// Url
|
||||
let url_type_option = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url_type_option).name("Shop Link").build();
|
||||
let url_field_id = url_field.id.clone();
|
||||
grid_builder.add_field(url_field);
|
||||
|
||||
// Insert rows
|
||||
for _ in 0..3 {
|
||||
for i in 0..10 {
|
||||
// insert single select
|
||||
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, not_started_option.id.clone());
|
||||
// insert multi select
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, apple_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, banana_option.id.clone());
|
||||
// insert text
|
||||
row_builder.insert_cell(&text_field_id, format!("Card {}", i));
|
||||
// insert date
|
||||
row_builder.insert_date_cell(&date_field_id, timestamp);
|
||||
// number
|
||||
row_builder.insert_cell(&number_field_id, format!("{}", i));
|
||||
// checkbox
|
||||
let is_check = if i % 2 == 0 {
|
||||
CHECK.to_string()
|
||||
} else {
|
||||
UNCHECK.to_string()
|
||||
};
|
||||
row_builder.insert_cell(&checkbox_field_id, is_check);
|
||||
// url
|
||||
row_builder.insert_cell(&url_field_id, "https://appflowy.io".to_string());
|
||||
|
||||
let row = row_builder.build();
|
||||
grid_builder.add_row(row);
|
||||
}
|
||||
|
@ -26,18 +26,14 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
|
||||
pub fn insert_text_cell(&mut self, data: &str) -> String {
|
||||
let text_field = self.field_rev_with_type(&FieldType::RichText);
|
||||
self.inner_builder
|
||||
.insert_cell(&text_field.id, data.to_string())
|
||||
.unwrap();
|
||||
self.inner_builder.insert_cell(&text_field.id, data.to_string());
|
||||
|
||||
text_field.id.clone()
|
||||
}
|
||||
|
||||
pub fn insert_number_cell(&mut self, data: &str) -> String {
|
||||
let number_field = self.field_rev_with_type(&FieldType::Number);
|
||||
self.inner_builder
|
||||
.insert_cell(&number_field.id, data.to_string())
|
||||
.unwrap();
|
||||
self.inner_builder.insert_cell(&number_field.id, data.to_string());
|
||||
number_field.id.clone()
|
||||
}
|
||||
|
||||
@ -48,22 +44,20 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
})
|
||||
.unwrap();
|
||||
let date_field = self.field_rev_with_type(&FieldType::DateTime);
|
||||
self.inner_builder.insert_cell(&date_field.id, value).unwrap();
|
||||
self.inner_builder.insert_cell(&date_field.id, value);
|
||||
date_field.id.clone()
|
||||
}
|
||||
|
||||
pub fn insert_checkbox_cell(&mut self, data: &str) -> String {
|
||||
let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox);
|
||||
self.inner_builder
|
||||
.insert_cell(&checkbox_field.id, data.to_string())
|
||||
.unwrap();
|
||||
self.inner_builder.insert_cell(&checkbox_field.id, data.to_string());
|
||||
|
||||
checkbox_field.id.clone()
|
||||
}
|
||||
|
||||
pub fn insert_url_cell(&mut self, data: &str) -> String {
|
||||
let url_field = self.field_rev_with_type(&FieldType::URL);
|
||||
self.inner_builder.insert_cell(&url_field.id, data.to_string()).unwrap();
|
||||
self.inner_builder.insert_cell(&url_field.id, data.to_string());
|
||||
url_field.id.clone()
|
||||
}
|
||||
|
||||
|
@ -147,9 +147,9 @@ fn make_test_grid() -> BuildGridContext {
|
||||
FieldType::MultiSelect => {
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(SelectOptionPB::new(GOOGLE))
|
||||
.option(SelectOptionPB::new(FACEBOOK))
|
||||
.option(SelectOptionPB::new(TWITTER));
|
||||
.add_option(SelectOptionPB::new(GOOGLE))
|
||||
.add_option(SelectOptionPB::new(FACEBOOK))
|
||||
.add_option(SelectOptionPB::new(TWITTER));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
|
Loading…
Reference in New Issue
Block a user