mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: Merge main 521 (#2574)
* chore: merge main branch * chore: remove document plugins * chore: add color generator * ci: tests
This commit is contained in:
@ -44,7 +44,6 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
|
||||
@override
|
||||
Future<Option<FlowyError>> save(DateCellData data) {
|
||||
var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
|
||||
|
||||
if (data.dateTime != null) {
|
||||
final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
|
||||
payload.date = date;
|
||||
|
@ -176,18 +176,28 @@ class DatabaseController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveRow({
|
||||
Future<Either<Unit, FlowyError>> moveGroupRow({
|
||||
required RowPB fromRow,
|
||||
required String groupId,
|
||||
RowPB? toRow,
|
||||
}) {
|
||||
return _databaseViewBackendSvc.moveRow(
|
||||
return _databaseViewBackendSvc.moveGroupRow(
|
||||
fromRowId: fromRow.id,
|
||||
toGroupId: groupId,
|
||||
toRowId: toRow?.id,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveRow({
|
||||
required RowPB fromRow,
|
||||
required RowPB toRow,
|
||||
}) {
|
||||
return _databaseViewBackendSvc.moveRow(
|
||||
fromRowId: fromRow.id,
|
||||
toRowId: toRow.id,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveGroup({
|
||||
required String fromGroupId,
|
||||
required String toGroupId,
|
||||
|
@ -43,7 +43,7 @@ class DatabaseViewBackendService {
|
||||
return DatabaseEventCreateRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveRow({
|
||||
Future<Either<Unit, FlowyError>> moveGroupRow({
|
||||
required RowId fromRowId,
|
||||
required String toGroupId,
|
||||
RowId? toRowId,
|
||||
@ -60,6 +60,18 @@ class DatabaseViewBackendService {
|
||||
return DatabaseEventMoveGroupRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveRow({
|
||||
required String fromRowId,
|
||||
required String toRowId,
|
||||
}) {
|
||||
var payload = MoveRowPayloadPB.create()
|
||||
..viewId = viewId
|
||||
..fromRowId = fromRowId
|
||||
..toRowId = toRowId;
|
||||
|
||||
return DatabaseEventMoveRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveGroup({
|
||||
required String fromGroupId,
|
||||
required String toGroupId,
|
||||
|
@ -30,7 +30,7 @@ abstract class RowCacheDelegate {
|
||||
class RowCache {
|
||||
final String viewId;
|
||||
|
||||
/// _rows containers the current block's rows
|
||||
/// _rows contains the current block's rows
|
||||
/// Use List to reverse the order of the GridRow.
|
||||
final RowList _rowList = RowList();
|
||||
|
||||
|
@ -54,7 +54,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
|
||||
if (fromRow != null) {
|
||||
_databaseController.moveRow(
|
||||
_databaseController.moveGroupRow(
|
||||
fromRow: fromRow,
|
||||
toRow: toRow,
|
||||
groupId: groupId,
|
||||
@ -70,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
|
||||
if (fromRow != null) {
|
||||
_databaseController.moveRow(
|
||||
_databaseController.moveGroupRow(
|
||||
fromRow: fromRow,
|
||||
toRow: toRow,
|
||||
groupId: toGroupId,
|
||||
|
@ -161,6 +161,9 @@ class CalendarDayCard extends StatelessWidget {
|
||||
});
|
||||
|
||||
renderHook.addSelectOptionHook((selectedOptions, cardData, _) {
|
||||
if (selectedOptions.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final children = selectedOptions.map(
|
||||
(option) {
|
||||
return SelectOptionTag.fromOption(
|
||||
|
@ -35,6 +35,17 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
);
|
||||
await rowService.deleteRow(rowInfo.rowPB.id);
|
||||
},
|
||||
moveRow: (int from, int to) {
|
||||
final List<RowInfo> rows = [...state.rowInfos];
|
||||
|
||||
final fromRow = rows[from].rowPB;
|
||||
final toRow = rows[to].rowPB;
|
||||
|
||||
rows.insert(to, rows.removeAt(from));
|
||||
emit(state.copyWith(rowInfos: rows));
|
||||
|
||||
databaseController.moveRow(fromRow: fromRow, toRow: toRow);
|
||||
},
|
||||
didReceiveGridUpdate: (grid) {
|
||||
emit(state.copyWith(grid: Some(grid)));
|
||||
},
|
||||
@ -110,6 +121,7 @@ class GridEvent with _$GridEvent {
|
||||
const factory GridEvent.initial() = InitialGrid;
|
||||
const factory GridEvent.createRow() = _CreateRow;
|
||||
const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
|
||||
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
|
||||
const factory GridEvent.didReceiveRowUpdate(
|
||||
List<RowInfo> rows,
|
||||
RowsChangedReason listState,
|
||||
|
@ -91,21 +91,6 @@ class _GridPageState extends State<GridPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant GridPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
}
|
||||
|
||||
class FlowyGrid extends StatefulWidget {
|
||||
@ -119,12 +104,12 @@ class _FlowyGridState extends State<FlowyGrid> {
|
||||
final _scrollController = GridScrollController(
|
||||
scrollGroupController: LinkedScrollControllerGroup(),
|
||||
);
|
||||
late ScrollController headerScrollController;
|
||||
late final ScrollController headerScrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
headerScrollController = _scrollController.linkHorizontalController();
|
||||
super.initState();
|
||||
headerScrollController = _scrollController.linkHorizontalController();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -216,49 +201,78 @@ class _GridRowsState extends State<_GridRows> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<GridBloc, GridState>(
|
||||
listenWhen: (previous, current) => previous.reason != current.reason,
|
||||
listener: (context, state) {
|
||||
state.reason.whenOrNull(
|
||||
insert: (item) {
|
||||
_key.currentState?.insertItem(item.index);
|
||||
},
|
||||
delete: (item) {
|
||||
_key.currentState?.removeItem(
|
||||
item.index,
|
||||
(context, animation) =>
|
||||
_renderRow(context, item.rowInfo, animation),
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
final filterState = context.watch<GridFilterMenuBloc>().state;
|
||||
final sortState = context.watch<SortMenuBloc>().state;
|
||||
|
||||
return BlocConsumer<GridBloc, GridState>(
|
||||
listenWhen: (previous, current) => previous.reason != current.reason,
|
||||
listener: (context, state) {
|
||||
state.reason.whenOrNull(
|
||||
insert: (item) {
|
||||
_key.currentState?.insertItem(item.index);
|
||||
},
|
||||
delete: (item) {
|
||||
_key.currentState?.removeItem(
|
||||
item.index,
|
||||
(context, animation) => _renderRow(
|
||||
context,
|
||||
item.rowInfo,
|
||||
animation: animation,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
reorderSingleRow: (reorderRow, rowInfo) {
|
||||
// _key.currentState?.removeItem(
|
||||
// reorderRow.oldIndex,
|
||||
// (context, animation) => _renderRow(context, rowInfo, animation),
|
||||
// );
|
||||
// _key.currentState?.insertItem(reorderRow.newIndex);
|
||||
},
|
||||
);
|
||||
},
|
||||
buildWhen: (previous, current) {
|
||||
return current.reason.whenOrNull(
|
||||
buildWhen: (previous, current) {
|
||||
return current.reason.maybeWhen(
|
||||
reorderRows: () => true,
|
||||
reorderSingleRow: (reorderRow, rowInfo) => true,
|
||||
) ??
|
||||
false;
|
||||
},
|
||||
builder: (context, state) {
|
||||
return SliverAnimatedList(
|
||||
key: _key,
|
||||
initialItemCount: context.read<GridBloc>().state.rowInfos.length,
|
||||
itemBuilder:
|
||||
(BuildContext context, int index, Animation<double> animation) {
|
||||
final rowInfos = context.read<GridBloc>().state.rowInfos;
|
||||
if (index >= rowInfos.length) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
final RowInfo rowInfo = rowInfos[index];
|
||||
return _renderRow(context, rowInfo, animation);
|
||||
}
|
||||
delete: (item) => true,
|
||||
insert: (item) => true,
|
||||
orElse: () => false,
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
final rowInfos = context.watch<GridBloc>().state.rowInfos;
|
||||
|
||||
return SliverFillRemaining(
|
||||
child: ReorderableListView.builder(
|
||||
key: _key,
|
||||
buildDefaultDragHandles: false,
|
||||
proxyDecorator: (child, index, animation) => Material(
|
||||
color: Colors.white.withOpacity(.1),
|
||||
child: Opacity(
|
||||
opacity: .5,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
onReorder: (fromIndex, newIndex) {
|
||||
final toIndex =
|
||||
newIndex > fromIndex ? newIndex - 1 : newIndex;
|
||||
|
||||
if (fromIndex == toIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
context
|
||||
.read<GridBloc>()
|
||||
.add(GridEvent.moveRow(fromIndex, toIndex));
|
||||
},
|
||||
itemCount: rowInfos.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final RowInfo rowInfo = rowInfos[index];
|
||||
return _renderRow(
|
||||
context,
|
||||
rowInfo,
|
||||
index: index,
|
||||
isSortEnabled: sortState.sortInfos.isNotEmpty,
|
||||
isFilterEnabled: filterState.filters.isNotEmpty,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -267,15 +281,18 @@ class _GridRowsState extends State<_GridRows> {
|
||||
|
||||
Widget _renderRow(
|
||||
BuildContext context,
|
||||
RowInfo rowInfo,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
RowInfo rowInfo, {
|
||||
int? index,
|
||||
bool isSortEnabled = false,
|
||||
bool isFilterEnabled = false,
|
||||
Animation<double>? animation,
|
||||
}) {
|
||||
final rowCache = context.read<GridBloc>().getRowCache(
|
||||
rowInfo.rowPB.id,
|
||||
);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return const SizedBox();
|
||||
if (rowCache == null) return const SizedBox.shrink();
|
||||
|
||||
final fieldController =
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
@ -285,24 +302,32 @@ class _GridRowsState extends State<_GridRows> {
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: GridRow(
|
||||
rowInfo: rowInfo,
|
||||
dataController: dataController,
|
||||
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
||||
openDetailPage: (context, cellBuilder) {
|
||||
_openRowDetailPage(
|
||||
context,
|
||||
rowInfo,
|
||||
fieldController,
|
||||
rowCache,
|
||||
cellBuilder,
|
||||
);
|
||||
},
|
||||
key: ValueKey(rowInfo.rowPB.id),
|
||||
),
|
||||
final child = GridRow(
|
||||
key: ValueKey(rowInfo.rowPB.id),
|
||||
index: index,
|
||||
isDraggable: !isSortEnabled && !isFilterEnabled,
|
||||
rowInfo: rowInfo,
|
||||
dataController: dataController,
|
||||
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
||||
openDetailPage: (context, cellBuilder) {
|
||||
_openRowDetailPage(
|
||||
context,
|
||||
rowInfo,
|
||||
fieldController,
|
||||
rowCache,
|
||||
cellBuilder,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (animation != null) {
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
void _openRowDetailPage(
|
||||
|
@ -25,29 +25,34 @@ class GridRow extends StatefulWidget {
|
||||
final GridCellBuilder cellBuilder;
|
||||
final void Function(BuildContext, GridCellBuilder) openDetailPage;
|
||||
|
||||
final int? index;
|
||||
final bool isDraggable;
|
||||
|
||||
const GridRow({
|
||||
super.key,
|
||||
required this.rowInfo,
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
required this.openDetailPage,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
this.index,
|
||||
this.isDraggable = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<GridRow> createState() => _GridRowState();
|
||||
}
|
||||
|
||||
class _GridRowState extends State<GridRow> {
|
||||
late RowBloc _rowBloc;
|
||||
late final RowBloc _rowBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_rowBloc = RowBloc(
|
||||
rowInfo: widget.rowInfo,
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
_rowBloc.add(const RowEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -70,9 +75,11 @@ class _GridRowState extends State<GridRow> {
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
const _RowLeading(),
|
||||
_RowLeading(
|
||||
index: widget.index,
|
||||
isDraggable: widget.isDraggable,
|
||||
),
|
||||
content,
|
||||
const _RowTrailing(),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -89,19 +96,25 @@ class _GridRowState extends State<GridRow> {
|
||||
}
|
||||
|
||||
class _RowLeading extends StatefulWidget {
|
||||
const _RowLeading({Key? key}) : super(key: key);
|
||||
final int? index;
|
||||
final bool isDraggable;
|
||||
|
||||
const _RowLeading({
|
||||
this.index,
|
||||
this.isDraggable = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_RowLeading> createState() => _RowLeadingState();
|
||||
}
|
||||
|
||||
class _RowLeadingState extends State<_RowLeading> {
|
||||
late PopoverController popoverController;
|
||||
late final PopoverController popoverController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
popoverController = PopoverController();
|
||||
super.initState();
|
||||
popoverController = PopoverController();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -131,23 +144,28 @@ class _RowLeadingState extends State<_RowLeading> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const _InsertButton(),
|
||||
_MenuButton(
|
||||
openMenu: () {
|
||||
popoverController.show();
|
||||
},
|
||||
),
|
||||
if (isDraggable) ...[
|
||||
ReorderableDragStartListener(
|
||||
index: widget.index!,
|
||||
child: _MenuButton(
|
||||
isDragEnabled: isDraggable,
|
||||
openMenu: () {
|
||||
popoverController.show();
|
||||
},
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
_MenuButton(
|
||||
openMenu: () {
|
||||
popoverController.show();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowTrailing extends StatelessWidget {
|
||||
const _RowTrailing({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox();
|
||||
}
|
||||
bool get isDraggable => widget.index != null && widget.isDraggable;
|
||||
}
|
||||
|
||||
class _InsertButton extends StatelessWidget {
|
||||
@ -172,25 +190,31 @@ class _InsertButton extends StatelessWidget {
|
||||
|
||||
class _MenuButton extends StatefulWidget {
|
||||
final VoidCallback openMenu;
|
||||
final bool isDragEnabled;
|
||||
|
||||
const _MenuButton({
|
||||
required this.openMenu,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
this.isDragEnabled = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_MenuButton> createState() => _MenuButtonState();
|
||||
}
|
||||
|
||||
class _MenuButtonState extends State<_MenuButton> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyIconButton(
|
||||
tooltipText: LocaleKeys.tooltip_openMenu.tr(),
|
||||
tooltipText:
|
||||
widget.isDragEnabled ? null : LocaleKeys.tooltip_openMenu.tr(),
|
||||
richTooltipText: widget.isDragEnabled
|
||||
? TextSpan(
|
||||
children: [
|
||||
TextSpan(text: '${LocaleKeys.tooltip_dragRow.tr()}\n'),
|
||||
TextSpan(text: LocaleKeys.tooltip_openMenu.tr()),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
width: 20,
|
||||
height: 30,
|
||||
@ -258,6 +282,7 @@ class RowContent extends StatelessWidget {
|
||||
if (builder != null) {
|
||||
accessories.addAll(builder(buildContext));
|
||||
}
|
||||
|
||||
return accessories;
|
||||
},
|
||||
child: child,
|
||||
@ -289,12 +314,12 @@ class _RowEnterRegion extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RowEnterRegionState extends State<_RowEnterRegion> {
|
||||
late RegionStateNotifier _rowStateNotifier;
|
||||
late final RegionStateNotifier _rowStateNotifier;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_rowStateNotifier = RegionStateNotifier();
|
||||
super.initState();
|
||||
_rowStateNotifier = RegionStateNotifier();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -74,6 +74,7 @@ class GridCellBuilder {
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
@ -83,7 +84,7 @@ class BlankCell extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,18 +68,15 @@ class CellContainer extends StatelessWidget {
|
||||
if (isFocus) {
|
||||
final borderSide = BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 1.0,
|
||||
);
|
||||
|
||||
return BoxDecoration(border: Border.fromBorderSide(borderSide));
|
||||
} else {
|
||||
final borderSide = BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0,
|
||||
);
|
||||
return BoxDecoration(
|
||||
border: Border(right: borderSide, bottom: borderSide),
|
||||
);
|
||||
}
|
||||
|
||||
final borderSide = BorderSide(color: Theme.of(context).dividerColor);
|
||||
return BoxDecoration(
|
||||
border: Border(right: borderSide, bottom: borderSide),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,6 @@ class DateCellCalendarBloc
|
||||
date != null && time == null,
|
||||
);
|
||||
String? newTime = time ?? state.time;
|
||||
|
||||
DateTime? newDate = _utcToLocalAddTime(date);
|
||||
if (time != null && time.isNotEmpty) {
|
||||
newDate = state.dateTime ?? DateTime.now();
|
||||
|
Reference in New Issue
Block a user