mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: create column errors
This commit is contained in:
parent
aaee3003ce
commit
292b90c14c
@ -1,17 +1,161 @@
|
|||||||
// ignore_for_file: unused_field
|
// ignore_for_file: unused_field
|
||||||
|
|
||||||
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BoardPage extends StatelessWidget {
|
class BoardPage extends StatefulWidget {
|
||||||
final ViewPB _view;
|
final ViewPB _view;
|
||||||
|
|
||||||
const BoardPage({required ViewPB view, Key? key})
|
const BoardPage({required ViewPB view, Key? key})
|
||||||
: _view = view,
|
: _view = view,
|
||||||
super(key: key);
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container();
|
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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
item.subtitle,
|
||||||
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextItem extends ColumnItem {
|
||||||
|
final String s;
|
||||||
|
|
||||||
|
TextItem(this.s);
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HexColor on Color {
|
||||||
|
static Color fromHex(String hexString) {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
|
||||||
|
buffer.write(hexString.replaceFirst('#', ''));
|
||||||
|
return Color(int.parse(buffer.toString(), radix: 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
|
|||||||
class DocumentBanner extends StatelessWidget {
|
class DocumentBanner extends StatelessWidget {
|
||||||
final void Function() onRestore;
|
final void Function() onRestore;
|
||||||
final void Function() onDelete;
|
final void Function() onDelete;
|
||||||
const DocumentBanner({required this.onRestore, required this.onDelete, Key? key}) : super(key: key);
|
const DocumentBanner(
|
||||||
|
{required this.onRestore, required this.onDelete, Key? key})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -26,7 +28,8 @@ class DocumentBanner extends StatelessWidget {
|
|||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(), color: Colors.white),
|
FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(),
|
||||||
|
color: Colors.white),
|
||||||
const HSpace(20),
|
const HSpace(20),
|
||||||
BaseStyledButton(
|
BaseStyledButton(
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
@ -37,7 +40,10 @@ class DocumentBanner extends StatelessWidget {
|
|||||||
downColor: theme.main1,
|
downColor: theme.main1,
|
||||||
outlineColor: Colors.white,
|
outlineColor: Colors.white,
|
||||||
borderRadius: Corners.s8Border,
|
borderRadius: Corners.s8Border,
|
||||||
child: FlowyText.medium(LocaleKeys.deletePagePrompt_restore.tr(), color: Colors.white, fontSize: 14),
|
child: FlowyText.medium(
|
||||||
|
LocaleKeys.deletePagePrompt_restore.tr(),
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14),
|
||||||
onPressed: onRestore),
|
onPressed: onRestore),
|
||||||
const HSpace(20),
|
const HSpace(20),
|
||||||
BaseStyledButton(
|
BaseStyledButton(
|
||||||
@ -49,8 +55,10 @@ class DocumentBanner extends StatelessWidget {
|
|||||||
downColor: theme.main1,
|
downColor: theme.main1,
|
||||||
outlineColor: Colors.white,
|
outlineColor: Colors.white,
|
||||||
borderRadius: Corners.s8Border,
|
borderRadius: Corners.s8Border,
|
||||||
child: FlowyText.medium(LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
child: FlowyText.medium(
|
||||||
color: Colors.white, fontSize: 14),
|
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14),
|
||||||
onPressed: onDelete),
|
onPressed: onDelete),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
|
|
||||||
import 'cell_service.dart';
|
import 'cell_service.dart';
|
||||||
|
|
||||||
abstract class GridFieldChangedNotifier {
|
abstract class IGridFieldChangedNotifier {
|
||||||
void onFieldChanged(void Function(GridFieldPB) callback);
|
void onFieldChanged(void Function(GridFieldPB) callback);
|
||||||
void dispose();
|
void dispose();
|
||||||
}
|
}
|
||||||
@ -12,9 +12,10 @@ abstract class GridFieldChangedNotifier {
|
|||||||
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
|
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
|
||||||
class GridCellFieldNotifier {
|
class GridCellFieldNotifier {
|
||||||
/// fieldId: {objectId: callback}
|
/// fieldId: {objectId: callback}
|
||||||
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
|
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId =
|
||||||
|
{};
|
||||||
|
|
||||||
GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) {
|
GridCellFieldNotifier({required IGridFieldChangedNotifier notifier}) {
|
||||||
notifier.onFieldChanged(
|
notifier.onFieldChanged(
|
||||||
(field) {
|
(field) {
|
||||||
final map = _fieldListenerByFieldId[field.id];
|
final map = _fieldListenerByFieldId[field.id];
|
||||||
|
@ -246,7 +246,7 @@ class IGridCellController<T, D> extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Save the cell data to disk
|
/// Save the cell data to disk
|
||||||
/// You can set [dedeplicate] to true (default is false) to reduce the save operation.
|
/// You can set [deduplicate] to true (default is false) to reduce the save operation.
|
||||||
/// It's useful when you call this method when user editing the [TextField].
|
/// It's useful when you call this method when user editing the [TextField].
|
||||||
/// The default debounce interval is 300 milliseconds.
|
/// The default debounce interval is 300 milliseconds.
|
||||||
void saveCellData(D data,
|
void saveCellData(D data,
|
||||||
@ -304,7 +304,7 @@ class IGridCellController<T, D> extends Equatable {
|
|||||||
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
|
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier {
|
class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier {
|
||||||
final GridFieldCache _cache;
|
final GridFieldCache _cache;
|
||||||
FieldChangesetCallback? _onChangesetFn;
|
FieldChangesetCallback? _onChangesetFn;
|
||||||
|
|
||||||
|
@ -1,40 +1,23 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:equatable/equatable.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-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'block/block_cache.dart';
|
import 'block/block_cache.dart';
|
||||||
import 'grid_service.dart';
|
import 'grid_data_controller.dart';
|
||||||
import 'row/row_service.dart';
|
import 'row/row_service.dart';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
part 'grid_bloc.freezed.dart';
|
part 'grid_bloc.freezed.dart';
|
||||||
|
|
||||||
class GridBloc extends Bloc<GridEvent, GridState> {
|
class GridBloc extends Bloc<GridEvent, GridState> {
|
||||||
final String gridId;
|
final GridDataController dataController;
|
||||||
final GridService _gridService;
|
|
||||||
final GridFieldCache fieldCache;
|
|
||||||
|
|
||||||
// key: the block id
|
|
||||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
|
||||||
|
|
||||||
List<GridRowInfo> get rowInfos {
|
|
||||||
final List<GridRowInfo> rows = [];
|
|
||||||
for (var block in _blocks.values) {
|
|
||||||
rows.addAll(block.rows);
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
GridBloc({required ViewPB view})
|
GridBloc({required ViewPB view})
|
||||||
: gridId = view.id,
|
: dataController = GridDataController(view: view),
|
||||||
_blocks = LinkedHashMap.identity(),
|
|
||||||
_gridService = GridService(gridId: view.id),
|
|
||||||
fieldCache = GridFieldCache(gridId: view.id),
|
|
||||||
super(GridState.initial(view.id)) {
|
super(GridState.initial(view.id)) {
|
||||||
on<GridEvent>(
|
on<GridEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
@ -44,13 +27,21 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
await _loadGrid(emit);
|
await _loadGrid(emit);
|
||||||
},
|
},
|
||||||
createRow: () {
|
createRow: () {
|
||||||
_gridService.createRow();
|
dataController.createRow();
|
||||||
},
|
},
|
||||||
didReceiveRowUpdate: (newRowInfos, reason) {
|
didReceiveGridUpdate: (grid) {
|
||||||
emit(state.copyWith(rowInfos: newRowInfos, reason: reason));
|
emit(state.copyWith(grid: Some(grid)));
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (fields) {
|
didReceiveFieldUpdate: (fields) {
|
||||||
emit(state.copyWith(rowInfos: rowInfos, fields: GridFieldEquatable(fields)));
|
emit(state.copyWith(
|
||||||
|
fields: GridFieldEquatable(fields),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
didReceiveRowUpdate: (newRowInfos, reason) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
rowInfos: newRowInfos,
|
||||||
|
reason: reason,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -59,89 +50,63 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _gridService.closeGrid();
|
await dataController.dispose();
|
||||||
await fieldCache.dispose();
|
|
||||||
|
|
||||||
for (final blockCache in _blocks.values) {
|
|
||||||
blockCache.dispose();
|
|
||||||
}
|
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
GridRowCache? getRowCache(String blockId, String rowId) {
|
GridRowCache? getRowCache(String blockId, String rowId) {
|
||||||
final GridBlockCache? blockCache = _blocks[blockId];
|
final GridBlockCache? blockCache = dataController.blocks[blockId];
|
||||||
return blockCache?.rowCache;
|
return blockCache?.rowCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
fieldCache.addListener(
|
dataController.addListener(
|
||||||
listenWhen: () => !isClosed,
|
onGridChanged: (grid) {
|
||||||
onFields: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
|
if (!isClosed) {
|
||||||
|
add(GridEvent.didReceiveGridUpdate(grid));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRowsChanged: (rowInfos, reason) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(GridEvent.didReceiveRowUpdate(rowInfos, reason));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFieldsChanged: (fields) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(GridEvent.didReceiveFieldUpdate(fields));
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadGrid(Emitter<GridState> emit) async {
|
Future<void> _loadGrid(Emitter<GridState> emit) async {
|
||||||
final result = await _gridService.loadGrid();
|
final result = await dataController.loadData();
|
||||||
return Future(
|
result.fold(
|
||||||
() => result.fold(
|
(grid) => emit(
|
||||||
(grid) async {
|
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||||
_initialBlocks(grid.blocks);
|
),
|
||||||
await _loadFields(grid, emit);
|
(err) => emit(
|
||||||
},
|
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
||||||
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadFields(GridPB grid, Emitter<GridState> emit) async {
|
|
||||||
final result = await _gridService.getFields(fieldIds: grid.fields);
|
|
||||||
return Future(
|
|
||||||
() => result.fold(
|
|
||||||
(fields) {
|
|
||||||
fieldCache.fields = fields.items;
|
|
||||||
|
|
||||||
emit(state.copyWith(
|
|
||||||
grid: Some(grid),
|
|
||||||
fields: GridFieldEquatable(fieldCache.fields),
|
|
||||||
rowInfos: rowInfos,
|
|
||||||
loadingState: GridLoadingState.finish(left(unit)),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initialBlocks(List<GridBlockPB> blocks) {
|
|
||||||
for (final block in blocks) {
|
|
||||||
if (_blocks[block.id] != null) {
|
|
||||||
Log.warn("Intial duplicate block's cache: ${block.id}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final cache = GridBlockCache(
|
|
||||||
gridId: gridId,
|
|
||||||
block: block,
|
|
||||||
fieldCache: fieldCache,
|
|
||||||
);
|
|
||||||
|
|
||||||
cache.addListener(
|
|
||||||
listenWhen: () => !isClosed,
|
|
||||||
onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rowInfos, reason)),
|
|
||||||
);
|
|
||||||
|
|
||||||
_blocks[block.id] = cache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class GridEvent with _$GridEvent {
|
class GridEvent with _$GridEvent {
|
||||||
const factory GridEvent.initial() = InitialGrid;
|
const factory GridEvent.initial() = InitialGrid;
|
||||||
const factory GridEvent.createRow() = _CreateRow;
|
const factory GridEvent.createRow() = _CreateRow;
|
||||||
const factory GridEvent.didReceiveRowUpdate(List<GridRowInfo> rows, GridRowChangeReason listState) =
|
const factory GridEvent.didReceiveRowUpdate(
|
||||||
_DidReceiveRowUpdate;
|
List<GridRowInfo> rows,
|
||||||
const factory GridEvent.didReceiveFieldUpdate(List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
|
GridRowChangeReason listState,
|
||||||
|
) = _DidReceiveRowUpdate;
|
||||||
|
const factory GridEvent.didReceiveFieldUpdate(
|
||||||
|
UnmodifiableListView<GridFieldPB> fields,
|
||||||
|
) = _DidReceiveFieldUpdate;
|
||||||
|
|
||||||
|
const factory GridEvent.didReceiveGridUpdate(
|
||||||
|
GridPB grid,
|
||||||
|
) = _DidReceiveGridUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -156,7 +121,7 @@ class GridState with _$GridState {
|
|||||||
}) = _GridState;
|
}) = _GridState;
|
||||||
|
|
||||||
factory GridState.initial(String gridId) => GridState(
|
factory GridState.initial(String gridId) => GridState(
|
||||||
fields: const GridFieldEquatable([]),
|
fields: GridFieldEquatable(UnmodifiableListView([])),
|
||||||
rowInfos: [],
|
rowInfos: [],
|
||||||
grid: none(),
|
grid: none(),
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
@ -168,18 +133,27 @@ class GridState with _$GridState {
|
|||||||
@freezed
|
@freezed
|
||||||
class GridLoadingState with _$GridLoadingState {
|
class GridLoadingState with _$GridLoadingState {
|
||||||
const factory GridLoadingState.loading() = _Loading;
|
const factory GridLoadingState.loading() = _Loading;
|
||||||
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
|
const factory GridLoadingState.finish(
|
||||||
|
Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridFieldEquatable extends Equatable {
|
class GridFieldEquatable extends Equatable {
|
||||||
final List<GridFieldPB> _fields;
|
final UnmodifiableListView<GridFieldPB> _fields;
|
||||||
const GridFieldEquatable(List<GridFieldPB> fields) : _fields = fields;
|
const GridFieldEquatable(
|
||||||
|
UnmodifiableListView<GridFieldPB> fields,
|
||||||
|
) : _fields = fields;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props {
|
List<Object?> get props {
|
||||||
|
if (_fields.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
_fields.length,
|
_fields.length,
|
||||||
_fields.map((field) => field.width).reduce((value, element) => value + element),
|
_fields
|
||||||
|
.map((field) => field.width)
|
||||||
|
.reduce((value, element) => value + element),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
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 'dart:async';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'block/block_cache.dart';
|
||||||
|
import 'prelude.dart';
|
||||||
|
|
||||||
|
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldPB>);
|
||||||
|
typedef OnGridChanged = void Function(GridPB);
|
||||||
|
|
||||||
|
typedef OnRowsChanged = void Function(
|
||||||
|
List<GridRowInfo> rowInfos,
|
||||||
|
GridRowChangeReason,
|
||||||
|
);
|
||||||
|
typedef ListenONRowChangedCondition = bool Function();
|
||||||
|
|
||||||
|
class GridDataController {
|
||||||
|
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);
|
||||||
|
|
||||||
|
OnRowsChanged? _onRowChanged;
|
||||||
|
OnFieldsChanged? _onFieldsChanged;
|
||||||
|
OnGridChanged? _onGridChanged;
|
||||||
|
|
||||||
|
List<GridRowInfo> get rowInfos {
|
||||||
|
final List<GridRowInfo> rows = [];
|
||||||
|
for (var block in _blocks.values) {
|
||||||
|
rows.addAll(block.rows);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
GridDataController({required ViewPB view})
|
||||||
|
: gridId = view.id,
|
||||||
|
_blocks = LinkedHashMap.identity(),
|
||||||
|
_gridFFIService = GridService(gridId: view.id),
|
||||||
|
fieldCache = GridFieldCache(gridId: view.id);
|
||||||
|
|
||||||
|
void addListener({
|
||||||
|
required OnGridChanged onGridChanged,
|
||||||
|
required OnRowsChanged onRowsChanged,
|
||||||
|
required OnFieldsChanged onFieldsChanged,
|
||||||
|
}) {
|
||||||
|
_onGridChanged = onGridChanged;
|
||||||
|
_onRowChanged = onRowsChanged;
|
||||||
|
_onFieldsChanged = onFieldsChanged;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_initialBlocks(grid.blocks);
|
||||||
|
_onGridChanged?.call(grid);
|
||||||
|
return await _loadFields(grid);
|
||||||
|
},
|
||||||
|
(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initialBlocks(List<GridBlockPB> blocks) {
|
||||||
|
for (final block in blocks) {
|
||||||
|
if (_blocks[block.id] != null) {
|
||||||
|
Log.warn("Initial duplicate block's cache: ${block.id}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final cache = GridBlockCache(
|
||||||
|
gridId: gridId,
|
||||||
|
block: block,
|
||||||
|
fieldCache: fieldCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.addListener(
|
||||||
|
onChangeReason: (reason) {
|
||||||
|
_onRowChanged?.call(rowInfos, reason);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
_blocks[block.id] = cache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
||||||
|
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
||||||
|
return Future(
|
||||||
|
() => result.fold(
|
||||||
|
(fields) {
|
||||||
|
fieldCache.fields = fields.items;
|
||||||
|
_onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields));
|
||||||
|
return left(unit);
|
||||||
|
},
|
||||||
|
(err) => right(err),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,25 +5,25 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'row_data_controller.dart';
|
||||||
import 'row_service.dart';
|
import 'row_service.dart';
|
||||||
|
|
||||||
part 'row_bloc.freezed.dart';
|
part 'row_bloc.freezed.dart';
|
||||||
|
|
||||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||||
final RowService _rowService;
|
final RowService _rowService;
|
||||||
final GridRowCache _rowCache;
|
final GridRowDataController _dataController;
|
||||||
void Function()? _rowListenFn;
|
|
||||||
|
|
||||||
RowBloc({
|
RowBloc({
|
||||||
required GridRowInfo rowInfo,
|
required GridRowInfo rowInfo,
|
||||||
required GridRowCache rowCache,
|
required GridRowDataController dataController,
|
||||||
}) : _rowService = RowService(
|
}) : _rowService = RowService(
|
||||||
gridId: rowInfo.gridId,
|
gridId: rowInfo.gridId,
|
||||||
blockId: rowInfo.blockId,
|
blockId: rowInfo.blockId,
|
||||||
rowId: rowInfo.id,
|
rowId: rowInfo.id,
|
||||||
),
|
),
|
||||||
_rowCache = rowCache,
|
_dataController = dataController,
|
||||||
super(RowState.initial(rowInfo, rowCache.loadGridCells(rowInfo.id))) {
|
super(RowState.initial(rowInfo, dataController.loadData())) {
|
||||||
on<RowEvent>(
|
on<RowEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
@ -33,7 +33,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
createRow: (_CreateRow value) {
|
createRow: (_CreateRow value) {
|
||||||
_rowService.createRow();
|
_rowService.createRow();
|
||||||
},
|
},
|
||||||
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
didReceiveCells: (_DidReceiveCells value) async {
|
||||||
final fields = value.gridCellMap.values
|
final fields = value.gridCellMap.values
|
||||||
.map((e) => GridCellEquatable(e.field))
|
.map((e) => GridCellEquatable(e.field))
|
||||||
.toList();
|
.toList();
|
||||||
@ -51,19 +51,17 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
if (_rowListenFn != null) {
|
_dataController.dispose();
|
||||||
_rowCache.removeRowListener(_rowListenFn!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _startListening() async {
|
Future<void> _startListening() async {
|
||||||
_rowListenFn = _rowCache.addListener(
|
_dataController.addListener(
|
||||||
rowId: state.rowInfo.id,
|
onRowChanged: (cells, reason) {
|
||||||
onCellUpdated: (cellDatas, reason) =>
|
if (!isClosed) {
|
||||||
add(RowEvent.didReceiveCellDatas(cellDatas, reason)),
|
add(RowEvent.didReceiveCells(cells, reason));
|
||||||
listenWhen: () => !isClosed,
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,9 +70,8 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
class RowEvent with _$RowEvent {
|
class RowEvent with _$RowEvent {
|
||||||
const factory RowEvent.initial() = _InitialRow;
|
const factory RowEvent.initial() = _InitialRow;
|
||||||
const factory RowEvent.createRow() = _CreateRow;
|
const factory RowEvent.createRow() = _CreateRow;
|
||||||
const factory RowEvent.didReceiveCellDatas(
|
const factory RowEvent.didReceiveCells(
|
||||||
GridCellMap gridCellMap, GridRowChangeReason reason) =
|
GridCellMap gridCellMap, GridRowChangeReason reason) = _DidReceiveCells;
|
||||||
_DidReceiveCellDatas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../cell/cell_service/cell_service.dart';
|
||||||
|
import '../grid_service.dart';
|
||||||
|
import 'row_service.dart';
|
||||||
|
|
||||||
|
typedef OnRowChanged = void Function(GridCellMap, GridRowChangeReason);
|
||||||
|
|
||||||
|
class GridRowDataController {
|
||||||
|
final String rowId;
|
||||||
|
VoidCallback? _onRowChangedListener;
|
||||||
|
final GridFieldCache _fieldCache;
|
||||||
|
final GridRowCache _rowCache;
|
||||||
|
|
||||||
|
GridFieldCache get fieldCache => _fieldCache;
|
||||||
|
|
||||||
|
GridRowCache get rowCache => _rowCache;
|
||||||
|
|
||||||
|
GridRowDataController({
|
||||||
|
required this.rowId,
|
||||||
|
required GridFieldCache fieldCache,
|
||||||
|
required GridRowCache rowCache,
|
||||||
|
}) : _fieldCache = fieldCache,
|
||||||
|
_rowCache = rowCache;
|
||||||
|
|
||||||
|
GridCellMap loadData() {
|
||||||
|
return _rowCache.loadGridCells(rowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addListener({OnRowChanged? onRowChanged}) {
|
||||||
|
_onRowChangedListener = _rowCache.addListener(
|
||||||
|
rowId: rowId,
|
||||||
|
onCellUpdated: onRowChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
if (_onRowChangedListener != null) {
|
||||||
|
_rowCache.removeRowListener(_onRowChangedListener!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -158,7 +158,7 @@ class GridRowCache {
|
|||||||
void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
|
void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
|
||||||
bool Function()? listenWhen,
|
bool Function()? listenWhen,
|
||||||
}) {
|
}) {
|
||||||
listenrHandler() async {
|
listenerHandler() async {
|
||||||
if (listenWhen != null && listenWhen() == false) {
|
if (listenWhen != null && listenWhen() == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -181,8 +181,8 @@ class GridRowCache {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_rowChangeReasonNotifier.addListener(listenrHandler);
|
_rowChangeReasonNotifier.addListener(listenerHandler);
|
||||||
return listenrHandler;
|
return listenerHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeRowListener(VoidCallback callback) {
|
void removeRowListener(VoidCallback callback) {
|
||||||
|
@ -2,19 +2,20 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||||
|
|
||||||
class GridScrollController {
|
class GridScrollController {
|
||||||
final LinkedScrollControllerGroup _scrollGroupContorller;
|
final LinkedScrollControllerGroup _scrollGroupController;
|
||||||
final ScrollController verticalController;
|
final ScrollController verticalController;
|
||||||
final ScrollController horizontalController;
|
final ScrollController horizontalController;
|
||||||
|
|
||||||
final List<ScrollController> _linkHorizontalControllers = [];
|
final List<ScrollController> _linkHorizontalControllers = [];
|
||||||
|
|
||||||
GridScrollController({required LinkedScrollControllerGroup scrollGroupContorller})
|
GridScrollController(
|
||||||
: _scrollGroupContorller = scrollGroupContorller,
|
{required LinkedScrollControllerGroup scrollGroupController})
|
||||||
|
: _scrollGroupController = scrollGroupController,
|
||||||
verticalController = ScrollController(),
|
verticalController = ScrollController(),
|
||||||
horizontalController = scrollGroupContorller.addAndGet();
|
horizontalController = scrollGroupController.addAndGet();
|
||||||
|
|
||||||
ScrollController linkHorizontalController() {
|
ScrollController linkHorizontalController() {
|
||||||
final controller = _scrollGroupContorller.addAndGet();
|
final controller = _scrollGroupController.addAndGet();
|
||||||
_linkHorizontalControllers.add(controller);
|
_linkHorizontalControllers.add(controller);
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||||
import 'package:app_flowy/startup/startup.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/grid_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||||
@ -79,7 +80,7 @@ class FlowyGrid extends StatefulWidget {
|
|||||||
|
|
||||||
class _FlowyGridState extends State<FlowyGrid> {
|
class _FlowyGridState extends State<FlowyGrid> {
|
||||||
final _scrollController = GridScrollController(
|
final _scrollController = GridScrollController(
|
||||||
scrollGroupContorller: LinkedScrollControllerGroup());
|
scrollGroupController: LinkedScrollControllerGroup());
|
||||||
late ScrollController headerScrollController;
|
late ScrollController headerScrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -153,7 +154,7 @@ class _FlowyGridState extends State<FlowyGrid> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _gridHeader(BuildContext context, String gridId) {
|
Widget _gridHeader(BuildContext context, String gridId) {
|
||||||
final fieldCache = context.read<GridBloc>().fieldCache;
|
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||||
return GridHeaderSliverAdaptor(
|
return GridHeaderSliverAdaptor(
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
fieldCache: fieldCache,
|
fieldCache: fieldCache,
|
||||||
@ -169,7 +170,7 @@ class _GridToolbarAdaptor extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocSelector<GridBloc, GridState, GridToolbarContext>(
|
return BlocSelector<GridBloc, GridState, GridToolbarContext>(
|
||||||
selector: (state) {
|
selector: (state) {
|
||||||
final fieldCache = context.read<GridBloc>().fieldCache;
|
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||||
return GridToolbarContext(
|
return GridToolbarContext(
|
||||||
gridId: state.gridId,
|
gridId: state.gridId,
|
||||||
fieldCache: fieldCache,
|
fieldCache: fieldCache,
|
||||||
@ -237,14 +238,20 @@ class _GridRowsState extends State<_GridRows> {
|
|||||||
) {
|
) {
|
||||||
final rowCache =
|
final rowCache =
|
||||||
context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id);
|
context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id);
|
||||||
final fieldCache = context.read<GridBloc>().fieldCache;
|
|
||||||
|
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
|
||||||
if (rowCache != null) {
|
if (rowCache != null) {
|
||||||
|
final dataController = GridRowDataController(
|
||||||
|
rowId: rowInfo.id,
|
||||||
|
fieldCache: fieldCache,
|
||||||
|
rowCache: rowCache,
|
||||||
|
);
|
||||||
|
|
||||||
return SizeTransition(
|
return SizeTransition(
|
||||||
sizeFactor: animation,
|
sizeFactor: animation,
|
||||||
child: GridRowWidget(
|
child: GridRowWidget(
|
||||||
rowData: rowInfo,
|
rowData: rowInfo,
|
||||||
rowCache: rowCache,
|
dataController: dataController,
|
||||||
fieldCache: fieldCache,
|
|
||||||
key: ValueKey(rowInfo.id),
|
key: ValueKey(rowInfo.id),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -27,39 +27,49 @@ class GridCellBuilder {
|
|||||||
cellCache: cellCache,
|
cellCache: cellCache,
|
||||||
fieldCache: fieldCache,
|
fieldCache: fieldCache,
|
||||||
);
|
);
|
||||||
|
|
||||||
final key = cell.key();
|
final key = cell.key();
|
||||||
switch (cell.fieldType) {
|
switch (cell.fieldType) {
|
||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
return GridCheckboxCell(
|
return GridCheckboxCell(
|
||||||
cellControllerBuilder: cellControllerBuilder, key: key);
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
case FieldType.DateTime:
|
case FieldType.DateTime:
|
||||||
return GridDateCell(
|
return GridDateCell(
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
key: key,
|
key: key,
|
||||||
style: style);
|
style: style,
|
||||||
|
);
|
||||||
case FieldType.SingleSelect:
|
case FieldType.SingleSelect:
|
||||||
return GridSingleSelectCell(
|
return GridSingleSelectCell(
|
||||||
cellContorllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
style: style,
|
style: style,
|
||||||
key: key);
|
key: key,
|
||||||
|
);
|
||||||
case FieldType.MultiSelect:
|
case FieldType.MultiSelect:
|
||||||
return GridMultiSelectCell(
|
return GridMultiSelectCell(
|
||||||
cellContorllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
style: style,
|
style: style,
|
||||||
key: key);
|
key: key,
|
||||||
|
);
|
||||||
case FieldType.Number:
|
case FieldType.Number:
|
||||||
return GridNumberCell(
|
return GridNumberCell(
|
||||||
cellContorllerBuilder: cellControllerBuilder, key: key);
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
case FieldType.RichText:
|
case FieldType.RichText:
|
||||||
return GridTextCell(
|
return GridTextCell(
|
||||||
cellContorllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
style: style,
|
style: style,
|
||||||
key: key);
|
key: key,
|
||||||
|
);
|
||||||
case FieldType.URL:
|
case FieldType.URL:
|
||||||
return GridURLCell(
|
return GridURLCell(
|
||||||
cellContorllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
style: style,
|
style: style,
|
||||||
key: key);
|
key: key,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw UnimplementedError;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
@ -93,7 +103,7 @@ abstract class GridCellWidget extends StatefulWidget
|
|||||||
@override
|
@override
|
||||||
final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
|
final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
// When the cell is focused, we assume that the accessory alse be hovered.
|
// When the cell is focused, we assume that the accessory also be hovered.
|
||||||
@override
|
@override
|
||||||
ValueNotifier<bool> get onAccessoryHover => onCellFocus;
|
ValueNotifier<bool> get onAccessoryHover => onCellFocus;
|
||||||
|
|
||||||
@ -150,7 +160,7 @@ abstract class GridCellState<T extends GridCellWidget> extends State<T> {
|
|||||||
|
|
||||||
abstract class GridFocusNodeCellState<T extends GridCellWidget>
|
abstract class GridFocusNodeCellState<T extends GridCellWidget>
|
||||||
extends GridCellState<T> {
|
extends GridCellState<T> {
|
||||||
SingleListenrFocusNode focusNode = SingleListenrFocusNode();
|
SingleListenerFocusNode focusNode = SingleListenerFocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -219,7 +229,7 @@ class GridCellFocusListener extends ChangeNotifier {
|
|||||||
|
|
||||||
abstract class GridCellStyle {}
|
abstract class GridCellStyle {}
|
||||||
|
|
||||||
class SingleListenrFocusNode extends FocusNode {
|
class SingleListenerFocusNode extends FocusNode {
|
||||||
VoidCallback? _listener;
|
VoidCallback? _listener;
|
||||||
|
|
||||||
void setListener(VoidCallback listener) {
|
void setListener(VoidCallback listener) {
|
||||||
|
@ -7,10 +7,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'cell_builder.dart';
|
import 'cell_builder.dart';
|
||||||
|
|
||||||
class GridNumberCell extends GridCellWidget {
|
class GridNumberCell extends GridCellWidget {
|
||||||
final GridCellControllerBuilder cellContorllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
GridNumberCell({
|
GridNumberCell({
|
||||||
required this.cellContorllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContorllerBuilder.build();
|
final cellContext = widget.cellControllerBuilder.build();
|
||||||
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)
|
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)
|
||||||
..add(const NumberCellEvent.initial());
|
..add(const NumberCellEvent.initial());
|
||||||
_controller =
|
_controller =
|
||||||
|
@ -22,11 +22,11 @@ class SelectOptionCellStyle extends GridCellStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GridSingleSelectCell extends GridCellWidget {
|
class GridSingleSelectCell extends GridCellWidget {
|
||||||
final GridCellControllerBuilder cellContorllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
late final SelectOptionCellStyle? cellStyle;
|
late final SelectOptionCellStyle? cellStyle;
|
||||||
|
|
||||||
GridSingleSelectCell({
|
GridSingleSelectCell({
|
||||||
required this.cellContorllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
GridCellStyle? style,
|
GridCellStyle? style,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
@ -47,7 +47,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext =
|
final cellContext =
|
||||||
widget.cellContorllerBuilder.build() as GridSelectOptionCellController;
|
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
|
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
|
||||||
..add(const SelectOptionCellEvent.initial());
|
..add(const SelectOptionCellEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -63,7 +63,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
|||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onCellEditing.value = value,
|
onFocus: (value) => widget.onCellEditing.value = value,
|
||||||
cellContorllerBuilder: widget.cellContorllerBuilder);
|
cellControllerBuilder: widget.cellControllerBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -78,11 +78,11 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
|||||||
|
|
||||||
//----------------------------------------------------------------
|
//----------------------------------------------------------------
|
||||||
class GridMultiSelectCell extends GridCellWidget {
|
class GridMultiSelectCell extends GridCellWidget {
|
||||||
final GridCellControllerBuilder cellContorllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
late final SelectOptionCellStyle? cellStyle;
|
late final SelectOptionCellStyle? cellStyle;
|
||||||
|
|
||||||
GridMultiSelectCell({
|
GridMultiSelectCell({
|
||||||
required this.cellContorllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
GridCellStyle? style,
|
GridCellStyle? style,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
@ -103,7 +103,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext =
|
final cellContext =
|
||||||
widget.cellContorllerBuilder.build() as GridSelectOptionCellController;
|
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
|
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
|
||||||
..add(const SelectOptionCellEvent.initial());
|
..add(const SelectOptionCellEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -119,7 +119,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
|||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onCellEditing.value = value,
|
onFocus: (value) => widget.onCellEditing.value = value,
|
||||||
cellContorllerBuilder: widget.cellContorllerBuilder);
|
cellControllerBuilder: widget.cellControllerBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -136,12 +136,12 @@ class _SelectOptionCell extends StatelessWidget {
|
|||||||
final List<SelectOptionPB> selectOptions;
|
final List<SelectOptionPB> selectOptions;
|
||||||
final void Function(bool) onFocus;
|
final void Function(bool) onFocus;
|
||||||
final SelectOptionCellStyle? cellStyle;
|
final SelectOptionCellStyle? cellStyle;
|
||||||
final GridCellControllerBuilder cellContorllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
const _SelectOptionCell({
|
const _SelectOptionCell({
|
||||||
required this.selectOptions,
|
required this.selectOptions,
|
||||||
required this.onFocus,
|
required this.onFocus,
|
||||||
required this.cellStyle,
|
required this.cellStyle,
|
||||||
required this.cellContorllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ class _SelectOptionCell extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
onFocus(true);
|
onFocus(true);
|
||||||
final cellContext =
|
final cellContext =
|
||||||
cellContorllerBuilder.build() as GridSelectOptionCellController;
|
cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||||
SelectOptionCellEditor.show(
|
SelectOptionCellEditor.show(
|
||||||
context, cellContext, () => onFocus(false));
|
context, cellContext, () => onFocus(false));
|
||||||
},
|
},
|
||||||
|
@ -14,10 +14,10 @@ class GridTextCellStyle extends GridCellStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GridTextCell extends GridCellWidget {
|
class GridTextCell extends GridCellWidget {
|
||||||
final GridCellControllerBuilder cellContorllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
late final GridTextCellStyle? cellStyle;
|
late final GridTextCellStyle? cellStyle;
|
||||||
GridTextCell({
|
GridTextCell({
|
||||||
required this.cellContorllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
GridCellStyle? style,
|
GridCellStyle? style,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
@ -39,7 +39,7 @@ class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContorllerBuilder.build();
|
final cellContext = widget.cellControllerBuilder.build();
|
||||||
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
||||||
_cellBloc.add(const TextCellEvent.initial());
|
_cellBloc.add(const TextCellEvent.initial());
|
||||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||||
|
@ -31,10 +31,10 @@ enum GridURLCellAccessoryType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GridURLCell extends GridCellWidget {
|
class GridURLCell extends GridCellWidget {
|
||||||
final GridCellControllerBuilder cellContorllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
late final GridURLCellStyle? cellStyle;
|
late final GridURLCellStyle? cellStyle;
|
||||||
GridURLCell({
|
GridURLCell({
|
||||||
required this.cellContorllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
GridCellStyle? style,
|
GridCellStyle? style,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
@ -53,14 +53,14 @@ class GridURLCell extends GridCellWidget {
|
|||||||
switch (ty) {
|
switch (ty) {
|
||||||
case GridURLCellAccessoryType.edit:
|
case GridURLCellAccessoryType.edit:
|
||||||
final cellContext =
|
final cellContext =
|
||||||
cellContorllerBuilder.build() as GridURLCellController;
|
cellControllerBuilder.build() as GridURLCellController;
|
||||||
return _EditURLAccessory(
|
return _EditURLAccessory(
|
||||||
cellContext: cellContext,
|
cellContext: cellContext,
|
||||||
anchorContext: buildContext.anchorContext);
|
anchorContext: buildContext.anchorContext);
|
||||||
|
|
||||||
case GridURLCellAccessoryType.copyURL:
|
case GridURLCellAccessoryType.copyURL:
|
||||||
final cellContext =
|
final cellContext =
|
||||||
cellContorllerBuilder.build() as GridURLCellController;
|
cellControllerBuilder.build() as GridURLCellController;
|
||||||
return _CopyURLAccessory(cellContext: cellContext);
|
return _CopyURLAccessory(cellContext: cellContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext =
|
final cellContext =
|
||||||
widget.cellContorllerBuilder.build() as GridURLCellController;
|
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||||
_cellBloc = URLCellBloc(cellContext: cellContext);
|
_cellBloc = URLCellBloc(cellContext: cellContext);
|
||||||
_cellBloc.add(const URLCellEvent.initial());
|
_cellBloc.add(const URLCellEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -141,7 +141,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
|||||||
await launchUrl(uri);
|
await launchUrl(uri);
|
||||||
} else {
|
} else {
|
||||||
final cellContext =
|
final cellContext =
|
||||||
widget.cellContorllerBuilder.build() as GridURLCellController;
|
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||||
widget.onCellEditing.value = true;
|
widget.onCellEditing.value = true;
|
||||||
URLCellEditor.show(context, cellContext, () {
|
URLCellEditor.show(context, cellContext, () {
|
||||||
widget.onCellEditing.value = false;
|
widget.onCellEditing.value = false;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
@ -9,24 +10,23 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import '../../layout/sizes.dart';
|
import '../../layout/sizes.dart';
|
||||||
import '../cell/cell_accessory.dart';
|
import '../cell/cell_accessory.dart';
|
||||||
import '../cell/cell_cotainer.dart';
|
import '../cell/cell_container.dart';
|
||||||
import '../cell/prelude.dart';
|
import '../cell/prelude.dart';
|
||||||
import 'row_action_sheet.dart';
|
import 'row_action_sheet.dart';
|
||||||
import 'row_detail.dart';
|
import 'row_detail.dart';
|
||||||
|
|
||||||
class GridRowWidget extends StatefulWidget {
|
class GridRowWidget extends StatefulWidget {
|
||||||
final GridRowInfo rowData;
|
final GridRowInfo rowData;
|
||||||
final GridRowCache rowCache;
|
final GridRowDataController dataController;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
|
|
||||||
GridRowWidget({
|
GridRowWidget({
|
||||||
required this.rowData,
|
required this.rowData,
|
||||||
required this.rowCache,
|
required this.dataController,
|
||||||
required GridFieldCache fieldCache,
|
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : cellBuilder = GridCellBuilder(
|
}) : cellBuilder = GridCellBuilder(
|
||||||
cellCache: rowCache.cellCache,
|
cellCache: dataController.rowCache.cellCache,
|
||||||
fieldCache: fieldCache,
|
fieldCache: dataController.fieldCache,
|
||||||
),
|
),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
_rowBloc = RowBloc(
|
_rowBloc = RowBloc(
|
||||||
rowInfo: widget.rowData,
|
rowInfo: widget.rowData,
|
||||||
rowCache: widget.rowCache,
|
dataController: widget.dataController,
|
||||||
);
|
);
|
||||||
_rowBloc.add(const RowEvent.initial());
|
_rowBloc.add(const RowEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -81,7 +81,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
|
|||||||
void _expandRow(BuildContext context) {
|
void _expandRow(BuildContext context) {
|
||||||
final page = RowDetailPage(
|
final page = RowDetailPage(
|
||||||
rowInfo: widget.rowData,
|
rowInfo: widget.rowData,
|
||||||
rowCache: widget.rowCache,
|
rowCache: widget.dataController.rowCache,
|
||||||
cellBuilder: widget.cellBuilder,
|
cellBuilder: widget.cellBuilder,
|
||||||
);
|
);
|
||||||
page.show(context);
|
page.show(context);
|
||||||
|
@ -62,8 +62,10 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
final bloc =
|
final bloc = RowDetailBloc(
|
||||||
RowDetailBloc(rowInfo: widget.rowInfo, rowCache: widget.rowCache);
|
rowInfo: widget.rowInfo,
|
||||||
|
rowCache: widget.rowCache,
|
||||||
|
);
|
||||||
bloc.add(const RowDetailEvent.initial());
|
bloc.add(const RowDetailEvent.initial());
|
||||||
return bloc;
|
return bloc;
|
||||||
},
|
},
|
||||||
|
@ -164,18 +164,11 @@ pub struct CreateFieldPayloadPB {
|
|||||||
pub grid_id: String,
|
pub grid_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub field_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
pub field_type: FieldType,
|
pub field_type: FieldType,
|
||||||
|
|
||||||
#[pb(index = 4)]
|
|
||||||
pub create_if_not_exist: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CreateFieldParams {
|
pub struct CreateFieldParams {
|
||||||
pub grid_id: String,
|
pub grid_id: String,
|
||||||
pub field_id: String,
|
|
||||||
pub field_type: FieldType,
|
pub field_type: FieldType,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,10 +177,8 @@ impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
|
|||||||
|
|
||||||
fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
|
fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
|
||||||
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
|
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
|
||||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
|
||||||
Ok(CreateFieldParams {
|
Ok(CreateFieldParams {
|
||||||
grid_id: grid_id.0,
|
grid_id: grid_id.0,
|
||||||
field_id: field_id.0,
|
|
||||||
field_type: self.field_type,
|
field_type: self.field_type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user