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 'dart:async';
|
||||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
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:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
@ -20,6 +21,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
final BoardDataController _dataController;
|
final BoardDataController _dataController;
|
||||||
late final AFBoardDataController boardDataController;
|
late final AFBoardDataController boardDataController;
|
||||||
|
|
||||||
|
GridFieldCache get fieldCache => _dataController.fieldCache;
|
||||||
|
String get gridId => _dataController.gridId;
|
||||||
|
|
||||||
BoardBloc({required ViewPB view})
|
BoardBloc({required ViewPB view})
|
||||||
: _dataController = BoardDataController(view: view),
|
: _dataController = BoardDataController(view: view),
|
||||||
super(BoardState.initial(view.id)) {
|
super(BoardState.initial(view.id)) {
|
||||||
@ -57,6 +61,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
didReceiveGroups: (List<GroupPB> groups) {
|
didReceiveGroups: (List<GroupPB> groups) {
|
||||||
emit(state.copyWith(groups: 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();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
GridRowCache? getRowCache(String blockId, String rowId) {
|
GridRowCache? getRowCache(String blockId) {
|
||||||
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
||||||
return blockCache?.rowCache;
|
return blockCache?.rowCache;
|
||||||
}
|
}
|
||||||
@ -92,24 +99,29 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
|
|
||||||
boardDataController.addColumns(columns);
|
boardDataController.addColumns(columns);
|
||||||
},
|
},
|
||||||
|
onRowsChanged: (List<RowInfo> rowInfos, RowChangeReason reason) {
|
||||||
|
add(BoardEvent.didReceiveRows(rowInfos));
|
||||||
|
},
|
||||||
onError: (err) {
|
onError: (err) {
|
||||||
Log.error(err);
|
Log.error(err);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BoardColumnItem> _buildRows(List<RowPB> rows) {
|
List<AFColumnItem> _buildRows(List<RowPB> rows) {
|
||||||
return rows.map((row) {
|
final items = rows.map((row) {
|
||||||
final rowInfo = RowInfo(
|
// final rowInfo = RowInfo(
|
||||||
gridId: _dataController.gridId,
|
// gridId: _dataController.gridId,
|
||||||
blockId: row.blockId,
|
// blockId: row.blockId,
|
||||||
id: row.id,
|
// id: row.id,
|
||||||
fields: _dataController.fieldCache.unmodifiableFields,
|
// fields: _dataController.fieldCache.unmodifiableFields,
|
||||||
height: row.height.toDouble(),
|
// height: row.height.toDouble(),
|
||||||
rawRow: row,
|
// rawRow: row,
|
||||||
);
|
// );
|
||||||
return BoardColumnItem(row: rowInfo);
|
return BoardColumnItem(row: row);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
return <AFColumnItem>[...items];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||||
@ -131,6 +143,8 @@ class BoardEvent with _$BoardEvent {
|
|||||||
const factory BoardEvent.createRow() = _CreateRow;
|
const factory BoardEvent.createRow() = _CreateRow;
|
||||||
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
||||||
_DidReceiveGroup;
|
_DidReceiveGroup;
|
||||||
|
const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
|
||||||
|
_DidReceiveRows;
|
||||||
const factory BoardEvent.didReceiveGridUpdate(
|
const factory BoardEvent.didReceiveGridUpdate(
|
||||||
GridPB grid,
|
GridPB grid,
|
||||||
) = _DidReceiveGridUpdate;
|
) = _DidReceiveGridUpdate;
|
||||||
@ -186,10 +200,15 @@ class GridFieldEquatable extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BoardColumnItem extends AFColumnItem {
|
class BoardColumnItem extends AFColumnItem {
|
||||||
final RowInfo row;
|
final RowPB row;
|
||||||
|
|
||||||
BoardColumnItem({required this.row});
|
BoardColumnItem({required this.row});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id => row.id;
|
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/block/block_cache.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/field_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/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-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@ -12,6 +14,10 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
|||||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||||
typedef OnGridChanged = void Function(GridPB);
|
typedef OnGridChanged = void Function(GridPB);
|
||||||
typedef OnGroupChanged = void Function(List<GroupPB>);
|
typedef OnGroupChanged = void Function(List<GroupPB>);
|
||||||
|
typedef OnRowsChanged = void Function(
|
||||||
|
List<RowInfo> rowInfos,
|
||||||
|
RowChangeReason,
|
||||||
|
);
|
||||||
typedef OnError = void Function(FlowyError);
|
typedef OnError = void Function(FlowyError);
|
||||||
|
|
||||||
class BoardDataController {
|
class BoardDataController {
|
||||||
@ -21,17 +27,25 @@ class BoardDataController {
|
|||||||
|
|
||||||
// key: the block id
|
// key: the block id
|
||||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||||
UnmodifiableMapView<String, GridBlockCache> get blocks =>
|
LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
|
||||||
UnmodifiableMapView(_blocks);
|
|
||||||
|
|
||||||
OnFieldsChanged? _onFieldsChanged;
|
OnFieldsChanged? _onFieldsChanged;
|
||||||
OnGridChanged? _onGridChanged;
|
OnGridChanged? _onGridChanged;
|
||||||
OnGroupChanged? _onGroupChanged;
|
OnGroupChanged? _onGroupChanged;
|
||||||
|
OnRowsChanged? _onRowsChanged;
|
||||||
OnError? _onError;
|
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})
|
BoardDataController({required ViewPB view})
|
||||||
: gridId = view.id,
|
: gridId = view.id,
|
||||||
_blocks = LinkedHashMap.identity(),
|
_blocks = LinkedHashMap.new(),
|
||||||
_gridFFIService = GridService(gridId: view.id),
|
_gridFFIService = GridService(gridId: view.id),
|
||||||
fieldCache = GridFieldCache(gridId: view.id);
|
fieldCache = GridFieldCache(gridId: view.id);
|
||||||
|
|
||||||
@ -39,11 +53,13 @@ class BoardDataController {
|
|||||||
OnGridChanged? onGridChanged,
|
OnGridChanged? onGridChanged,
|
||||||
OnFieldsChanged? onFieldsChanged,
|
OnFieldsChanged? onFieldsChanged,
|
||||||
OnGroupChanged? onGroupChanged,
|
OnGroupChanged? onGroupChanged,
|
||||||
|
OnRowsChanged? onRowsChanged,
|
||||||
OnError? onError,
|
OnError? onError,
|
||||||
}) {
|
}) {
|
||||||
_onGridChanged = onGridChanged;
|
_onGridChanged = onGridChanged;
|
||||||
_onFieldsChanged = onFieldsChanged;
|
_onFieldsChanged = onFieldsChanged;
|
||||||
_onGroupChanged = onGroupChanged;
|
_onGroupChanged = onGroupChanged;
|
||||||
|
_onRowsChanged = onRowsChanged;
|
||||||
_onError = onError;
|
_onError = onError;
|
||||||
|
|
||||||
fieldCache.addListener(onFields: (fields) {
|
fieldCache.addListener(onFields: (fields) {
|
||||||
@ -57,6 +73,7 @@ class BoardDataController {
|
|||||||
() => result.fold(
|
() => result.fold(
|
||||||
(grid) async {
|
(grid) async {
|
||||||
_onGridChanged?.call(grid);
|
_onGridChanged?.call(grid);
|
||||||
|
_initialBlocks(grid.blocks);
|
||||||
return await _loadFields(grid).then((result) {
|
return await _loadFields(grid).then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(l) {
|
(l) {
|
||||||
@ -72,8 +89,8 @@ class BoardDataController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void createRow() {
|
Future<Either<RowPB, FlowyError>> createRow() {
|
||||||
_gridFFIService.createRow();
|
return _gridFFIService.createRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
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 {
|
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
||||||
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
||||||
return Future(
|
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
|
// 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:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.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 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../application/board_bloc.dart';
|
import '../application/board_bloc.dart';
|
||||||
import 'card/card.dart';
|
import 'card/card.dart';
|
||||||
|
import 'card/card_cell_builder.dart';
|
||||||
|
|
||||||
class BoardPage extends StatelessWidget {
|
class BoardPage extends StatelessWidget {
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
@ -49,12 +51,14 @@ class BoardContent extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||||
child: AFBoard(
|
child: AFBoard(
|
||||||
|
key: UniqueKey(),
|
||||||
|
scrollController: ScrollController(),
|
||||||
dataController: context.read<BoardBloc>().boardDataController,
|
dataController: context.read<BoardBloc>().boardDataController,
|
||||||
headerBuilder: _buildHeader,
|
headerBuilder: _buildHeader,
|
||||||
footBuilder: _buildFooter,
|
footBuilder: _buildFooter,
|
||||||
cardBuilder: _buildCard,
|
cardBuilder: (_, data) => _buildCard(context, data),
|
||||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||||
config: AFBoardConfig(
|
config: AFBoardConfig(
|
||||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||||
@ -87,10 +91,29 @@ class BoardContent extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
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(
|
return AppFlowyColumnItemCard(
|
||||||
key: ObjectKey(item),
|
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/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/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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@ -34,9 +34,15 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
|||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SelectOptionWrap(
|
final children = state.selectedOptions
|
||||||
selectOptions: state.selectedOptions,
|
.map((option) => SelectOptionTag.fromOption(
|
||||||
cellControllerBuilder: widget.cellControllerBuilder,
|
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,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SizedBox(
|
if (state.content.isEmpty) {
|
||||||
height: 30,
|
return const SizedBox();
|
||||||
child: FlowyText.medium(state.content),
|
} 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/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'card_cell_builder.dart';
|
||||||
|
import 'card_container.dart';
|
||||||
|
|
||||||
class BoardCard extends StatelessWidget {
|
class BoardCard extends StatefulWidget {
|
||||||
final RowInfo rowInfo;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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';
|
part of 'cell_service.dart';
|
||||||
|
|
||||||
typedef GridCellController = IGridCellController<String, String>;
|
typedef GridCellController = IGridCellController<String, String>;
|
||||||
|
typedef GridCheckboxCellController = IGridCellController<String, String>;
|
||||||
|
typedef GridNumberCellController = IGridCellController<String, String>;
|
||||||
typedef GridSelectOptionCellController
|
typedef GridSelectOptionCellController
|
||||||
= IGridCellController<SelectOptionCellDataPB, String>;
|
= IGridCellController<SelectOptionCellDataPB, String>;
|
||||||
typedef GridDateCellController
|
typedef GridDateCellController
|
||||||
@ -58,7 +60,7 @@ class GridCellControllerBuilder {
|
|||||||
parser: StringCellDataParser(),
|
parser: StringCellDataParser(),
|
||||||
reloadOnFieldChanged: true,
|
reloadOnFieldChanged: true,
|
||||||
);
|
);
|
||||||
return GridCellController(
|
return GridNumberCellController(
|
||||||
cellId: _cellId,
|
cellId: _cellId,
|
||||||
cellCache: _cellCache,
|
cellCache: _cellCache,
|
||||||
cellDataLoader: cellDataLoader,
|
cellDataLoader: cellDataLoader,
|
||||||
@ -127,7 +129,7 @@ class IGridCellController<T, D> extends Equatable {
|
|||||||
final GridCellDataLoader<T> _cellDataLoader;
|
final GridCellDataLoader<T> _cellDataLoader;
|
||||||
final IGridCellDataPersistence<D> _cellDataPersistence;
|
final IGridCellDataPersistence<D> _cellDataPersistence;
|
||||||
|
|
||||||
late final CellListener _cellListener;
|
CellListener? _cellListener;
|
||||||
ValueNotifier<T?>? _cellDataNotifier;
|
ValueNotifier<T?>? _cellDataNotifier;
|
||||||
|
|
||||||
bool isListening = false;
|
bool isListening = false;
|
||||||
@ -186,7 +188,7 @@ class IGridCellController<T, D> extends Equatable {
|
|||||||
/// For example:
|
/// For example:
|
||||||
/// user input: 12
|
/// user input: 12
|
||||||
/// cell display: $12
|
/// cell display: $12
|
||||||
_cellListener.start(onCellChanged: (result) {
|
_cellListener?.start(onCellChanged: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
(_) => _loadData(),
|
(_) => _loadData(),
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
@ -289,7 +291,7 @@ class IGridCellController<T, D> extends Equatable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_isDispose = true;
|
_isDispose = true;
|
||||||
_cellListener.stop();
|
_cellListener?.stop();
|
||||||
_loadDataOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
_saveDataOperation?.cancel();
|
_saveDataOperation?.cancel();
|
||||||
_cellDataNotifier = null;
|
_cellDataNotifier = null;
|
||||||
|
@ -6,7 +6,7 @@ import 'cell_service/cell_service.dart';
|
|||||||
part 'checkbox_cell_bloc.freezed.dart';
|
part 'checkbox_cell_bloc.freezed.dart';
|
||||||
|
|
||||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||||
final GridCellController cellController;
|
final GridCheckboxCellController cellController;
|
||||||
void Function()? _onCellChangedFn;
|
void Function()? _onCellChangedFn;
|
||||||
|
|
||||||
CheckboxCellBloc({
|
CheckboxCellBloc({
|
||||||
|
@ -8,7 +8,7 @@ import 'cell_service/cell_service.dart';
|
|||||||
part 'number_cell_bloc.freezed.dart';
|
part 'number_cell_bloc.freezed.dart';
|
||||||
|
|
||||||
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||||
final GridCellController cellController;
|
final GridNumberCellController cellController;
|
||||||
void Function()? _onCellChangedFn;
|
void Function()? _onCellChangedFn;
|
||||||
|
|
||||||
NumberCellBloc({
|
NumberCellBloc({
|
||||||
|
@ -46,7 +46,7 @@ class GridDataController {
|
|||||||
|
|
||||||
GridDataController({required ViewPB view})
|
GridDataController({required ViewPB view})
|
||||||
: gridId = view.id,
|
: gridId = view.id,
|
||||||
_blocks = LinkedHashMap.identity(),
|
_blocks = LinkedHashMap.new(),
|
||||||
_gridFFIService = GridService(gridId: view.id),
|
_gridFFIService = GridService(gridId: view.id),
|
||||||
fieldCache = GridFieldCache(gridId: view.id);
|
fieldCache = GridFieldCache(gridId: view.id);
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ part 'row_action_sheet_bloc.freezed.dart';
|
|||||||
|
|
||||||
class RowActionSheetBloc
|
class RowActionSheetBloc
|
||||||
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
||||||
final RowService _rowService;
|
final RowFFIService _rowService;
|
||||||
|
|
||||||
RowActionSheetBloc({required RowInfo rowData})
|
RowActionSheetBloc({required RowInfo rowData})
|
||||||
: _rowService = RowService(
|
: _rowService = RowFFIService(
|
||||||
gridId: rowData.gridId,
|
gridId: rowData.gridId,
|
||||||
blockId: rowData.blockId,
|
blockId: rowData.blockId,
|
||||||
rowId: rowData.id,
|
rowId: rowData.id,
|
||||||
|
@ -12,13 +12,13 @@ import 'row_service.dart';
|
|||||||
part 'row_bloc.freezed.dart';
|
part 'row_bloc.freezed.dart';
|
||||||
|
|
||||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||||
final RowService _rowService;
|
final RowFFIService _rowService;
|
||||||
final GridRowDataController _dataController;
|
final GridRowDataController _dataController;
|
||||||
|
|
||||||
RowBloc({
|
RowBloc({
|
||||||
required RowInfo rowInfo,
|
required RowInfo rowInfo,
|
||||||
required GridRowDataController dataController,
|
required GridRowDataController dataController,
|
||||||
}) : _rowService = RowService(
|
}) : _rowService = RowFFIService(
|
||||||
gridId: rowInfo.gridId,
|
gridId: rowInfo.gridId,
|
||||||
blockId: rowInfo.blockId,
|
blockId: rowInfo.blockId,
|
||||||
rowId: rowInfo.id,
|
rowId: rowInfo.id,
|
||||||
@ -35,13 +35,12 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
_rowService.createRow();
|
_rowService.createRow();
|
||||||
},
|
},
|
||||||
didReceiveCells: (_DidReceiveCells value) async {
|
didReceiveCells: (_DidReceiveCells value) async {
|
||||||
final fields = value.gridCellMap.values
|
final cells = value.gridCellMap.values
|
||||||
.map((e) => GridCellEquatable(e.field))
|
.map((e) => GridCellEquatable(e.field))
|
||||||
.toList();
|
.toList();
|
||||||
final snapshots = UnmodifiableListView(fields);
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
gridCellMap: value.gridCellMap,
|
gridCellMap: value.gridCellMap,
|
||||||
snapshots: snapshots,
|
cells: UnmodifiableListView(cells),
|
||||||
changeReason: value.reason,
|
changeReason: value.reason,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
@ -80,7 +79,7 @@ class RowState with _$RowState {
|
|||||||
const factory RowState({
|
const factory RowState({
|
||||||
required RowInfo rowInfo,
|
required RowInfo rowInfo,
|
||||||
required GridCellMap gridCellMap,
|
required GridCellMap gridCellMap,
|
||||||
required UnmodifiableListView<GridCellEquatable> snapshots,
|
required UnmodifiableListView<GridCellEquatable> cells,
|
||||||
RowChangeReason? changeReason,
|
RowChangeReason? changeReason,
|
||||||
}) = _RowState;
|
}) = _RowState;
|
||||||
|
|
||||||
@ -88,8 +87,9 @@ class RowState with _$RowState {
|
|||||||
RowState(
|
RowState(
|
||||||
rowInfo: rowInfo,
|
rowInfo: rowInfo,
|
||||||
gridCellMap: cellDataMap,
|
gridCellMap: cellDataMap,
|
||||||
snapshots: UnmodifiableListView(
|
cells: UnmodifiableListView(
|
||||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()),
|
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,10 +13,6 @@ class GridRowDataController extends GridCellBuilderDelegate {
|
|||||||
final GridFieldCache _fieldCache;
|
final GridFieldCache _fieldCache;
|
||||||
final GridRowCache _rowCache;
|
final GridRowCache _rowCache;
|
||||||
|
|
||||||
GridFieldCache get fieldCache => _fieldCache;
|
|
||||||
|
|
||||||
GridRowCache get rowCache => _rowCache;
|
|
||||||
|
|
||||||
GridRowDataController({
|
GridRowDataController({
|
||||||
required this.rowInfo,
|
required this.rowInfo,
|
||||||
required GridFieldCache fieldCache,
|
required GridFieldCache fieldCache,
|
||||||
@ -49,5 +45,5 @@ class GridRowDataController extends GridCellBuilderDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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/grid_entities.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||||
|
|
||||||
class RowService {
|
class RowFFIService {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
final String blockId;
|
final String blockId;
|
||||||
final String rowId;
|
final String rowId;
|
||||||
|
|
||||||
RowService(
|
RowFFIService(
|
||||||
{required this.gridId, required this.blockId, required this.rowId});
|
{required this.gridId, required this.blockId, required this.rowId});
|
||||||
|
|
||||||
Future<Either<RowPB, FlowyError>> createRow() {
|
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:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
import 'cell_builder.dart';
|
||||||
|
|
||||||
class GridCellAccessoryBuildContext {
|
class GridCellAccessoryBuildContext {
|
||||||
final BuildContext anchorContext;
|
final BuildContext anchorContext;
|
||||||
final bool isCellEditing;
|
final bool isCellEditing;
|
||||||
@ -57,18 +59,6 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
|||||||
bool enable() => !isCellEditing;
|
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 {
|
class AccessoryHover extends StatefulWidget {
|
||||||
final CellAccessory child;
|
final CellAccessory child;
|
||||||
final EdgeInsets contentPadding;
|
final EdgeInsets contentPadding;
|
||||||
|
@ -94,6 +94,18 @@ abstract class CellEditable {
|
|||||||
ValueNotifier<bool> get onCellEditing;
|
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
|
abstract class GridCellWidget extends StatefulWidget
|
||||||
implements CellAccessory, CellEditable, CellShortcuts {
|
implements CellAccessory, CellEditable, CellShortcuts {
|
||||||
GridCellWidget({Key? key}) : super(key: key) {
|
GridCellWidget({Key? key}) : super(key: key) {
|
||||||
|
@ -25,24 +25,28 @@ class CellContainer extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProxyProvider<RegionStateNotifier,
|
return ChangeNotifierProxyProvider<RegionStateNotifier,
|
||||||
CellContainerNotifier>(
|
_CellContainerNotifier>(
|
||||||
create: (_) => CellContainerNotifier(child),
|
create: (_) => _CellContainerNotifier(child),
|
||||||
update: (_, rowStateNotifier, cellStateNotifier) =>
|
update: (_, rowStateNotifier, cellStateNotifier) =>
|
||||||
cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
|
cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
|
||||||
child: Selector<CellContainerNotifier, bool>(
|
child: Selector<_CellContainerNotifier, bool>(
|
||||||
selector: (context, notifier) => notifier.isFocus,
|
selector: (context, notifier) => notifier.isFocus,
|
||||||
builder: (context, isFocus, _) {
|
builder: (context, isFocus, _) {
|
||||||
Widget container = Center(child: GridCellShortcuts(child: child));
|
Widget container = Center(child: GridCellShortcuts(child: child));
|
||||||
|
|
||||||
if (accessoryBuilder != null) {
|
if (accessoryBuilder != null) {
|
||||||
final accessories = accessoryBuilder!(GridCellAccessoryBuildContext(
|
final accessories = accessoryBuilder!(
|
||||||
|
GridCellAccessoryBuildContext(
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
isCellEditing: isFocus,
|
isCellEditing: isFocus,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (accessories.isNotEmpty) {
|
if (accessories.isNotEmpty) {
|
||||||
container =
|
container = _GridCellEnterRegion(
|
||||||
CellEnterRegion(child: container, accessories: accessories);
|
child: container,
|
||||||
|
accessories: accessories,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,16 +78,16 @@ class CellContainer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellEnterRegion extends StatelessWidget {
|
class _GridCellEnterRegion extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final List<GridCellAccessory> accessories;
|
final List<GridCellAccessory> accessories;
|
||||||
const CellEnterRegion(
|
const _GridCellEnterRegion(
|
||||||
{required this.child, required this.accessories, Key? key})
|
{required this.child, required this.accessories, Key? key})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector<CellContainerNotifier, bool>(
|
return Selector<_CellContainerNotifier, bool>(
|
||||||
selector: (context, notifier) => notifier.onEnter,
|
selector: (context, notifier) => notifier.onEnter,
|
||||||
builder: (context, onEnter, _) {
|
builder: (context, onEnter, _) {
|
||||||
List<Widget> children = [child];
|
List<Widget> children = [child];
|
||||||
@ -95,10 +99,10 @@ class CellEnterRegion extends StatelessWidget {
|
|||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
onEnter: (p) =>
|
onEnter: (p) =>
|
||||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||||
.onEnter = true,
|
.onEnter = true,
|
||||||
onExit: (p) =>
|
onExit: (p) =>
|
||||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||||
.onEnter = false,
|
.onEnter = false,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
@ -111,13 +115,13 @@ class CellEnterRegion extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellContainerNotifier extends ChangeNotifier {
|
class _CellContainerNotifier extends ChangeNotifier {
|
||||||
final CellEditable cellEditable;
|
final CellEditable cellEditable;
|
||||||
VoidCallback? _onCellFocusListener;
|
VoidCallback? _onCellFocusListener;
|
||||||
bool _isFocus = false;
|
bool _isFocus = false;
|
||||||
bool _onEnter = false;
|
bool _onEnter = false;
|
||||||
|
|
||||||
CellContainerNotifier(this.cellEditable) {
|
_CellContainerNotifier(this.cellEditable) {
|
||||||
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
||||||
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ class _CheckboxCellState extends GridCellState<GridCheckboxCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellController = widget.cellControllerBuilder.build();
|
final cellController =
|
||||||
|
widget.cellControllerBuilder.build() as GridCheckboxCellController;
|
||||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellController)
|
_cellBloc = getIt<CheckboxCellBloc>(param1: cellController)
|
||||||
..add(const CheckboxCellEvent.initial());
|
..add(const CheckboxCellEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -73,7 +73,7 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
factory SelectOptionTag.fromSelectOption({
|
factory SelectOptionTag.fromOption({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required SelectOptionPB option,
|
required SelectOptionPB option,
|
||||||
VoidCallback? onSelected,
|
VoidCallback? onSelected,
|
||||||
@ -91,7 +91,8 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChoiceChip(
|
return ChoiceChip(
|
||||||
pressElevation: 1,
|
pressElevation: 1,
|
||||||
label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
label:
|
||||||
|
FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||||
selectedColor: color,
|
selectedColor: color,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
@ -133,7 +134,7 @@ class SelectOptionTagCell extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.loose,
|
fit: FlexFit.loose,
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: SelectOptionTag.fromSelectOption(
|
child: SelectOptionTag.fromOption(
|
||||||
context: context,
|
context: context,
|
||||||
option: option,
|
option: option,
|
||||||
onSelected: () => onSelected(option),
|
onSelected: () => onSelected(option),
|
||||||
|
@ -153,21 +153,25 @@ class SelectOptionWrap extends StatelessWidget {
|
|||||||
if (selectOptions.isEmpty && cellStyle != null) {
|
if (selectOptions.isEmpty && cellStyle != null) {
|
||||||
child = Align(
|
child = Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: FlowyText.medium(cellStyle!.placeholder,
|
child: FlowyText.medium(
|
||||||
fontSize: 14, color: theme.shader3),
|
cellStyle!.placeholder,
|
||||||
|
fontSize: 14,
|
||||||
|
color: theme.shader3,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final tags = selectOptions
|
|
||||||
.map(
|
|
||||||
(option) => SelectOptionTag.fromSelectOption(
|
|
||||||
context: context,
|
|
||||||
option: option,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
child = Align(
|
child = Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Wrap(children: tags, spacing: 4, runSpacing: 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,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
child,
|
child,
|
||||||
InkWell(
|
InkWell(onTap: () {
|
||||||
onTap: () {
|
|
||||||
onFocus?.call(true);
|
onFocus?.call(true);
|
||||||
final cellContext =
|
|
||||||
cellControllerBuilder.build() as GridSelectOptionCellController;
|
|
||||||
SelectOptionCellEditor.show(
|
SelectOptionCellEditor.show(
|
||||||
context, cellContext, () => onFocus?.call(false));
|
context,
|
||||||
},
|
cellControllerBuilder.build() as GridSelectOptionCellController,
|
||||||
),
|
() => onFocus?.call(false),
|
||||||
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,8 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
initialTags: selectedOptionMap.keys.toList(),
|
initialTags: selectedOptionMap.keys.toList(),
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
textSeparators: const [' ', ','],
|
textSeparators: const [' ', ','],
|
||||||
inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) {
|
inputfieldBuilder: (BuildContext context, editController, focusNode,
|
||||||
|
error, onChanged, onSubmitted) {
|
||||||
return ((context, sc, tags, onTagDelegate) {
|
return ((context, sc, tags, onTagDelegate) {
|
||||||
return TextField(
|
return TextField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@ -99,7 +100,8 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final children = selectedOptionMap.values
|
final children = selectedOptionMap.values
|
||||||
.map((option) => SelectOptionTag.fromSelectOption(context: context, option: option))
|
.map((option) =>
|
||||||
|
SelectOptionTag.fromOption(context: context, option: option))
|
||||||
.toList();
|
.toList();
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
@ -90,9 +90,9 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext =
|
final cellController =
|
||||||
widget.cellControllerBuilder.build() as GridURLCellController;
|
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||||
_cellBloc = URLCellBloc(cellController: cellContext);
|
_cellBloc = URLCellBloc(cellController: cellController);
|
||||||
_cellBloc.add(const URLCellEvent.initial());
|
_cellBloc.add(const URLCellEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ class RowContent extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<RowBloc, RowState>(
|
return BlocBuilder<RowBloc, RowState>(
|
||||||
buildWhen: (previous, current) =>
|
buildWhen: (previous, current) =>
|
||||||
!listEquals(previous.snapshots, current.snapshots),
|
!listEquals(previous.cells, current.cells),
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return IntrinsicHeight(
|
return IntrinsicHeight(
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -181,7 +181,13 @@ class RowContent extends StatelessWidget {
|
|||||||
return gridCellMap.values.map(
|
return gridCellMap.values.map(
|
||||||
(cellId) {
|
(cellId) {
|
||||||
final GridCellWidget child = builder.build(cellId);
|
final GridCellWidget child = builder.build(cellId);
|
||||||
accessoryBuilder(GridCellAccessoryBuildContext buildContext) {
|
|
||||||
|
return CellContainer(
|
||||||
|
width: cellId.field.width.toDouble(),
|
||||||
|
child: child,
|
||||||
|
rowStateNotifier:
|
||||||
|
Provider.of<RegionStateNotifier>(context, listen: false),
|
||||||
|
accessoryBuilder: (buildContext) {
|
||||||
final builder = child.accessoryBuilder;
|
final builder = child.accessoryBuilder;
|
||||||
List<GridCellAccessory> accessories = [];
|
List<GridCellAccessory> accessories = [];
|
||||||
if (cellId.field.isPrimary) {
|
if (cellId.field.isPrimary) {
|
||||||
@ -195,14 +201,7 @@ class RowContent extends StatelessWidget {
|
|||||||
accessories.addAll(builder(buildContext));
|
accessories.addAll(builder(buildContext));
|
||||||
}
|
}
|
||||||
return accessories;
|
return accessories;
|
||||||
}
|
},
|
||||||
|
|
||||||
return CellContainer(
|
|
||||||
width: cellId.field.width.toDouble(),
|
|
||||||
child: child,
|
|
||||||
rowStateNotifier:
|
|
||||||
Provider.of<RegionStateNotifier>(context, listen: false),
|
|
||||||
accessoryBuilder: accessoryBuilder,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
|
@ -23,18 +23,19 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final column1 = AFBoardColumnData(id: "To Do", items: [
|
List<AFColumnItem> a = [
|
||||||
TextItem("Card 1"),
|
TextItem("Card 1"),
|
||||||
TextItem("Card 2"),
|
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"),
|
TextItem("Card 4"),
|
||||||
]);
|
];
|
||||||
final column2 = AFBoardColumnData(id: "In Progress", items: [
|
final column1 = AFBoardColumnData(id: "To Do", items: a);
|
||||||
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
final column2 = AFBoardColumnData(id: "In Progress", items: <AFColumnItem>[
|
||||||
TextItem("Card 6"),
|
// 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(column1);
|
||||||
boardDataController.addColumn(column2);
|
boardDataController.addColumn(column2);
|
||||||
|
@ -46,6 +46,8 @@ class AFBoard extends StatelessWidget {
|
|||||||
///
|
///
|
||||||
final BoardPhantomController phantomController;
|
final BoardPhantomController phantomController;
|
||||||
|
|
||||||
|
final ScrollController? scrollController;
|
||||||
|
|
||||||
final AFBoardConfig config;
|
final AFBoardConfig config;
|
||||||
|
|
||||||
AFBoard({
|
AFBoard({
|
||||||
@ -54,6 +56,7 @@ class AFBoard extends StatelessWidget {
|
|||||||
this.background,
|
this.background,
|
||||||
this.footBuilder,
|
this.footBuilder,
|
||||||
this.headerBuilder,
|
this.headerBuilder,
|
||||||
|
this.scrollController,
|
||||||
this.columnConstraints = const BoxConstraints(maxWidth: 200),
|
this.columnConstraints = const BoxConstraints(maxWidth: 200),
|
||||||
this.config = const AFBoardConfig(),
|
this.config = const AFBoardConfig(),
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -69,6 +72,7 @@ class AFBoard extends StatelessWidget {
|
|||||||
return BoardContent(
|
return BoardContent(
|
||||||
config: config,
|
config: config,
|
||||||
dataController: dataController,
|
dataController: dataController,
|
||||||
|
scrollController: scrollController,
|
||||||
background: background,
|
background: background,
|
||||||
delegate: phantomController,
|
delegate: phantomController,
|
||||||
columnConstraints: columnConstraints,
|
columnConstraints: columnConstraints,
|
||||||
@ -202,7 +206,7 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
key: ValueKey(columnData.id),
|
key: ValueKey(columnData.id),
|
||||||
value: widget.dataController.columnController(columnData.id),
|
value: widget.dataController.columnController(columnData.id),
|
||||||
child: Consumer<BoardColumnDataController>(
|
child: Consumer<AFBoardColumnDataController>(
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: widget.columnConstraints,
|
constraints: widget.columnConstraints,
|
||||||
|
@ -12,7 +12,7 @@ abstract class AFColumnItem extends ReoderFlexItem {
|
|||||||
String toString() => id;
|
String toString() => id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [BoardColumnDataController] is used to handle the [AFBoardColumnData].
|
/// [AFBoardColumnDataController] is used to handle the [AFBoardColumnData].
|
||||||
/// * Remove an item by calling [removeAt] method.
|
/// * Remove an item by calling [removeAt] method.
|
||||||
/// * Move item to another position by calling [move] method.
|
/// * Move item to another position by calling [move] method.
|
||||||
/// * Insert item to index by calling [insert] 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.
|
/// All there operations will notify listeners by default.
|
||||||
///
|
///
|
||||||
class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||||
final AFBoardColumnData columnData;
|
final AFBoardColumnData columnData;
|
||||||
|
|
||||||
BoardColumnDataController({
|
AFBoardColumnDataController({
|
||||||
required this.columnData,
|
required this.columnData,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -42,7 +42,8 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
|||||||
AFColumnItem removeAt(int index, {bool notify = true}) {
|
AFColumnItem removeAt(int index, {bool notify = true}) {
|
||||||
assert(index >= 0);
|
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);
|
final item = columnData._items.removeAt(index);
|
||||||
if (notify) {
|
if (notify) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -64,7 +65,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Log.debug(
|
Log.debug(
|
||||||
'[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
'[$AFBoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
||||||
final item = columnData._items.removeAt(fromIndex);
|
final item = columnData._items.removeAt(fromIndex);
|
||||||
columnData._items.insert(toIndex, item);
|
columnData._items.insert(toIndex, item);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -78,7 +79,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
|||||||
bool insert(int index, AFColumnItem item, {bool notify = true}) {
|
bool insert(int index, AFColumnItem item, {bool notify = true}) {
|
||||||
assert(index >= 0);
|
assert(index >= 0);
|
||||||
Log.debug(
|
Log.debug(
|
||||||
'[$BoardColumnDataController] $columnData insert $item at $index');
|
'[$AFBoardColumnDataController] $columnData insert $item at $index');
|
||||||
|
|
||||||
if (columnData._items.length > index) {
|
if (columnData._items.length > index) {
|
||||||
columnData._items.insert(index, item);
|
columnData._items.insert(index, item);
|
||||||
@ -100,12 +101,12 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
|||||||
void replace(int index, AFColumnItem newItem) {
|
void replace(int index, AFColumnItem newItem) {
|
||||||
if (columnData._items.isEmpty) {
|
if (columnData._items.isEmpty) {
|
||||||
columnData._items.add(newItem);
|
columnData._items.add(newItem);
|
||||||
Log.debug('[$BoardColumnDataController] $columnData add $newItem');
|
Log.debug('[$AFBoardColumnDataController] $columnData add $newItem');
|
||||||
} else {
|
} else {
|
||||||
final removedItem = columnData._items.removeAt(index);
|
final removedItem = columnData._items.removeAt(index);
|
||||||
columnData._items.insert(index, newItem);
|
columnData._items.insert(index, newItem);
|
||||||
Log.debug(
|
Log.debug(
|
||||||
'[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
'[$AFBoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -35,7 +35,7 @@ class AFBoardDataController extends ChangeNotifier
|
|||||||
List<String> get columnIds =>
|
List<String> get columnIds =>
|
||||||
_columnDatas.map((columnData) => columnData.id).toList();
|
_columnDatas.map((columnData) => columnData.id).toList();
|
||||||
|
|
||||||
final LinkedHashMap<String, BoardColumnDataController> _columnControllers =
|
final LinkedHashMap<String, AFBoardColumnDataController> _columnControllers =
|
||||||
LinkedHashMap();
|
LinkedHashMap();
|
||||||
|
|
||||||
AFBoardDataController({
|
AFBoardDataController({
|
||||||
@ -47,7 +47,7 @@ class AFBoardDataController extends ChangeNotifier
|
|||||||
void addColumn(AFBoardColumnData columnData, {bool notify = true}) {
|
void addColumn(AFBoardColumnData columnData, {bool notify = true}) {
|
||||||
if (_columnControllers[columnData.id] != null) return;
|
if (_columnControllers[columnData.id] != null) return;
|
||||||
|
|
||||||
final controller = BoardColumnDataController(columnData: columnData);
|
final controller = AFBoardColumnDataController(columnData: columnData);
|
||||||
_columnDatas.add(columnData);
|
_columnDatas.add(columnData);
|
||||||
_columnControllers[columnData.id] = controller;
|
_columnControllers[columnData.id] = controller;
|
||||||
if (notify) notifyListeners();
|
if (notify) notifyListeners();
|
||||||
@ -84,11 +84,11 @@ class AFBoardDataController extends ChangeNotifier
|
|||||||
if (columnIds.isNotEmpty && notify) notifyListeners();
|
if (columnIds.isNotEmpty && notify) notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
BoardColumnDataController columnController(String columnId) {
|
AFBoardColumnDataController columnController(String columnId) {
|
||||||
return _columnControllers[columnId]!;
|
return _columnControllers[columnId]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoardColumnDataController? getColumnController(String columnId) {
|
AFBoardColumnDataController? getColumnController(String columnId) {
|
||||||
final columnController = _columnControllers[columnId];
|
final columnController = _columnControllers[columnId];
|
||||||
if (columnController == null) {
|
if (columnController == null) {
|
||||||
Log.warn('Column:[$columnId] \'s controller is not exist');
|
Log.warn('Column:[$columnId] \'s controller is not exist');
|
||||||
@ -153,7 +153,7 @@ class AFBoardDataController extends ChangeNotifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BoardColumnDataController? controller(String columnId) {
|
AFBoardColumnDataController? controller(String columnId) {
|
||||||
return _columnControllers[columnId];
|
return _columnControllers[columnId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import '../reorder_flex/drag_target_inteceptor.dart';
|
|||||||
import 'phantom_state.dart';
|
import 'phantom_state.dart';
|
||||||
|
|
||||||
abstract class BoardPhantomControllerDelegate {
|
abstract class BoardPhantomControllerDelegate {
|
||||||
BoardColumnDataController? controller(String columnId);
|
AFBoardColumnDataController? controller(String columnId);
|
||||||
|
|
||||||
bool removePhantom(String columnId);
|
bool removePhantom(String columnId);
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ impl FolderTest {
|
|||||||
// assert_eq!(json, expected_json);
|
// assert_eq!(json, expected_json);
|
||||||
// }
|
// }
|
||||||
FolderScript::AssertWorkspace(workspace) => {
|
FolderScript::AssertWorkspace(workspace) => {
|
||||||
assert_eq!(self.workspace, workspace);
|
assert_eq!(self.workspace, workspace, "Workspace not equal");
|
||||||
}
|
}
|
||||||
FolderScript::ReadWorkspace(workspace_id) => {
|
FolderScript::ReadWorkspace(workspace_id) => {
|
||||||
let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap();
|
let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap();
|
||||||
@ -166,7 +166,7 @@ impl FolderTest {
|
|||||||
// assert_eq!(json, expected_json);
|
// assert_eq!(json, expected_json);
|
||||||
// }
|
// }
|
||||||
FolderScript::AssertApp(app) => {
|
FolderScript::AssertApp(app) => {
|
||||||
assert_eq!(self.app, app);
|
assert_eq!(self.app, app, "App not equal");
|
||||||
}
|
}
|
||||||
FolderScript::ReadApp(app_id) => {
|
FolderScript::ReadApp(app_id) => {
|
||||||
let app = read_app(sdk, &app_id).await;
|
let app = read_app(sdk, &app_id).await;
|
||||||
@ -184,7 +184,7 @@ impl FolderTest {
|
|||||||
self.view = view;
|
self.view = view;
|
||||||
}
|
}
|
||||||
FolderScript::AssertView(view) => {
|
FolderScript::AssertView(view) => {
|
||||||
assert_eq!(self.view, view);
|
assert_eq!(self.view, view, "View not equal");
|
||||||
}
|
}
|
||||||
FolderScript::ReadView(view_id) => {
|
FolderScript::ReadView(view_id) => {
|
||||||
let view = read_view(sdk, &view_id).await;
|
let view = read_view(sdk, &view_id).await;
|
||||||
@ -215,7 +215,7 @@ impl FolderTest {
|
|||||||
}
|
}
|
||||||
FolderScript::AssertRevisionState { rev_id, state } => {
|
FolderScript::AssertRevisionState { rev_id, state } => {
|
||||||
let record = cache.get(rev_id).await.unwrap();
|
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 {
|
if let RevisionState::Ack = state {
|
||||||
// There is a defer action that writes the revisions to disk, so we wait here.
|
// There is a defer action that writes the revisions to disk, so we wait here.
|
||||||
// Make sure everything is written.
|
// 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()));
|
.unwrap_or_else(|| panic!("Expected Next revision is {}, but receive None", rev_id.unwrap()));
|
||||||
let mut notify = rev_manager.ack_notify();
|
let mut notify = rev_manager.ack_notify();
|
||||||
let _ = notify.recv().await;
|
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_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
|
||||||
impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB);
|
impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB);
|
||||||
impl MultiSelectTypeOptionBuilder {
|
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.0.options.push(opt);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -127,9 +127,9 @@ mod tests {
|
|||||||
let facebook_option = SelectOptionPB::new("Facebook");
|
let facebook_option = SelectOptionPB::new("Facebook");
|
||||||
let twitter_option = SelectOptionPB::new("Twitter");
|
let twitter_option = SelectOptionPB::new("Twitter");
|
||||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||||
.option(google_option.clone())
|
.add_option(google_option.clone())
|
||||||
.option(facebook_option.clone())
|
.add_option(facebook_option.clone())
|
||||||
.option(twitter_option);
|
.add_option(twitter_option);
|
||||||
|
|
||||||
let field_rev = FieldBuilder::new(multi_select)
|
let field_rev = FieldBuilder::new(multi_select)
|
||||||
.name("Platform")
|
.name("Platform")
|
||||||
|
@ -156,8 +156,8 @@ mod tests {
|
|||||||
let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
|
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 cell_data_changeset = SelectOptionCellChangeset::from_insert(&ids).to_str();
|
||||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||||
.option(google_option.clone())
|
.add_option(google_option.clone())
|
||||||
.option(facebook_option.clone());
|
.add_option(facebook_option.clone());
|
||||||
let multi_select_field_rev = FieldBuilder::new(multi_select).build();
|
let multi_select_field_rev = FieldBuilder::new(multi_select).build();
|
||||||
let multi_type_option = MultiSelectTypeOptionPB::from(&multi_select_field_rev);
|
let multi_type_option = MultiSelectTypeOptionPB::from(&multi_select_field_rev);
|
||||||
let cell_data = multi_type_option
|
let cell_data = multi_type_option
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::services::cell::apply_cell_data_changeset;
|
use crate::services::cell::apply_cell_data_changeset;
|
||||||
use crate::services::field::SelectOptionCellChangeset;
|
use crate::services::field::{DateCellChangesetPB, SelectOptionCellChangeset};
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
|
||||||
use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
|
use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use std::collections::HashMap;
|
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()) {
|
match self.field_rev_map.get(&field_id.to_owned()) {
|
||||||
None => {
|
None => {
|
||||||
let msg = format!("Can't find the field with id: {}", field_id);
|
tracing::warn!("Can't find the field with id: {}", field_id);
|
||||||
Err(FlowyError::internal().context(msg))
|
|
||||||
}
|
}
|
||||||
Some(field_rev) => {
|
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);
|
let cell = CellRevision::new(data);
|
||||||
self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);
|
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 crate::services::row::RowRevisionBuilder;
|
||||||
use flowy_grid_data_model::revision::BuildGridContext;
|
use flowy_grid_data_model::revision::BuildGridContext;
|
||||||
use flowy_sync::client_grid::GridBuilder;
|
use flowy_sync::client_grid::GridBuilder;
|
||||||
|
use lib_infra::util::timestamp;
|
||||||
|
|
||||||
pub fn make_default_grid() -> BuildGridContext {
|
pub fn make_default_grid() -> BuildGridContext {
|
||||||
let mut grid_builder = GridBuilder::new();
|
let mut grid_builder = GridBuilder::new();
|
||||||
@ -40,24 +41,94 @@ pub fn make_default_board() -> BuildGridContext {
|
|||||||
.visibility(true)
|
.visibility(true)
|
||||||
.primary(true)
|
.primary(true)
|
||||||
.build();
|
.build();
|
||||||
|
let text_field_id = text_field.id.clone();
|
||||||
grid_builder.add_field(text_field);
|
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
|
// single select
|
||||||
let in_progress_option = SelectOptionPB::new("In progress");
|
let in_progress_option = SelectOptionPB::new("In progress");
|
||||||
let not_started_option = SelectOptionPB::new("Not started");
|
let not_started_option = SelectOptionPB::new("Not started");
|
||||||
let done_option = SelectOptionPB::new("Done");
|
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(not_started_option.clone())
|
||||||
.add_option(in_progress_option)
|
.add_option(in_progress_option)
|
||||||
.add_option(done_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();
|
let single_select_field_id = single_select_field.id.clone();
|
||||||
grid_builder.add_field(single_select_field);
|
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
|
// 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());
|
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());
|
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();
|
let row = row_builder.build();
|
||||||
grid_builder.add_row(row);
|
grid_builder.add_row(row);
|
||||||
}
|
}
|
||||||
|
@ -26,18 +26,14 @@ impl<'a> GridRowTestBuilder<'a> {
|
|||||||
|
|
||||||
pub fn insert_text_cell(&mut self, data: &str) -> String {
|
pub fn insert_text_cell(&mut self, data: &str) -> String {
|
||||||
let text_field = self.field_rev_with_type(&FieldType::RichText);
|
let text_field = self.field_rev_with_type(&FieldType::RichText);
|
||||||
self.inner_builder
|
self.inner_builder.insert_cell(&text_field.id, data.to_string());
|
||||||
.insert_cell(&text_field.id, data.to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
text_field.id.clone()
|
text_field.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_number_cell(&mut self, data: &str) -> String {
|
pub fn insert_number_cell(&mut self, data: &str) -> String {
|
||||||
let number_field = self.field_rev_with_type(&FieldType::Number);
|
let number_field = self.field_rev_with_type(&FieldType::Number);
|
||||||
self.inner_builder
|
self.inner_builder.insert_cell(&number_field.id, data.to_string());
|
||||||
.insert_cell(&number_field.id, data.to_string())
|
|
||||||
.unwrap();
|
|
||||||
number_field.id.clone()
|
number_field.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,22 +44,20 @@ impl<'a> GridRowTestBuilder<'a> {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let date_field = self.field_rev_with_type(&FieldType::DateTime);
|
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()
|
date_field.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_checkbox_cell(&mut self, data: &str) -> String {
|
pub fn insert_checkbox_cell(&mut self, data: &str) -> String {
|
||||||
let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox);
|
let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox);
|
||||||
self.inner_builder
|
self.inner_builder.insert_cell(&checkbox_field.id, data.to_string());
|
||||||
.insert_cell(&checkbox_field.id, data.to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
checkbox_field.id.clone()
|
checkbox_field.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_url_cell(&mut self, data: &str) -> String {
|
pub fn insert_url_cell(&mut self, data: &str) -> String {
|
||||||
let url_field = self.field_rev_with_type(&FieldType::URL);
|
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()
|
url_field.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,9 +147,9 @@ fn make_test_grid() -> BuildGridContext {
|
|||||||
FieldType::MultiSelect => {
|
FieldType::MultiSelect => {
|
||||||
// MultiSelect
|
// MultiSelect
|
||||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||||
.option(SelectOptionPB::new(GOOGLE))
|
.add_option(SelectOptionPB::new(GOOGLE))
|
||||||
.option(SelectOptionPB::new(FACEBOOK))
|
.add_option(SelectOptionPB::new(FACEBOOK))
|
||||||
.option(SelectOptionPB::new(TWITTER));
|
.add_option(SelectOptionPB::new(TWITTER));
|
||||||
let multi_select_field = FieldBuilder::new(multi_select)
|
let multi_select_field = FieldBuilder::new(multi_select)
|
||||||
.name("Platform")
|
.name("Platform")
|
||||||
.visibility(true)
|
.visibility(true)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user