Merge pull request #1001 from AppFlowy-IO/merge/release_005

Merge/release 005
This commit is contained in:
Nathan.fooo 2022-09-07 14:42:50 +08:00 committed by GitHub
commit 3d2bfcc7c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 284 additions and 250 deletions

View file

@ -99,7 +99,6 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
)); ));
}, },
endEditRow: (rowId) { endEditRow: (rowId) {
assert(state.editingRow.isSome());
state.editingRow.fold(() => null, (editingRow) { state.editingRow.fold(() => null, (editingRow) {
assert(editingRow.row.id == rowId); assert(editingRow.row.id == rowId);
emit(state.copyWith(editingRow: none())); emit(state.copyWith(editingRow: none()));

View file

@ -7,20 +7,20 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_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 'card_data_controller.dart'; import 'card_data_controller.dart';
part 'card_bloc.freezed.dart'; part 'card_bloc.freezed.dart';
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> { class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
final String fieldId; final String groupFieldId;
final RowFFIService _rowService; final RowFFIService _rowService;
final CardDataController _dataController; final CardDataController _dataController;
BoardCardBloc({ BoardCardBloc({
required this.fieldId, required this.groupFieldId,
required String gridId, required String gridId,
required CardDataController dataController, required CardDataController dataController,
required bool isEditing,
}) : _rowService = RowFFIService( }) : _rowService = RowFFIService(
gridId: gridId, gridId: gridId,
blockId: dataController.rowPB.blockId, blockId: dataController.rowPB.blockId,
@ -29,7 +29,8 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
super( super(
BoardCardState.initial( BoardCardState.initial(
dataController.rowPB, dataController.rowPB,
_makeCells(fieldId, dataController.loadData()), _makeCells(groupFieldId, dataController.loadData()),
isEditing,
), ),
) { ) {
on<BoardCardEvent>( on<BoardCardEvent>(
@ -44,6 +45,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
changeReason: reason, changeReason: reason,
)); ));
}, },
setIsEditing: (bool isEditing) {
emit(state.copyWith(isEditing: isEditing));
},
); );
}, },
); );
@ -69,7 +73,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
_dataController.addListener( _dataController.addListener(
onRowChanged: (cellMap, reason) { onRowChanged: (cellMap, reason) {
if (!isClosed) { if (!isClosed) {
final cells = _makeCells(fieldId, cellMap); final cells = _makeCells(groupFieldId, cellMap);
add(BoardCardEvent.didReceiveCells(cells, reason)); add(BoardCardEvent.didReceiveCells(cells, reason));
} }
}, },
@ -77,22 +81,24 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
} }
} }
UnmodifiableListView<BoardCellEquatable> _makeCells( List<BoardCellEquatable> _makeCells(
String fieldId, GridCellMap originalCellMap) { String groupFieldId, GridCellMap originalCellMap) {
List<BoardCellEquatable> cells = []; List<BoardCellEquatable> cells = [];
for (final entry in originalCellMap.entries) { for (final entry in originalCellMap.entries) {
if (entry.value.fieldId != fieldId) { // Filter out the cell if it's fieldId equal to the groupFieldId
if (entry.value.fieldId != groupFieldId) {
cells.add(BoardCellEquatable(entry.value)); cells.add(BoardCellEquatable(entry.value));
} }
} }
return UnmodifiableListView(cells); return cells;
} }
@freezed @freezed
class BoardCardEvent with _$BoardCardEvent { class BoardCardEvent with _$BoardCardEvent {
const factory BoardCardEvent.initial() = _InitialRow; const factory BoardCardEvent.initial() = _InitialRow;
const factory BoardCardEvent.setIsEditing(bool isEditing) = _IsEditing;
const factory BoardCardEvent.didReceiveCells( const factory BoardCardEvent.didReceiveCells(
UnmodifiableListView<BoardCellEquatable> cells, List<BoardCellEquatable> cells,
RowsChangedReason reason, RowsChangedReason reason,
) = _DidReceiveCells; ) = _DidReceiveCells;
} }
@ -101,15 +107,20 @@ class BoardCardEvent with _$BoardCardEvent {
class BoardCardState with _$BoardCardState { class BoardCardState with _$BoardCardState {
const factory BoardCardState({ const factory BoardCardState({
required RowPB rowPB, required RowPB rowPB,
required UnmodifiableListView<BoardCellEquatable> cells, required List<BoardCellEquatable> cells,
required bool isEditing,
RowsChangedReason? changeReason, RowsChangedReason? changeReason,
}) = _BoardCardState; }) = _BoardCardState;
factory BoardCardState.initial( factory BoardCardState.initial(
RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) => RowPB rowPB,
List<BoardCellEquatable> cells,
bool isEditing,
) =>
BoardCardState( BoardCardState(
rowPB: rowPB, rowPB: rowPB,
cells: cells, cells: cells,
isEditing: isEditing,
); );
} }
@ -119,10 +130,12 @@ class BoardCellEquatable extends Equatable {
const BoardCellEquatable(this.identifier); const BoardCellEquatable(this.identifier);
@override @override
List<Object?> get props => [ List<Object?> get props {
identifier.fieldContext.id, return [
identifier.fieldContext.fieldType, identifier.fieldContext.id,
identifier.fieldContext.visibility, identifier.fieldContext.fieldType,
identifier.fieldContext.width, identifier.fieldContext.visibility,
]; identifier.fieldContext.width,
];
}
} }

View file

@ -64,6 +64,7 @@ class BoardContent extends StatefulWidget {
class _BoardContentState extends State<BoardContent> { class _BoardContentState extends State<BoardContent> {
late AppFlowyBoardScrollController scrollManager; late AppFlowyBoardScrollController scrollManager;
final Map<String, ValueKey> cardKeysCache = {};
final config = AppFlowyBoardConfig( final config = AppFlowyBoardConfig(
groupBackgroundColor: HexColor.fromHex('#F7F8FC'), groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
@ -83,6 +84,7 @@ class _BoardContentState extends State<BoardContent> {
buildWhen: (previous, current) => previous.groupIds != current.groupIds, buildWhen: (previous, current) => previous.groupIds != current.groupIds,
builder: (context, state) { builder: (context, state) {
final column = Column( final column = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [const _ToolbarBlocAdaptor(), _buildBoard(context)], children: [const _ToolbarBlocAdaptor(), _buildBoard(context)],
); );
@ -240,8 +242,15 @@ class _BoardContentState extends State<BoardContent> {
}, },
); );
ValueKey? key = cardKeysCache[columnItem.id];
if (key == null) {
final newKey = ValueKey(columnItem.id);
cardKeysCache[columnItem.id] = newKey;
key = newKey;
}
return AppFlowyGroupCard( return AppFlowyGroupCard(
key: ValueKey(columnItem.id), key: key,
margin: config.cardPadding, margin: config.cardPadding,
decoration: _makeBoxDecoration(context), decoration: _makeBoxDecoration(context),
child: BoardCard( child: BoardCard(

View file

@ -1,47 +1,74 @@
import 'package:app_flowy/plugins/grid/application/prelude.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:flowy_infra/notifier.dart'; import 'package:flutter/material.dart';
abstract class FocusableBoardCell { abstract class FocusableBoardCell {
set becomeFocus(bool isFocus); set becomeFocus(bool isFocus);
} }
class EditableCellNotifier { class EditableCellNotifier {
final Notifier becomeFirstResponder = Notifier(); final ValueNotifier<bool> isCellEditing;
final Notifier resignFirstResponder = Notifier(); EditableCellNotifier({bool isEditing = false})
: isCellEditing = ValueNotifier(isEditing);
EditableCellNotifier(); void dispose() {
isCellEditing.dispose();
}
} }
class EditableRowNotifier { class EditableRowNotifier {
final Map<EditableCellId, EditableCellNotifier> _cells = {}; final Map<EditableCellId, EditableCellNotifier> _cells = {};
final ValueNotifier<bool> isEditing;
EditableRowNotifier({required bool isEditing})
: isEditing = ValueNotifier(isEditing);
void insertCell( void insertCell(
GridCellIdentifier cellIdentifier, GridCellIdentifier cellIdentifier,
EditableCellNotifier notifier, EditableCellNotifier notifier,
) { ) {
assert(
_cells.values.isEmpty,
'Only one cell can receive the notification',
);
final id = EditableCellId.from(cellIdentifier);
_cells[id]?.dispose();
notifier.isCellEditing.addListener(() {
isEditing.value = notifier.isCellEditing.value;
});
_cells[EditableCellId.from(cellIdentifier)] = notifier; _cells[EditableCellId.from(cellIdentifier)] = notifier;
} }
void becomeFirstResponder() { void becomeFirstResponder() {
for (final notifier in _cells.values) { if (_cells.values.isEmpty) return;
notifier.becomeFirstResponder.notify(); assert(
} _cells.values.length == 1,
'Only one cell can receive the notification',
);
_cells.values.first.isCellEditing.value = true;
} }
void resignFirstResponder() { void resignFirstResponder() {
for (final notifier in _cells.values) { if (_cells.values.isEmpty) return;
notifier.resignFirstResponder.notify(); assert(
} _cells.values.length == 1,
'Only one cell can receive the notification',
);
_cells.values.first.isCellEditing.value = false;
} }
void clear() { void clear() {
for (final notifier in _cells.values) {
notifier.dispose();
}
_cells.clear(); _cells.clear();
} }
void dispose() { void dispose() {
for (final notifier in _cells.values) { for (final notifier in _cells.values) {
notifier.resignFirstResponder.notify(); notifier.dispose();
} }
_cells.clear(); _cells.clear();

View file

@ -5,6 +5,8 @@ import 'package:flowy_infra_ui/style_widget/text.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 'define.dart';
class BoardDateCell extends StatefulWidget { class BoardDateCell extends StatefulWidget {
final String groupId; final String groupId;
final GridCellControllerBuilder cellControllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
@ -44,10 +46,15 @@ class _BoardDateCellState extends State<BoardDateCell> {
} else { } else {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: FlowyText.regular( child: Padding(
state.dateStr, padding: EdgeInsets.symmetric(
fontSize: 13, vertical: BoardSizes.cardCellVPadding,
color: context.read<AppTheme>().shader3, ),
child: FlowyText.regular(
state.dateStr,
fontSize: 13,
color: context.read<AppTheme>().shader3,
),
), ),
); );
} }

View file

@ -4,6 +4,8 @@ import 'package:flowy_infra_ui/style_widget/text.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 'define.dart';
class BoardNumberCell extends StatefulWidget { class BoardNumberCell extends StatefulWidget {
final String groupId; final String groupId;
final GridCellControllerBuilder cellControllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
@ -43,9 +45,14 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
} else { } else {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: FlowyText.medium( child: Padding(
state.content, padding: EdgeInsets.symmetric(
fontSize: 14, vertical: BoardSizes.cardCellVPadding,
),
child: FlowyText.medium(
state.content,
fontSize: 14,
),
), ),
); );
} }

View file

@ -56,23 +56,23 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
(option) => SelectOptionTag.fromOption( (option) => SelectOptionTag.fromOption(
context: context, context: context,
option: option, option: option,
onSelected: () {
SelectOptionCellEditor.show(
context: context,
cellController: widget.cellControllerBuilder.build()
as GridSelectOptionCellController,
);
},
), ),
) )
.toList(); .toList();
return IntrinsicHeight( return IntrinsicHeight(
child: Stack( child: Padding(
alignment: AlignmentDirectional.center, padding: const EdgeInsets.symmetric(vertical: 6),
fit: StackFit.expand, child: SizedBox.expand(
children: [ child: Wrap(spacing: 4, runSpacing: 2, children: children),
Padding( ),
padding: const EdgeInsets.symmetric(vertical: 6),
child: Wrap(spacing: 4, runSpacing: 2, children: children),
),
_SelectOptionDialog(
controller: widget.cellControllerBuilder.build(),
),
],
), ),
); );
} }
@ -87,23 +87,3 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
super.dispose(); super.dispose();
} }
} }
class _SelectOptionDialog extends StatelessWidget {
final GridSelectOptionCellController _controller;
const _SelectOptionDialog({
Key? key,
required IGridCellController controller,
}) : _controller = controller as GridSelectOptionCellController,
super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(onTap: () {
SelectOptionCellEditor.show(
context,
_controller,
() {},
);
});
}
}

View file

@ -9,7 +9,6 @@ import 'define.dart';
class BoardTextCell extends StatefulWidget with EditableCell { class BoardTextCell extends StatefulWidget with EditableCell {
final String groupId; final String groupId;
final bool isFocus;
@override @override
final EditableCellNotifier? editableNotifier; final EditableCellNotifier? editableNotifier;
final GridCellControllerBuilder cellControllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
@ -18,7 +17,6 @@ class BoardTextCell extends StatefulWidget with EditableCell {
required this.groupId, required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
this.editableNotifier, this.editableNotifier,
this.isFocus = false,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -39,40 +37,42 @@ class _BoardTextCellState extends State<BoardTextCell> {
_cellBloc = BoardTextCellBloc(cellController: cellController) _cellBloc = BoardTextCellBloc(cellController: cellController)
..add(const BoardTextCellEvent.initial()); ..add(const BoardTextCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content); _controller = TextEditingController(text: _cellBloc.state.content);
focusWhenInit = widget.isFocus; focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false;
if (focusWhenInit) {
if (widget.isFocus) {
focusNode.requestFocus(); focusNode.requestFocus();
} }
focusNode.addListener(() { focusNode.addListener(() {
if (!focusNode.hasFocus) { if (!focusNode.hasFocus) {
focusWhenInit = false;
widget.editableNotifier?.isCellEditing.value = false;
_cellBloc.add(const BoardTextCellEvent.enableEdit(false)); _cellBloc.add(const BoardTextCellEvent.enableEdit(false));
if (focusWhenInit) {
setState(() {
focusWhenInit = false;
});
}
} }
}); });
_bindEditableNotifier();
widget.editableNotifier?.becomeFirstResponder.addListener(() {
if (!mounted) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
focusNode.requestFocus();
});
_cellBloc.add(const BoardTextCellEvent.enableEdit(true));
});
widget.editableNotifier?.resignFirstResponder.addListener(() {
if (!mounted) return;
_cellBloc.add(const BoardTextCellEvent.enableEdit(false));
});
super.initState(); super.initState();
} }
void _bindEditableNotifier() {
widget.editableNotifier?.isCellEditing.addListener(() {
if (!mounted) return;
final isEditing = widget.editableNotifier?.isCellEditing.value ?? false;
if (isEditing) {
WidgetsBinding.instance.addPostFrameCallback((_) {
focusNode.requestFocus();
});
}
_cellBloc.add(BoardTextCellEvent.enableEdit(isEditing));
});
}
@override
void didUpdateWidget(covariant BoardTextCell oldWidget) {
_bindEditableNotifier();
super.didUpdateWidget(oldWidget);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
@ -84,6 +84,15 @@ class _BoardTextCellState extends State<BoardTextCell> {
} }
}, },
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>( child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
buildWhen: (previous, current) {
if (previous.content != current.content &&
_controller.text == current.content &&
current.enableEdit) {
return false;
}
return previous != current;
},
builder: (context, state) { builder: (context, state) {
if (state.content.isEmpty && if (state.content.isEmpty &&
state.enableEdit == false && state.enableEdit == false &&
@ -127,24 +136,26 @@ class _BoardTextCellState extends State<BoardTextCell> {
} }
Widget _buildTextField() { Widget _buildTextField() {
return TextField( return IntrinsicHeight(
controller: _controller, child: TextField(
focusNode: focusNode, controller: _controller,
onChanged: (value) => focusChanged(), focusNode: focusNode,
onEditingComplete: () => focusNode.unfocus(), onChanged: (value) => focusChanged(),
maxLines: 1, onEditingComplete: () => focusNode.unfocus(),
style: const TextStyle( maxLines: null,
fontSize: 14, style: const TextStyle(
fontWeight: FontWeight.w500, fontSize: 14,
fontFamily: 'Mulish', fontWeight: FontWeight.w500,
), fontFamily: 'Mulish',
decoration: InputDecoration( ),
// Magic number 4 makes the textField take up the same space as FlowyText decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric( // Magic number 4 makes the textField take up the same space as FlowyText
vertical: BoardSizes.cardCellVPadding + 4, contentPadding: EdgeInsets.symmetric(
vertical: BoardSizes.cardCellVPadding + 4,
),
border: InputBorder.none,
isDense: true,
), ),
border: InputBorder.none,
isDense: true,
), ),
); );
} }

View file

@ -4,6 +4,8 @@ import 'package:flowy_infra/theme.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 'define.dart';
class BoardUrlCell extends StatefulWidget { class BoardUrlCell extends StatefulWidget {
final String groupId; final String groupId;
final GridCellControllerBuilder cellControllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
@ -43,14 +45,19 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
} else { } else {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: RichText( child: Padding(
textAlign: TextAlign.left, padding: EdgeInsets.symmetric(
text: TextSpan( vertical: BoardSizes.cardCellVPadding,
text: state.content, ),
style: TextStyle( child: RichText(
color: theme.main2, textAlign: TextAlign.left,
fontSize: 14, text: TextSpan(
decoration: TextDecoration.underline, text: state.content,
style: TextStyle(
color: theme.main2,
fontSize: 14,
decoration: TextDecoration.underline,
),
), ),
), ),
), ),

View file

@ -5,6 +5,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet
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/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flutter/foundation.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 'board_cell.dart'; import 'board_cell.dart';
@ -41,12 +42,19 @@ class _BoardCardState extends State<BoardCard> {
@override @override
void initState() { void initState() {
rowNotifier = EditableRowNotifier(); rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
_cardBloc = BoardCardBloc( _cardBloc = BoardCardBloc(
gridId: widget.gridId, gridId: widget.gridId,
fieldId: widget.fieldId, groupFieldId: widget.fieldId,
dataController: widget.dataController, dataController: widget.dataController,
isEditing: widget.isEditing,
)..add(const BoardCardEvent.initial()); )..add(const BoardCardEvent.initial());
rowNotifier.isEditing.addListener(() {
if (!mounted) return;
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
});
super.initState(); super.initState();
} }
@ -56,10 +64,15 @@ class _BoardCardState extends State<BoardCard> {
value: _cardBloc, value: _cardBloc,
child: BlocBuilder<BoardCardBloc, BoardCardState>( child: BlocBuilder<BoardCardBloc, BoardCardState>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
return previous.cells.length != current.cells.length; if (previous.cells.length != current.cells.length ||
previous.isEditing != current.isEditing) {
return true;
}
return !listEquals(previous.cells, current.cells);
}, },
builder: (context, state) { builder: (context, state) {
return BoardCardContainer( return BoardCardContainer(
buildAccessoryWhen: () => state.isEditing == false,
accessoryBuilder: (context) { accessoryBuilder: (context) {
return [ return [
_CardEditOption( _CardEditOption(
@ -92,17 +105,24 @@ class _BoardCardState extends State<BoardCard> {
rowNotifier.clear(); rowNotifier.clear();
cells.asMap().forEach( cells.asMap().forEach(
(int index, GridCellIdentifier cellId) { (int index, GridCellIdentifier cellId) {
final cellNotifier = EditableCellNotifier(); EditableCellNotifier cellNotifier;
if (index == 0) {
// Only use the first cell to receive user's input when click the edit
// button
cellNotifier = EditableCellNotifier(
isEditing: rowNotifier.isEditing.value,
);
rowNotifier.insertCell(cellId, cellNotifier);
} else {
cellNotifier = EditableCellNotifier();
}
Widget child = widget.cellBuilder.buildCell( Widget child = widget.cellBuilder.buildCell(
widget.groupId, widget.groupId,
cellId, cellId,
index == 0 ? widget.isEditing : false,
cellNotifier, cellNotifier,
); );
if (index == 0) {
rowNotifier.insertCell(cellId, cellNotifier);
}
child = Padding( child = Padding(
key: cellId.key(), key: cellId.key(),
padding: const EdgeInsets.only(left: 4, right: 4), padding: const EdgeInsets.only(left: 4, right: 4),

View file

@ -23,7 +23,6 @@ class BoardCellBuilder {
Widget buildCell( Widget buildCell(
String groupId, String groupId,
GridCellIdentifier cellId, GridCellIdentifier cellId,
bool isEditing,
EditableCellNotifier cellNotifier, EditableCellNotifier cellNotifier,
) { ) {
final cellControllerBuilder = GridCellControllerBuilder( final cellControllerBuilder = GridCellControllerBuilder(
@ -69,7 +68,6 @@ class BoardCellBuilder {
return BoardTextCell( return BoardTextCell(
groupId: groupId, groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
isFocus: isEditing,
editableNotifier: cellNotifier, editableNotifier: cellNotifier,
key: key, key: key,
); );

View file

@ -7,11 +7,13 @@ import 'package:styled_widget/styled_widget.dart';
class BoardCardContainer extends StatelessWidget { class BoardCardContainer extends StatelessWidget {
final Widget child; final Widget child;
final CardAccessoryBuilder? accessoryBuilder; final CardAccessoryBuilder? accessoryBuilder;
final bool Function()? buildAccessoryWhen;
final void Function(BuildContext) onTap; final void Function(BuildContext) onTap;
const BoardCardContainer({ const BoardCardContainer({
required this.child, required this.child,
required this.onTap, required this.onTap,
this.accessoryBuilder, this.accessoryBuilder,
this.buildAccessoryWhen,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -22,7 +24,12 @@ class BoardCardContainer extends StatelessWidget {
child: Consumer<_CardContainerNotifier>( child: Consumer<_CardContainerNotifier>(
builder: (context, notifier, _) { builder: (context, notifier, _) {
Widget container = Center(child: child); Widget container = Center(child: child);
if (accessoryBuilder != null) { bool shouldBuildAccessory = true;
if (buildAccessoryWhen != null) {
shouldBuildAccessory = buildAccessoryWhen!.call();
}
if (accessoryBuilder != null && shouldBuildAccessory) {
final accessories = accessoryBuilder!(context); final accessories = accessoryBuilder!(context);
if (accessories.isNotEmpty) { if (accessories.isNotEmpty) {
container = _CardEnterRegion( container = _CardEnterRegion(

View file

@ -46,7 +46,8 @@ class _DateCellState extends GridCellState<GridDateCell> {
@override @override
void initState() { void initState() {
_popover = PopoverController(); _popover = PopoverController();
final cellController = widget.cellControllerBuilder.build(); final cellController =
widget.cellControllerBuilder.build() as GridDateCellController;
_cellBloc = getIt<DateCellBloc>(param1: cellController) _cellBloc = getIt<DateCellBloc>(param1: cellController)
..add(const DateCellEvent.initial()); ..add(const DateCellEvent.initial());
super.initState(); super.initState();

View file

@ -27,13 +27,13 @@ const double _editorPannelWidth = 300;
class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
final GridSelectOptionCellController cellController; final GridSelectOptionCellController cellController;
final VoidCallback onDismissed; final VoidCallback? onDismissed;
static double editorPanelWidth = 300; static double editorPanelWidth = 300;
const SelectOptionCellEditor({ const SelectOptionCellEditor({
required this.cellController, required this.cellController,
required this.onDismissed, this.onDismissed,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -61,14 +61,14 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
); );
} }
static void show( static void show({
BuildContext context, required BuildContext context,
GridSelectOptionCellController cellContext, required GridSelectOptionCellController cellController,
VoidCallback onDismissed, VoidCallback? onDismissed,
) { }) {
SelectOptionCellEditor.remove(context); SelectOptionCellEditor.remove(context);
final editor = SelectOptionCellEditor( final editor = SelectOptionCellEditor(
cellController: cellContext, cellController: cellController,
onDismissed: onDismissed, onDismissed: onDismissed,
); );
@ -97,7 +97,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
bool asBarrier() => true; bool asBarrier() => true;
@override @override
void didRemove() => onDismissed(); void didRemove() => onDismissed?.call();
} }
class _OptionList extends StatelessWidget { class _OptionList extends StatelessWidget {

View file

@ -25,7 +25,7 @@ class AppFlowyGroupFooter extends StatefulWidget {
class _AppFlowyGroupFooterState extends State<AppFlowyGroupFooter> { class _AppFlowyGroupFooterState extends State<AppFlowyGroupFooter> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return InkWell(
onTap: widget.onAddButtonClick, onTap: widget.onAddButtonClick,
child: SizedBox( child: SizedBox(
height: widget.height, height: widget.height,

View file

@ -8,7 +8,9 @@ pub trait GroupAction: Send + Sync {
fn default_cell_rev(&self) -> Option<CellRevision> { fn default_cell_rev(&self) -> Option<CellRevision> {
None None
} }
fn use_default_group(&self) -> bool {
true
}
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool; fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>; fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>; fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;

View file

@ -97,11 +97,8 @@ where
self.groups_map.values().collect() self.groups_map.values().collect()
} }
/// Returns the all the groups that contain the default group. pub(crate) fn default_group(&self) -> &Group {
pub(crate) fn clone_groups(&self) -> Vec<Group> { &self.default_group
let mut groups: Vec<Group> = self.groups_map.values().cloned().collect();
groups.push(self.default_group.clone());
groups
} }
/// Iterate mut the groups. The default group will be the last one that get mutated. /// Iterate mut the groups. The default group will be the last one that get mutated.

View file

@ -182,7 +182,13 @@ where
} }
fn groups(&self) -> Vec<Group> { fn groups(&self) -> Vec<Group> {
self.group_ctx.clone_groups() if self.use_default_group() {
let mut groups: Vec<Group> = self.group_ctx.concrete_groups().into_iter().cloned().collect();
groups.push(self.group_ctx.default_group().clone());
groups
} else {
self.group_ctx.concrete_groups().into_iter().cloned().collect()
}
} }
fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
@ -243,7 +249,7 @@ where
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
let mut changesets = self.add_row_if_match(row_rev, &cell_data); let mut changesets = self.add_row_if_match(row_rev, &cell_data);
let default_group_changeset = self.update_default_group(row_rev, &changesets); let default_group_changeset = self.update_default_group(row_rev, &changesets);
tracing::info!("default_group_changeset: {}", default_group_changeset); tracing::trace!("default_group_changeset: {}", default_group_changeset);
if !default_group_changeset.is_empty() { if !default_group_changeset.is_empty() {
changesets.push(default_group_changeset); changesets.push(default_group_changeset);
} }

View file

@ -27,6 +27,10 @@ impl GroupAction for CheckboxGroupController {
Some(CellRevision::new(UNCHECK.to_string())) Some(CellRevision::new(UNCHECK.to_string()))
} }
fn use_default_group(&self) -> bool {
false
}
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool { fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
if cell_data.is_check() { if cell_data.is_check() {
content == CHECK content == CHECK

View file

@ -3,7 +3,6 @@ use crate::services::field::*;
use crate::services::row::RowRevisionBuilder; use crate::services::row::RowRevisionBuilder;
use flowy_grid_data_model::revision::BuildGridContext; use flowy_grid_data_model::revision::BuildGridContext;
use flowy_sync::client_grid::GridBuilder; use flowy_sync::client_grid::GridBuilder;
use lib_infra::util::timestamp;
pub fn make_default_grid() -> BuildGridContext { pub fn make_default_grid() -> BuildGridContext {
let mut grid_builder = GridBuilder::new(); let mut grid_builder = GridBuilder::new();
@ -59,6 +58,45 @@ pub fn make_default_board() -> BuildGridContext {
let single_select_field_id = single_select_field.id.clone(); let single_select_field_id = single_select_field.id.clone();
grid_builder.add_field(single_select_field); grid_builder.add_field(single_select_field);
for i in 0..3 {
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
row_builder.insert_select_option_cell(&single_select_field_id, to_do_option.id.clone());
let data = format!("Card {}", i + 1);
row_builder.insert_text_cell(&text_field_id, data);
let row = row_builder.build();
grid_builder.add_row(row);
}
grid_builder.build()
}
#[allow(dead_code)]
pub fn make_default_board_2() -> BuildGridContext {
let mut grid_builder = GridBuilder::new();
// text
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Description")
.visibility(true)
.primary(true)
.build();
let text_field_id = text_field.id.clone();
grid_builder.add_field(text_field);
// single select
let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple);
let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange);
let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow);
let single_select_type_option = SingleSelectTypeOptionBuilder::default()
.add_option(to_do_option.clone())
.add_option(doing_option.clone())
.add_option(done_option.clone());
let single_select_field = FieldBuilder::new(single_select_type_option)
.name("Status")
.visibility(true)
.build();
let single_select_field_id = single_select_field.id.clone();
grid_builder.add_field(single_select_field);
// MultiSelect // MultiSelect
let work_option = SelectOptionPB::with_color("Work", SelectOptionColorPB::Aqua); let work_option = SelectOptionPB::with_color("Work", SelectOptionColorPB::Aqua);
let travel_option = SelectOptionPB::with_color("Travel", SelectOptionColorPB::Green); let travel_option = SelectOptionPB::with_color("Travel", SelectOptionColorPB::Green);
@ -152,102 +190,3 @@ pub fn make_default_board() -> BuildGridContext {
grid_builder.build() grid_builder.build()
} }
#[allow(dead_code)]
pub fn make_default_board2() -> BuildGridContext {
let mut grid_builder = GridBuilder::new();
// text
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.primary(true)
.build();
let text_field_id = text_field.id.clone();
grid_builder.add_field(text_field);
// date
let date_type_option = DateTypeOptionBuilder::default();
let date_field = FieldBuilder::new(date_type_option)
.name("Date")
.visibility(true)
.build();
let date_field_id = date_field.id.clone();
let timestamp = timestamp();
grid_builder.add_field(date_field);
// single select
let in_progress_option = SelectOptionPB::new("In progress");
let not_started_option = SelectOptionPB::new("Not started");
let done_option = SelectOptionPB::new("Done");
let single_select_type_option = SingleSelectTypeOptionBuilder::default()
.add_option(not_started_option.clone())
.add_option(in_progress_option)
.add_option(done_option);
let single_select_field = FieldBuilder::new(single_select_type_option)
.name("Status")
.visibility(true)
.build();
let single_select_field_id = single_select_field.id.clone();
grid_builder.add_field(single_select_field);
// MultiSelect
let apple_option = SelectOptionPB::new("Apple");
let banana_option = SelectOptionPB::new("Banana");
let pear_option = SelectOptionPB::new("Pear");
let multi_select_type_option = MultiSelectTypeOptionBuilder::default()
.add_option(banana_option.clone())
.add_option(apple_option.clone())
.add_option(pear_option);
let multi_select_field = FieldBuilder::new(multi_select_type_option)
.name("Fruit")
.visibility(true)
.build();
let multi_select_field_id = multi_select_field.id.clone();
grid_builder.add_field(multi_select_field);
// Number
let number_type_option = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number_type_option)
.name("Price")
.visibility(true)
.build();
let number_field_id = number_field.id.clone();
grid_builder.add_field(number_field);
// Checkbox
let checkbox_type_option = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox_type_option).name("Reimbursement").build();
let checkbox_field_id = checkbox_field.id.clone();
grid_builder.add_field(checkbox_field);
// Url
let url_type_option = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url_type_option).name("Shop Link").build();
let url_field_id = url_field.id.clone();
grid_builder.add_field(url_field);
// Insert rows
for i in 0..10 {
// insert single select
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
row_builder.insert_select_option_cell(&single_select_field_id, not_started_option.id.clone());
// insert multi select
row_builder.insert_select_option_cell(&multi_select_field_id, apple_option.id.clone());
row_builder.insert_select_option_cell(&multi_select_field_id, banana_option.id.clone());
// insert text
row_builder.insert_text_cell(&text_field_id, format!("Card {}", i));
// insert date
row_builder.insert_date_cell(&date_field_id, timestamp);
// number
row_builder.insert_number_cell(&number_field_id, i);
// checkbox
row_builder.insert_checkbox_cell(&checkbox_field_id, i % 2 == 0);
// url
row_builder.insert_url_cell(&url_field_id, "https://appflowy.io".to_string());
let row = row_builder.build();
grid_builder.add_row(row);
}
grid_builder.build()
}