mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-11-10 10:18:57 +03:00
Merge pull request #820 from AppFlowy-IO/feat/appflowy_board
Feat/appflowy board
This commit is contained in:
commit
435d1b8354
2
frontend/.vscode/launch.json
vendored
2
frontend/.vscode/launch.json
vendored
@ -44,7 +44,7 @@
|
||||
"type": "dart",
|
||||
"preLaunchTask": "AF: Clean + Rebuild All",
|
||||
"env": {
|
||||
"RUST_LOG": "info"
|
||||
"RUST_LOG": "trace"
|
||||
},
|
||||
"cwd": "${workspaceRoot}/app_flowy"
|
||||
},
|
||||
|
@ -0,0 +1,195 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'board_data_controller.dart';
|
||||
|
||||
part 'board_bloc.freezed.dart';
|
||||
|
||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final BoardDataController _dataController;
|
||||
late final AFBoardDataController boardDataController;
|
||||
|
||||
BoardBloc({required ViewPB view})
|
||||
: _dataController = BoardDataController(view: view),
|
||||
super(BoardState.initial(view.id)) {
|
||||
boardDataController = AFBoardDataController(
|
||||
onMoveColumn: (
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {},
|
||||
onMoveColumnItem: (
|
||||
columnId,
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {},
|
||||
onMoveColumnItemToColumn: (
|
||||
fromColumnId,
|
||||
fromIndex,
|
||||
toColumnId,
|
||||
toIndex,
|
||||
) {},
|
||||
);
|
||||
|
||||
on<BoardEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
await _loadGrid(emit);
|
||||
},
|
||||
createRow: () {
|
||||
_dataController.createRow();
|
||||
},
|
||||
didReceiveGridUpdate: (GridPB grid) {
|
||||
emit(state.copyWith(grid: Some(grid)));
|
||||
},
|
||||
didReceiveGroups: (List<GroupPB> groups) {
|
||||
emit(state.copyWith(groups: groups));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _dataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
GridRowCache? getRowCache(String blockId, String rowId) {
|
||||
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
||||
return blockCache?.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_dataController.addListener(
|
||||
onGridChanged: (grid) {
|
||||
if (!isClosed) {
|
||||
add(BoardEvent.didReceiveGridUpdate(grid));
|
||||
}
|
||||
},
|
||||
onGroupChanged: (groups) {
|
||||
List<AFBoardColumnData> columns = groups.map((group) {
|
||||
return AFBoardColumnData(
|
||||
id: group.groupId,
|
||||
desc: group.desc,
|
||||
items: _buildRows(group.rows),
|
||||
customData: group,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
boardDataController.addColumns(columns);
|
||||
},
|
||||
onError: (err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<BoardColumnItem> _buildRows(List<RowPB> rows) {
|
||||
return rows.map((row) {
|
||||
final rowInfo = RowInfo(
|
||||
gridId: _dataController.gridId,
|
||||
blockId: row.blockId,
|
||||
id: row.id,
|
||||
fields: _dataController.fieldCache.unmodifiableFields,
|
||||
height: row.height.toDouble(),
|
||||
rawRow: row,
|
||||
);
|
||||
return BoardColumnItem(row: rowInfo);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||
final result = await _dataController.loadData();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
),
|
||||
(err) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.initial() = InitialGrid;
|
||||
const factory BoardEvent.createRow() = _CreateRow;
|
||||
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
||||
_DidReceiveGroup;
|
||||
const factory BoardEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
) = _DidReceiveGridUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardState with _$BoardState {
|
||||
const factory BoardState({
|
||||
required String gridId,
|
||||
required Option<GridPB> grid,
|
||||
required List<GroupPB> groups,
|
||||
required List<RowInfo> rowInfos,
|
||||
required GridLoadingState loadingState,
|
||||
}) = _BoardState;
|
||||
|
||||
factory BoardState.initial(String gridId) => BoardState(
|
||||
rowInfos: [],
|
||||
groups: [],
|
||||
grid: none(),
|
||||
gridId: gridId,
|
||||
loadingState: const _Loading(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridLoadingState with _$GridLoadingState {
|
||||
const factory GridLoadingState.loading() = _Loading;
|
||||
const factory GridLoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||
}
|
||||
|
||||
class GridFieldEquatable extends Equatable {
|
||||
final UnmodifiableListView<FieldPB> _fields;
|
||||
const GridFieldEquatable(
|
||||
UnmodifiableListView<FieldPB> fields,
|
||||
) : _fields = fields;
|
||||
|
||||
@override
|
||||
List<Object?> get props {
|
||||
if (_fields.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
_fields.length,
|
||||
_fields
|
||||
.map((field) => field.width)
|
||||
.reduce((value, element) => value + element),
|
||||
];
|
||||
}
|
||||
|
||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
||||
class BoardColumnItem extends AFColumnItem {
|
||||
final RowInfo row;
|
||||
|
||||
BoardColumnItem({required this.row});
|
||||
|
||||
@override
|
||||
String get id => row.id;
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||
typedef OnGridChanged = void Function(GridPB);
|
||||
typedef OnGroupChanged = void Function(List<GroupPB>);
|
||||
typedef OnError = void Function(FlowyError);
|
||||
|
||||
class BoardDataController {
|
||||
final String gridId;
|
||||
final GridService _gridFFIService;
|
||||
final GridFieldCache fieldCache;
|
||||
|
||||
// key: the block id
|
||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||
UnmodifiableMapView<String, GridBlockCache> get blocks =>
|
||||
UnmodifiableMapView(_blocks);
|
||||
|
||||
OnFieldsChanged? _onFieldsChanged;
|
||||
OnGridChanged? _onGridChanged;
|
||||
OnGroupChanged? _onGroupChanged;
|
||||
OnError? _onError;
|
||||
|
||||
BoardDataController({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.identity(),
|
||||
_gridFFIService = GridService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id);
|
||||
|
||||
void addListener({
|
||||
OnGridChanged? onGridChanged,
|
||||
OnFieldsChanged? onFieldsChanged,
|
||||
OnGroupChanged? onGroupChanged,
|
||||
OnError? onError,
|
||||
}) {
|
||||
_onGridChanged = onGridChanged;
|
||||
_onFieldsChanged = onFieldsChanged;
|
||||
_onGroupChanged = onGroupChanged;
|
||||
_onError = onError;
|
||||
|
||||
fieldCache.addListener(onFields: (fields) {
|
||||
_onFieldsChanged?.call(UnmodifiableListView(fields));
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadData() async {
|
||||
final result = await _gridFFIService.loadGrid();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(grid) async {
|
||||
_onGridChanged?.call(grid);
|
||||
return await _loadFields(grid).then((result) {
|
||||
return result.fold(
|
||||
(l) {
|
||||
_loadGroups();
|
||||
return left(l);
|
||||
},
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void createRow() {
|
||||
_gridFFIService.createRow();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _gridFFIService.closeGrid();
|
||||
await fieldCache.dispose();
|
||||
|
||||
for (final blockCache in _blocks.values) {
|
||||
blockCache.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
||||
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(fields) {
|
||||
fieldCache.fields = fields.items;
|
||||
_onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields));
|
||||
return left(unit);
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadGroups() async {
|
||||
final result = await _gridFFIService.loadGroups();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(groups) {
|
||||
_onGroupChanged?.call(groups.items);
|
||||
},
|
||||
(err) => _onError?.call(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import 'dart:async';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
|
||||
part 'board_select_option_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardSelectOptionCellBloc
|
||||
extends Bloc<BoardSelectOptionCellEvent, BoardSelectOptionCellState> {
|
||||
final GridSelectOptionCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
BoardSelectOptionCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardSelectOptionCellState.initial(cellController)) {
|
||||
on<BoardSelectOptionCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveOptions: (List<SelectOptionPB> selectedOptions) {
|
||||
emit(state.copyWith(selectedOptions: selectedOptions));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((selectOptionContext) {
|
||||
if (!isClosed) {
|
||||
add(BoardSelectOptionCellEvent.didReceiveOptions(
|
||||
selectOptionContext?.selectOptions ?? [],
|
||||
));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardSelectOptionCellEvent with _$BoardSelectOptionCellEvent {
|
||||
const factory BoardSelectOptionCellEvent.initial() = _InitialCell;
|
||||
const factory BoardSelectOptionCellEvent.didReceiveOptions(
|
||||
List<SelectOptionPB> selectedOptions,
|
||||
) = _DidReceiveOptions;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardSelectOptionCellState with _$BoardSelectOptionCellState {
|
||||
const factory BoardSelectOptionCellState({
|
||||
required List<SelectOptionPB> selectedOptions,
|
||||
}) = _BoardSelectOptionCellState;
|
||||
|
||||
factory BoardSelectOptionCellState.initial(
|
||||
GridSelectOptionCellController context) {
|
||||
final data = context.getCellData();
|
||||
|
||||
return BoardSelectOptionCellState(
|
||||
selectedOptions: data?.selectOptions ?? [],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
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_text_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
||||
final GridCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardTextCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardTextCellState.initial(cellController)) {
|
||||
on<BoardTextCellEvent>(
|
||||
(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(BoardTextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardTextCellEvent with _$BoardTextCellEvent {
|
||||
const factory BoardTextCellEvent.initial() = _InitialCell;
|
||||
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardTextCellState with _$BoardTextCellState {
|
||||
const factory BoardTextCellState({
|
||||
required String content,
|
||||
}) = _BoardTextCellState;
|
||||
|
||||
factory BoardTextCellState.initial(GridCellController context) =>
|
||||
BoardTextCellState(
|
||||
content: context.getCellData() ?? "",
|
||||
);
|
||||
}
|
20
frontend/app_flowy/lib/plugins/board/application/group.dart
Normal file
20
frontend/app_flowy/lib/plugins/board/application/group.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
class BoardGroupService {
|
||||
final String gridId;
|
||||
FieldPB? groupField;
|
||||
|
||||
BoardGroupService(this.gridId);
|
||||
|
||||
void setGroupField(FieldPB field) {
|
||||
groupField = field;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CanBeGroupField {
|
||||
String get groupContent;
|
||||
}
|
||||
|
||||
// class SingleSelectGroup extends CanBeGroupField {
|
||||
// final SingleSelectTypeOptionContext typeOptionContext;
|
||||
// }
|
@ -23,12 +23,15 @@ class BoardPluginBuilder implements PluginBuilder {
|
||||
PluginType get pluginType => DefaultPlugin.board.type();
|
||||
|
||||
@override
|
||||
ViewDataType get dataType => ViewDataType.Grid;
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
||||
|
||||
@override
|
||||
SubViewDataTypePB get subDataType => SubViewDataTypePB.Board;
|
||||
}
|
||||
|
||||
class BoardPluginConfig implements PluginConfig {
|
||||
@override
|
||||
bool get creatable => true;
|
||||
bool get creatable => false;
|
||||
}
|
||||
|
||||
class BoardPlugin extends Plugin {
|
||||
|
@ -1,154 +1,98 @@
|
||||
// ignore_for_file: unused_field
|
||||
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../application/board_bloc.dart';
|
||||
import 'card/card.dart';
|
||||
|
||||
class BoardPage extends StatefulWidget {
|
||||
final ViewPB _view;
|
||||
|
||||
const BoardPage({required ViewPB view, Key? key})
|
||||
: _view = view,
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardPage> createState() => _BoardPageState();
|
||||
}
|
||||
|
||||
class _BoardPageState extends State<BoardPage> {
|
||||
final BoardDataController boardDataController = BoardDataController(
|
||||
onMoveColumn: (fromIndex, toIndex) {
|
||||
debugPrint('Move column from $fromIndex to $toIndex');
|
||||
},
|
||||
onMoveColumnItem: (columnId, fromIndex, toIndex) {
|
||||
debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
|
||||
},
|
||||
onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
|
||||
debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final column1 = BoardColumnData(id: "To Do", items: [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 4"),
|
||||
]);
|
||||
final column2 = BoardColumnData(id: "In Progress", items: [
|
||||
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 6"),
|
||||
]);
|
||||
|
||||
final column3 = BoardColumnData(id: "Done", items: []);
|
||||
|
||||
boardDataController.addColumn(column1);
|
||||
boardDataController.addColumn(column2);
|
||||
boardDataController.addColumn(column3);
|
||||
super.initState();
|
||||
}
|
||||
class BoardPage extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
BoardPage({required this.view, Key? key}) : super(key: ValueKey(view.id));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final config = BoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Board(
|
||||
dataController: boardDataController,
|
||||
footBuilder: (context, columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
headerBuilder: (context, columnData) {
|
||||
return AppFlowyColumnHeader(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.id),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
cardBuilder: (context, item) {
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
child: _buildCard(item),
|
||||
);
|
||||
},
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
config: BoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
),
|
||||
),
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
BoardBloc(view: view)..add(const BoardEvent.initial()),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
return state.loadingState.map(
|
||||
loading: (_) =>
|
||||
const Center(child: CircularProgressIndicator.adaptive()),
|
||||
finish: (result) {
|
||||
return result.successOrFail.fold(
|
||||
(_) => BoardContent(),
|
||||
(err) => FlowyErrorPage(err.toString()),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCard(ColumnItem item) {
|
||||
if (item is TextItem) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(item.s),
|
||||
),
|
||||
);
|
||||
}
|
||||
class BoardContent extends StatelessWidget {
|
||||
final config = AFBoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
|
||||
if (item is RichTextItem) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
textAlign: TextAlign.left,
|
||||
BoardContent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: AFBoard(
|
||||
dataController: context.read<BoardBloc>().boardDataController,
|
||||
headerBuilder: _buildHeader,
|
||||
footBuilder: _buildFooter,
|
||||
cardBuilder: _buildCard,
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
config: AFBoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
item.subtitle,
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
throw UnimplementedError();
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextItem extends ColumnItem {
|
||||
final String s;
|
||||
Widget _buildHeader(BuildContext context, AFBoardColumnData columnData) {
|
||||
return AppFlowyColumnHeader(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.desc),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
}
|
||||
|
||||
TextItem(this.s);
|
||||
Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get id => s;
|
||||
}
|
||||
|
||||
class RichTextItem extends ColumnItem {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
RichTextItem({required this.title, required this.subtitle});
|
||||
|
||||
@override
|
||||
String get id => title;
|
||||
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
||||
final rowInfo = (item as BoardColumnItem).row;
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
child: BoardCard(rowInfo: rowInfo),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension HexColor on Color {
|
||||
|
@ -0,0 +1,51 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardSelectOptionCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardSelectOptionCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardSelectOptionCell> createState() => _BoardSelectOptionCellState();
|
||||
}
|
||||
|
||||
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
late BoardSelectOptionCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
|
||||
..add(const BoardSelectOptionCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||
builder: (context, state) {
|
||||
return SelectOptionWrap(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellControllerBuilder: widget.cellControllerBuilder,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_text_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 BoardTextCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
const BoardTextCell({required this.cellControllerBuilder, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardTextCell> createState() => _BoardTextCellState();
|
||||
}
|
||||
|
||||
class _BoardTextCellState extends State<BoardTextCell> {
|
||||
late BoardTextCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCellController;
|
||||
|
||||
_cellBloc = BoardTextCellBloc(cellController: cellController)
|
||||
..add(const BoardTextCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: FlowyText.medium(state.content),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BoardCard extends StatelessWidget {
|
||||
final RowInfo rowInfo;
|
||||
|
||||
const BoardCard({required this.rowInfo, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox(height: 20, child: Text('1234'));
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
library docuemnt_plugin;
|
||||
library document_plugin;
|
||||
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/startup/plugin/plugin.dart';
|
||||
@ -42,10 +42,10 @@ class DocumentPluginBuilder extends PluginBuilder {
|
||||
String get menuName => LocaleKeys.document_menuName.tr();
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.quill.type();
|
||||
PluginType get pluginType => DefaultPlugin.editor.type();
|
||||
|
||||
@override
|
||||
ViewDataType get dataType => ViewDataType.TextBlock;
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.TextBlock;
|
||||
}
|
||||
|
||||
class DocumentPlugin implements Plugin {
|
||||
|
@ -1,19 +1,19 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
|
||||
import '../field/field_cache.dart';
|
||||
import '../row/row_cache.dart';
|
||||
import 'block_listener.dart';
|
||||
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information
|
||||
class GridBlockCache {
|
||||
final String gridId;
|
||||
final GridBlockPB block;
|
||||
final BlockPB block;
|
||||
late GridRowCache _rowCache;
|
||||
late GridBlockListener _listener;
|
||||
|
||||
List<GridRowInfo> get rows => _rowCache.rows;
|
||||
List<RowInfo> get rows => _rowCache.rows;
|
||||
GridRowCache get rowCache => _rowCache;
|
||||
|
||||
GridBlockCache({
|
||||
@ -24,7 +24,7 @@ class GridBlockCache {
|
||||
_rowCache = GridRowCache(
|
||||
gridId: gridId,
|
||||
block: block,
|
||||
notifier: GridRowCacheFieldNotifierImpl(fieldCache),
|
||||
notifier: GridRowFieldNotifierImpl(fieldCache),
|
||||
);
|
||||
|
||||
_listener = GridBlockListener(blockId: block.id);
|
||||
@ -42,7 +42,7 @@ class GridBlockCache {
|
||||
}
|
||||
|
||||
void addListener({
|
||||
required void Function(GridRowChangeReason) onChangeReason,
|
||||
required void Function(RowChangeReason) onChangeReason,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
_rowCache.onRowsChanged((reason) {
|
||||
|
@ -3,20 +3,22 @@ import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'cell_service.dart';
|
||||
|
||||
abstract class IGridFieldChangedNotifier {
|
||||
void onFieldChanged(void Function(GridFieldPB) callback);
|
||||
void dispose();
|
||||
abstract class IGridCellFieldNotifier {
|
||||
void onCellFieldChanged(void Function(FieldPB) callback);
|
||||
void onCellDispose();
|
||||
}
|
||||
|
||||
/// GridPB's cell helper wrapper that enables each cell will get notified when the corresponding field was changed.
|
||||
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
|
||||
class GridCellFieldNotifier {
|
||||
final IGridCellFieldNotifier notifier;
|
||||
|
||||
/// fieldId: {objectId: callback}
|
||||
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId =
|
||||
{};
|
||||
|
||||
GridCellFieldNotifier({required IGridFieldChangedNotifier notifier}) {
|
||||
notifier.onFieldChanged(
|
||||
GridCellFieldNotifier({required this.notifier}) {
|
||||
notifier.onCellFieldChanged(
|
||||
(field) {
|
||||
final map = _fieldListenerByFieldId[field.id];
|
||||
if (map != null) {
|
||||
@ -56,6 +58,7 @@ class GridCellFieldNotifier {
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
notifier.onCellDispose();
|
||||
_fieldListenerByFieldId.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
@ -18,7 +16,8 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_listener.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'dart:convert' show utf8;
|
||||
|
||||
import '../../field/type_option/type_option_service.dart';
|
||||
import '../../field/field_cache.dart';
|
||||
import '../../field/type_option/type_option_context.dart';
|
||||
import 'cell_field_notifier.dart';
|
||||
part 'cell_service.freezed.dart';
|
||||
part 'cell_data_loader.dart';
|
||||
@ -61,7 +60,7 @@ class GridCellIdentifier with _$GridCellIdentifier {
|
||||
const factory GridCellIdentifier({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _GridCellIdentifier;
|
||||
|
||||
// ignore: unused_element
|
||||
|
@ -7,23 +7,24 @@ typedef GridDateCellController
|
||||
= IGridCellController<DateCellDataPB, CalendarData>;
|
||||
typedef GridURLCellController = IGridCellController<URLCellDataPB, String>;
|
||||
|
||||
abstract class GridCellControllerBuilderDelegate {
|
||||
GridCellFieldNotifier buildFieldNotifier();
|
||||
}
|
||||
|
||||
class GridCellControllerBuilder {
|
||||
final GridCellIdentifier _cellId;
|
||||
final GridCellCache _cellCache;
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridCellControllerBuilderDelegate delegate;
|
||||
|
||||
GridCellControllerBuilder({
|
||||
required this.delegate,
|
||||
required GridCellIdentifier cellId,
|
||||
required GridCellCache cellCache,
|
||||
required GridFieldCache fieldCache,
|
||||
}) : _cellCache = cellCache,
|
||||
_fieldCache = fieldCache,
|
||||
_cellId = cellId;
|
||||
|
||||
IGridCellController build() {
|
||||
final cellFieldNotifier = GridCellFieldNotifier(
|
||||
notifier: _GridFieldChangedNotifierImpl(_fieldCache));
|
||||
|
||||
final cellFieldNotifier = delegate.buildFieldNotifier();
|
||||
switch (_cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
final cellDataLoader = GridCellDataLoader(
|
||||
@ -165,7 +166,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
|
||||
String get fieldId => cellId.field.id;
|
||||
|
||||
GridFieldPB get field => cellId.field;
|
||||
FieldPB get field => cellId.field;
|
||||
|
||||
FieldType get fieldType => cellId.field.fieldType;
|
||||
|
||||
@ -295,6 +296,7 @@ class IGridCellController<T, D> extends Equatable {
|
||||
|
||||
if (_onFieldChangedFn != null) {
|
||||
_fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
|
||||
_fieldNotifier.dispose();
|
||||
_onFieldChangedFn = null;
|
||||
}
|
||||
}
|
||||
@ -304,14 +306,14 @@ class IGridCellController<T, D> extends Equatable {
|
||||
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
|
||||
}
|
||||
|
||||
class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier {
|
||||
class GridCellFieldNotifierImpl extends IGridCellFieldNotifier {
|
||||
final GridFieldCache _cache;
|
||||
FieldChangesetCallback? _onChangesetFn;
|
||||
|
||||
_GridFieldChangedNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
GridCellFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
void onCellDispose() {
|
||||
if (_onChangesetFn != null) {
|
||||
_cache.removeListener(onChangesetListener: _onChangesetFn!);
|
||||
_onChangesetFn = null;
|
||||
@ -319,8 +321,8 @@ class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier {
|
||||
}
|
||||
|
||||
@override
|
||||
void onFieldChanged(void Function(GridFieldPB p1) callback) {
|
||||
_onChangesetFn = (GridFieldChangesetPB changeset) {
|
||||
void onCellFieldChanged(void Function(FieldPB p1) callback) {
|
||||
_onChangesetFn = (FieldChangesetPB changeset) {
|
||||
for (final updatedField in changeset.updatedFields) {
|
||||
callback(updatedField);
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ import 'cell_service/cell_service.dart';
|
||||
part 'checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
final GridCellController cellContext;
|
||||
final GridCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
CheckboxCellBloc({
|
||||
required CellService service,
|
||||
required this.cellContext,
|
||||
}) : super(CheckboxCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(CheckboxCellState.initial(cellController)) {
|
||||
on<CheckboxCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -33,16 +33,17 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(onCellChanged: ((cellData) {
|
||||
_onCellChangedFn =
|
||||
cellController.startListening(onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(CheckboxCellEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
@ -50,7 +51,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
}
|
||||
|
||||
void _updateCellData() {
|
||||
cellContext.saveCellData(!state.isSelected ? "Yes" : "No");
|
||||
cellController.saveCellData(!state.isSelected ? "Yes" : "No");
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +59,8 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
class CheckboxCellEvent with _$CheckboxCellEvent {
|
||||
const factory CheckboxCellEvent.initial() = _Initial;
|
||||
const factory CheckboxCellEvent.select() = _Selected;
|
||||
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate;
|
||||
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -18,14 +18,14 @@ import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
part 'date_cal_bloc.freezed.dart';
|
||||
|
||||
class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
final GridDateCellController cellContext;
|
||||
final GridDateCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
DateCalBloc({
|
||||
required DateTypeOption dateTypeOption,
|
||||
required DateTypeOptionPB dateTypeOptionPB,
|
||||
required DateCellDataPB? cellData,
|
||||
required this.cellContext,
|
||||
}) : super(DateCalState.initial(dateTypeOption, cellData)) {
|
||||
required this.cellController,
|
||||
}) : super(DateCalState.initial(dateTypeOptionPB, cellData)) {
|
||||
on<DateCalEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -102,7 +102,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
}
|
||||
}
|
||||
|
||||
cellContext.saveCellData(newCalData, resultCallback: (result) {
|
||||
cellController.saveCellData(newCalData, resultCallback: (result) {
|
||||
result.fold(
|
||||
() => updateCalData(Some(newCalData), none()),
|
||||
(err) {
|
||||
@ -120,7 +120,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
|
||||
String timeFormatPrompt(FlowyError error) {
|
||||
String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
|
||||
switch (state.dateTypeOption.timeFormat) {
|
||||
switch (state.dateTypeOptionPB.timeFormat) {
|
||||
case TimeFormat.TwelveHour:
|
||||
msg = msg + "e.g. 01: 00 AM";
|
||||
break;
|
||||
@ -136,15 +136,15 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cell) {
|
||||
if (!isClosed) {
|
||||
add(DateCalEvent.didReceiveCellUpdate(cell));
|
||||
@ -159,8 +159,8 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
TimeFormat? timeFormat,
|
||||
bool? includeTime,
|
||||
}) async {
|
||||
state.dateTypeOption.freeze();
|
||||
final newDateTypeOption = state.dateTypeOption.rebuild((typeOption) {
|
||||
state.dateTypeOptionPB.freeze();
|
||||
final newDateTypeOption = state.dateTypeOptionPB.rebuild((typeOption) {
|
||||
if (dateFormat != null) {
|
||||
typeOption.dateFormat = dateFormat;
|
||||
}
|
||||
@ -175,14 +175,14 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
});
|
||||
|
||||
final result = await FieldService.updateFieldTypeOption(
|
||||
gridId: cellContext.gridId,
|
||||
fieldId: cellContext.field.id,
|
||||
gridId: cellController.gridId,
|
||||
fieldId: cellController.field.id,
|
||||
typeOptionData: newDateTypeOption.writeToBuffer(),
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(l) => emit(state.copyWith(
|
||||
dateTypeOption: newDateTypeOption,
|
||||
dateTypeOptionPB: newDateTypeOption,
|
||||
timeHintText: _timeHintText(newDateTypeOption))),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
@ -210,7 +210,7 @@ class DateCalEvent with _$DateCalEvent {
|
||||
@freezed
|
||||
class DateCalState with _$DateCalState {
|
||||
const factory DateCalState({
|
||||
required DateTypeOption dateTypeOption,
|
||||
required DateTypeOptionPB dateTypeOptionPB,
|
||||
required CalendarFormat format,
|
||||
required DateTime focusedDay,
|
||||
required Option<String> timeFormatError,
|
||||
@ -220,24 +220,24 @@ class DateCalState with _$DateCalState {
|
||||
}) = _DateCalState;
|
||||
|
||||
factory DateCalState.initial(
|
||||
DateTypeOption dateTypeOption,
|
||||
DateTypeOptionPB dateTypeOptionPB,
|
||||
DateCellDataPB? cellData,
|
||||
) {
|
||||
Option<CalendarData> calData = calDataFromCellData(cellData);
|
||||
final time = calData.foldRight("", (dateData, previous) => dateData.time);
|
||||
return DateCalState(
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
format: CalendarFormat.month,
|
||||
focusedDay: DateTime.now(),
|
||||
time: time,
|
||||
calData: calData,
|
||||
timeFormatError: none(),
|
||||
timeHintText: _timeHintText(dateTypeOption),
|
||||
timeHintText: _timeHintText(dateTypeOptionPB),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _timeHintText(DateTypeOption typeOption) {
|
||||
String _timeHintText(DateTypeOptionPB typeOption) {
|
||||
switch (typeOption.timeFormat) {
|
||||
case TimeFormat.TwelveHour:
|
||||
return LocaleKeys.document_date_timeHintTextInTwelveHour.tr();
|
||||
|
@ -7,18 +7,21 @@ import 'cell_service/cell_service.dart';
|
||||
part 'date_cell_bloc.freezed.dart';
|
||||
|
||||
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
final GridDateCellController cellContext;
|
||||
final GridDateCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
|
||||
DateCellBloc({required this.cellController})
|
||||
: super(DateCellState.initial(cellController)) {
|
||||
on<DateCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () => _startListening(),
|
||||
didReceiveCellUpdate: (DateCellDataPB? cellData) {
|
||||
emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||
emit(state.copyWith(
|
||||
data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||
},
|
||||
didReceiveFieldUpdate: (GridFieldPB value) => emit(state.copyWith(field: value)),
|
||||
didReceiveFieldUpdate: (FieldPB value) =>
|
||||
emit(state.copyWith(field: value)),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -27,15 +30,15 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((data) {
|
||||
if (!isClosed) {
|
||||
add(DateCellEvent.didReceiveCellUpdate(data));
|
||||
@ -48,8 +51,10 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
@freezed
|
||||
class DateCellEvent with _$DateCellEvent {
|
||||
const factory DateCellEvent.initial() = _InitialCell;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveFieldUpdate(GridFieldPB field) = _DidReceiveFieldUpdate;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveFieldUpdate(FieldPB field) =
|
||||
_DidReceiveFieldUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -57,7 +62,7 @@ class DateCellState with _$DateCellState {
|
||||
const factory DateCellState({
|
||||
required DateCellDataPB? data,
|
||||
required String dateStr,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _DateCellState;
|
||||
|
||||
factory DateCellState.initial(GridDateCellController context) {
|
||||
|
@ -8,12 +8,12 @@ import 'cell_service/cell_service.dart';
|
||||
part 'number_cell_bloc.freezed.dart';
|
||||
|
||||
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
final GridCellController cellContext;
|
||||
final GridCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
NumberCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(NumberCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(NumberCellState.initial(cellController)) {
|
||||
on<NumberCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
@ -24,11 +24,13 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
emit(state.copyWith(content: content));
|
||||
},
|
||||
updateCell: (text) {
|
||||
cellContext.saveCellData(text, resultCallback: (result) {
|
||||
cellController.saveCellData(text, resultCallback: (result) {
|
||||
result.fold(
|
||||
() => null,
|
||||
(err) {
|
||||
if (!isClosed) add(NumberCellEvent.didReceiveCellUpdate(right(err)));
|
||||
if (!isClosed) {
|
||||
add(NumberCellEvent.didReceiveCellUpdate(right(err)));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -41,15 +43,15 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(NumberCellEvent.didReceiveCellUpdate(left(cellContent ?? "")));
|
||||
@ -63,7 +65,8 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
class NumberCellEvent with _$NumberCellEvent {
|
||||
const factory NumberCellEvent.initial() = _Initial;
|
||||
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
||||
const factory NumberCellEvent.didReceiveCellUpdate(Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
|
||||
const factory NumberCellEvent.didReceiveCellUpdate(
|
||||
Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -8,12 +8,12 @@ part 'select_option_cell_bloc.freezed.dart';
|
||||
|
||||
class SelectOptionCellBloc
|
||||
extends Bloc<SelectOptionCellEvent, SelectOptionCellState> {
|
||||
final GridSelectOptionCellController cellContext;
|
||||
final GridSelectOptionCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
SelectOptionCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(SelectOptionCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(SelectOptionCellState.initial(cellController)) {
|
||||
on<SelectOptionCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
@ -33,15 +33,15 @@ class SelectOptionCellBloc
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((selectOptionContext) {
|
||||
if (!isClosed) {
|
||||
add(SelectOptionCellEvent.didReceiveOptions(
|
||||
|
@ -15,7 +15,7 @@ class SelectOptionService {
|
||||
String get rowId => cellId.rowId;
|
||||
|
||||
Future<Either<Unit, FlowyError>> create({required String name}) {
|
||||
return TypeOptionService(gridId: gridId, fieldId: fieldId)
|
||||
return TypeOptionFFIService(gridId: gridId, fieldId: fieldId)
|
||||
.newOption(name: name)
|
||||
.then(
|
||||
(result) {
|
||||
|
@ -6,11 +6,11 @@ import 'cell_service/cell_service.dart';
|
||||
part 'text_cell_bloc.freezed.dart';
|
||||
|
||||
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
final GridCellController cellContext;
|
||||
final GridCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
TextCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(TextCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(TextCellState.initial(cellController)) {
|
||||
on<TextCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -18,7 +18,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (text) {
|
||||
cellContext.saveCellData(text);
|
||||
cellController.saveCellData(text);
|
||||
emit(state.copyWith(content: text));
|
||||
},
|
||||
didReceiveCellUpdate: (content) {
|
||||
@ -32,15 +32,15 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(TextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
@ -53,7 +53,8 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
@freezed
|
||||
class TextCellEvent with _$TextCellEvent {
|
||||
const factory TextCellEvent.initial() = _InitialCell;
|
||||
const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate;
|
||||
const factory TextCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory TextCellEvent.updateText(String text) = _UpdateText;
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,11 @@ import 'cell_service/cell_service.dart';
|
||||
part 'url_cell_bloc.freezed.dart';
|
||||
|
||||
class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
final GridURLCellController cellContext;
|
||||
final GridURLCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
URLCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(URLCellState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(URLCellState.initial(cellController)) {
|
||||
on<URLCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
@ -25,7 +25,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
));
|
||||
},
|
||||
updateURL: (String url) {
|
||||
cellContext.saveCellData(url, deduplicate: true);
|
||||
cellController.saveCellData(url, deduplicate: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -35,15 +35,15 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(URLCellEvent.didReceiveCellUpdate(cellData));
|
||||
@ -57,7 +57,8 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
class URLCellEvent with _$URLCellEvent {
|
||||
const factory URLCellEvent.initial() = _InitialCell;
|
||||
const factory URLCellEvent.updateURL(String url) = _UpdateURL;
|
||||
const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
|
||||
const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -7,11 +7,11 @@ import 'cell_service/cell_service.dart';
|
||||
part 'url_cell_editor_bloc.freezed.dart';
|
||||
|
||||
class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
final GridURLCellController cellContext;
|
||||
final GridURLCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
URLCellEditorBloc({
|
||||
required this.cellContext,
|
||||
}) : super(URLCellEditorState.initial(cellContext)) {
|
||||
required this.cellController,
|
||||
}) : super(URLCellEditorState.initial(cellController)) {
|
||||
on<URLCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
@ -19,7 +19,7 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (text) {
|
||||
cellContext.saveCellData(text, deduplicate: true);
|
||||
cellController.saveCellData(text, deduplicate: true);
|
||||
emit(state.copyWith(content: text));
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
@ -33,15 +33,15 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(URLCellEditorEvent.didReceiveCellUpdate(cellData));
|
||||
@ -54,7 +54,8 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
@freezed
|
||||
class URLCellEditorEvent with _$URLCellEditorEvent {
|
||||
const factory URLCellEditorEvent.initial() = _InitialCell;
|
||||
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
|
||||
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
const factory URLCellEditorEvent.updateText(String text) = _UpdateText;
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,13 @@ import 'field_service.dart';
|
||||
|
||||
part 'field_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
||||
class FieldActionSheetBloc
|
||||
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
||||
final FieldService fieldService;
|
||||
|
||||
FieldActionSheetBloc({required GridFieldPB field, required this.fieldService})
|
||||
: super(FieldActionSheetState.initial(FieldTypeOptionDataPB.create()..field_2 = field)) {
|
||||
FieldActionSheetBloc({required FieldPB field, required this.fieldService})
|
||||
: super(FieldActionSheetState.initial(
|
||||
FieldTypeOptionDataPB.create()..field_2 = field)) {
|
||||
on<FieldActionSheetEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
@ -57,7 +59,8 @@ class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetS
|
||||
|
||||
@freezed
|
||||
class FieldActionSheetEvent with _$FieldActionSheetEvent {
|
||||
const factory FieldActionSheetEvent.updateFieldName(String name) = _UpdateFieldName;
|
||||
const factory FieldActionSheetEvent.updateFieldName(String name) =
|
||||
_UpdateFieldName;
|
||||
const factory FieldActionSheetEvent.hideField() = _HideField;
|
||||
const factory FieldActionSheetEvent.duplicateField() = _DuplicateField;
|
||||
const factory FieldActionSheetEvent.deleteField() = _DeleteField;
|
||||
@ -72,7 +75,8 @@ class FieldActionSheetState with _$FieldActionSheetState {
|
||||
required String fieldName,
|
||||
}) = _FieldActionSheetState;
|
||||
|
||||
factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) => FieldActionSheetState(
|
||||
factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) =>
|
||||
FieldActionSheetState(
|
||||
fieldTypeOptionData: data,
|
||||
errorText: '',
|
||||
fieldName: data.field_2.name,
|
||||
|
@ -0,0 +1,192 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../row/row_cache.dart';
|
||||
|
||||
class FieldsNotifier extends ChangeNotifier {
|
||||
List<FieldPB> _fields = [];
|
||||
|
||||
set fields(List<FieldPB> fields) {
|
||||
_fields = fields;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<FieldPB> get fields => _fields;
|
||||
}
|
||||
|
||||
typedef FieldChangesetCallback = void Function(FieldChangesetPB);
|
||||
typedef FieldsCallback = void Function(List<FieldPB>);
|
||||
|
||||
class GridFieldCache {
|
||||
final String gridId;
|
||||
final GridFieldsListener _fieldListener;
|
||||
FieldsNotifier? _fieldNotifier = FieldsNotifier();
|
||||
final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
|
||||
final Map<FieldChangesetCallback, FieldChangesetCallback>
|
||||
_changesetCallbackMap = {};
|
||||
|
||||
GridFieldCache({required this.gridId})
|
||||
: _fieldListener = GridFieldsListener(gridId: gridId) {
|
||||
_fieldListener.start(onFieldsChanged: (result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
_deleteFields(changeset.deletedFields);
|
||||
_insertFields(changeset.insertedFields);
|
||||
_updateFields(changeset.updatedFields);
|
||||
for (final listener in _changesetCallbackMap.values) {
|
||||
listener(changeset);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _fieldListener.stop();
|
||||
_fieldNotifier?.dispose();
|
||||
_fieldNotifier = null;
|
||||
}
|
||||
|
||||
UnmodifiableListView<FieldPB> get unmodifiableFields =>
|
||||
UnmodifiableListView(_fieldNotifier?.fields ?? []);
|
||||
|
||||
List<FieldPB> get fields => [..._fieldNotifier?.fields ?? []];
|
||||
|
||||
set fields(List<FieldPB> fields) {
|
||||
_fieldNotifier?.fields = [...fields];
|
||||
}
|
||||
|
||||
void addListener({
|
||||
FieldsCallback? onFields,
|
||||
FieldChangesetCallback? onChangeset,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
if (onChangeset != null) {
|
||||
fn(c) {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onChangeset(c);
|
||||
}
|
||||
|
||||
_changesetCallbackMap[onChangeset] = fn;
|
||||
}
|
||||
|
||||
if (onFields != null) {
|
||||
fn() {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onFields(fields);
|
||||
}
|
||||
|
||||
_fieldsCallbackMap[onFields] = fn;
|
||||
_fieldNotifier?.addListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener({
|
||||
FieldsCallback? onFieldsListener,
|
||||
FieldChangesetCallback? onChangesetListener,
|
||||
}) {
|
||||
if (onFieldsListener != null) {
|
||||
final fn = _fieldsCallbackMap.remove(onFieldsListener);
|
||||
if (fn != null) {
|
||||
_fieldNotifier?.removeListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
if (onChangesetListener != null) {
|
||||
_changesetCallbackMap.remove(onChangesetListener);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteFields(List<FieldIdPB> deletedFields) {
|
||||
if (deletedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<FieldPB> newFields = fields;
|
||||
final Map<String, FieldIdPB> deletedFieldMap = {
|
||||
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
||||
};
|
||||
|
||||
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _insertFields(List<IndexFieldPB> insertedFields) {
|
||||
if (insertedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<FieldPB> newFields = fields;
|
||||
for (final indexField in insertedFields) {
|
||||
if (newFields.length > indexField.index) {
|
||||
newFields.insert(indexField.index, indexField.field_1);
|
||||
} else {
|
||||
newFields.add(indexField.field_1);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _updateFields(List<FieldPB> updatedFields) {
|
||||
if (updatedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<FieldPB> newFields = fields;
|
||||
for (final updatedField in updatedFields) {
|
||||
final index =
|
||||
newFields.indexWhere((field) => field.id == updatedField.id);
|
||||
if (index != -1) {
|
||||
newFields.removeAt(index);
|
||||
newFields.insert(index, updatedField);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
}
|
||||
|
||||
class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
|
||||
final GridFieldCache _cache;
|
||||
FieldChangesetCallback? _onChangesetFn;
|
||||
FieldsCallback? _onFieldFn;
|
||||
GridRowFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
|
||||
@override
|
||||
UnmodifiableListView<FieldPB> get fields => _cache.unmodifiableFields;
|
||||
|
||||
@override
|
||||
void onRowFieldsChanged(VoidCallback callback) {
|
||||
_onFieldFn = (_) => callback();
|
||||
_cache.addListener(onFields: _onFieldFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRowFieldChanged(void Function(FieldPB) callback) {
|
||||
_onChangesetFn = (FieldChangesetPB changeset) {
|
||||
for (final updatedField in changeset.updatedFields) {
|
||||
callback(updatedField);
|
||||
}
|
||||
};
|
||||
|
||||
_cache.addListener(onChangeset: _onChangesetFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRowDispose() {
|
||||
if (_onFieldFn != null) {
|
||||
_cache.removeListener(onFieldsListener: _onFieldFn!);
|
||||
_onFieldFn = null;
|
||||
}
|
||||
|
||||
if (_onChangesetFn != null) {
|
||||
_cache.removeListener(onChangesetListener: _onChangesetFn!);
|
||||
_onChangesetFn = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
|
||||
@freezed
|
||||
class FieldCellEvent with _$FieldCellEvent {
|
||||
const factory FieldCellEvent.initial() = _InitialCell;
|
||||
const factory FieldCellEvent.didReceiveFieldUpdate(GridFieldPB field) =
|
||||
const factory FieldCellEvent.didReceiveFieldUpdate(FieldPB field) =
|
||||
_DidReceiveFieldUpdate;
|
||||
const factory FieldCellEvent.startUpdateWidth(double offset) =
|
||||
_StartUpdateWidth;
|
||||
@ -74,7 +74,7 @@ class FieldCellEvent with _$FieldCellEvent {
|
||||
class FieldCellState with _$FieldCellState {
|
||||
const factory FieldCellState({
|
||||
required String gridId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
required double width,
|
||||
}) = _FieldCellState;
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
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 'field_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'type_option/type_option_context.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'type_option/type_option_data_controller.dart';
|
||||
|
||||
part 'field_editor_bloc.freezed.dart';
|
||||
|
||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
@ -13,7 +16,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
required String gridId,
|
||||
required String fieldName,
|
||||
required IFieldTypeOptionLoader loader,
|
||||
}) : dataController = TypeOptionDataController(gridId: gridId, loader: loader),
|
||||
}) : dataController =
|
||||
TypeOptionDataController(gridId: gridId, loader: loader),
|
||||
super(FieldEditorState.initial(gridId, fieldName)) {
|
||||
on<FieldEditorEvent>(
|
||||
(event, emit) async {
|
||||
@ -24,13 +28,13 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(field));
|
||||
}
|
||||
});
|
||||
await dataController.loadData();
|
||||
await dataController.loadTypeOptionData();
|
||||
},
|
||||
updateName: (name) {
|
||||
dataController.fieldName = name;
|
||||
emit(state.copyWith(name: name));
|
||||
},
|
||||
didReceiveFieldChanged: (GridFieldPB field) {
|
||||
didReceiveFieldChanged: (FieldPB field) {
|
||||
emit(state.copyWith(field: Some(field)));
|
||||
},
|
||||
);
|
||||
@ -48,7 +52,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
class FieldEditorEvent with _$FieldEditorEvent {
|
||||
const factory FieldEditorEvent.initial() = _InitialField;
|
||||
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(GridFieldPB field) = _DidReceiveFieldChanged;
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
|
||||
_DidReceiveFieldChanged;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -57,7 +62,7 @@ class FieldEditorState with _$FieldEditorState {
|
||||
required String gridId,
|
||||
required String errorText,
|
||||
required String name,
|
||||
required Option<GridFieldPB> field,
|
||||
required Option<FieldPB> field,
|
||||
}) = _FieldEditorState;
|
||||
|
||||
factory FieldEditorState.initial(
|
||||
|
@ -7,16 +7,18 @@ import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
typedef UpdateFieldNotifiedValue = Either<GridFieldPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<FieldPB, FlowyError>;
|
||||
|
||||
class SingleFieldListener {
|
||||
final String fieldId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? _updateFieldNotifier = PublishNotifier();
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? _updateFieldNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
|
||||
SingleFieldListener({required this.fieldId});
|
||||
|
||||
void start({required void Function(UpdateFieldNotifiedValue) onFieldChanged}) {
|
||||
void start(
|
||||
{required void Function(UpdateFieldNotifiedValue) onFieldChanged}) {
|
||||
_updateFieldNotifier?.addPublishListener(onFieldChanged);
|
||||
_listener = GridNotificationListener(
|
||||
objectId: fieldId,
|
||||
@ -31,7 +33,8 @@ class SingleFieldListener {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateField:
|
||||
result.fold(
|
||||
(payload) => _updateFieldNotifier?.value = left(GridFieldPB.fromBuffer(payload)),
|
||||
(payload) =>
|
||||
_updateFieldNotifier?.value = left(FieldPB.fromBuffer(payload)),
|
||||
(error) => _updateFieldNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -1,13 +1,10 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
part 'field_service.freezed.dart';
|
||||
|
||||
/// FieldService consists of lots of event functions. We define the events in the backend(Rust),
|
||||
@ -73,7 +70,7 @@ class FieldService {
|
||||
// Create the field if it does not exist. Otherwise, update the field.
|
||||
static Future<Either<Unit, FlowyError>> insertField({
|
||||
required String gridId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
List<int>? typeOptionData,
|
||||
String? startFieldId,
|
||||
}) {
|
||||
@ -121,7 +118,7 @@ class FieldService {
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> getFieldTypeOptionData({
|
||||
required FieldType fieldType,
|
||||
}) {
|
||||
final payload = GridFieldTypeOptionIdPB.create()
|
||||
final payload = FieldTypeOptionIdPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
@ -138,158 +135,6 @@ class FieldService {
|
||||
class GridFieldCellContext with _$GridFieldCellContext {
|
||||
const factory GridFieldCellContext({
|
||||
required String gridId,
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _GridFieldCellContext;
|
||||
}
|
||||
|
||||
abstract class IFieldTypeOptionLoader {
|
||||
String get gridId;
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
|
||||
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
|
||||
final payload = EditFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
|
||||
return GridEventSwitchToField(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
NewFieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = CreateFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldType = FieldType.RichText;
|
||||
|
||||
return GridEventCreateFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
final GridFieldPB field;
|
||||
|
||||
FieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = GridFieldTypeOptionIdPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = field.id
|
||||
..fieldType = field.fieldType;
|
||||
|
||||
return GridEventGetFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionDataController {
|
||||
final String gridId;
|
||||
final IFieldTypeOptionLoader _loader;
|
||||
|
||||
late FieldTypeOptionDataPB _data;
|
||||
final PublishNotifier<GridFieldPB> _fieldNotifier = PublishNotifier();
|
||||
|
||||
TypeOptionDataController({
|
||||
required this.gridId,
|
||||
required IFieldTypeOptionLoader loader,
|
||||
}) : _loader = loader;
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadData() async {
|
||||
final result = await _loader.load();
|
||||
return result.fold(
|
||||
(data) {
|
||||
data.freeze();
|
||||
_data = data;
|
||||
_fieldNotifier.value = data.field_2;
|
||||
return left(unit);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GridFieldPB get field => _data.field_2;
|
||||
|
||||
set field(GridFieldPB field) {
|
||||
_updateData(newField: field);
|
||||
}
|
||||
|
||||
List<int> get typeOptionData => _data.typeOptionData;
|
||||
|
||||
set fieldName(String name) {
|
||||
_updateData(newName: name);
|
||||
}
|
||||
|
||||
set typeOptionData(List<int> typeOptionData) {
|
||||
_updateData(newTypeOptionData: typeOptionData);
|
||||
}
|
||||
|
||||
void _updateData({String? newName, GridFieldPB? newField, List<int>? newTypeOptionData}) {
|
||||
_data = _data.rebuild((rebuildData) {
|
||||
if (newName != null) {
|
||||
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
|
||||
rebuildField.name = newName;
|
||||
});
|
||||
}
|
||||
|
||||
if (newField != null) {
|
||||
rebuildData.field_2 = newField;
|
||||
}
|
||||
|
||||
if (newTypeOptionData != null) {
|
||||
rebuildData.typeOptionData = newTypeOptionData;
|
||||
}
|
||||
});
|
||||
|
||||
_fieldNotifier.value = _data.field_2;
|
||||
|
||||
FieldService.insertField(
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
typeOptionData: typeOptionData,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> switchToField(FieldType newFieldType) {
|
||||
return _loader.switchToField(field.id, newFieldType).then((result) {
|
||||
return result.fold(
|
||||
(fieldTypeOptionData) {
|
||||
_updateData(
|
||||
newField: fieldTypeOptionData.field_2,
|
||||
newTypeOptionData: fieldTypeOptionData.typeOptionData,
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void Function() addFieldListener(void Function(GridFieldPB) callback) {
|
||||
listener() {
|
||||
callback(field);
|
||||
}
|
||||
|
||||
_fieldNotifier.addListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
void removeFieldListener(void Function() listener) {
|
||||
_fieldNotifier.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'field_service.dart';
|
||||
|
||||
import 'type_option/type_option_data_controller.dart';
|
||||
part 'field_type_option_edit_bloc.freezed.dart';
|
||||
|
||||
class FieldTypeOptionEditBloc extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
|
||||
class FieldTypeOptionEditBloc
|
||||
extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
|
||||
final TypeOptionDataController _dataController;
|
||||
void Function()? _fieldListenFn;
|
||||
|
||||
@ -42,16 +42,19 @@ class FieldTypeOptionEditBloc extends Bloc<FieldTypeOptionEditEvent, FieldTypeOp
|
||||
@freezed
|
||||
class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent {
|
||||
const factory FieldTypeOptionEditEvent.initial() = _Initial;
|
||||
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(GridFieldPB field) = _DidReceiveFieldUpdated;
|
||||
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(FieldPB field) =
|
||||
_DidReceiveFieldUpdated;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
|
||||
const factory FieldTypeOptionEditState({
|
||||
required GridFieldPB field,
|
||||
required FieldPB field,
|
||||
}) = _FieldTypeOptionEditState;
|
||||
|
||||
factory FieldTypeOptionEditState.initial(TypeOptionDataController fieldContext) => FieldTypeOptionEditState(
|
||||
factory FieldTypeOptionEditState.initial(
|
||||
TypeOptionDataController fieldContext) =>
|
||||
FieldTypeOptionEditState(
|
||||
field: fieldContext.field,
|
||||
);
|
||||
}
|
||||
|
@ -7,15 +7,17 @@ import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
typedef UpdateFieldNotifiedValue = Either<GridFieldChangesetPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<FieldChangesetPB, FlowyError>;
|
||||
|
||||
class GridFieldsListener {
|
||||
final String gridId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier = PublishNotifier();
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
GridFieldsListener({required this.gridId});
|
||||
|
||||
void start({required void Function(UpdateFieldNotifiedValue) onFieldsChanged}) {
|
||||
void start(
|
||||
{required void Function(UpdateFieldNotifiedValue) onFieldsChanged}) {
|
||||
updateFieldsNotifier?.addPublishListener(onFieldsChanged);
|
||||
_listener = GridNotificationListener(
|
||||
objectId: gridId,
|
||||
@ -27,7 +29,8 @@ class GridFieldsListener {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateGridField:
|
||||
result.fold(
|
||||
(payload) => updateFieldsNotifier?.value = left(GridFieldChangesetPB.fromBuffer(payload)),
|
||||
(payload) => updateFieldsNotifier?.value =
|
||||
left(FieldChangesetPB.fromBuffer(payload)),
|
||||
(error) => updateFieldsNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -1,21 +1,13 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'type_option_context.dart';
|
||||
part 'date_bloc.freezed.dart';
|
||||
|
||||
typedef DateTypeOptionContext = TypeOptionWidgetContext<DateTypeOption>;
|
||||
|
||||
class DateTypeOptionDataParser extends TypeOptionDataParser<DateTypeOption> {
|
||||
@override
|
||||
DateTypeOption fromBuffer(List<int> buffer) {
|
||||
return DateTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class DateTypeOptionBloc
|
||||
extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
|
||||
DateTypeOptionBloc({required DateTypeOptionContext typeOptionContext})
|
||||
@ -40,7 +32,7 @@ class DateTypeOptionBloc
|
||||
);
|
||||
}
|
||||
|
||||
DateTypeOption _updateTypeOption({
|
||||
DateTypeOptionPB _updateTypeOption({
|
||||
DateFormat? dateFormat,
|
||||
TimeFormat? timeFormat,
|
||||
bool? includeTime,
|
||||
@ -80,9 +72,9 @@ class DateTypeOptionEvent with _$DateTypeOptionEvent {
|
||||
@freezed
|
||||
class DateTypeOptionState with _$DateTypeOptionState {
|
||||
const factory DateTypeOptionState({
|
||||
required DateTypeOption typeOption,
|
||||
required DateTypeOptionPB typeOption,
|
||||
}) = _DateTypeOptionState;
|
||||
|
||||
factory DateTypeOptionState.initial(DateTypeOption typeOption) =>
|
||||
factory DateTypeOptionState.initial(DateTypeOptionPB typeOption) =>
|
||||
DateTypeOptionState(typeOption: typeOption);
|
||||
}
|
||||
|
@ -1,25 +1,32 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'select_option_type_option_bloc.dart';
|
||||
import 'type_option_context.dart';
|
||||
import 'type_option_service.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
class MultiSelectTypeOptionContext
|
||||
extends TypeOptionWidgetContext<MultiSelectTypeOption>
|
||||
with SelectOptionTypeOptionAction {
|
||||
final TypeOptionService service;
|
||||
class MultiSelectAction with ISelectOptionAction {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
final TypeOptionFFIService service;
|
||||
final MultiSelectTypeOptionContext typeOptionContext;
|
||||
|
||||
MultiSelectTypeOptionContext({
|
||||
required MultiSelectTypeOptionWidgetDataParser dataBuilder,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : service = TypeOptionService(
|
||||
gridId: dataController.gridId,
|
||||
fieldId: dataController.field.id,
|
||||
),
|
||||
super(dataParser: dataBuilder, dataController: dataController);
|
||||
MultiSelectAction({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
required this.typeOptionContext,
|
||||
}) : service = TypeOptionFFIService(
|
||||
gridId: gridId,
|
||||
fieldId: fieldId,
|
||||
);
|
||||
|
||||
MultiSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
|
||||
|
||||
set typeOption(MultiSelectTypeOptionPB newTypeOption) {
|
||||
typeOptionContext.typeOption = newTypeOption;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
@ -59,7 +66,7 @@ class MultiSelectTypeOptionContext
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
@ -73,11 +80,3 @@ class MultiSelectTypeOptionContext
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<MultiSelectTypeOption> {
|
||||
@override
|
||||
MultiSelectTypeOption fromBuffer(List<int> buffer) {
|
||||
return MultiSelectTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,13 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'type_option_context.dart';
|
||||
|
||||
part 'number_bloc.freezed.dart';
|
||||
|
||||
typedef NumberTypeOptionContext = TypeOptionWidgetContext<NumberTypeOption>;
|
||||
|
||||
class NumberTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<NumberTypeOption> {
|
||||
@override
|
||||
NumberTypeOption fromBuffer(List<int> buffer) {
|
||||
return NumberTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class NumberTypeOptionBloc
|
||||
extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
|
||||
NumberTypeOptionBloc({required NumberTypeOptionContext typeOptionContext})
|
||||
@ -33,7 +23,7 @@ class NumberTypeOptionBloc
|
||||
);
|
||||
}
|
||||
|
||||
NumberTypeOption _updateNumberFormat(NumberFormat format) {
|
||||
NumberTypeOptionPB _updateNumberFormat(NumberFormat format) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
typeOption.format = format;
|
||||
@ -55,10 +45,10 @@ class NumberTypeOptionEvent with _$NumberTypeOptionEvent {
|
||||
@freezed
|
||||
class NumberTypeOptionState with _$NumberTypeOptionState {
|
||||
const factory NumberTypeOptionState({
|
||||
required NumberTypeOption typeOption,
|
||||
required NumberTypeOptionPB typeOption,
|
||||
}) = _NumberTypeOptionState;
|
||||
|
||||
factory NumberTypeOptionState.initial(NumberTypeOption typeOption) =>
|
||||
factory NumberTypeOptionState.initial(NumberTypeOptionPB typeOption) =>
|
||||
NumberTypeOptionState(
|
||||
typeOption: typeOption,
|
||||
);
|
||||
|
@ -5,16 +5,17 @@ import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
part 'select_option_type_option_bloc.freezed.dart';
|
||||
|
||||
abstract class SelectOptionTypeOptionAction {
|
||||
abstract class ISelectOptionAction {
|
||||
Future<List<SelectOptionPB>> Function(String) get insertOption;
|
||||
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption;
|
||||
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption;
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption;
|
||||
}
|
||||
|
||||
class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
|
||||
final SelectOptionTypeOptionAction typeOptionAction;
|
||||
class SelectOptionTypeOptionBloc
|
||||
extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
|
||||
final ISelectOptionAction typeOptionAction;
|
||||
|
||||
SelectOptionTypeOptionBloc({
|
||||
required List<SelectOptionPB> options,
|
||||
@ -24,7 +25,8 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
createOption: (optionName) async {
|
||||
final List<SelectOptionPB> options = await typeOptionAction.insertOption(optionName);
|
||||
final List<SelectOptionPB> options =
|
||||
await typeOptionAction.insertOption(optionName);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
addingOption: () {
|
||||
@ -34,11 +36,13 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
|
||||
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
|
||||
},
|
||||
updateOption: (option) {
|
||||
final List<SelectOptionPB> options = typeOptionAction.udpateOption(option);
|
||||
final List<SelectOptionPB> options =
|
||||
typeOptionAction.updateOption(option);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
deleteOption: (option) {
|
||||
final List<SelectOptionPB> options = typeOptionAction.deleteOption(option);
|
||||
final List<SelectOptionPB> options =
|
||||
typeOptionAction.deleteOption(option);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
);
|
||||
@ -54,11 +58,15 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
|
||||
|
||||
@freezed
|
||||
class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent {
|
||||
const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption;
|
||||
const factory SelectOptionTypeOptionEvent.createOption(String optionName) =
|
||||
_CreateOption;
|
||||
const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption;
|
||||
const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption;
|
||||
const factory SelectOptionTypeOptionEvent.updateOption(SelectOptionPB option) = _UpdateOption;
|
||||
const factory SelectOptionTypeOptionEvent.deleteOption(SelectOptionPB option) = _DeleteOption;
|
||||
const factory SelectOptionTypeOptionEvent.endAddingOption() =
|
||||
_EndAddingOption;
|
||||
const factory SelectOptionTypeOptionEvent.updateOption(
|
||||
SelectOptionPB option) = _UpdateOption;
|
||||
const factory SelectOptionTypeOptionEvent.deleteOption(
|
||||
SelectOptionPB option) = _DeleteOption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -67,9 +75,10 @@ class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState {
|
||||
required List<SelectOptionPB> options,
|
||||
required bool isEditingOption,
|
||||
required Option<String> newOptionName,
|
||||
}) = _SelectOptionTyepOptionState;
|
||||
}) = _SelectOptionTypeOptionState;
|
||||
|
||||
factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) => SelectOptionTypeOptionState(
|
||||
factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) =>
|
||||
SelectOptionTypeOptionState(
|
||||
options: options,
|
||||
isEditingOption: false,
|
||||
newOptionName: none(),
|
||||
|
@ -1,25 +1,29 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'select_option_type_option_bloc.dart';
|
||||
import 'type_option_context.dart';
|
||||
import 'type_option_service.dart';
|
||||
|
||||
class SingleSelectTypeOptionContext
|
||||
extends TypeOptionWidgetContext<SingleSelectTypeOptionPB>
|
||||
with SelectOptionTypeOptionAction {
|
||||
final TypeOptionService service;
|
||||
class SingleSelectAction with ISelectOptionAction {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
final SingleSelectTypeOptionContext typeOptionContext;
|
||||
final TypeOptionFFIService service;
|
||||
|
||||
SingleSelectTypeOptionContext({
|
||||
required SingleSelectTypeOptionWidgetDataParser dataBuilder,
|
||||
required TypeOptionDataController fieldContext,
|
||||
}) : service = TypeOptionService(
|
||||
gridId: fieldContext.gridId,
|
||||
fieldId: fieldContext.field.id,
|
||||
),
|
||||
super(dataParser: dataBuilder, dataController: fieldContext);
|
||||
SingleSelectAction({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
required this.typeOptionContext,
|
||||
}) : service = TypeOptionFFIService(gridId: gridId, fieldId: fieldId);
|
||||
|
||||
SingleSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
|
||||
|
||||
set typeOption(SingleSelectTypeOptionPB newTypeOption) {
|
||||
typeOptionContext.typeOption = newTypeOption;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
@ -59,7 +63,7 @@ class SingleSelectTypeOptionContext
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
@ -73,11 +77,3 @@ class SingleSelectTypeOptionContext
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class SingleSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<SingleSelectTypeOptionPB> {
|
||||
@override
|
||||
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return SingleSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,195 @@
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'type_option_data_controller.dart';
|
||||
|
||||
abstract class TypeOptionDataParser<T> {
|
||||
T fromBuffer(List<int> buffer);
|
||||
}
|
||||
|
||||
// Number
|
||||
typedef NumberTypeOptionContext = TypeOptionContext<NumberTypeOptionPB>;
|
||||
|
||||
class NumberTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<NumberTypeOptionPB> {
|
||||
@override
|
||||
NumberTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return NumberTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// RichText
|
||||
typedef RichTextTypeOptionContext = TypeOptionContext<RichTextTypeOptionPB>;
|
||||
|
||||
class RichTextTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<RichTextTypeOptionPB> {
|
||||
@override
|
||||
RichTextTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return RichTextTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
typedef CheckboxTypeOptionContext = TypeOptionContext<CheckboxTypeOptionPB>;
|
||||
|
||||
class CheckboxTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<CheckboxTypeOptionPB> {
|
||||
@override
|
||||
CheckboxTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return CheckboxTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// URL
|
||||
typedef URLTypeOptionContext = TypeOptionContext<URLTypeOptionPB>;
|
||||
|
||||
class URLTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<URLTypeOptionPB> {
|
||||
@override
|
||||
URLTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return URLTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Date
|
||||
typedef DateTypeOptionContext = TypeOptionContext<DateTypeOptionPB>;
|
||||
|
||||
class DateTypeOptionDataParser extends TypeOptionDataParser<DateTypeOptionPB> {
|
||||
@override
|
||||
DateTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return DateTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// SingleSelect
|
||||
typedef SingleSelectTypeOptionContext
|
||||
= TypeOptionContext<SingleSelectTypeOptionPB>;
|
||||
|
||||
class SingleSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<SingleSelectTypeOptionPB> {
|
||||
@override
|
||||
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return SingleSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-select
|
||||
typedef MultiSelectTypeOptionContext
|
||||
= TypeOptionContext<MultiSelectTypeOptionPB>;
|
||||
|
||||
class MultiSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<MultiSelectTypeOptionPB> {
|
||||
@override
|
||||
MultiSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return MultiSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionContext<T extends GeneratedMessage> {
|
||||
T? _typeOptionObject;
|
||||
final TypeOptionDataParser<T> dataParser;
|
||||
final TypeOptionDataController _dataController;
|
||||
|
||||
TypeOptionContext({
|
||||
required this.dataParser,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : _dataController = dataController;
|
||||
|
||||
String get gridId => _dataController.gridId;
|
||||
|
||||
String get fieldId => _dataController.field.id;
|
||||
|
||||
Future<void> loadTypeOptionData({
|
||||
required void Function(T) onCompleted,
|
||||
required void Function(FlowyError) onError,
|
||||
}) async {
|
||||
await _dataController.loadTypeOptionData().then((result) {
|
||||
result.fold((l) => null, (err) => onError(err));
|
||||
});
|
||||
|
||||
onCompleted(typeOption);
|
||||
}
|
||||
|
||||
T get typeOption {
|
||||
if (_typeOptionObject != null) {
|
||||
return _typeOptionObject!;
|
||||
}
|
||||
|
||||
final T object = _dataController.getTypeOption(dataParser);
|
||||
_typeOptionObject = object;
|
||||
return object;
|
||||
}
|
||||
|
||||
set typeOption(T typeOption) {
|
||||
_dataController.typeOptionData = typeOption.writeToBuffer();
|
||||
_typeOptionObject = typeOption;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionFieldDelegate {
|
||||
void onFieldChanged(void Function(String) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
abstract class IFieldTypeOptionLoader {
|
||||
String get gridId;
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
|
||||
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(
|
||||
String fieldId, FieldType fieldType) {
|
||||
final payload = EditFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
|
||||
return GridEventSwitchToField(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
NewFieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = CreateFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldType = FieldType.RichText;
|
||||
|
||||
return GridEventCreateFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
|
||||
@override
|
||||
final String gridId;
|
||||
final FieldPB field;
|
||||
|
||||
FieldTypeOptionLoader({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
|
||||
final payload = FieldTypeOptionIdPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = field.id
|
||||
..fieldType = field.fieldType;
|
||||
|
||||
return GridEventGetFieldTypeOption(payload).send();
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
|
||||
import 'type_option_context.dart';
|
||||
|
||||
class TypeOptionDataController {
|
||||
final String gridId;
|
||||
final IFieldTypeOptionLoader loader;
|
||||
late FieldTypeOptionDataPB _data;
|
||||
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
|
||||
|
||||
TypeOptionDataController({
|
||||
required this.gridId,
|
||||
required this.loader,
|
||||
FieldPB? field,
|
||||
}) {
|
||||
if (field != null) {
|
||||
_data = FieldTypeOptionDataPB.create()
|
||||
..gridId = gridId
|
||||
..field_2 = field;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadTypeOptionData() async {
|
||||
final result = await loader.load();
|
||||
return result.fold(
|
||||
(data) {
|
||||
data.freeze();
|
||||
_data = data;
|
||||
_fieldNotifier.value = data.field_2;
|
||||
return left(unit);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
FieldPB get field {
|
||||
return _data.field_2;
|
||||
}
|
||||
|
||||
set field(FieldPB field) {
|
||||
_updateData(newField: field);
|
||||
}
|
||||
|
||||
T getTypeOption<T>(TypeOptionDataParser<T> parser) {
|
||||
return parser.fromBuffer(_data.typeOptionData);
|
||||
}
|
||||
|
||||
set fieldName(String name) {
|
||||
_updateData(newName: name);
|
||||
}
|
||||
|
||||
set typeOptionData(List<int> typeOptionData) {
|
||||
_updateData(newTypeOptionData: typeOptionData);
|
||||
}
|
||||
|
||||
void _updateData({
|
||||
String? newName,
|
||||
FieldPB? newField,
|
||||
List<int>? newTypeOptionData,
|
||||
}) {
|
||||
_data = _data.rebuild((rebuildData) {
|
||||
if (newName != null) {
|
||||
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
|
||||
rebuildField.name = newName;
|
||||
});
|
||||
}
|
||||
|
||||
if (newField != null) {
|
||||
rebuildData.field_2 = newField;
|
||||
}
|
||||
|
||||
if (newTypeOptionData != null) {
|
||||
rebuildData.typeOptionData = newTypeOptionData;
|
||||
}
|
||||
});
|
||||
|
||||
_fieldNotifier.value = _data.field_2;
|
||||
|
||||
FieldService.insertField(
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
typeOptionData: _data.typeOptionData,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> switchToField(FieldType newFieldType) {
|
||||
return loader.switchToField(field.id, newFieldType).then((result) {
|
||||
return result.fold(
|
||||
(fieldTypeOptionData) {
|
||||
_updateData(
|
||||
newField: fieldTypeOptionData.field_2,
|
||||
newTypeOptionData: fieldTypeOptionData.typeOptionData,
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void Function() addFieldListener(void Function(FieldPB) callback) {
|
||||
listener() {
|
||||
callback(field);
|
||||
}
|
||||
|
||||
_fieldNotifier.addListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
void removeFieldListener(void Function() listener) {
|
||||
_fieldNotifier.removeListener(listener);
|
||||
}
|
||||
}
|
@ -1,19 +1,14 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
class TypeOptionService {
|
||||
class TypeOptionFFIService {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
|
||||
TypeOptionService({
|
||||
TypeOptionFFIService({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
});
|
||||
@ -29,79 +24,3 @@ class TypeOptionService {
|
||||
return GridEventNewSelectOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionDataParser<T> {
|
||||
T fromBuffer(List<int> buffer);
|
||||
}
|
||||
|
||||
class TypeOptionWidgetContext<T extends GeneratedMessage> {
|
||||
T? _typeOptionObject;
|
||||
final TypeOptionDataController _dataController;
|
||||
final TypeOptionDataParser<T> dataParser;
|
||||
|
||||
TypeOptionWidgetContext({
|
||||
required this.dataParser,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : _dataController = dataController;
|
||||
|
||||
String get gridId => _dataController.gridId;
|
||||
|
||||
GridFieldPB get field => _dataController.field;
|
||||
|
||||
T get typeOption {
|
||||
if (_typeOptionObject != null) {
|
||||
return _typeOptionObject!;
|
||||
}
|
||||
|
||||
final T object = dataParser.fromBuffer(_dataController.typeOptionData);
|
||||
_typeOptionObject = object;
|
||||
return object;
|
||||
}
|
||||
|
||||
set typeOption(T typeOption) {
|
||||
_dataController.typeOptionData = typeOption.writeToBuffer();
|
||||
_typeOptionObject = typeOption;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionFieldDelegate {
|
||||
void onFieldChanged(void Function(String) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
class TypeOptionContext2<T> {
|
||||
final String gridId;
|
||||
final GridFieldPB field;
|
||||
final FieldService _fieldService;
|
||||
T? _data;
|
||||
final TypeOptionDataParser dataBuilder;
|
||||
|
||||
TypeOptionContext2({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
required this.dataBuilder,
|
||||
Uint8List? data,
|
||||
}) : _fieldService = FieldService(gridId: gridId, fieldId: field.id) {
|
||||
if (data != null) {
|
||||
_data = dataBuilder.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<T, FlowyError>> typeOptionData() {
|
||||
if (_data != null) {
|
||||
return Future(() => left(_data!));
|
||||
}
|
||||
|
||||
return _fieldService
|
||||
.getFieldTypeOptionData(fieldType: field.fieldType)
|
||||
.then((result) {
|
||||
return result.fold(
|
||||
(data) {
|
||||
_data = dataBuilder.fromBuffer(data.typeOptionData);
|
||||
return left(_data!);
|
||||
},
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'block/block_cache.dart';
|
||||
import 'grid_data_controller.dart';
|
||||
import 'row/row_service.dart';
|
||||
import 'row/row_cache.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
part 'grid_bloc.freezed.dart';
|
||||
@ -97,11 +97,11 @@ class GridEvent with _$GridEvent {
|
||||
const factory GridEvent.initial() = InitialGrid;
|
||||
const factory GridEvent.createRow() = _CreateRow;
|
||||
const factory GridEvent.didReceiveRowUpdate(
|
||||
List<GridRowInfo> rows,
|
||||
GridRowChangeReason listState,
|
||||
List<RowInfo> rows,
|
||||
RowChangeReason listState,
|
||||
) = _DidReceiveRowUpdate;
|
||||
const factory GridEvent.didReceiveFieldUpdate(
|
||||
UnmodifiableListView<GridFieldPB> fields,
|
||||
UnmodifiableListView<FieldPB> fields,
|
||||
) = _DidReceiveFieldUpdate;
|
||||
|
||||
const factory GridEvent.didReceiveGridUpdate(
|
||||
@ -115,9 +115,9 @@ class GridState with _$GridState {
|
||||
required String gridId,
|
||||
required Option<GridPB> grid,
|
||||
required GridFieldEquatable fields,
|
||||
required List<GridRowInfo> rowInfos,
|
||||
required List<RowInfo> rowInfos,
|
||||
required GridLoadingState loadingState,
|
||||
required GridRowChangeReason reason,
|
||||
required RowChangeReason reason,
|
||||
}) = _GridState;
|
||||
|
||||
factory GridState.initial(String gridId) => GridState(
|
||||
@ -138,9 +138,9 @@ class GridLoadingState with _$GridLoadingState {
|
||||
}
|
||||
|
||||
class GridFieldEquatable extends Equatable {
|
||||
final UnmodifiableListView<GridFieldPB> _fields;
|
||||
final UnmodifiableListView<FieldPB> _fields;
|
||||
const GridFieldEquatable(
|
||||
UnmodifiableListView<GridFieldPB> fields,
|
||||
UnmodifiableListView<FieldPB> fields,
|
||||
) : _fields = fields;
|
||||
|
||||
@override
|
||||
@ -157,5 +157,5 @@ class GridFieldEquatable extends Equatable {
|
||||
];
|
||||
}
|
||||
|
||||
UnmodifiableListView<GridFieldPB> get value => UnmodifiableListView(_fields);
|
||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
@ -9,16 +9,18 @@ import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'block/block_cache.dart';
|
||||
import 'field/field_cache.dart';
|
||||
import 'prelude.dart';
|
||||
import 'row/row_cache.dart';
|
||||
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldPB>);
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||
typedef OnGridChanged = void Function(GridPB);
|
||||
|
||||
typedef OnRowsChanged = void Function(
|
||||
List<GridRowInfo> rowInfos,
|
||||
GridRowChangeReason,
|
||||
List<RowInfo> rowInfos,
|
||||
RowChangeReason,
|
||||
);
|
||||
typedef ListenONRowChangedCondition = bool Function();
|
||||
typedef ListenOnRowChangedCondition = bool Function();
|
||||
|
||||
class GridDataController {
|
||||
final String gridId;
|
||||
@ -34,8 +36,8 @@ class GridDataController {
|
||||
OnFieldsChanged? _onFieldsChanged;
|
||||
OnGridChanged? _onGridChanged;
|
||||
|
||||
List<GridRowInfo> get rowInfos {
|
||||
final List<GridRowInfo> rows = [];
|
||||
List<RowInfo> get rowInfos {
|
||||
final List<RowInfo> rows = [];
|
||||
for (var block in _blocks.values) {
|
||||
rows.addAll(block.rows);
|
||||
}
|
||||
@ -89,7 +91,7 @@ class GridDataController {
|
||||
}
|
||||
}
|
||||
|
||||
void _initialBlocks(List<GridBlockPB> blocks) {
|
||||
void _initialBlocks(List<BlockPB> blocks) {
|
||||
for (final block in blocks) {
|
||||
if (_blocks[block.id] != null) {
|
||||
Log.warn("Initial duplicate block's cache: ${block.id}");
|
||||
|
@ -4,7 +4,8 @@ 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 'grid_service.dart';
|
||||
|
||||
import 'field/field_cache.dart';
|
||||
|
||||
part 'grid_header_bloc.freezed.dart';
|
||||
|
||||
@ -35,7 +36,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
||||
|
||||
Future<void> _moveField(
|
||||
_MoveField value, Emitter<GridHeaderState> emit) async {
|
||||
final fields = List<GridFieldPB>.from(state.fields);
|
||||
final fields = List<FieldPB>.from(state.fields);
|
||||
fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
|
||||
emit(state.copyWith(fields: fields));
|
||||
|
||||
@ -63,19 +64,19 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
||||
@freezed
|
||||
class GridHeaderEvent with _$GridHeaderEvent {
|
||||
const factory GridHeaderEvent.initial() = _InitialHeader;
|
||||
const factory GridHeaderEvent.didReceiveFieldUpdate(
|
||||
List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
|
||||
const factory GridHeaderEvent.didReceiveFieldUpdate(List<FieldPB> fields) =
|
||||
_DidReceiveFieldUpdate;
|
||||
const factory GridHeaderEvent.moveField(
|
||||
GridFieldPB field, int fromIndex, int toIndex) = _MoveField;
|
||||
FieldPB field, int fromIndex, int toIndex) = _MoveField;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridHeaderState with _$GridHeaderState {
|
||||
const factory GridHeaderState({required List<GridFieldPB> fields}) =
|
||||
const factory GridHeaderState({required List<FieldPB> fields}) =
|
||||
_GridHeaderState;
|
||||
|
||||
factory GridHeaderState.initial(List<GridFieldPB> fields) {
|
||||
// final List<GridFieldPB> newFields = List.from(fields);
|
||||
factory GridHeaderState.initial(List<FieldPB> fields) {
|
||||
// final List<FieldPB> newFields = List.from(fields);
|
||||
// newFields.retainWhere((field) => field.visibility);
|
||||
return GridHeaderState(fields: fields);
|
||||
}
|
||||
|
@ -1,17 +1,12 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'row/row_service.dart';
|
||||
|
||||
class GridService {
|
||||
final String gridId;
|
||||
@ -26,18 +21,17 @@ class GridService {
|
||||
return GridEventGetGrid(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<GridRowPB, FlowyError>> createRow(
|
||||
{Option<String>? startRowId}) {
|
||||
Future<Either<RowPB, FlowyError>> createRow({Option<String>? startRowId}) {
|
||||
CreateRowPayloadPB payload = CreateRowPayloadPB.create()..gridId = gridId;
|
||||
startRowId?.fold(() => null, (id) => payload.startRowId = id);
|
||||
return GridEventCreateRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<RepeatedGridFieldPB, FlowyError>> getFields(
|
||||
{required List<GridFieldIdPB> fieldIds}) {
|
||||
Future<Either<RepeatedFieldPB, FlowyError>> getFields(
|
||||
{required List<FieldIdPB> fieldIds}) {
|
||||
final payload = QueryFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldIds = RepeatedGridFieldIdPB(items: fieldIds);
|
||||
..fieldIds = RepeatedFieldIdPB(items: fieldIds);
|
||||
return GridEventGetFields(payload).send();
|
||||
}
|
||||
|
||||
@ -45,188 +39,9 @@ class GridService {
|
||||
final request = ViewIdPB(value: gridId);
|
||||
return FolderEventCloseView(request).send();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldsNotifier extends ChangeNotifier {
|
||||
List<GridFieldPB> _fields = [];
|
||||
|
||||
set fields(List<GridFieldPB> fields) {
|
||||
_fields = fields;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<GridFieldPB> get fields => _fields;
|
||||
}
|
||||
|
||||
typedef FieldChangesetCallback = void Function(GridFieldChangesetPB);
|
||||
typedef FieldsCallback = void Function(List<GridFieldPB>);
|
||||
|
||||
class GridFieldCache {
|
||||
final String gridId;
|
||||
final GridFieldsListener _fieldListener;
|
||||
FieldsNotifier? _fieldNotifier = FieldsNotifier();
|
||||
final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
|
||||
final Map<FieldChangesetCallback, FieldChangesetCallback>
|
||||
_changesetCallbackMap = {};
|
||||
|
||||
GridFieldCache({required this.gridId})
|
||||
: _fieldListener = GridFieldsListener(gridId: gridId) {
|
||||
_fieldListener.start(onFieldsChanged: (result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
_deleteFields(changeset.deletedFields);
|
||||
_insertFields(changeset.insertedFields);
|
||||
_updateFields(changeset.updatedFields);
|
||||
for (final listener in _changesetCallbackMap.values) {
|
||||
listener(changeset);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _fieldListener.stop();
|
||||
_fieldNotifier?.dispose();
|
||||
_fieldNotifier = null;
|
||||
}
|
||||
|
||||
UnmodifiableListView<GridFieldPB> get unmodifiableFields =>
|
||||
UnmodifiableListView(_fieldNotifier?.fields ?? []);
|
||||
|
||||
List<GridFieldPB> get fields => [..._fieldNotifier?.fields ?? []];
|
||||
|
||||
set fields(List<GridFieldPB> fields) {
|
||||
_fieldNotifier?.fields = [...fields];
|
||||
}
|
||||
|
||||
void addListener({
|
||||
FieldsCallback? onFields,
|
||||
FieldChangesetCallback? onChangeset,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
if (onChangeset != null) {
|
||||
fn(c) {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onChangeset(c);
|
||||
}
|
||||
|
||||
_changesetCallbackMap[onChangeset] = fn;
|
||||
}
|
||||
|
||||
if (onFields != null) {
|
||||
fn() {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onFields(fields);
|
||||
}
|
||||
|
||||
_fieldsCallbackMap[onFields] = fn;
|
||||
_fieldNotifier?.addListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener({
|
||||
FieldsCallback? onFieldsListener,
|
||||
FieldChangesetCallback? onChangesetListener,
|
||||
}) {
|
||||
if (onFieldsListener != null) {
|
||||
final fn = _fieldsCallbackMap.remove(onFieldsListener);
|
||||
if (fn != null) {
|
||||
_fieldNotifier?.removeListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
if (onChangesetListener != null) {
|
||||
_changesetCallbackMap.remove(onChangesetListener);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteFields(List<GridFieldIdPB> deletedFields) {
|
||||
if (deletedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
final Map<String, GridFieldIdPB> deletedFieldMap = {
|
||||
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
||||
};
|
||||
|
||||
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _insertFields(List<IndexFieldPB> insertedFields) {
|
||||
if (insertedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
for (final indexField in insertedFields) {
|
||||
if (newFields.length > indexField.index) {
|
||||
newFields.insert(indexField.index, indexField.field_1);
|
||||
} else {
|
||||
newFields.add(indexField.field_1);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _updateFields(List<GridFieldPB> updatedFields) {
|
||||
if (updatedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
for (final updatedField in updatedFields) {
|
||||
final index =
|
||||
newFields.indexWhere((field) => field.id == updatedField.id);
|
||||
if (index != -1) {
|
||||
newFields.removeAt(index);
|
||||
newFields.insert(index, updatedField);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
}
|
||||
|
||||
class GridRowCacheFieldNotifierImpl extends GridRowCacheFieldNotifier {
|
||||
final GridFieldCache _cache;
|
||||
FieldChangesetCallback? _onChangesetFn;
|
||||
FieldsCallback? _onFieldFn;
|
||||
GridRowCacheFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
|
||||
@override
|
||||
UnmodifiableListView<GridFieldPB> get fields => _cache.unmodifiableFields;
|
||||
|
||||
@override
|
||||
void onFieldsChanged(VoidCallback callback) {
|
||||
_onFieldFn = (_) => callback();
|
||||
_cache.addListener(onFields: _onFieldFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onFieldChanged(void Function(GridFieldPB) callback) {
|
||||
_onChangesetFn = (GridFieldChangesetPB changeset) {
|
||||
for (final updatedField in changeset.updatedFields) {
|
||||
callback(updatedField);
|
||||
}
|
||||
};
|
||||
|
||||
_cache.addListener(onChangeset: _onChangesetFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_onFieldFn != null) {
|
||||
_cache.removeListener(onFieldsListener: _onFieldFn!);
|
||||
_onFieldFn = null;
|
||||
}
|
||||
|
||||
if (_onChangesetFn != null) {
|
||||
_cache.removeListener(onChangesetListener: _onChangesetFn!);
|
||||
_onChangesetFn = null;
|
||||
}
|
||||
Future<Either<RepeatedGridGroupPB, FlowyError>> loadGroups() {
|
||||
final payload = GridIdPB(value: gridId);
|
||||
return GridEventGetGroup(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ export 'row/row_service.dart';
|
||||
export 'grid_service.dart';
|
||||
export 'grid_header_bloc.dart';
|
||||
|
||||
// GridFieldPB
|
||||
// FieldPB
|
||||
export 'field/field_service.dart';
|
||||
export 'field/field_action_sheet_bloc.dart';
|
||||
export 'field/field_editor_bloc.dart';
|
||||
export 'field/field_type_option_edit_bloc.dart';
|
||||
|
||||
// GridFieldPB Type Option
|
||||
// FieldPB Type Option
|
||||
export 'field/type_option/date_bloc.dart';
|
||||
export 'field/type_option/number_bloc.dart';
|
||||
export 'field/type_option/single_select_type_option.dart';
|
||||
|
@ -6,13 +6,15 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import 'row_cache.dart';
|
||||
|
||||
part 'row_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class RowActionSheetBloc
|
||||
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
||||
final RowService _rowService;
|
||||
|
||||
RowActionSheetBloc({required GridRowInfo rowData})
|
||||
RowActionSheetBloc({required RowInfo rowData})
|
||||
: _rowService = RowService(
|
||||
gridId: rowData.gridId,
|
||||
blockId: rowData.blockId,
|
||||
@ -54,11 +56,10 @@ class RowActionSheetEvent with _$RowActionSheetEvent {
|
||||
@freezed
|
||||
class RowActionSheetState with _$RowActionSheetState {
|
||||
const factory RowActionSheetState({
|
||||
required GridRowInfo rowData,
|
||||
required RowInfo rowData,
|
||||
}) = _RowActionSheetState;
|
||||
|
||||
factory RowActionSheetState.initial(GridRowInfo rowData) =>
|
||||
RowActionSheetState(
|
||||
factory RowActionSheetState.initial(RowInfo rowData) => RowActionSheetState(
|
||||
rowData: rowData,
|
||||
);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ 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 'row_cache.dart';
|
||||
import 'row_data_controller.dart';
|
||||
import 'row_service.dart';
|
||||
|
||||
@ -15,7 +16,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
final GridRowDataController _dataController;
|
||||
|
||||
RowBloc({
|
||||
required GridRowInfo rowInfo,
|
||||
required RowInfo rowInfo,
|
||||
required GridRowDataController dataController,
|
||||
}) : _rowService = RowService(
|
||||
gridId: rowInfo.gridId,
|
||||
@ -71,19 +72,19 @@ class RowEvent with _$RowEvent {
|
||||
const factory RowEvent.initial() = _InitialRow;
|
||||
const factory RowEvent.createRow() = _CreateRow;
|
||||
const factory RowEvent.didReceiveCells(
|
||||
GridCellMap gridCellMap, GridRowChangeReason reason) = _DidReceiveCells;
|
||||
GridCellMap gridCellMap, RowChangeReason reason) = _DidReceiveCells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowState with _$RowState {
|
||||
const factory RowState({
|
||||
required GridRowInfo rowInfo,
|
||||
required RowInfo rowInfo,
|
||||
required GridCellMap gridCellMap,
|
||||
required UnmodifiableListView<GridCellEquatable> snapshots,
|
||||
GridRowChangeReason? changeReason,
|
||||
RowChangeReason? changeReason,
|
||||
}) = _RowState;
|
||||
|
||||
factory RowState.initial(GridRowInfo rowInfo, GridCellMap cellDataMap) =>
|
||||
factory RowState.initial(RowInfo rowInfo, GridCellMap cellDataMap) =>
|
||||
RowState(
|
||||
rowInfo: rowInfo,
|
||||
gridCellMap: cellDataMap,
|
||||
@ -93,9 +94,9 @@ class RowState with _$RowState {
|
||||
}
|
||||
|
||||
class GridCellEquatable extends Equatable {
|
||||
final GridFieldPB _field;
|
||||
final FieldPB _field;
|
||||
|
||||
const GridCellEquatable(GridFieldPB field) : _field = field;
|
||||
const GridCellEquatable(FieldPB field) : _field = field;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
|
@ -0,0 +1,328 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.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:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'row_cache.freezed.dart';
|
||||
|
||||
typedef RowUpdateCallback = void Function();
|
||||
|
||||
abstract class IGridRowFieldNotifier {
|
||||
UnmodifiableListView<FieldPB> get fields;
|
||||
void onRowFieldsChanged(VoidCallback callback);
|
||||
void onRowFieldChanged(void Function(FieldPB) callback);
|
||||
void onRowDispose();
|
||||
}
|
||||
|
||||
/// Cache the rows in memory
|
||||
/// Insert / delete / update row
|
||||
///
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
||||
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final BlockPB block;
|
||||
|
||||
/// _rows containers the current block's rows
|
||||
/// Use List to reverse the order of the GridRow.
|
||||
List<RowInfo> _rowInfos = [];
|
||||
|
||||
/// Use Map for faster access the raw row data.
|
||||
final HashMap<String, RowPB> _rowByRowId;
|
||||
|
||||
final GridCellCache _cellCache;
|
||||
final IGridRowFieldNotifier _fieldNotifier;
|
||||
final _RowChangesetNotifier _rowChangeReasonNotifier;
|
||||
|
||||
UnmodifiableListView<RowInfo> get rows => UnmodifiableListView(_rowInfos);
|
||||
GridCellCache get cellCache => _cellCache;
|
||||
|
||||
GridRowCache({
|
||||
required this.gridId,
|
||||
required this.block,
|
||||
required IGridRowFieldNotifier notifier,
|
||||
}) : _cellCache = GridCellCache(gridId: gridId),
|
||||
_rowByRowId = HashMap(),
|
||||
_rowChangeReasonNotifier = _RowChangesetNotifier(),
|
||||
_fieldNotifier = notifier {
|
||||
//
|
||||
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
|
||||
.receive(const RowChangeReason.fieldDidChange()));
|
||||
notifier.onRowFieldChanged((field) => _cellCache.remove(field.id));
|
||||
_rowInfos = block.rows
|
||||
.map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
_fieldNotifier.onRowDispose();
|
||||
_rowChangeReasonNotifier.dispose();
|
||||
await _cellCache.dispose();
|
||||
}
|
||||
|
||||
void applyChangesets(List<GridBlockChangesetPB> changesets) {
|
||||
for (final changeset in changesets) {
|
||||
_deleteRows(changeset.deletedRows);
|
||||
_insertRows(changeset.insertedRows);
|
||||
_updateRows(changeset.updatedRows);
|
||||
_hideRows(changeset.hideRows);
|
||||
_showRows(changeset.visibleRows);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteRows(List<String> deletedRows) {
|
||||
if (deletedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<RowInfo> newRows = [];
|
||||
final DeletedIndexs deletedIndex = [];
|
||||
final Map<String, String> deletedRowByRowId = {
|
||||
for (var rowId in deletedRows) rowId: rowId
|
||||
};
|
||||
|
||||
_rowInfos.asMap().forEach((index, row) {
|
||||
if (deletedRowByRowId[row.id] == null) {
|
||||
newRows.add(row);
|
||||
} else {
|
||||
_rowByRowId.remove(row.id);
|
||||
deletedIndex.add(DeletedIndex(index: index, row: row));
|
||||
}
|
||||
});
|
||||
_rowInfos = newRows;
|
||||
_rowChangeReasonNotifier.receive(RowChangeReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void _insertRows(List<InsertedRowPB> insertRows) {
|
||||
if (insertRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
for (final insertRow in insertRows) {
|
||||
final insertIndex = InsertedIndex(
|
||||
index: insertRow.index,
|
||||
rowId: insertRow.rowId,
|
||||
);
|
||||
insertIndexs.add(insertIndex);
|
||||
_rowInfos.insert(insertRow.index,
|
||||
(buildGridRow(insertRow.rowId, insertRow.height.toDouble())));
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(RowChangeReason.insert(insertIndexs));
|
||||
}
|
||||
|
||||
void _updateRows(List<UpdatedRowPB> updatedRows) {
|
||||
if (updatedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
for (final updatedRow in updatedRows) {
|
||||
final rowId = updatedRow.rowId;
|
||||
final index = _rowInfos.indexWhere((row) => row.id == rowId);
|
||||
if (index != -1) {
|
||||
_rowByRowId[rowId] = updatedRow.row;
|
||||
|
||||
_rowInfos.removeAt(index);
|
||||
_rowInfos.insert(
|
||||
index, buildGridRow(rowId, updatedRow.row.height.toDouble()));
|
||||
updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(RowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
|
||||
void _hideRows(List<String> hideRows) {}
|
||||
|
||||
void _showRows(List<String> visibleRows) {}
|
||||
|
||||
void onRowsChanged(
|
||||
void Function(RowChangeReason) onRowChanged,
|
||||
) {
|
||||
_rowChangeReasonNotifier.addListener(() {
|
||||
onRowChanged(_rowChangeReasonNotifier.reason);
|
||||
});
|
||||
}
|
||||
|
||||
RowUpdateCallback addListener({
|
||||
required String rowId,
|
||||
void Function(GridCellMap, RowChangeReason)? onCellUpdated,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
listenerHandler() async {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyUpdate() {
|
||||
if (onCellUpdated != null) {
|
||||
final row = _rowByRowId[rowId];
|
||||
if (row != null) {
|
||||
final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
||||
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.reason.whenOrNull(
|
||||
update: (indexs) {
|
||||
if (indexs[rowId] != null) notifyUpdate();
|
||||
},
|
||||
fieldDidChange: () => notifyUpdate(),
|
||||
);
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.addListener(listenerHandler);
|
||||
return listenerHandler;
|
||||
}
|
||||
|
||||
void removeRowListener(VoidCallback callback) {
|
||||
_rowChangeReasonNotifier.removeListener(callback);
|
||||
}
|
||||
|
||||
GridCellMap loadGridCells(String rowId) {
|
||||
final RowPB? data = _rowByRowId[rowId];
|
||||
if (data == null) {
|
||||
_loadRow(rowId);
|
||||
}
|
||||
return _makeGridCells(rowId, data);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(String rowId) async {
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = block.id
|
||||
..rowId = rowId;
|
||||
|
||||
final result = await GridEventGetRow(payload).send();
|
||||
result.fold(
|
||||
(optionRow) => _refreshRow(optionRow),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
GridCellMap _makeGridCells(String rowId, RowPB? row) {
|
||||
var cellDataMap = GridCellMap.new();
|
||||
for (final field in _fieldNotifier.fields) {
|
||||
if (field.visibility) {
|
||||
cellDataMap[field.id] = GridCellIdentifier(
|
||||
rowId: rowId,
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return cellDataMap;
|
||||
}
|
||||
|
||||
void _refreshRow(OptionalRowPB optionRow) {
|
||||
if (!optionRow.hasRow()) {
|
||||
return;
|
||||
}
|
||||
final updatedRow = optionRow.row;
|
||||
updatedRow.freeze();
|
||||
|
||||
_rowByRowId[updatedRow.id] = updatedRow;
|
||||
final index =
|
||||
_rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id);
|
||||
if (index != -1) {
|
||||
// update the corresponding row in _rows if they are not the same
|
||||
if (_rowInfos[index].rawRow != updatedRow) {
|
||||
final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow);
|
||||
_rowInfos.insert(index, row);
|
||||
|
||||
// Calculate the update index
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id);
|
||||
|
||||
//
|
||||
_rowChangeReasonNotifier.receive(RowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowInfo buildGridRow(String rowId, double rowHeight) {
|
||||
return RowInfo(
|
||||
gridId: gridId,
|
||||
blockId: block.id,
|
||||
fields: _fieldNotifier.fields,
|
||||
id: rowId,
|
||||
height: rowHeight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowChangesetNotifier extends ChangeNotifier {
|
||||
RowChangeReason reason = const InitialListState();
|
||||
|
||||
_RowChangesetNotifier();
|
||||
|
||||
void receive(RowChangeReason newReason) {
|
||||
reason = newReason;
|
||||
reason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowInfo with _$RowInfo {
|
||||
const factory RowInfo({
|
||||
required String gridId,
|
||||
required String blockId,
|
||||
required String id,
|
||||
required UnmodifiableListView<FieldPB> fields,
|
||||
required double height,
|
||||
RowPB? rawRow,
|
||||
}) = _RowInfo;
|
||||
}
|
||||
|
||||
typedef InsertedIndexs = List<InsertedIndex>;
|
||||
typedef DeletedIndexs = List<DeletedIndex>;
|
||||
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
|
||||
|
||||
@freezed
|
||||
class RowChangeReason with _$RowChangeReason {
|
||||
const factory RowChangeReason.insert(InsertedIndexs items) = _Insert;
|
||||
const factory RowChangeReason.delete(DeletedIndexs items) = _Delete;
|
||||
const factory RowChangeReason.update(UpdatedIndexs indexs) = _Update;
|
||||
const factory RowChangeReason.fieldDidChange() = _FieldDidChange;
|
||||
const factory RowChangeReason.initial() = InitialListState;
|
||||
}
|
||||
|
||||
class InsertedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
InsertedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
||||
|
||||
class DeletedIndex {
|
||||
final int index;
|
||||
final RowInfo row;
|
||||
DeletedIndex({
|
||||
required this.index,
|
||||
required this.row,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdatedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
UpdatedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../presentation/widgets/cell/cell_builder.dart';
|
||||
import '../cell/cell_service/cell_service.dart';
|
||||
import '../grid_service.dart';
|
||||
import 'row_service.dart';
|
||||
import '../field/field_cache.dart';
|
||||
import 'row_cache.dart';
|
||||
|
||||
typedef OnRowChanged = void Function(GridCellMap, GridRowChangeReason);
|
||||
typedef OnRowChanged = void Function(GridCellMap, RowChangeReason);
|
||||
|
||||
class GridRowDataController {
|
||||
final String rowId;
|
||||
VoidCallback? _onRowChangedListener;
|
||||
class GridRowDataController extends GridCellBuilderDelegate {
|
||||
final RowInfo rowInfo;
|
||||
final List<VoidCallback> _onRowChangedListeners = [];
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridRowCache _rowCache;
|
||||
|
||||
@ -16,26 +18,36 @@ class GridRowDataController {
|
||||
GridRowCache get rowCache => _rowCache;
|
||||
|
||||
GridRowDataController({
|
||||
required this.rowId,
|
||||
required this.rowInfo,
|
||||
required GridFieldCache fieldCache,
|
||||
required GridRowCache rowCache,
|
||||
}) : _fieldCache = fieldCache,
|
||||
_rowCache = rowCache;
|
||||
|
||||
GridCellMap loadData() {
|
||||
return _rowCache.loadGridCells(rowId);
|
||||
return _rowCache.loadGridCells(rowInfo.id);
|
||||
}
|
||||
|
||||
void addListener({OnRowChanged? onRowChanged}) {
|
||||
_onRowChangedListener = _rowCache.addListener(
|
||||
rowId: rowId,
|
||||
_onRowChangedListeners.add(_rowCache.addListener(
|
||||
rowId: rowInfo.id,
|
||||
onCellUpdated: onRowChanged,
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (_onRowChangedListener != null) {
|
||||
_rowCache.removeRowListener(_onRowChangedListener!);
|
||||
for (final fn in _onRowChangedListeners) {
|
||||
_rowCache.removeRowListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
// GridCellBuilderDelegate implementation
|
||||
@override
|
||||
GridCellFieldNotifier buildFieldNotifier() {
|
||||
return GridCellFieldNotifier(
|
||||
notifier: GridCellFieldNotifierImpl(_fieldCache));
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellCache get cellCache => rowCache.cellCache;
|
||||
}
|
||||
|
@ -2,26 +2,24 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'row_service.dart';
|
||||
|
||||
import 'row_data_controller.dart';
|
||||
part 'row_detail_bloc.freezed.dart';
|
||||
|
||||
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
final GridRowInfo rowInfo;
|
||||
final GridRowCache _rowCache;
|
||||
void Function()? _rowListenFn;
|
||||
final GridRowDataController dataController;
|
||||
|
||||
RowDetailBloc({
|
||||
required this.rowInfo,
|
||||
required GridRowCache rowCache,
|
||||
}) : _rowCache = rowCache,
|
||||
super(RowDetailState.initial()) {
|
||||
required this.dataController,
|
||||
}) : super(RowDetailState.initial()) {
|
||||
on<RowDetailEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) async {
|
||||
await _startListening();
|
||||
_loadCellData();
|
||||
final cells = dataController.loadData();
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
||||
}
|
||||
},
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) {
|
||||
emit(state.copyWith(gridCells: value.gridCells));
|
||||
@ -33,27 +31,19 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_rowListenFn != null) {
|
||||
_rowCache.removeRowListener(_rowListenFn!);
|
||||
}
|
||||
dataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_rowListenFn = _rowCache.addListener(
|
||||
rowId: rowInfo.id,
|
||||
onCellUpdated: (cellDatas, reason) =>
|
||||
add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
|
||||
listenWhen: () => !isClosed,
|
||||
dataController.addListener(
|
||||
onRowChanged: (cells, reason) {
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final cellDataMap = _rowCache.loadGridCells(rowInfo.id);
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -8,12 +8,13 @@ import 'dart:typed_data';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
|
||||
typedef UpdateRowNotifiedValue = Either<GridRowPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<List<GridFieldPB>, FlowyError>;
|
||||
typedef UpdateRowNotifiedValue = Either<RowPB, FlowyError>;
|
||||
typedef UpdateFieldNotifiedValue = Either<List<FieldPB>, FlowyError>;
|
||||
|
||||
class RowListener {
|
||||
final String rowId;
|
||||
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier = PublishNotifier();
|
||||
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
|
||||
RowListener({required this.rowId});
|
||||
@ -26,7 +27,8 @@ class RowListener {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateRow:
|
||||
result.fold(
|
||||
(payload) => updateRowNotifier?.value = left(GridRowPB.fromBuffer(payload)),
|
||||
(payload) =>
|
||||
updateRowNotifier?.value = left(RowPB.fromBuffer(payload)),
|
||||
(error) => updateRowNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -1,283 +1,9 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.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:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'row_service.freezed.dart';
|
||||
|
||||
typedef RowUpdateCallback = void Function();
|
||||
|
||||
abstract class GridRowCacheFieldNotifier {
|
||||
UnmodifiableListView<GridFieldPB> get fields;
|
||||
void onFieldsChanged(VoidCallback callback);
|
||||
void onFieldChanged(void Function(GridFieldPB) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
/// Cache the rows in memory
|
||||
/// Insert / delete / update row
|
||||
///
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
||||
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final GridBlockPB block;
|
||||
|
||||
/// _rows containers the current block's rows
|
||||
/// Use List to reverse the order of the GridRow.
|
||||
List<GridRowInfo> _rowInfos = [];
|
||||
|
||||
/// Use Map for faster access the raw row data.
|
||||
final HashMap<String, GridRowPB> _rowByRowId;
|
||||
|
||||
final GridCellCache _cellCache;
|
||||
final GridRowCacheFieldNotifier _fieldNotifier;
|
||||
final _GridRowChangesetNotifier _rowChangeReasonNotifier;
|
||||
|
||||
UnmodifiableListView<GridRowInfo> get rows => UnmodifiableListView(_rowInfos);
|
||||
GridCellCache get cellCache => _cellCache;
|
||||
|
||||
GridRowCache({
|
||||
required this.gridId,
|
||||
required this.block,
|
||||
required GridRowCacheFieldNotifier notifier,
|
||||
}) : _cellCache = GridCellCache(gridId: gridId),
|
||||
_rowByRowId = HashMap(),
|
||||
_rowChangeReasonNotifier = _GridRowChangesetNotifier(),
|
||||
_fieldNotifier = notifier {
|
||||
//
|
||||
notifier.onFieldsChanged(() => _rowChangeReasonNotifier
|
||||
.receive(const GridRowChangeReason.fieldDidChange()));
|
||||
notifier.onFieldChanged((field) => _cellCache.remove(field.id));
|
||||
_rowInfos = block.rows
|
||||
.map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
_fieldNotifier.dispose();
|
||||
_rowChangeReasonNotifier.dispose();
|
||||
await _cellCache.dispose();
|
||||
}
|
||||
|
||||
void applyChangesets(List<GridBlockChangesetPB> changesets) {
|
||||
for (final changeset in changesets) {
|
||||
_deleteRows(changeset.deletedRows);
|
||||
_insertRows(changeset.insertedRows);
|
||||
_updateRows(changeset.updatedRows);
|
||||
_hideRows(changeset.hideRows);
|
||||
_showRows(changeset.visibleRows);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteRows(List<String> deletedRows) {
|
||||
if (deletedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<GridRowInfo> newRows = [];
|
||||
final DeletedIndexs deletedIndex = [];
|
||||
final Map<String, String> deletedRowByRowId = {
|
||||
for (var rowId in deletedRows) rowId: rowId
|
||||
};
|
||||
|
||||
_rowInfos.asMap().forEach((index, row) {
|
||||
if (deletedRowByRowId[row.id] == null) {
|
||||
newRows.add(row);
|
||||
} else {
|
||||
_rowByRowId.remove(row.id);
|
||||
deletedIndex.add(DeletedIndex(index: index, row: row));
|
||||
}
|
||||
});
|
||||
_rowInfos = newRows;
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void _insertRows(List<InsertedRowPB> insertRows) {
|
||||
if (insertRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
for (final insertRow in insertRows) {
|
||||
final insertIndex = InsertedIndex(
|
||||
index: insertRow.index,
|
||||
rowId: insertRow.rowId,
|
||||
);
|
||||
insertIndexs.add(insertIndex);
|
||||
_rowInfos.insert(insertRow.index,
|
||||
(buildGridRow(insertRow.rowId, insertRow.height.toDouble())));
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.insert(insertIndexs));
|
||||
}
|
||||
|
||||
void _updateRows(List<UpdatedRowPB> updatedRows) {
|
||||
if (updatedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
for (final updatedRow in updatedRows) {
|
||||
final rowId = updatedRow.rowId;
|
||||
final index = _rowInfos.indexWhere((row) => row.id == rowId);
|
||||
if (index != -1) {
|
||||
_rowByRowId[rowId] = updatedRow.row;
|
||||
|
||||
_rowInfos.removeAt(index);
|
||||
_rowInfos.insert(
|
||||
index, buildGridRow(rowId, updatedRow.row.height.toDouble()));
|
||||
updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
|
||||
void _hideRows(List<String> hideRows) {}
|
||||
|
||||
void _showRows(List<String> visibleRows) {}
|
||||
|
||||
void onRowsChanged(
|
||||
void Function(GridRowChangeReason) onRowChanged,
|
||||
) {
|
||||
_rowChangeReasonNotifier.addListener(() {
|
||||
onRowChanged(_rowChangeReasonNotifier.reason);
|
||||
});
|
||||
}
|
||||
|
||||
RowUpdateCallback addListener({
|
||||
required String rowId,
|
||||
void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
listenerHandler() async {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyUpdate() {
|
||||
if (onCellUpdated != null) {
|
||||
final row = _rowByRowId[rowId];
|
||||
if (row != null) {
|
||||
final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
||||
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.reason.whenOrNull(
|
||||
update: (indexs) {
|
||||
if (indexs[rowId] != null) notifyUpdate();
|
||||
},
|
||||
fieldDidChange: () => notifyUpdate(),
|
||||
);
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.addListener(listenerHandler);
|
||||
return listenerHandler;
|
||||
}
|
||||
|
||||
void removeRowListener(VoidCallback callback) {
|
||||
_rowChangeReasonNotifier.removeListener(callback);
|
||||
}
|
||||
|
||||
GridCellMap loadGridCells(String rowId) {
|
||||
final GridRowPB? data = _rowByRowId[rowId];
|
||||
if (data == null) {
|
||||
_loadRow(rowId);
|
||||
}
|
||||
return _makeGridCells(rowId, data);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(String rowId) async {
|
||||
final payload = GridRowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = block.id
|
||||
..rowId = rowId;
|
||||
|
||||
final result = await GridEventGetRow(payload).send();
|
||||
result.fold(
|
||||
(optionRow) => _refreshRow(optionRow),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
GridCellMap _makeGridCells(String rowId, GridRowPB? row) {
|
||||
var cellDataMap = GridCellMap.new();
|
||||
for (final field in _fieldNotifier.fields) {
|
||||
if (field.visibility) {
|
||||
cellDataMap[field.id] = GridCellIdentifier(
|
||||
rowId: rowId,
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return cellDataMap;
|
||||
}
|
||||
|
||||
void _refreshRow(OptionalRowPB optionRow) {
|
||||
if (!optionRow.hasRow()) {
|
||||
return;
|
||||
}
|
||||
final updatedRow = optionRow.row;
|
||||
updatedRow.freeze();
|
||||
|
||||
_rowByRowId[updatedRow.id] = updatedRow;
|
||||
final index =
|
||||
_rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id);
|
||||
if (index != -1) {
|
||||
// update the corresponding row in _rows if they are not the same
|
||||
if (_rowInfos[index].rawRow != updatedRow) {
|
||||
final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow);
|
||||
_rowInfos.insert(index, row);
|
||||
|
||||
// Calculate the update index
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id);
|
||||
|
||||
//
|
||||
_rowChangeReasonNotifier
|
||||
.receive(GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridRowInfo buildGridRow(String rowId, double rowHeight) {
|
||||
return GridRowInfo(
|
||||
gridId: gridId,
|
||||
blockId: block.id,
|
||||
fields: _fieldNotifier.fields,
|
||||
id: rowId,
|
||||
height: rowHeight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GridRowChangesetNotifier extends ChangeNotifier {
|
||||
GridRowChangeReason reason = const InitialListState();
|
||||
|
||||
_GridRowChangesetNotifier();
|
||||
|
||||
void receive(GridRowChangeReason newReason) {
|
||||
reason = newReason;
|
||||
reason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RowService {
|
||||
final String gridId;
|
||||
@ -287,7 +13,7 @@ class RowService {
|
||||
RowService(
|
||||
{required this.gridId, required this.blockId, required this.rowId});
|
||||
|
||||
Future<Either<GridRowPB, FlowyError>> createRow() {
|
||||
Future<Either<RowPB, FlowyError>> createRow() {
|
||||
CreateRowPayloadPB payload = CreateRowPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..startRowId = rowId;
|
||||
@ -308,7 +34,7 @@ class RowService {
|
||||
}
|
||||
|
||||
Future<Either<OptionalRowPB, FlowyError>> getRow() {
|
||||
final payload = GridRowIdPB.create()
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = blockId
|
||||
..rowId = rowId;
|
||||
@ -317,7 +43,7 @@ class RowService {
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> deleteRow() {
|
||||
final payload = GridRowIdPB.create()
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = blockId
|
||||
..rowId = rowId;
|
||||
@ -326,7 +52,7 @@ class RowService {
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> duplicateRow() {
|
||||
final payload = GridRowIdPB.create()
|
||||
final payload = RowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = blockId
|
||||
..rowId = rowId;
|
||||
@ -334,55 +60,3 @@ class RowService {
|
||||
return GridEventDuplicateRow(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridRowInfo with _$GridRowInfo {
|
||||
const factory GridRowInfo({
|
||||
required String gridId,
|
||||
required String blockId,
|
||||
required String id,
|
||||
required UnmodifiableListView<GridFieldPB> fields,
|
||||
required double height,
|
||||
GridRowPB? rawRow,
|
||||
}) = _GridRowInfo;
|
||||
}
|
||||
|
||||
typedef InsertedIndexs = List<InsertedIndex>;
|
||||
typedef DeletedIndexs = List<DeletedIndex>;
|
||||
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
|
||||
|
||||
@freezed
|
||||
class GridRowChangeReason with _$GridRowChangeReason {
|
||||
const factory GridRowChangeReason.insert(InsertedIndexs items) = _Insert;
|
||||
const factory GridRowChangeReason.delete(DeletedIndexs items) = _Delete;
|
||||
const factory GridRowChangeReason.update(UpdatedIndexs indexs) = _Update;
|
||||
const factory GridRowChangeReason.fieldDidChange() = _FieldDidChange;
|
||||
const factory GridRowChangeReason.initial() = InitialListState;
|
||||
}
|
||||
|
||||
class InsertedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
InsertedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
||||
|
||||
class DeletedIndex {
|
||||
final int index;
|
||||
final GridRowInfo row;
|
||||
DeletedIndex({
|
||||
required this.index,
|
||||
required this.row,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdatedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
UpdatedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
||||
|
@ -1,16 +1,17 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:flowy_sdk/log.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 '../field/field_cache.dart';
|
||||
|
||||
part 'property_bloc.freezed.dart';
|
||||
|
||||
class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
|
||||
final GridFieldCache _fieldCache;
|
||||
Function(List<GridFieldPB>)? _onFieldsFn;
|
||||
Function(List<FieldPB>)? _onFieldsFn;
|
||||
|
||||
GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
|
||||
: _fieldCache = fieldCache,
|
||||
@ -66,8 +67,8 @@ class GridPropertyEvent with _$GridPropertyEvent {
|
||||
const factory GridPropertyEvent.initial() = _Initial;
|
||||
const factory GridPropertyEvent.setFieldVisibility(
|
||||
String fieldId, bool visibility) = _SetFieldVisibility;
|
||||
const factory GridPropertyEvent.didReceiveFieldUpdate(
|
||||
List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
|
||||
const factory GridPropertyEvent.didReceiveFieldUpdate(List<FieldPB> fields) =
|
||||
_DidReceiveFieldUpdate;
|
||||
const factory GridPropertyEvent.moveField(int fromIndex, int toIndex) =
|
||||
_MoveField;
|
||||
}
|
||||
@ -76,10 +77,10 @@ class GridPropertyEvent with _$GridPropertyEvent {
|
||||
class GridPropertyState with _$GridPropertyState {
|
||||
const factory GridPropertyState({
|
||||
required String gridId,
|
||||
required List<GridFieldPB> fields,
|
||||
required List<FieldPB> fields,
|
||||
}) = _GridPropertyState;
|
||||
|
||||
factory GridPropertyState.initial(String gridId, List<GridFieldPB> fields) =>
|
||||
factory GridPropertyState.initial(String gridId, List<FieldPB> fields) =>
|
||||
GridPropertyState(
|
||||
gridId: gridId,
|
||||
fields: fields,
|
||||
|
@ -25,7 +25,10 @@ class GridPluginBuilder implements PluginBuilder {
|
||||
PluginType get pluginType => DefaultPlugin.grid.type();
|
||||
|
||||
@override
|
||||
ViewDataType get dataType => ViewDataType.Grid;
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
||||
|
||||
@override
|
||||
SubViewDataTypePB? get subDataType => SubViewDataTypePB.Grid;
|
||||
}
|
||||
|
||||
class GridPluginConfig implements PluginConfig {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
||||
@ -12,12 +12,15 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||
import '../application/row/row_cache.dart';
|
||||
import 'controller/grid_scroll.dart';
|
||||
import 'layout/layout.dart';
|
||||
import 'layout/sizes.dart';
|
||||
import 'widgets/cell/cell_builder.dart';
|
||||
import 'widgets/row/grid_row.dart';
|
||||
import 'widgets/footer/grid_footer.dart';
|
||||
import 'widgets/header/grid_header.dart';
|
||||
import 'widgets/row/row_detail.dart';
|
||||
import 'widgets/shortcuts.dart';
|
||||
import 'widgets/toolbar/grid_toolbar.dart';
|
||||
|
||||
@ -222,7 +225,7 @@ class _GridRowsState extends State<_GridRows> {
|
||||
initialItemCount: context.read<GridBloc>().state.rowInfos.length,
|
||||
itemBuilder:
|
||||
(BuildContext context, int index, Animation<double> animation) {
|
||||
final GridRowInfo rowInfo =
|
||||
final RowInfo rowInfo =
|
||||
context.read<GridBloc>().state.rowInfos[index];
|
||||
return _renderRow(context, rowInfo, animation);
|
||||
},
|
||||
@ -233,31 +236,59 @@ class _GridRowsState extends State<_GridRows> {
|
||||
|
||||
Widget _renderRow(
|
||||
BuildContext context,
|
||||
GridRowInfo rowInfo,
|
||||
RowInfo rowInfo,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
final rowCache =
|
||||
context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id);
|
||||
|
||||
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||
if (rowCache != null) {
|
||||
final dataController = GridRowDataController(
|
||||
rowId: rowInfo.id,
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return const SizedBox();
|
||||
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: GridRowWidget(
|
||||
rowData: rowInfo,
|
||||
dataController: dataController,
|
||||
key: ValueKey(rowInfo.id),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||
final dataController = GridRowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: GridRowWidget(
|
||||
rowInfo: rowInfo,
|
||||
dataController: dataController,
|
||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||
openDetailPage: (context, cellBuilder) {
|
||||
_openRowDetailPage(
|
||||
context,
|
||||
rowInfo,
|
||||
fieldCache,
|
||||
rowCache,
|
||||
cellBuilder,
|
||||
);
|
||||
},
|
||||
key: ValueKey(rowInfo.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openRowDetailPage(
|
||||
BuildContext context,
|
||||
RowInfo rowInfo,
|
||||
GridFieldCache fieldCache,
|
||||
GridRowCache rowCache,
|
||||
GridCellBuilder cellBuilder,
|
||||
) {
|
||||
final dataController = GridRowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
RowDetailPage(
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: dataController,
|
||||
).show(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,15 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'sizes.dart';
|
||||
|
||||
class GridLayout {
|
||||
static double headerWidth(List<GridFieldPB> fields) {
|
||||
static double headerWidth(List<FieldPB> fields) {
|
||||
if (fields.isEmpty) return 0;
|
||||
|
||||
final fieldsWidth = fields.map((field) => field.width.toDouble()).reduce((value, element) => value + element);
|
||||
final fieldsWidth = fields
|
||||
.map((field) => field.width.toDouble())
|
||||
.reduce((value, element) => value + element);
|
||||
|
||||
return fieldsWidth + GridSize.leadingHeaderPadding + GridSize.trailHeaderPadding;
|
||||
return fieldsWidth +
|
||||
GridSize.leadingHeaderPadding +
|
||||
GridSize.trailHeaderPadding;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -13,23 +12,26 @@ import 'select_option_cell/select_option_cell.dart';
|
||||
import 'text_cell.dart';
|
||||
import 'url_cell/url_cell.dart';
|
||||
|
||||
abstract class GridCellBuilderDelegate
|
||||
extends GridCellControllerBuilderDelegate {
|
||||
GridCellCache get cellCache;
|
||||
}
|
||||
|
||||
class GridCellBuilder {
|
||||
final GridCellCache cellCache;
|
||||
final GridFieldCache fieldCache;
|
||||
final GridCellBuilderDelegate delegate;
|
||||
GridCellBuilder({
|
||||
required this.cellCache,
|
||||
required this.fieldCache,
|
||||
required this.delegate,
|
||||
});
|
||||
|
||||
GridCellWidget build(GridCellIdentifier cell, {GridCellStyle? style}) {
|
||||
GridCellWidget build(GridCellIdentifier cellId, {GridCellStyle? style}) {
|
||||
final cellControllerBuilder = GridCellControllerBuilder(
|
||||
cellId: cell,
|
||||
cellCache: cellCache,
|
||||
fieldCache: fieldCache,
|
||||
cellId: cellId,
|
||||
cellCache: delegate.cellCache,
|
||||
delegate: delegate,
|
||||
);
|
||||
|
||||
final key = cell.key();
|
||||
switch (cell.fieldType) {
|
||||
final key = cellId.key();
|
||||
switch (cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return GridCheckboxCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
|
@ -22,8 +22,8 @@ class _CheckboxCellState extends GridCellState<GridCheckboxCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellController)
|
||||
..add(const CheckboxCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
@ -43,8 +43,8 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<DateCellBloc>(param1: cellContext)
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<DateCellBloc>(param1: cellController)
|
||||
..add(const DateCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
@ -84,7 +84,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
|
||||
calendar.show(
|
||||
context,
|
||||
cellController: bloc.cellContext.clone(),
|
||||
cellController: bloc.cellController.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -39,10 +40,10 @@ class DateCellEditor with FlowyOverlayDelegate {
|
||||
final result =
|
||||
await cellController.getFieldTypeOption(DateTypeOptionDataParser());
|
||||
result.fold(
|
||||
(dateTypeOption) {
|
||||
(dateTypeOptionPB) {
|
||||
final calendar = _CellCalendarWidget(
|
||||
cellContext: cellController,
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
);
|
||||
|
||||
FlowyOverlay.of(context).insertWithAnchor(
|
||||
@ -78,11 +79,11 @@ class DateCellEditor with FlowyOverlayDelegate {
|
||||
|
||||
class _CellCalendarWidget extends StatelessWidget {
|
||||
final GridDateCellController cellContext;
|
||||
final DateTypeOption dateTypeOption;
|
||||
final DateTypeOptionPB dateTypeOptionPB;
|
||||
|
||||
const _CellCalendarWidget({
|
||||
required this.cellContext,
|
||||
required this.dateTypeOption,
|
||||
required this.dateTypeOptionPB,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -92,9 +93,9 @@ class _CellCalendarWidget extends StatelessWidget {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return DateCalBloc(
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
cellData: cellContext.getCellData(),
|
||||
cellContext: cellContext,
|
||||
cellController: cellContext,
|
||||
)..add(const DateCalEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<DateCalBloc, DateCalState>(
|
||||
@ -196,7 +197,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocSelector<DateCalBloc, DateCalState, bool>(
|
||||
selector: (state) => state.dateTypeOption.includeTime,
|
||||
selector: (state) => state.dateTypeOptionPB.includeTime,
|
||||
builder: (context, includeTime) {
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
@ -243,7 +244,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
void initState() {
|
||||
_focusNode = FocusNode();
|
||||
_controller = TextEditingController(text: widget.bloc.state.time);
|
||||
if (widget.bloc.state.dateTypeOption.includeTime) {
|
||||
if (widget.bloc.state.dateTypeOptionPB.includeTime) {
|
||||
_focusNode.addListener(() {
|
||||
if (mounted) {
|
||||
_CalDateTimeSetting.hide(context);
|
||||
@ -264,7 +265,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
},
|
||||
listenWhen: (p, c) => p.time != c.time,
|
||||
builder: (context, state) {
|
||||
if (state.dateTypeOption.includeTime) {
|
||||
if (state.dateTypeOptionPB.includeTime) {
|
||||
return Padding(
|
||||
padding: kMargin,
|
||||
child: RoundedInputField(
|
||||
@ -306,23 +307,24 @@ class _DateTypeOptionButton extends StatelessWidget {
|
||||
final title = LocaleKeys.grid_field_dateFormat.tr() +
|
||||
" &" +
|
||||
LocaleKeys.grid_field_timeFormat.tr();
|
||||
return BlocSelector<DateCalBloc, DateCalState, DateTypeOption>(
|
||||
selector: (state) => state.dateTypeOption,
|
||||
builder: (context, dateTypeOption) {
|
||||
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
|
||||
selector: (state) => state.dateTypeOptionPB,
|
||||
builder: (context, dateTypeOptionPB) {
|
||||
return FlowyButton(
|
||||
text: FlowyText.medium(title, fontSize: 12),
|
||||
hoverColor: theme.hover,
|
||||
margin: kMargin,
|
||||
onTap: () => _showTimeSetting(dateTypeOption, context),
|
||||
onTap: () => _showTimeSetting(dateTypeOptionPB, context),
|
||||
rightIcon: svgWidget("grid/more", color: theme.iconColor),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showTimeSetting(DateTypeOption dateTypeOption, BuildContext context) {
|
||||
void _showTimeSetting(
|
||||
DateTypeOptionPB dateTypeOptionPB, BuildContext context) {
|
||||
final setting = _CalDateTimeSetting(
|
||||
dateTypeOption: dateTypeOption,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
onEvent: (event) => context.read<DateCalBloc>().add(event),
|
||||
);
|
||||
setting.show(context);
|
||||
@ -330,10 +332,10 @@ class _DateTypeOptionButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _CalDateTimeSetting extends StatefulWidget {
|
||||
final DateTypeOption dateTypeOption;
|
||||
final DateTypeOptionPB dateTypeOptionPB;
|
||||
final Function(DateCalEvent) onEvent;
|
||||
const _CalDateTimeSetting(
|
||||
{required this.dateTypeOption, required this.onEvent, Key? key})
|
||||
{required this.dateTypeOptionPB, required this.onEvent, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -370,17 +372,17 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
||||
List<Widget> children = [
|
||||
DateFormatButton(onTap: () {
|
||||
final list = DateFormatList(
|
||||
selectedFormat: widget.dateTypeOption.dateFormat,
|
||||
selectedFormat: widget.dateTypeOptionPB.dateFormat,
|
||||
onSelected: (format) =>
|
||||
widget.onEvent(DateCalEvent.setDateFormat(format)),
|
||||
);
|
||||
_showOverlay(context, list);
|
||||
}),
|
||||
TimeFormatButton(
|
||||
timeFormat: widget.dateTypeOption.timeFormat,
|
||||
timeFormat: widget.dateTypeOptionPB.timeFormat,
|
||||
onTap: () {
|
||||
final list = TimeFormatList(
|
||||
selectedFormat: widget.dateTypeOption.timeFormat,
|
||||
selectedFormat: widget.dateTypeOptionPB.timeFormat,
|
||||
onSelected: (format) =>
|
||||
widget.onEvent(DateCalEvent.setTimeFormat(format)),
|
||||
);
|
||||
|
@ -25,8 +25,8 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<NumberCellBloc>(param1: cellController)
|
||||
..add(const NumberCellEvent.initial());
|
||||
_controller =
|
||||
TextEditingController(text: contentFromState(_cellBloc.state));
|
||||
|
@ -46,9 +46,9 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext =
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
|
||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellController)
|
||||
..add(const SelectOptionCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
@ -59,7 +59,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
|
||||
builder: (context, state) {
|
||||
return _SelectOptionCell(
|
||||
return SelectOptionWrap(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onFocus: (value) => widget.onCellEditing.value = value,
|
||||
@ -102,9 +102,9 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext =
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
|
||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellController)
|
||||
..add(const SelectOptionCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
@ -115,11 +115,12 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
|
||||
builder: (context, state) {
|
||||
return _SelectOptionCell(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onFocus: (value) => widget.onCellEditing.value = value,
|
||||
cellControllerBuilder: widget.cellControllerBuilder);
|
||||
return SelectOptionWrap(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onFocus: (value) => widget.onCellEditing.value = value,
|
||||
cellControllerBuilder: widget.cellControllerBuilder,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -132,16 +133,16 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
||||
}
|
||||
}
|
||||
|
||||
class _SelectOptionCell extends StatelessWidget {
|
||||
class SelectOptionWrap extends StatelessWidget {
|
||||
final List<SelectOptionPB> selectOptions;
|
||||
final void Function(bool) onFocus;
|
||||
final void Function(bool)? onFocus;
|
||||
final SelectOptionCellStyle? cellStyle;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
const _SelectOptionCell({
|
||||
const SelectOptionWrap({
|
||||
required this.selectOptions,
|
||||
required this.onFocus,
|
||||
required this.cellStyle,
|
||||
required this.cellControllerBuilder,
|
||||
this.onFocus,
|
||||
this.cellStyle,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -177,11 +178,11 @@ class _SelectOptionCell extends StatelessWidget {
|
||||
child,
|
||||
InkWell(
|
||||
onTap: () {
|
||||
onFocus(true);
|
||||
onFocus?.call(true);
|
||||
final cellContext =
|
||||
cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
SelectOptionCellEditor.show(
|
||||
context, cellContext, () => onFocus(false));
|
||||
context, cellContext, () => onFocus?.call(false));
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -39,8 +39,8 @@ class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
_cellBloc = getIt<TextCellBloc>(param1: cellController);
|
||||
_cellBloc.add(const TextCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
super.initState();
|
||||
|
@ -64,7 +64,7 @@ class _URLCellEditorState extends State<URLCellEditor> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = URLCellEditorBloc(cellContext: widget.cellController);
|
||||
_cellBloc = URLCellEditorBloc(cellController: widget.cellController);
|
||||
_cellBloc.add(const URLCellEditorEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
|
||||
|
@ -52,10 +52,10 @@ class GridURLCell extends GridCellWidget {
|
||||
GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) {
|
||||
switch (ty) {
|
||||
case GridURLCellAccessoryType.edit:
|
||||
final cellContext =
|
||||
final cellController =
|
||||
cellControllerBuilder.build() as GridURLCellController;
|
||||
return _EditURLAccessory(
|
||||
cellContext: cellContext,
|
||||
cellContext: cellController,
|
||||
anchorContext: buildContext.anchorContext);
|
||||
|
||||
case GridURLCellAccessoryType.copyURL:
|
||||
@ -92,7 +92,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
void initState() {
|
||||
final cellContext =
|
||||
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||
_cellBloc = URLCellBloc(cellContext: cellContext);
|
||||
_cellBloc = URLCellBloc(cellController: cellContext);
|
||||
_cellBloc.add(const URLCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
@ -143,7 +144,7 @@ class _DragToExpandLine extends StatelessWidget {
|
||||
|
||||
class FieldCellButton extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final GridFieldPB field;
|
||||
final FieldPB field;
|
||||
const FieldCellButton({
|
||||
required this.field,
|
||||
required this.onTap,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -15,7 +16,7 @@ import 'field_type_extension.dart';
|
||||
import 'field_type_list.dart';
|
||||
import 'type_option/builder.dart';
|
||||
|
||||
typedef UpdateFieldCallback = void Function(GridFieldPB, Uint8List);
|
||||
typedef UpdateFieldCallback = void Function(FieldPB, Uint8List);
|
||||
typedef SwitchToFieldCallback
|
||||
= Future<Either<FieldTypeOptionDataPB, FlowyError>> Function(
|
||||
String fieldId,
|
||||
@ -63,7 +64,7 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _switchFieldTypeButton(BuildContext context, GridFieldPB field) {
|
||||
Widget _switchFieldTypeButton(BuildContext context, FieldPB field) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
@ -94,8 +95,8 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
|
||||
|
||||
return makeTypeOptionWidget(
|
||||
context: context,
|
||||
dataController: widget.dataController,
|
||||
overlayDelegate: overlayDelegate,
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -168,7 +170,7 @@ class CreateFieldButton extends StatelessWidget {
|
||||
class SliverHeaderDelegateImplementation
|
||||
extends SliverPersistentHeaderDelegate {
|
||||
final String gridId;
|
||||
final List<GridFieldPB> fields;
|
||||
final List<FieldPB> fields;
|
||||
|
||||
SliverHeaderDelegateImplementation(
|
||||
{required this.gridId, required this.fields});
|
||||
|
@ -1,7 +1,15 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'checkbox.dart';
|
||||
@ -39,70 +47,147 @@ Widget? makeTypeOptionWidget({
|
||||
required TypeOptionDataController dataController,
|
||||
required TypeOptionOverlayDelegate overlayDelegate,
|
||||
}) {
|
||||
final builder = makeTypeOptionWidgetBuilder(dataController, overlayDelegate);
|
||||
final builder = makeTypeOptionWidgetBuilder(
|
||||
dataController: dataController,
|
||||
overlayDelegate: overlayDelegate,
|
||||
);
|
||||
return builder.build(context);
|
||||
}
|
||||
|
||||
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder(
|
||||
TypeOptionDataController dataController,
|
||||
TypeOptionOverlayDelegate overlayDelegate,
|
||||
) {
|
||||
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
|
||||
required TypeOptionDataController dataController,
|
||||
required TypeOptionOverlayDelegate overlayDelegate,
|
||||
}) {
|
||||
final gridId = dataController.gridId;
|
||||
final fieldType = dataController.field.fieldType;
|
||||
|
||||
switch (dataController.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
final context = CheckboxTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: CheckboxTypeOptionWidgetDataParser(),
|
||||
return CheckboxTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<CheckboxTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
return CheckboxTypeOptionWidgetBuilder(context);
|
||||
case FieldType.DateTime:
|
||||
final context = DateTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: DateTypeOptionDataParser(),
|
||||
);
|
||||
return DateTypeOptionWidgetBuilder(
|
||||
context,
|
||||
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
overlayDelegate,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
final context = SingleSelectTypeOptionContext(
|
||||
fieldContext: dataController,
|
||||
dataBuilder: SingleSelectTypeOptionWidgetDataParser(),
|
||||
);
|
||||
return SingleSelectTypeOptionWidgetBuilder(
|
||||
context,
|
||||
makeTypeOptionContextWithDataController<SingleSelectTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
overlayDelegate,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
final context = MultiSelectTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataBuilder: MultiSelectTypeOptionWidgetDataParser(),
|
||||
);
|
||||
return MultiSelectTypeOptionWidgetBuilder(
|
||||
context,
|
||||
makeTypeOptionContextWithDataController<MultiSelectTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
overlayDelegate,
|
||||
);
|
||||
case FieldType.Number:
|
||||
final context = NumberTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: NumberTypeOptionWidgetDataParser(),
|
||||
);
|
||||
return NumberTypeOptionWidgetBuilder(
|
||||
context,
|
||||
makeTypeOptionContextWithDataController<NumberTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
overlayDelegate,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
final context = RichTextTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: RichTextTypeOptionWidgetDataParser(),
|
||||
return RichTextTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<RichTextTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
return RichTextTypeOptionWidgetBuilder(context);
|
||||
|
||||
case FieldType.URL:
|
||||
final context = URLTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: URLTypeOptionWidgetDataParser(),
|
||||
return URLTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<URLTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
return URLTypeOptionWidgetBuilder(context);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
||||
TypeOptionContext<T> makeTypeOptionContext<T extends GeneratedMessage>({
|
||||
required String gridId,
|
||||
required FieldPB field,
|
||||
}) {
|
||||
final loader = FieldTypeOptionLoader(gridId: gridId, field: field);
|
||||
final dataController = TypeOptionDataController(
|
||||
gridId: gridId,
|
||||
loader: loader,
|
||||
field: field,
|
||||
);
|
||||
return makeTypeOptionContextWithDataController(
|
||||
gridId: gridId,
|
||||
fieldType: field.fieldType,
|
||||
dataController: dataController,
|
||||
);
|
||||
}
|
||||
|
||||
TypeOptionContext<T>
|
||||
makeTypeOptionContextWithDataController<T extends GeneratedMessage>({
|
||||
required String gridId,
|
||||
required FieldType fieldType,
|
||||
required TypeOptionDataController dataController,
|
||||
}) {
|
||||
switch (fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return CheckboxTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: CheckboxTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.DateTime:
|
||||
return DateTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: DateTypeOptionDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.SingleSelect:
|
||||
return SingleSelectTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: SingleSelectTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.MultiSelect:
|
||||
return MultiSelectTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: MultiSelectTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.Number:
|
||||
return NumberTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: NumberTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.RichText:
|
||||
return RichTextTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: RichTextTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
|
||||
case FieldType.URL:
|
||||
return URLTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: URLTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
}
|
||||
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
@ -1,18 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
typedef CheckboxTypeOptionContext = TypeOptionWidgetContext<CheckboxTypeOption>;
|
||||
|
||||
class CheckboxTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<CheckboxTypeOption> {
|
||||
@override
|
||||
CheckboxTypeOption fromBuffer(List<int> buffer) {
|
||||
return CheckboxTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
CheckboxTypeOptionWidgetBuilder(CheckboxTypeOptionContext typeOptionContext);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/date_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide DateFormat;
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../field_type_option_editor.dart';
|
||||
@ -12,7 +13,11 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
MultiSelectTypeOptionContext typeOptionContext,
|
||||
TypeOptionOverlayDelegate overlayDelegate,
|
||||
) : _widget = MultiSelectTypeOptionWidget(
|
||||
typeOptionContext: typeOptionContext,
|
||||
selectOptionAction: MultiSelectAction(
|
||||
fieldId: typeOptionContext.fieldId,
|
||||
gridId: typeOptionContext.gridId,
|
||||
typeOptionContext: typeOptionContext,
|
||||
),
|
||||
overlayDelegate: overlayDelegate,
|
||||
);
|
||||
|
||||
@ -21,11 +26,11 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
}
|
||||
|
||||
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
final MultiSelectTypeOptionContext typeOptionContext;
|
||||
final MultiSelectAction selectOptionAction;
|
||||
final TypeOptionOverlayDelegate overlayDelegate;
|
||||
|
||||
const MultiSelectTypeOptionWidget({
|
||||
required this.typeOptionContext,
|
||||
required this.selectOptionAction,
|
||||
required this.overlayDelegate,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -33,10 +38,10 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectOptionTypeOptionWidget(
|
||||
options: typeOptionContext.typeOption.options,
|
||||
options: selectOptionAction.typeOption.options,
|
||||
beginEdit: () => overlayDelegate.hideOverlay(context),
|
||||
overlayDelegate: overlayDelegate,
|
||||
typeOptionAction: typeOptionContext,
|
||||
typeOptionAction: selectOptionAction,
|
||||
// key: ValueKey(state.typeOption.hashCode),
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/number_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/number_format_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
@ -1,18 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
typedef RichTextTypeOptionContext = TypeOptionWidgetContext<RichTextTypeOption>;
|
||||
|
||||
class RichTextTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<RichTextTypeOption> {
|
||||
@override
|
||||
RichTextTypeOption fromBuffer(List<int> buffer) {
|
||||
return RichTextTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class RichTextTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
RichTextTypeOptionWidgetBuilder(RichTextTypeOptionContext typeOptionContext);
|
||||
|
||||
|
@ -20,7 +20,7 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
||||
final List<SelectOptionPB> options;
|
||||
final VoidCallback beginEdit;
|
||||
final TypeOptionOverlayDelegate overlayDelegate;
|
||||
final SelectOptionTypeOptionAction typeOptionAction;
|
||||
final ISelectOptionAction typeOptionAction;
|
||||
|
||||
const SelectOptionTypeOptionWidget({
|
||||
required this.options,
|
||||
@ -34,7 +34,9 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => SelectOptionTypeOptionBloc(
|
||||
options: options, typeOptionAction: typeOptionAction),
|
||||
options: options,
|
||||
typeOptionAction: typeOptionAction,
|
||||
),
|
||||
child:
|
||||
BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
||||
builder: (context, state) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/single_select_type_option.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../field_type_option_editor.dart';
|
||||
import 'builder.dart';
|
||||
@ -8,10 +9,14 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
final SingleSelectTypeOptionWidget _widget;
|
||||
|
||||
SingleSelectTypeOptionWidgetBuilder(
|
||||
SingleSelectTypeOptionContext typeOptionContext,
|
||||
SingleSelectTypeOptionContext singleSelectTypeOption,
|
||||
TypeOptionOverlayDelegate overlayDelegate,
|
||||
) : _widget = SingleSelectTypeOptionWidget(
|
||||
typeOptionContext: typeOptionContext,
|
||||
selectOptionAction: SingleSelectAction(
|
||||
fieldId: singleSelectTypeOption.fieldId,
|
||||
gridId: singleSelectTypeOption.gridId,
|
||||
typeOptionContext: singleSelectTypeOption,
|
||||
),
|
||||
overlayDelegate: overlayDelegate,
|
||||
);
|
||||
|
||||
@ -20,11 +25,11 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
}
|
||||
|
||||
class SingleSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
final SingleSelectTypeOptionContext typeOptionContext;
|
||||
final SingleSelectAction selectOptionAction;
|
||||
final TypeOptionOverlayDelegate overlayDelegate;
|
||||
|
||||
const SingleSelectTypeOptionWidget({
|
||||
required this.typeOptionContext,
|
||||
required this.selectOptionAction,
|
||||
required this.overlayDelegate,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -32,10 +37,10 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectOptionTypeOptionWidget(
|
||||
options: typeOptionContext.typeOption.options,
|
||||
options: selectOptionAction.typeOption.options,
|
||||
beginEdit: () => overlayDelegate.hideOverlay(context),
|
||||
overlayDelegate: overlayDelegate,
|
||||
typeOptionAction: typeOptionContext,
|
||||
typeOptionAction: selectOptionAction,
|
||||
// key: ValueKey(state.typeOption.hashCode),
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
typedef URLTypeOptionContext = TypeOptionWidgetContext<URLTypeOption>;
|
||||
|
||||
class URLTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<URLTypeOption> {
|
||||
@override
|
||||
URLTypeOption fromBuffer(List<int> buffer) {
|
||||
return URLTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class URLTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
URLTypeOptionWidgetBuilder(URLTypeOptionContext typeOptionContext);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -13,22 +14,20 @@ import '../cell/cell_accessory.dart';
|
||||
import '../cell/cell_container.dart';
|
||||
import '../cell/prelude.dart';
|
||||
import 'row_action_sheet.dart';
|
||||
import 'row_detail.dart';
|
||||
|
||||
class GridRowWidget extends StatefulWidget {
|
||||
final GridRowInfo rowData;
|
||||
final RowInfo rowInfo;
|
||||
final GridRowDataController dataController;
|
||||
final GridCellBuilder cellBuilder;
|
||||
final void Function(BuildContext, GridCellBuilder) openDetailPage;
|
||||
|
||||
GridRowWidget({
|
||||
required this.rowData,
|
||||
const GridRowWidget({
|
||||
required this.rowInfo,
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
required this.openDetailPage,
|
||||
Key? key,
|
||||
}) : cellBuilder = GridCellBuilder(
|
||||
cellCache: dataController.rowCache.cellCache,
|
||||
fieldCache: dataController.fieldCache,
|
||||
),
|
||||
super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<GridRowWidget> createState() => _GridRowWidgetState();
|
||||
@ -40,7 +39,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
_rowBloc = RowBloc(
|
||||
rowInfo: widget.rowData,
|
||||
rowInfo: widget.rowInfo,
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
_rowBloc.add(const RowEvent.initial());
|
||||
@ -55,17 +54,20 @@ class _GridRowWidgetState extends State<GridRowWidget> {
|
||||
child: BlocBuilder<RowBloc, RowState>(
|
||||
buildWhen: (p, c) => p.rowInfo.height != c.rowInfo.height,
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
children: [
|
||||
const _RowLeading(),
|
||||
Expanded(
|
||||
child: _RowCells(
|
||||
final children = [
|
||||
const _RowLeading(),
|
||||
Expanded(
|
||||
child: RowContent(
|
||||
builder: widget.cellBuilder,
|
||||
onExpand: () => _expandRow(context),
|
||||
)),
|
||||
const _RowTrailing(),
|
||||
],
|
||||
);
|
||||
onExpand: () => widget.openDetailPage(
|
||||
context,
|
||||
widget.cellBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
const _RowTrailing(),
|
||||
];
|
||||
return Row(children: children);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -77,15 +79,6 @@ class _GridRowWidgetState extends State<GridRowWidget> {
|
||||
_rowBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _expandRow(BuildContext context) {
|
||||
final page = RowDetailPage(
|
||||
rowInfo: widget.rowData,
|
||||
rowCache: widget.dataController.rowCache,
|
||||
cellBuilder: widget.cellBuilder,
|
||||
);
|
||||
page.show(context);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowLeading extends StatelessWidget {
|
||||
@ -158,10 +151,10 @@ class _DeleteRowButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _RowCells extends StatelessWidget {
|
||||
class RowContent extends StatelessWidget {
|
||||
final VoidCallback onExpand;
|
||||
final GridCellBuilder builder;
|
||||
const _RowCells({
|
||||
const RowContent({
|
||||
required this.builder,
|
||||
required this.onExpand,
|
||||
Key? key,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_action_sheet_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -12,10 +11,11 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../application/row/row_cache.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
|
||||
class GridRowActionSheet extends StatelessWidget {
|
||||
final GridRowInfo rowData;
|
||||
final RowInfo rowData;
|
||||
const GridRowActionSheet({required this.rowData, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -21,13 +21,11 @@ import '../header/field_cell.dart';
|
||||
import '../header/field_editor.dart';
|
||||
|
||||
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
|
||||
final GridRowInfo rowInfo;
|
||||
final GridRowCache rowCache;
|
||||
final GridRowDataController dataController;
|
||||
final GridCellBuilder cellBuilder;
|
||||
|
||||
const RowDetailPage({
|
||||
required this.rowInfo,
|
||||
required this.rowCache,
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -63,8 +61,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
final bloc = RowDetailBloc(
|
||||
rowInfo: widget.rowInfo,
|
||||
rowCache: widget.rowCache,
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
bloc.add(const RowDetailEvent.initial());
|
||||
return bloc;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/setting/property_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -15,6 +14,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../../../application/field/field_cache.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
import '../header/field_editor.dart';
|
||||
|
||||
@ -78,7 +78,7 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
|
||||
}
|
||||
|
||||
class _GridPropertyCell extends StatelessWidget {
|
||||
final GridFieldPB field;
|
||||
final FieldPB field;
|
||||
final String gridId;
|
||||
const _GridPropertyCell({required this.gridId, required this.field, Key? key})
|
||||
: super(key: key);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -12,6 +11,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import '../../../application/field/field_cache.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
import 'grid_property.dart';
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||
@ -6,6 +5,7 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../application/field/field_cache.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
import 'grid_setting.dart';
|
||||
|
||||
|
@ -21,6 +21,8 @@ import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../plugins/grid/application/field/field_cache.dart';
|
||||
|
||||
class DependencyResolver {
|
||||
static Future<void> resolve(GetIt getIt) async {
|
||||
_resolveUserDeps(getIt);
|
||||
@ -168,33 +170,33 @@ void _resolveGridDeps(GetIt getIt) {
|
||||
|
||||
getIt.registerFactoryParam<TextCellBloc, GridCellController, void>(
|
||||
(context, _) => TextCellBloc(
|
||||
cellContext: context,
|
||||
cellController: context,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<SelectOptionCellBloc,
|
||||
GridSelectOptionCellController, void>(
|
||||
(context, _) => SelectOptionCellBloc(
|
||||
cellContext: context,
|
||||
cellController: context,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NumberCellBloc, GridCellController, void>(
|
||||
(context, _) => NumberCellBloc(
|
||||
cellContext: context,
|
||||
cellController: context,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<DateCellBloc, GridDateCellController, void>(
|
||||
(context, _) => DateCellBloc(
|
||||
cellContext: context,
|
||||
cellController: context,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<CheckboxCellBloc, GridCellController, void>(
|
||||
(cellData, _) => CheckboxCellBloc(
|
||||
service: CellService(),
|
||||
cellContext: cellData,
|
||||
cellController: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -10,7 +10,7 @@ import 'package:flutter/widgets.dart';
|
||||
export "./src/sandbox.dart";
|
||||
|
||||
enum DefaultPlugin {
|
||||
quill,
|
||||
editor,
|
||||
blank,
|
||||
trash,
|
||||
grid,
|
||||
@ -20,7 +20,7 @@ enum DefaultPlugin {
|
||||
extension FlowyDefaultPluginExt on DefaultPlugin {
|
||||
int type() {
|
||||
switch (this) {
|
||||
case DefaultPlugin.quill:
|
||||
case DefaultPlugin.editor:
|
||||
return 0;
|
||||
case DefaultPlugin.blank:
|
||||
return 1;
|
||||
@ -35,7 +35,6 @@ extension FlowyDefaultPluginExt on DefaultPlugin {
|
||||
}
|
||||
|
||||
typedef PluginType = int;
|
||||
typedef PluginDataType = ViewDataType;
|
||||
typedef PluginId = String;
|
||||
|
||||
abstract class Plugin {
|
||||
@ -55,7 +54,9 @@ abstract class PluginBuilder {
|
||||
|
||||
PluginType get pluginType;
|
||||
|
||||
ViewDataType get dataType => ViewDataType.TextBlock;
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.TextBlock;
|
||||
|
||||
SubViewDataTypePB? get subDataType => null;
|
||||
}
|
||||
|
||||
abstract class PluginConfig {
|
||||
|
@ -86,6 +86,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
||||
desc: value.desc,
|
||||
dataType: value.dataType,
|
||||
pluginType: value.pluginType,
|
||||
subDataType: value.subDataType,
|
||||
);
|
||||
viewOrFailed.fold(
|
||||
(view) => emit(state.copyWith(
|
||||
@ -138,7 +139,8 @@ class AppEvent with _$AppEvent {
|
||||
const factory AppEvent.createView(
|
||||
String name,
|
||||
String desc,
|
||||
PluginDataType dataType,
|
||||
ViewDataTypePB dataType,
|
||||
SubViewDataTypePB? subDataType,
|
||||
PluginType pluginType,
|
||||
) = CreateView;
|
||||
const factory AppEvent.delete() = Delete;
|
||||
|
@ -24,16 +24,21 @@ class AppService {
|
||||
required String appId,
|
||||
required String name,
|
||||
required String desc,
|
||||
required PluginDataType dataType,
|
||||
required ViewDataTypePB dataType,
|
||||
required PluginType pluginType,
|
||||
SubViewDataTypePB? subDataType,
|
||||
}) {
|
||||
final payload = CreateViewPayloadPB.create()
|
||||
var payload = CreateViewPayloadPB.create()
|
||||
..belongToId = appId
|
||||
..name = name
|
||||
..desc = desc
|
||||
..dataType = dataType
|
||||
..pluginType = pluginType;
|
||||
|
||||
if (subDataType != null) {
|
||||
payload.subDataType = subDataType;
|
||||
}
|
||||
|
||||
return FolderEventCreateView(payload).send();
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,9 @@ class MenuAppHeader extends StatelessWidget {
|
||||
height: MenuAppSizes.headerHeight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
ExpandableController.of(context, rebuildOnChange: false, required: true)?.toggle();
|
||||
ExpandableController.of(context,
|
||||
rebuildOnChange: false, required: true)
|
||||
?.toggle();
|
||||
},
|
||||
child: ExpandableIcon(
|
||||
theme: ExpandableThemeData(
|
||||
@ -68,18 +70,23 @@ class MenuAppHeader extends StatelessWidget {
|
||||
Widget _renderTitle(BuildContext context, AppTheme theme) {
|
||||
return Expanded(
|
||||
child: BlocListener<AppBloc, AppState>(
|
||||
listenWhen: (p, c) => (p.latestCreatedView == null && c.latestCreatedView != null),
|
||||
listenWhen: (p, c) =>
|
||||
(p.latestCreatedView == null && c.latestCreatedView != null),
|
||||
listener: (context, state) {
|
||||
final expandableController = ExpandableController.of(context, rebuildOnChange: false, required: true)!;
|
||||
final expandableController = ExpandableController.of(context,
|
||||
rebuildOnChange: false, required: true)!;
|
||||
if (!expandableController.expanded) {
|
||||
expandableController.toggle();
|
||||
}
|
||||
},
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => ExpandableController.of(context, rebuildOnChange: false, required: true)?.toggle(),
|
||||
onTap: () => ExpandableController.of(context,
|
||||
rebuildOnChange: false, required: true)
|
||||
?.toggle(),
|
||||
onSecondaryTap: () {
|
||||
final actionList = AppDisclosureActionSheet(onSelected: (action) => _handleAction(context, action));
|
||||
final actionList = AppDisclosureActionSheet(
|
||||
onSelected: (action) => _handleAction(context, action));
|
||||
actionList.show(
|
||||
context,
|
||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||
@ -107,6 +114,7 @@ class MenuAppHeader extends StatelessWidget {
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
"",
|
||||
pluginBuilder.dataType,
|
||||
pluginBuilder.subDataType,
|
||||
pluginBuilder.pluginType,
|
||||
));
|
||||
},
|
||||
|
@ -1632,7 +1632,7 @@ final Map<String, String> activities = Map.fromIterables([
|
||||
'Flying Disc',
|
||||
'Bowling',
|
||||
'Cricket Game',
|
||||
'GridFieldPB Hockey',
|
||||
'FieldPB Hockey',
|
||||
'Ice Hockey',
|
||||
'Lacrosse',
|
||||
'Ping Pong',
|
||||
|
@ -9,7 +9,7 @@ class MultiBoardListExample extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
final BoardDataController boardDataController = BoardDataController(
|
||||
final AFBoardDataController boardDataController = AFBoardDataController(
|
||||
onMoveColumn: (fromIndex, toIndex) {
|
||||
debugPrint('Move column from $fromIndex to $toIndex');
|
||||
},
|
||||
@ -23,18 +23,18 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final column1 = BoardColumnData(id: "To Do", items: [
|
||||
final column1 = AFBoardColumnData(id: "To Do", items: [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 4"),
|
||||
]);
|
||||
final column2 = BoardColumnData(id: "In Progress", items: [
|
||||
final column2 = AFBoardColumnData(id: "In Progress", items: [
|
||||
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 6"),
|
||||
]);
|
||||
|
||||
final column3 = BoardColumnData(id: "Done", items: []);
|
||||
final column3 = AFBoardColumnData(id: "Done", items: []);
|
||||
|
||||
boardDataController.addColumn(column1);
|
||||
boardDataController.addColumn(column2);
|
||||
@ -45,14 +45,14 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final config = BoardConfig(
|
||||
final config = AFBoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Board(
|
||||
child: AFBoard(
|
||||
dataController: boardDataController,
|
||||
footBuilder: (context, columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
@ -79,7 +79,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
);
|
||||
},
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
config: BoardConfig(
|
||||
config: AFBoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
),
|
||||
),
|
||||
@ -87,7 +87,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(ColumnItem item) {
|
||||
Widget _buildCard(AFColumnItem item) {
|
||||
if (item is TextItem) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
@ -126,7 +126,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
}
|
||||
}
|
||||
|
||||
class TextItem extends ColumnItem {
|
||||
class TextItem extends AFColumnItem {
|
||||
final String s;
|
||||
|
||||
TextItem(this.s);
|
||||
@ -135,7 +135,7 @@ class TextItem extends ColumnItem {
|
||||
String get id => s;
|
||||
}
|
||||
|
||||
class RichTextItem extends ColumnItem {
|
||||
class RichTextItem extends AFColumnItem {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
|
@ -9,11 +9,11 @@ class SingleBoardListExample extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SingleBoardListExampleState extends State<SingleBoardListExample> {
|
||||
final BoardDataController boardData = BoardDataController();
|
||||
final AFBoardDataController boardData = AFBoardDataController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final column = BoardColumnData(id: "1", items: [
|
||||
final column = AFBoardColumnData(id: "1", items: [
|
||||
TextItem("a"),
|
||||
TextItem("b"),
|
||||
TextItem("c"),
|
||||
@ -26,7 +26,7 @@ class _SingleBoardListExampleState extends State<SingleBoardListExample> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Board(
|
||||
return AFBoard(
|
||||
dataController: boardData,
|
||||
cardBuilder: (context, item) {
|
||||
return _RowWidget(item: item as TextItem, key: ObjectKey(item));
|
||||
@ -50,7 +50,7 @@ class _RowWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class TextItem extends ColumnItem {
|
||||
class TextItem extends AFColumnItem {
|
||||
final String s;
|
||||
|
||||
TextItem(this.s);
|
||||
|
@ -20,6 +20,12 @@ class Log {
|
||||
}
|
||||
}
|
||||
|
||||
static void warn(String? message) {
|
||||
if (enableLog) {
|
||||
debugPrint('🐛[Warn]=> $message');
|
||||
}
|
||||
}
|
||||
|
||||
static void trace(String? message) {
|
||||
if (enableLog) {
|
||||
// debugPrint('❗️[Trace]=> $message');
|
||||
|
@ -8,13 +8,13 @@ import 'reorder_flex/reorder_flex.dart';
|
||||
import 'reorder_phantom/phantom_controller.dart';
|
||||
import '../rendering/board_overlay.dart';
|
||||
|
||||
class BoardConfig {
|
||||
class AFBoardConfig {
|
||||
final double cornerRadius;
|
||||
final EdgeInsets columnPadding;
|
||||
final EdgeInsets columnItemPadding;
|
||||
final Color columnBackgroundColor;
|
||||
|
||||
const BoardConfig({
|
||||
const AFBoardConfig({
|
||||
this.cornerRadius = 6.0,
|
||||
this.columnPadding = const EdgeInsets.symmetric(horizontal: 8),
|
||||
this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 10),
|
||||
@ -22,7 +22,7 @@ class BoardConfig {
|
||||
});
|
||||
}
|
||||
|
||||
class Board extends StatelessWidget {
|
||||
class AFBoard extends StatelessWidget {
|
||||
/// The direction to use as the main axis.
|
||||
final Axis direction = Axis.vertical;
|
||||
|
||||
@ -30,32 +30,32 @@ class Board extends StatelessWidget {
|
||||
final Widget? background;
|
||||
|
||||
///
|
||||
final BoardColumnCardBuilder cardBuilder;
|
||||
final AFBoardColumnCardBuilder cardBuilder;
|
||||
|
||||
///
|
||||
final BoardColumnHeaderBuilder? headerBuilder;
|
||||
final AFBoardColumnHeaderBuilder? headerBuilder;
|
||||
|
||||
///
|
||||
final BoardColumnFooterBuilder? footBuilder;
|
||||
final AFBoardColumnFooterBuilder? footBuilder;
|
||||
|
||||
///
|
||||
final BoardDataController dataController;
|
||||
final AFBoardDataController dataController;
|
||||
|
||||
final BoxConstraints columnConstraints;
|
||||
|
||||
///
|
||||
final BoardPhantomController phantomController;
|
||||
|
||||
final BoardConfig config;
|
||||
final AFBoardConfig config;
|
||||
|
||||
Board({
|
||||
AFBoard({
|
||||
required this.dataController,
|
||||
required this.cardBuilder,
|
||||
this.background,
|
||||
this.footBuilder,
|
||||
this.headerBuilder,
|
||||
this.columnConstraints = const BoxConstraints(maxWidth: 200),
|
||||
this.config = const BoardConfig(),
|
||||
this.config = const AFBoardConfig(),
|
||||
Key? key,
|
||||
}) : phantomController = BoardPhantomController(delegate: dataController),
|
||||
super(key: key);
|
||||
@ -64,7 +64,7 @@ class Board extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: dataController,
|
||||
child: Consumer<BoardDataController>(
|
||||
child: Consumer<AFBoardDataController>(
|
||||
builder: (context, notifier, child) {
|
||||
return BoardContent(
|
||||
config: config,
|
||||
@ -89,20 +89,20 @@ class BoardContent extends StatefulWidget {
|
||||
final OnDragStarted? onDragStarted;
|
||||
final OnReorder onReorder;
|
||||
final OnDragEnded? onDragEnded;
|
||||
final BoardDataController dataController;
|
||||
final AFBoardDataController dataController;
|
||||
final Widget? background;
|
||||
final BoardConfig config;
|
||||
final AFBoardConfig config;
|
||||
final ReorderFlexConfig reorderFlexConfig;
|
||||
final BoxConstraints columnConstraints;
|
||||
|
||||
///
|
||||
final BoardColumnCardBuilder cardBuilder;
|
||||
final AFBoardColumnCardBuilder cardBuilder;
|
||||
|
||||
///
|
||||
final BoardColumnHeaderBuilder? headerBuilder;
|
||||
final AFBoardColumnHeaderBuilder? headerBuilder;
|
||||
|
||||
///
|
||||
final BoardColumnFooterBuilder? footBuilder;
|
||||
final AFBoardColumnFooterBuilder? footBuilder;
|
||||
|
||||
final OverlapDragTargetDelegate delegate;
|
||||
|
||||
@ -206,7 +206,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
builder: (context, value, child) {
|
||||
return ConstrainedBox(
|
||||
constraints: widget.columnConstraints,
|
||||
child: BoardColumnWidget(
|
||||
child: AFBoardColumnWidget(
|
||||
margin: _marginFromIndex(columnIndex),
|
||||
itemMargin: widget.config.columnItemPadding,
|
||||
headerBuilder: widget.headerBuilder,
|
||||
@ -246,9 +246,9 @@ class _BoardContentState extends State<BoardContent> {
|
||||
}
|
||||
}
|
||||
|
||||
class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource {
|
||||
class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
|
||||
String columnId;
|
||||
final BoardDataController dataController;
|
||||
final AFBoardDataController dataController;
|
||||
|
||||
_BoardColumnDataSourceImpl({
|
||||
required this.columnId,
|
||||
@ -256,7 +256,7 @@ class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource {
|
||||
});
|
||||
|
||||
@override
|
||||
BoardColumnData get columnData =>
|
||||
AFBoardColumnData get columnData =>
|
||||
dataController.columnController(columnId).columnData;
|
||||
|
||||
@override
|
||||
|
@ -22,23 +22,23 @@ typedef OnColumnDeleted = void Function(String listId, int deletedIndex);
|
||||
|
||||
typedef OnColumnInserted = void Function(String listId, int insertedIndex);
|
||||
|
||||
typedef BoardColumnCardBuilder = Widget Function(
|
||||
typedef AFBoardColumnCardBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
ColumnItem item,
|
||||
AFColumnItem item,
|
||||
);
|
||||
|
||||
typedef BoardColumnHeaderBuilder = Widget Function(
|
||||
typedef AFBoardColumnHeaderBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
BoardColumnData columnData,
|
||||
AFBoardColumnData columnData,
|
||||
);
|
||||
|
||||
typedef BoardColumnFooterBuilder = Widget Function(
|
||||
typedef AFBoardColumnFooterBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
BoardColumnData columnData,
|
||||
AFBoardColumnData columnData,
|
||||
);
|
||||
|
||||
abstract class BoardColumnDataDataSource extends ReoderFlextDataSource {
|
||||
BoardColumnData get columnData;
|
||||
abstract class AFBoardColumnDataDataSource extends ReoderFlextDataSource {
|
||||
AFBoardColumnData get columnData;
|
||||
|
||||
List<String> get acceptedColumnIds;
|
||||
|
||||
@ -46,10 +46,10 @@ abstract class BoardColumnDataDataSource extends ReoderFlextDataSource {
|
||||
String get identifier => columnData.id;
|
||||
|
||||
@override
|
||||
UnmodifiableListView<ColumnItem> get items => columnData.items;
|
||||
UnmodifiableListView<AFColumnItem> get items => columnData.items;
|
||||
|
||||
void debugPrint() {
|
||||
String msg = '[$BoardColumnDataDataSource] $columnData data: ';
|
||||
String msg = '[$AFBoardColumnDataDataSource] $columnData data: ';
|
||||
for (var element in items) {
|
||||
msg = '$msg$element,';
|
||||
}
|
||||
@ -58,10 +58,10 @@ abstract class BoardColumnDataDataSource extends ReoderFlextDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// [BoardColumnWidget] represents the column of the Board.
|
||||
/// [AFBoardColumnWidget] represents the column of the Board.
|
||||
///
|
||||
class BoardColumnWidget extends StatefulWidget {
|
||||
final BoardColumnDataDataSource dataSource;
|
||||
class AFBoardColumnWidget extends StatefulWidget {
|
||||
final AFBoardColumnDataDataSource dataSource;
|
||||
final ScrollController? scrollController;
|
||||
final ReorderFlexConfig config;
|
||||
|
||||
@ -73,11 +73,11 @@ class BoardColumnWidget extends StatefulWidget {
|
||||
|
||||
String get columnId => dataSource.columnData.id;
|
||||
|
||||
final BoardColumnCardBuilder cardBuilder;
|
||||
final AFBoardColumnCardBuilder cardBuilder;
|
||||
|
||||
final BoardColumnHeaderBuilder? headerBuilder;
|
||||
final AFBoardColumnHeaderBuilder? headerBuilder;
|
||||
|
||||
final BoardColumnFooterBuilder? footBuilder;
|
||||
final AFBoardColumnFooterBuilder? footBuilder;
|
||||
|
||||
final EdgeInsets margin;
|
||||
|
||||
@ -87,7 +87,7 @@ class BoardColumnWidget extends StatefulWidget {
|
||||
|
||||
final Color backgroundColor;
|
||||
|
||||
const BoardColumnWidget({
|
||||
const AFBoardColumnWidget({
|
||||
Key? key,
|
||||
this.headerBuilder,
|
||||
this.footBuilder,
|
||||
@ -106,12 +106,12 @@ class BoardColumnWidget extends StatefulWidget {
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardColumnWidget> createState() => _BoardColumnWidgetState();
|
||||
State<AFBoardColumnWidget> createState() => _AFBoardColumnWidgetState();
|
||||
}
|
||||
|
||||
class _BoardColumnWidgetState extends State<BoardColumnWidget> {
|
||||
class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
|
||||
final GlobalKey _columnOverlayKey =
|
||||
GlobalKey(debugLabel: '$BoardColumnWidget overlay key');
|
||||
GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key');
|
||||
|
||||
late BoardOverlayEntry _overlayEntry;
|
||||
|
||||
@ -194,7 +194,7 @@ class _BoardColumnWidgetState extends State<BoardColumnWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWidget(BuildContext context, ColumnItem item) {
|
||||
Widget _buildWidget(BuildContext context, AFColumnItem item) {
|
||||
if (item is PhantomColumnItem) {
|
||||
return PassthroughPhantomWidget(
|
||||
key: UniqueKey(),
|
||||
|
@ -5,14 +5,14 @@ import 'package:flutter/material.dart';
|
||||
import '../../utils/log.dart';
|
||||
import '../reorder_flex/reorder_flex.dart';
|
||||
|
||||
abstract class ColumnItem extends ReoderFlexItem {
|
||||
abstract class AFColumnItem extends ReoderFlexItem {
|
||||
bool get isPhantom => false;
|
||||
|
||||
@override
|
||||
String toString() => id;
|
||||
}
|
||||
|
||||
/// [BoardColumnDataController] is used to handle the [BoardColumnData].
|
||||
/// [BoardColumnDataController] is used to handle the [AFBoardColumnData].
|
||||
/// * Remove an item by calling [removeAt] method.
|
||||
/// * Move item to another position by calling [move] method.
|
||||
/// * Insert item to index by calling [insert] method
|
||||
@ -21,7 +21,7 @@ abstract class ColumnItem extends ReoderFlexItem {
|
||||
/// All there operations will notify listeners by default.
|
||||
///
|
||||
class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
final BoardColumnData columnData;
|
||||
final AFBoardColumnData columnData;
|
||||
|
||||
BoardColumnDataController({
|
||||
required this.columnData,
|
||||
@ -31,7 +31,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
List<Object?> get props => columnData.props;
|
||||
|
||||
/// Returns the readonly List<ColumnItem>
|
||||
UnmodifiableListView<ColumnItem> get items =>
|
||||
UnmodifiableListView<AFColumnItem> get items =>
|
||||
UnmodifiableListView(columnData.items);
|
||||
|
||||
/// Remove the item at [index].
|
||||
@ -39,7 +39,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
/// * [notify] the default value of [notify] is true, it will notify the
|
||||
/// listener. Set to [false] if you do not want to notify the listeners.
|
||||
///
|
||||
ColumnItem removeAt(int index, {bool notify = true}) {
|
||||
AFColumnItem removeAt(int index, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
|
||||
Log.debug('[$BoardColumnDataController] $columnData remove item at $index');
|
||||
@ -50,31 +50,32 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
return item;
|
||||
}
|
||||
|
||||
int removeWhere(bool Function(ColumnItem) condition) {
|
||||
int removeWhere(bool Function(AFColumnItem) condition) {
|
||||
return items.indexWhere(condition);
|
||||
}
|
||||
|
||||
/// Move the item from [fromIndex] to [toIndex]. It will do nothing if the
|
||||
/// [fromIndex] equal to the [toIndex].
|
||||
void move(int fromIndex, int toIndex) {
|
||||
bool move(int fromIndex, int toIndex) {
|
||||
assert(fromIndex >= 0);
|
||||
assert(toIndex >= 0);
|
||||
|
||||
if (fromIndex == toIndex) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
||||
final item = columnData._items.removeAt(fromIndex);
|
||||
columnData._items.insert(toIndex, item);
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Insert an item to [index] and notify the listen if the value of [notify]
|
||||
/// is true.
|
||||
///
|
||||
/// The default value of [notify] is true.
|
||||
void insert(int index, ColumnItem item, {bool notify = true}) {
|
||||
bool insert(int index, AFColumnItem item, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData insert $item at $index');
|
||||
@ -85,13 +86,18 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
columnData._items.add(item);
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
notifyListeners();
|
||||
}
|
||||
if (notify) notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool add(AFColumnItem item, {bool notify = true}) {
|
||||
columnData._items.add(item);
|
||||
if (notify) notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Replace the item at index with the [newItem].
|
||||
void replace(int index, ColumnItem newItem) {
|
||||
void replace(int index, AFColumnItem newItem) {
|
||||
if (columnData._items.isEmpty) {
|
||||
columnData._items.add(newItem);
|
||||
Log.debug('[$BoardColumnDataController] $columnData add $newItem');
|
||||
@ -106,19 +112,23 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
}
|
||||
}
|
||||
|
||||
/// [BoardColumnData] represents the data of each Column of the Board.
|
||||
class BoardColumnData extends ReoderFlexItem with EquatableMixin {
|
||||
/// [AFBoardColumnData] represents the data of each Column of the Board.
|
||||
class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
|
||||
@override
|
||||
final String id;
|
||||
final List<ColumnItem> _items;
|
||||
final String desc;
|
||||
final List<AFColumnItem> _items;
|
||||
final CustomData? customData;
|
||||
|
||||
BoardColumnData({
|
||||
AFBoardColumnData({
|
||||
this.customData,
|
||||
required this.id,
|
||||
required List<ColumnItem> items,
|
||||
this.desc = "",
|
||||
List<AFColumnItem> items = const [],
|
||||
}) : _items = items;
|
||||
|
||||
/// Returns the readonly List<ColumnItem>
|
||||
UnmodifiableListView<ColumnItem> get items => UnmodifiableListView(_items);
|
||||
UnmodifiableListView<AFColumnItem> get items => UnmodifiableListView(_items);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, ..._items];
|
||||
|
@ -23,14 +23,14 @@ typedef OnMoveColumnItemToColumn = void Function(
|
||||
int toIndex,
|
||||
);
|
||||
|
||||
class BoardDataController extends ChangeNotifier
|
||||
class AFBoardDataController extends ChangeNotifier
|
||||
with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource {
|
||||
final List<BoardColumnData> _columnDatas = [];
|
||||
final List<AFBoardColumnData> _columnDatas = [];
|
||||
final OnMoveColumn? onMoveColumn;
|
||||
final OnMoveColumnItem? onMoveColumnItem;
|
||||
final OnMoveColumnItemToColumn? onMoveColumnItemToColumn;
|
||||
|
||||
List<BoardColumnData> get columnDatas => _columnDatas;
|
||||
List<AFBoardColumnData> get columnDatas => _columnDatas;
|
||||
|
||||
List<String> get columnIds =>
|
||||
_columnDatas.map((columnData) => columnData.id).toList();
|
||||
@ -38,38 +38,90 @@ class BoardDataController extends ChangeNotifier
|
||||
final LinkedHashMap<String, BoardColumnDataController> _columnControllers =
|
||||
LinkedHashMap();
|
||||
|
||||
BoardDataController({
|
||||
AFBoardDataController({
|
||||
this.onMoveColumn,
|
||||
this.onMoveColumnItem,
|
||||
this.onMoveColumnItemToColumn,
|
||||
});
|
||||
|
||||
void addColumn(BoardColumnData columnData) {
|
||||
void addColumn(AFBoardColumnData columnData, {bool notify = true}) {
|
||||
if (_columnControllers[columnData.id] != null) return;
|
||||
|
||||
final controller = BoardColumnDataController(columnData: columnData);
|
||||
_columnDatas.add(columnData);
|
||||
_columnControllers[columnData.id] = controller;
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
void addColumns(List<AFBoardColumnData> columns, {bool notify = true}) {
|
||||
for (final column in columns) {
|
||||
addColumn(column, notify: false);
|
||||
}
|
||||
|
||||
if (columns.isNotEmpty && notify) notifyListeners();
|
||||
}
|
||||
|
||||
void removeColumn(String columnId, {bool notify = true}) {
|
||||
final index = _columnDatas.indexWhere((column) => column.id == columnId);
|
||||
if (index == -1) {
|
||||
Log.warn(
|
||||
'Try to remove Column:[$columnId] failed. Column:[$columnId] not exist');
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
_columnDatas.removeAt(index);
|
||||
_columnControllers.remove(columnId);
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void removeColumns(List<String> columnIds, {bool notify = true}) {
|
||||
for (final columnId in columnIds) {
|
||||
removeColumn(columnId, notify: false);
|
||||
}
|
||||
|
||||
if (columnIds.isNotEmpty && notify) notifyListeners();
|
||||
}
|
||||
|
||||
BoardColumnDataController columnController(String columnId) {
|
||||
return _columnControllers[columnId]!;
|
||||
}
|
||||
|
||||
void moveColumn(int fromIndex, int toIndex) {
|
||||
BoardColumnDataController? getColumnController(String columnId) {
|
||||
final columnController = _columnControllers[columnId];
|
||||
if (columnController == null) {
|
||||
Log.warn('Column:[$columnId] \'s controller is not exist');
|
||||
}
|
||||
|
||||
return columnController;
|
||||
}
|
||||
|
||||
void moveColumn(int fromIndex, int toIndex, {bool notify = true}) {
|
||||
final columnData = _columnDatas.removeAt(fromIndex);
|
||||
_columnDatas.insert(toIndex, columnData);
|
||||
onMoveColumn?.call(fromIndex, toIndex);
|
||||
notifyListeners();
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
void moveColumnItem(String columnId, int fromIndex, int toIndex) {
|
||||
final columnController = _columnControllers[columnId];
|
||||
assert(columnController != null);
|
||||
if (columnController != null) {
|
||||
columnController.move(fromIndex, toIndex);
|
||||
if (getColumnController(columnId)?.move(fromIndex, toIndex) ?? false) {
|
||||
onMoveColumnItem?.call(columnId, fromIndex, toIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void addColumnItem(String columnId, AFColumnItem item) {
|
||||
getColumnController(columnId)?.add(item);
|
||||
}
|
||||
|
||||
void insertColumnItem(String columnId, int index, AFColumnItem item) {
|
||||
getColumnController(columnId)?.insert(index, item);
|
||||
}
|
||||
|
||||
void removeColumnItem(String columnId, String itemId) {
|
||||
getColumnController(columnId)?.removeWhere((item) => item.id == itemId);
|
||||
}
|
||||
|
||||
@override
|
||||
@protected
|
||||
void swapColumnItem(
|
||||
@ -106,7 +158,7 @@ class BoardDataController extends ChangeNotifier
|
||||
}
|
||||
|
||||
@override
|
||||
String get identifier => '$BoardDataController';
|
||||
String get identifier => '$AFBoardDataController';
|
||||
|
||||
@override
|
||||
UnmodifiableListView<ReoderFlexItem> get items =>
|
||||
@ -123,7 +175,7 @@ class BoardDataController extends ChangeNotifier
|
||||
columnController.removeAt(index);
|
||||
|
||||
Log.debug(
|
||||
'[$BoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}');
|
||||
'[$AFBoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}');
|
||||
}
|
||||
return isExist;
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ class PhantomRecord {
|
||||
}
|
||||
}
|
||||
|
||||
class PhantomColumnItem extends ColumnItem {
|
||||
class PhantomColumnItem extends AFColumnItem {
|
||||
final PassthroughPhantomContext phantomContext;
|
||||
|
||||
PhantomColumnItem(PassthroughPhantomContext insertedPhantom)
|
||||
@ -290,7 +290,7 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger
|
||||
|
||||
Widget? get draggingWidget => dragTargetData.draggingWidget;
|
||||
|
||||
ColumnItem get itemData => dragTargetData.reorderFlexItem as ColumnItem;
|
||||
AFColumnItem get itemData => dragTargetData.reorderFlexItem as AFColumnItem;
|
||||
|
||||
@override
|
||||
VoidCallback? onInserted;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user