mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: refactor row cache
This commit is contained in:
parent
5ee4b8a2bc
commit
b3a99be7f8
@ -51,13 +51,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
fieldCache.addListener(
|
fieldCache.addListener(
|
||||||
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
|
|
||||||
listenWhen: () => !isClosed,
|
listenWhen: () => !isClosed,
|
||||||
|
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
|
||||||
);
|
);
|
||||||
|
|
||||||
rowCache.addListener(
|
rowCache.addListener(
|
||||||
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
|
|
||||||
listenWhen: () => !isClosed,
|
listenWhen: () => !isClosed,
|
||||||
|
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,23 +53,17 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
|
|
||||||
void _handleRowUpdate(Row row, Emitter<RowState> emit) {
|
void _handleRowUpdate(Row row, Emitter<RowState> emit) {
|
||||||
final CellDataMap cellDataMap = _makeCellDatas(row, state.rowData.fields);
|
final CellDataMap cellDataMap = _makeCellDatas(row, state.rowData.fields);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(cellDataMap: Some(cellDataMap)));
|
||||||
row: Future(() => Some(row)),
|
|
||||||
cellDataMap: Some(cellDataMap),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleFieldUpdate(Emitter<RowState> emit) async {
|
Future<void> _handleFieldUpdate(Emitter<RowState> emit) async {
|
||||||
final optionRow = await state.row;
|
final data = state.rowData.data;
|
||||||
final CellDataMap cellDataMap = optionRow.fold(
|
if (data == null) {
|
||||||
() => CellDataMap.identity(),
|
return;
|
||||||
(row) => _makeCellDatas(row, _fieldCache.unmodifiableFields),
|
}
|
||||||
);
|
|
||||||
|
|
||||||
emit(state.copyWith(
|
final CellDataMap cellDataMap = _makeCellDatas(data, state.rowData.fields);
|
||||||
rowData: state.rowData.copyWith(fields: _fieldCache.unmodifiableFields),
|
emit(state.copyWith(cellDataMap: Some(cellDataMap)));
|
||||||
cellDataMap: Some(cellDataMap),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -98,11 +92,12 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadRow(Emitter<RowState> emit) async {
|
Future<void> _loadRow(Emitter<RowState> emit) async {
|
||||||
final data = await _rowCache.getRowData(state.rowData.rowId);
|
final data = _rowCache.loadRow(state.rowData.rowId);
|
||||||
if (isClosed) {
|
data.foldRight(null, (data, _) {
|
||||||
return;
|
if (!isClosed) {
|
||||||
}
|
add(RowEvent.didLoadRow(data));
|
||||||
data.foldRight(null, (data, _) => add(RowEvent.didLoadRow(data)));
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CellDataMap _makeCellDatas(Row row, List<Field> fields) {
|
CellDataMap _makeCellDatas(Row row, List<Field> fields) {
|
||||||
@ -134,13 +129,11 @@ class RowEvent with _$RowEvent {
|
|||||||
class RowState with _$RowState {
|
class RowState with _$RowState {
|
||||||
const factory RowState({
|
const factory RowState({
|
||||||
required GridRow rowData,
|
required GridRow rowData,
|
||||||
required Future<Option<Row>> row,
|
|
||||||
required Option<CellDataMap> cellDataMap,
|
required Option<CellDataMap> cellDataMap,
|
||||||
}) = _RowState;
|
}) = _RowState;
|
||||||
|
|
||||||
factory RowState.initial(GridRow rowData) => RowState(
|
factory RowState.initial(GridRow rowData) => RowState(
|
||||||
rowData: rowData,
|
rowData: rowData,
|
||||||
row: Future(() => none()),
|
|
||||||
cellDataMap: none(),
|
cellDataMap: none(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
@ -7,37 +8,31 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
|
import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
|
||||||
|
|
||||||
part 'row_service.freezed.dart';
|
part 'row_service.freezed.dart';
|
||||||
|
|
||||||
class RowsNotifier extends ChangeNotifier {
|
|
||||||
List<GridRow> _rows = [];
|
|
||||||
GridRowChangeReason _changeReason = const InitialListState();
|
|
||||||
|
|
||||||
void updateRows(List<GridRow> rows, GridRowChangeReason changeReason) {
|
|
||||||
_rows = rows;
|
|
||||||
_changeReason = changeReason;
|
|
||||||
|
|
||||||
changeReason.map(
|
|
||||||
insert: (_) => notifyListeners(),
|
|
||||||
delete: (_) => notifyListeners(),
|
|
||||||
update: (_) => notifyListeners(),
|
|
||||||
initial: (_) {},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GridRow> get rows => _rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GridRowCache {
|
class GridRowCache {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
final GridRowListener _rowsListener;
|
final GridRowListener _rowsListener;
|
||||||
final RowsNotifier _rowNotifier = RowsNotifier();
|
late final _RowsNotifier _rowNotifier;
|
||||||
final HashMap<String, Row> _rowDataMap = HashMap();
|
|
||||||
UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
|
UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
|
||||||
|
|
||||||
|
List<GridRow> get clonedRows => _rowNotifier.clonedRows;
|
||||||
|
|
||||||
GridRowCache({required this.gridId}) : _rowsListener = GridRowListener(gridId: gridId) {
|
GridRowCache({required this.gridId}) : _rowsListener = GridRowListener(gridId: gridId) {
|
||||||
|
_rowNotifier = _RowsNotifier(
|
||||||
|
rowBuilder: (rowOrder) {
|
||||||
|
return GridRow(
|
||||||
|
gridId: gridId,
|
||||||
|
fields: _fields,
|
||||||
|
rowId: rowOrder.rowId,
|
||||||
|
height: rowOrder.height.toDouble(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
|
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
(changesets) {
|
(changesets) {
|
||||||
@ -58,8 +53,6 @@ class GridRowCache {
|
|||||||
_rowNotifier.dispose();
|
_rowNotifier.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GridRow> get clonedRows => [..._rowNotifier.rows];
|
|
||||||
|
|
||||||
void addListener({
|
void addListener({
|
||||||
void Function(List<GridRow>, GridRowChangeReason)? onChanged,
|
void Function(List<GridRow>, GridRowChangeReason)? onChanged,
|
||||||
bool Function()? listenWhen,
|
bool Function()? listenWhen,
|
||||||
@ -80,7 +73,7 @@ class GridRowCache {
|
|||||||
void Function(Row)? onUpdated,
|
void Function(Row)? onUpdated,
|
||||||
bool Function()? listenWhen,
|
bool Function()? listenWhen,
|
||||||
}) {
|
}) {
|
||||||
f() {
|
listenrHandler() {
|
||||||
if (onUpdated == null) {
|
if (onUpdated == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -90,57 +83,76 @@ class GridRowCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_rowNotifier._changeReason.whenOrNull(update: (indexs) {
|
_rowNotifier._changeReason.whenOrNull(update: (indexs) {
|
||||||
final row = _rowDataMap[rowId];
|
final row = _rowNotifier.rowDataWithId(rowId);
|
||||||
if (indexs[rowId] != null && row != null) {
|
if (indexs[rowId] != null && row != null) {
|
||||||
onUpdated(row);
|
onUpdated(row);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_rowNotifier.addListener(f);
|
_rowNotifier.addListener(listenrHandler);
|
||||||
return f;
|
return listenrHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeRowListener(VoidCallback callback) {
|
void removeRowListener(VoidCallback callback) {
|
||||||
_rowNotifier.removeListener(callback);
|
_rowNotifier.removeListener(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Option<Row>> getRowData(String rowId) async {
|
Option<Row> loadRow(String rowId) {
|
||||||
final Row? data = _rowDataMap[rowId];
|
final Row? data = _rowNotifier.rowDataWithId(rowId);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
return Future(() => Some(data));
|
return Some(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
final payload = RowIdentifierPayload.create()
|
final payload = RowIdentifierPayload.create()
|
||||||
..gridId = gridId
|
..gridId = gridId
|
||||||
..rowId = rowId;
|
..rowId = rowId;
|
||||||
|
|
||||||
final result = await GridEventGetRow(payload).send();
|
GridEventGetRow(payload).send().then((result) {
|
||||||
return Future(() {
|
result.fold(
|
||||||
return result.fold(
|
(rowData) => _rowNotifier.rowData = rowData,
|
||||||
(data) {
|
(err) => Log.error(err),
|
||||||
data.freeze();
|
|
||||||
_rowDataMap[data.id] = data;
|
|
||||||
return Some(data);
|
|
||||||
},
|
|
||||||
(err) {
|
|
||||||
Log.error(err);
|
|
||||||
return none();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
return none();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
|
void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
|
||||||
_fields = fields;
|
_fields = fields;
|
||||||
final newRows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
|
final rowOrders = blocks.expand((block) => block.rowOrders).toList();
|
||||||
return GridRow.fromBlockRow(gridId, rowOrder, _fields);
|
_rowNotifier.reset(rowOrders);
|
||||||
}).toList();
|
|
||||||
|
|
||||||
_rowNotifier.updateRows(newRows, const GridRowChangeReason.initial());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deleteRows(List<RowOrder> deletedRows) {
|
void _deleteRows(List<RowOrder> deletedRows) {
|
||||||
|
_rowNotifier.deleteRows(deletedRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _insertRows(List<IndexRowOrder> createdRows) {
|
||||||
|
_rowNotifier.insertRows(createdRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateRows(List<RowOrder> rowOrders) {
|
||||||
|
_rowNotifier.updateRows(rowOrders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RowsNotifier extends ChangeNotifier {
|
||||||
|
List<GridRow> _rows = [];
|
||||||
|
HashMap<String, Row> _rowDataMap = HashMap();
|
||||||
|
GridRowChangeReason _changeReason = const InitialListState();
|
||||||
|
final GridRow Function(RowOrder) rowBuilder;
|
||||||
|
|
||||||
|
_RowsNotifier({
|
||||||
|
required this.rowBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
void reset(List<RowOrder> rowOrders) {
|
||||||
|
_rowDataMap = HashMap();
|
||||||
|
final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList();
|
||||||
|
_update(rows, const GridRowChangeReason.initial());
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteRows(List<RowOrder> deletedRows) {
|
||||||
if (deletedRows.isEmpty) {
|
if (deletedRows.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -149,7 +161,7 @@ class GridRowCache {
|
|||||||
final DeletedIndexs deletedIndex = [];
|
final DeletedIndexs deletedIndex = [];
|
||||||
final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
|
final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
|
||||||
|
|
||||||
_rowNotifier.rows.asMap().forEach((index, row) {
|
_rows.asMap().forEach((index, row) {
|
||||||
if (deletedRowMap[row.rowId] == null) {
|
if (deletedRowMap[row.rowId] == null) {
|
||||||
newRows.add(row);
|
newRows.add(row);
|
||||||
} else {
|
} else {
|
||||||
@ -157,48 +169,81 @@ class GridRowCache {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_rowNotifier.updateRows(newRows, GridRowChangeReason.delete(deletedIndex));
|
_update(newRows, GridRowChangeReason.delete(deletedIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _insertRows(List<IndexRowOrder> createdRows) {
|
void insertRows(List<IndexRowOrder> createdRows) {
|
||||||
if (createdRows.isEmpty) {
|
if (createdRows.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InsertedIndexs insertIndexs = [];
|
InsertedIndexs insertIndexs = [];
|
||||||
final List<GridRow> newRows = _rowNotifier.rows;
|
final List<GridRow> newRows = _rows;
|
||||||
for (final createdRow in createdRows) {
|
for (final createdRow in createdRows) {
|
||||||
final gridRow = GridRow.fromBlockRow(gridId, createdRow.rowOrder, _fields);
|
final rowOrder = createdRow.rowOrder;
|
||||||
insertIndexs.add(
|
final insertIndex = InsertedIndex(index: createdRow.index, rowId: rowOrder.rowId);
|
||||||
InsertedIndex(
|
insertIndexs.add(insertIndex);
|
||||||
index: createdRow.index,
|
newRows.insert(createdRow.index, (rowBuilder(rowOrder)));
|
||||||
rowId: gridRow.rowId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
newRows.insert(createdRow.index, gridRow);
|
|
||||||
}
|
}
|
||||||
_rowNotifier.updateRows(newRows, GridRowChangeReason.insert(insertIndexs));
|
_update(newRows, GridRowChangeReason.insert(insertIndexs));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateRows(List<RowOrder> updatedRows) {
|
void updateRows(List<RowOrder> updatedRows) {
|
||||||
if (updatedRows.isEmpty) {
|
if (updatedRows.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||||
final List<GridRow> newRows = _rowNotifier.rows;
|
final List<GridRow> newRows = _rows;
|
||||||
for (final rowOrder in updatedRows) {
|
for (final rowOrder in updatedRows) {
|
||||||
final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
|
final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
newRows.removeAt(index);
|
newRows.removeAt(index);
|
||||||
newRows.insert(index, GridRow.fromBlockRow(gridId, rowOrder, _fields));
|
// Remove the cache data
|
||||||
_rowDataMap.remove(rowOrder.rowId);
|
_rowDataMap.remove(rowOrder.rowId);
|
||||||
|
newRows.insert(index, rowBuilder(rowOrder));
|
||||||
updatedIndexs[rowOrder.rowId] = UpdatedIndex(index: index, rowId: rowOrder.rowId);
|
updatedIndexs[rowOrder.rowId] = UpdatedIndex(index: index, rowId: rowOrder.rowId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_rowNotifier.updateRows(newRows, GridRowChangeReason.update(updatedIndexs));
|
_update(newRows, GridRowChangeReason.update(updatedIndexs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _update(List<GridRow> rows, GridRowChangeReason changeReason) {
|
||||||
|
_rows = rows;
|
||||||
|
_changeReason = changeReason;
|
||||||
|
|
||||||
|
changeReason.map(
|
||||||
|
insert: (_) => notifyListeners(),
|
||||||
|
delete: (_) => notifyListeners(),
|
||||||
|
update: (_) => notifyListeners(),
|
||||||
|
initial: (_) {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
set rowData(Row rowData) {
|
||||||
|
rowData.freeze();
|
||||||
|
|
||||||
|
_rowDataMap[rowData.id] = rowData;
|
||||||
|
final index = _rows.indexWhere((row) => row.rowId == rowData.id);
|
||||||
|
if (index != -1) {
|
||||||
|
if (_rows[index].data != rowData) {
|
||||||
|
final row = _rows.removeAt(index).copyWith(data: rowData);
|
||||||
|
_rows.insert(index, row);
|
||||||
|
|
||||||
|
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||||
|
updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId);
|
||||||
|
_changeReason = GridRowChangeReason.update(updatedIndexs);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row? rowDataWithId(String rowId) {
|
||||||
|
return _rowDataMap[rowId];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GridRow> get clonedRows => [..._rows];
|
||||||
}
|
}
|
||||||
|
|
||||||
class RowService {
|
class RowService {
|
||||||
@ -258,7 +303,7 @@ class GridCellIdentifier with _$GridCellIdentifier {
|
|||||||
required String rowId,
|
required String rowId,
|
||||||
required Field field,
|
required Field field,
|
||||||
Cell? cell,
|
Cell? cell,
|
||||||
}) = _CellData;
|
}) = _GridCellIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -268,18 +313,8 @@ class GridRow with _$GridRow {
|
|||||||
required String rowId,
|
required String rowId,
|
||||||
required List<Field> fields,
|
required List<Field> fields,
|
||||||
required double height,
|
required double height,
|
||||||
required Future<Option<Row>> data,
|
Row? data,
|
||||||
}) = _GridRow;
|
}) = _GridRow;
|
||||||
|
|
||||||
factory GridRow.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
|
|
||||||
return GridRow(
|
|
||||||
gridId: gridId,
|
|
||||||
fields: fields,
|
|
||||||
rowId: row.rowId,
|
|
||||||
data: Future(() => none()),
|
|
||||||
height: row.height.toDouble(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef InsertedIndexs = List<InsertedIndex>;
|
typedef InsertedIndexs = List<InsertedIndex>;
|
||||||
|
@ -222,7 +222,11 @@ class _GridRowsState extends State<_GridRows> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _renderRow(BuildContext context, GridRow rowData, Animation<double> animation) {
|
Widget _renderRow(
|
||||||
|
BuildContext context,
|
||||||
|
GridRow rowData,
|
||||||
|
Animation<double> animation,
|
||||||
|
) {
|
||||||
final bloc = context.read<GridBloc>();
|
final bloc = context.read<GridBloc>();
|
||||||
final fieldCache = bloc.fieldCache;
|
final fieldCache = bloc.fieldCache;
|
||||||
final rowCache = bloc.rowCache;
|
final rowCache = bloc.rowCache;
|
||||||
|
@ -47,7 +47,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
|||||||
() => widget.setFocus(context, false),
|
() => widget.setFocus(context, false),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Row(children: children),
|
child: ClipRRect(child: Row(children: children)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -103,7 +103,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
|||||||
() => widget.setFocus(context, false),
|
() => widget.setFocus(context, false),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Row(children: children),
|
child: ClipRRect(child: Row(children: children)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,6 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'row_action_sheet.dart';
|
import 'row_action_sheet.dart';
|
||||||
|
|
||||||
class GridRowWidget extends StatefulWidget {
|
class GridRowWidget extends StatefulWidget {
|
||||||
|
Loading…
Reference in New Issue
Block a user