mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge branch 'release/0.0.5' into merge/release_005
This commit is contained in:
commit
4f97133183
@ -99,7 +99,6 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
));
|
||||
},
|
||||
endEditRow: (rowId) {
|
||||
assert(state.editingRow.isSome());
|
||||
state.editingRow.fold(() => null, (editingRow) {
|
||||
assert(editingRow.row.id == rowId);
|
||||
emit(state.copyWith(editingRow: none()));
|
||||
|
@ -7,20 +7,20 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'card_data_controller.dart';
|
||||
|
||||
part 'card_bloc.freezed.dart';
|
||||
|
||||
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
final String fieldId;
|
||||
final String groupFieldId;
|
||||
final RowFFIService _rowService;
|
||||
final CardDataController _dataController;
|
||||
|
||||
BoardCardBloc({
|
||||
required this.fieldId,
|
||||
required this.groupFieldId,
|
||||
required String gridId,
|
||||
required CardDataController dataController,
|
||||
required bool isEditing,
|
||||
}) : _rowService = RowFFIService(
|
||||
gridId: gridId,
|
||||
blockId: dataController.rowPB.blockId,
|
||||
@ -29,7 +29,8 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
super(
|
||||
BoardCardState.initial(
|
||||
dataController.rowPB,
|
||||
_makeCells(fieldId, dataController.loadData()),
|
||||
_makeCells(groupFieldId, dataController.loadData()),
|
||||
isEditing,
|
||||
),
|
||||
) {
|
||||
on<BoardCardEvent>(
|
||||
@ -44,6 +45,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
changeReason: reason,
|
||||
));
|
||||
},
|
||||
setIsEditing: (bool isEditing) {
|
||||
emit(state.copyWith(isEditing: isEditing));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -69,7 +73,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
_dataController.addListener(
|
||||
onRowChanged: (cellMap, reason) {
|
||||
if (!isClosed) {
|
||||
final cells = _makeCells(fieldId, cellMap);
|
||||
final cells = _makeCells(groupFieldId, cellMap);
|
||||
add(BoardCardEvent.didReceiveCells(cells, reason));
|
||||
}
|
||||
},
|
||||
@ -77,22 +81,24 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
}
|
||||
}
|
||||
|
||||
UnmodifiableListView<BoardCellEquatable> _makeCells(
|
||||
String fieldId, GridCellMap originalCellMap) {
|
||||
List<BoardCellEquatable> _makeCells(
|
||||
String groupFieldId, GridCellMap originalCellMap) {
|
||||
List<BoardCellEquatable> cells = [];
|
||||
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));
|
||||
}
|
||||
}
|
||||
return UnmodifiableListView(cells);
|
||||
return cells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCardEvent with _$BoardCardEvent {
|
||||
const factory BoardCardEvent.initial() = _InitialRow;
|
||||
const factory BoardCardEvent.setIsEditing(bool isEditing) = _IsEditing;
|
||||
const factory BoardCardEvent.didReceiveCells(
|
||||
UnmodifiableListView<BoardCellEquatable> cells,
|
||||
List<BoardCellEquatable> cells,
|
||||
RowsChangedReason reason,
|
||||
) = _DidReceiveCells;
|
||||
}
|
||||
@ -101,15 +107,20 @@ class BoardCardEvent with _$BoardCardEvent {
|
||||
class BoardCardState with _$BoardCardState {
|
||||
const factory BoardCardState({
|
||||
required RowPB rowPB,
|
||||
required UnmodifiableListView<BoardCellEquatable> cells,
|
||||
required List<BoardCellEquatable> cells,
|
||||
required bool isEditing,
|
||||
RowsChangedReason? changeReason,
|
||||
}) = _BoardCardState;
|
||||
|
||||
factory BoardCardState.initial(
|
||||
RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) =>
|
||||
RowPB rowPB,
|
||||
List<BoardCellEquatable> cells,
|
||||
bool isEditing,
|
||||
) =>
|
||||
BoardCardState(
|
||||
rowPB: rowPB,
|
||||
cells: cells,
|
||||
isEditing: isEditing,
|
||||
);
|
||||
}
|
||||
|
||||
@ -119,10 +130,12 @@ class BoardCellEquatable extends Equatable {
|
||||
const BoardCellEquatable(this.identifier);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
identifier.fieldContext.id,
|
||||
identifier.fieldContext.fieldType,
|
||||
identifier.fieldContext.visibility,
|
||||
identifier.fieldContext.width,
|
||||
];
|
||||
List<Object?> get props {
|
||||
return [
|
||||
identifier.fieldContext.id,
|
||||
identifier.fieldContext.fieldType,
|
||||
identifier.fieldContext.visibility,
|
||||
identifier.fieldContext.width,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ class BoardContent extends StatefulWidget {
|
||||
|
||||
class _BoardContentState extends State<BoardContent> {
|
||||
late AppFlowyBoardScrollController scrollManager;
|
||||
final Map<String, ValueKey> cardKeysCache = {};
|
||||
|
||||
final config = AppFlowyBoardConfig(
|
||||
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
@ -83,6 +84,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||
builder: (context, state) {
|
||||
final column = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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(
|
||||
key: ValueKey(columnItem.id),
|
||||
key: key,
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: BoardCard(
|
||||
|
@ -1,47 +1,74 @@
|
||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class FocusableBoardCell {
|
||||
set becomeFocus(bool isFocus);
|
||||
}
|
||||
|
||||
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 {
|
||||
final Map<EditableCellId, EditableCellNotifier> _cells = {};
|
||||
final ValueNotifier<bool> isEditing;
|
||||
|
||||
EditableRowNotifier({required bool isEditing})
|
||||
: isEditing = ValueNotifier(isEditing);
|
||||
|
||||
void insertCell(
|
||||
GridCellIdentifier cellIdentifier,
|
||||
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;
|
||||
}
|
||||
|
||||
void becomeFirstResponder() {
|
||||
for (final notifier in _cells.values) {
|
||||
notifier.becomeFirstResponder.notify();
|
||||
}
|
||||
if (_cells.values.isEmpty) return;
|
||||
assert(
|
||||
_cells.values.length == 1,
|
||||
'Only one cell can receive the notification',
|
||||
);
|
||||
_cells.values.first.isCellEditing.value = true;
|
||||
}
|
||||
|
||||
void resignFirstResponder() {
|
||||
for (final notifier in _cells.values) {
|
||||
notifier.resignFirstResponder.notify();
|
||||
}
|
||||
if (_cells.values.isEmpty) return;
|
||||
assert(
|
||||
_cells.values.length == 1,
|
||||
'Only one cell can receive the notification',
|
||||
);
|
||||
_cells.values.first.isCellEditing.value = false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for (final notifier in _cells.values) {
|
||||
notifier.dispose();
|
||||
}
|
||||
_cells.clear();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final notifier in _cells.values) {
|
||||
notifier.resignFirstResponder.notify();
|
||||
notifier.dispose();
|
||||
}
|
||||
|
||||
_cells.clear();
|
||||
|
@ -5,6 +5,8 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'define.dart';
|
||||
|
||||
class BoardDateCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
@ -44,10 +46,15 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
||||
fontSize: 13,
|
||||
color: context.read<AppTheme>().shader3,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
||||
fontSize: 13,
|
||||
color: context.read<AppTheme>().shader3,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'define.dart';
|
||||
|
||||
class BoardNumberCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
@ -43,9 +45,14 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -56,23 +56,23 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
(option) => SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
onSelected: () {
|
||||
SelectOptionCellEditor.show(
|
||||
context: context,
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridSelectOptionCellController,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Wrap(spacing: 4, runSpacing: 2, children: children),
|
||||
),
|
||||
_SelectOptionDialog(
|
||||
controller: widget.cellControllerBuilder.build(),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: SizedBox.expand(
|
||||
child: Wrap(spacing: 4, runSpacing: 2, children: children),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -98,12 +98,13 @@ class _SelectOptionDialog extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(onTap: () {
|
||||
SelectOptionCellEditor.show(
|
||||
context,
|
||||
_controller,
|
||||
() {},
|
||||
);
|
||||
});
|
||||
return SizedBox(
|
||||
child: InkWell(onTap: () {
|
||||
SelectOptionCellEditor.show(
|
||||
context: context,
|
||||
cellController: _controller,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import 'define.dart';
|
||||
|
||||
class BoardTextCell extends StatefulWidget with EditableCell {
|
||||
final String groupId;
|
||||
final bool isFocus;
|
||||
@override
|
||||
final EditableCellNotifier? editableNotifier;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
@ -18,7 +17,6 @@ class BoardTextCell extends StatefulWidget with EditableCell {
|
||||
required this.groupId,
|
||||
required this.cellControllerBuilder,
|
||||
this.editableNotifier,
|
||||
this.isFocus = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -39,40 +37,42 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
_cellBloc = BoardTextCellBloc(cellController: cellController)
|
||||
..add(const BoardTextCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
focusWhenInit = widget.isFocus;
|
||||
|
||||
if (widget.isFocus) {
|
||||
focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false;
|
||||
if (focusWhenInit) {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
focusNode.addListener(() {
|
||||
if (!focusNode.hasFocus) {
|
||||
focusWhenInit = false;
|
||||
widget.editableNotifier?.isCellEditing.value = false;
|
||||
_cellBloc.add(const BoardTextCellEvent.enableEdit(false));
|
||||
|
||||
if (focusWhenInit) {
|
||||
setState(() {
|
||||
focusWhenInit = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
_bindEditableNotifier();
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
@ -84,6 +84,15 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
}
|
||||
},
|
||||
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) {
|
||||
if (state.content.isEmpty &&
|
||||
state.enableEdit == false &&
|
||||
@ -127,24 +136,26 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
}
|
||||
|
||||
Widget _buildTextField() {
|
||||
return TextField(
|
||||
controller: _controller,
|
||||
focusNode: focusNode,
|
||||
onChanged: (value) => focusChanged(),
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Mulish',
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
// Magic number 4 makes the textField take up the same space as FlowyText
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding + 4,
|
||||
return IntrinsicHeight(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
focusNode: focusNode,
|
||||
onChanged: (value) => focusChanged(),
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
maxLines: null,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Mulish',
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
// Magic number 4 makes the textField take up the same space as FlowyText
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding + 4,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'define.dart';
|
||||
|
||||
class BoardUrlCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
@ -43,14 +45,19 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: RichText(
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
text: state.content,
|
||||
style: TextStyle(
|
||||
color: theme.main2,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
text: state.content,
|
||||
style: TextStyle(
|
||||
color: theme.main2,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -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/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'board_cell.dart';
|
||||
@ -41,12 +42,19 @@ class _BoardCardState extends State<BoardCard> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
rowNotifier = EditableRowNotifier();
|
||||
rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
|
||||
_cardBloc = BoardCardBloc(
|
||||
gridId: widget.gridId,
|
||||
fieldId: widget.fieldId,
|
||||
groupFieldId: widget.fieldId,
|
||||
dataController: widget.dataController,
|
||||
isEditing: widget.isEditing,
|
||||
)..add(const BoardCardEvent.initial());
|
||||
|
||||
rowNotifier.isEditing.addListener(() {
|
||||
if (!mounted) return;
|
||||
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -56,10 +64,15 @@ class _BoardCardState extends State<BoardCard> {
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
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) {
|
||||
return BoardCardContainer(
|
||||
buildAccessoryWhen: () => state.isEditing == false,
|
||||
accessoryBuilder: (context) {
|
||||
return [
|
||||
_CardEditOption(
|
||||
@ -92,17 +105,24 @@ class _BoardCardState extends State<BoardCard> {
|
||||
rowNotifier.clear();
|
||||
cells.asMap().forEach(
|
||||
(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.groupId,
|
||||
cellId,
|
||||
index == 0 ? widget.isEditing : false,
|
||||
cellNotifier,
|
||||
);
|
||||
|
||||
if (index == 0) {
|
||||
rowNotifier.insertCell(cellId, cellNotifier);
|
||||
}
|
||||
child = Padding(
|
||||
key: cellId.key(),
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
|
@ -23,7 +23,6 @@ class BoardCellBuilder {
|
||||
Widget buildCell(
|
||||
String groupId,
|
||||
GridCellIdentifier cellId,
|
||||
bool isEditing,
|
||||
EditableCellNotifier cellNotifier,
|
||||
) {
|
||||
final cellControllerBuilder = GridCellControllerBuilder(
|
||||
@ -69,7 +68,6 @@ class BoardCellBuilder {
|
||||
return BoardTextCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
isFocus: isEditing,
|
||||
editableNotifier: cellNotifier,
|
||||
key: key,
|
||||
);
|
||||
|
@ -7,11 +7,13 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
class BoardCardContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final CardAccessoryBuilder? accessoryBuilder;
|
||||
final bool Function()? buildAccessoryWhen;
|
||||
final void Function(BuildContext) onTap;
|
||||
const BoardCardContainer({
|
||||
required this.child,
|
||||
required this.onTap,
|
||||
this.accessoryBuilder,
|
||||
this.buildAccessoryWhen,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -22,7 +24,12 @@ class BoardCardContainer extends StatelessWidget {
|
||||
child: Consumer<_CardContainerNotifier>(
|
||||
builder: (context, notifier, _) {
|
||||
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);
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _CardEnterRegion(
|
||||
|
@ -46,7 +46,8 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
@override
|
||||
void initState() {
|
||||
_popover = PopoverController();
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridDateCellController;
|
||||
_cellBloc = getIt<DateCellBloc>(param1: cellController)
|
||||
..add(const DateCellEvent.initial());
|
||||
super.initState();
|
||||
|
@ -27,13 +27,13 @@ const double _editorPannelWidth = 300;
|
||||
|
||||
class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
||||
final GridSelectOptionCellController cellController;
|
||||
final VoidCallback onDismissed;
|
||||
final VoidCallback? onDismissed;
|
||||
|
||||
static double editorPanelWidth = 300;
|
||||
|
||||
const SelectOptionCellEditor({
|
||||
required this.cellController,
|
||||
required this.onDismissed,
|
||||
this.onDismissed,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -61,14 +61,14 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
||||
);
|
||||
}
|
||||
|
||||
static void show(
|
||||
BuildContext context,
|
||||
GridSelectOptionCellController cellContext,
|
||||
VoidCallback onDismissed,
|
||||
) {
|
||||
static void show({
|
||||
required BuildContext context,
|
||||
required GridSelectOptionCellController cellController,
|
||||
VoidCallback? onDismissed,
|
||||
}) {
|
||||
SelectOptionCellEditor.remove(context);
|
||||
final editor = SelectOptionCellEditor(
|
||||
cellController: cellContext,
|
||||
cellController: cellController,
|
||||
onDismissed: onDismissed,
|
||||
);
|
||||
|
||||
@ -97,7 +97,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
||||
bool asBarrier() => true;
|
||||
|
||||
@override
|
||||
void didRemove() => onDismissed();
|
||||
void didRemove() => onDismissed?.call();
|
||||
}
|
||||
|
||||
class _OptionList extends StatelessWidget {
|
||||
|
@ -25,7 +25,7 @@ class AppFlowyGroupFooter extends StatefulWidget {
|
||||
class _AppFlowyGroupFooterState extends State<AppFlowyGroupFooter> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
return InkWell(
|
||||
onTap: widget.onAddButtonClick,
|
||||
child: SizedBox(
|
||||
height: widget.height,
|
||||
|
@ -8,7 +8,9 @@ pub trait GroupAction: Send + Sync {
|
||||
fn default_cell_rev(&self) -> Option<CellRevision> {
|
||||
None
|
||||
}
|
||||
|
||||
fn use_default_group(&self) -> bool {
|
||||
true
|
||||
}
|
||||
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 remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
|
||||
|
@ -97,11 +97,8 @@ where
|
||||
self.groups_map.values().collect()
|
||||
}
|
||||
|
||||
/// Returns the all the groups that contain the default group.
|
||||
pub(crate) fn clone_groups(&self) -> Vec<Group> {
|
||||
let mut groups: Vec<Group> = self.groups_map.values().cloned().collect();
|
||||
groups.push(self.default_group.clone());
|
||||
groups
|
||||
pub(crate) fn default_group(&self) -> &Group {
|
||||
&self.default_group
|
||||
}
|
||||
|
||||
/// Iterate mut the groups. The default group will be the last one that get mutated.
|
||||
|
@ -182,7 +182,13 @@ where
|
||||
}
|
||||
|
||||
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)> {
|
||||
@ -243,7 +249,7 @@ where
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
let mut changesets = self.add_row_if_match(row_rev, &cell_data);
|
||||
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() {
|
||||
changesets.push(default_group_changeset);
|
||||
}
|
||||
|
@ -27,6 +27,10 @@ impl GroupAction for CheckboxGroupController {
|
||||
Some(CellRevision::new(UNCHECK.to_string()))
|
||||
}
|
||||
|
||||
fn use_default_group(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
|
||||
if cell_data.is_check() {
|
||||
content == CHECK
|
||||
|
@ -3,7 +3,6 @@ use crate::services::field::*;
|
||||
use crate::services::row::RowRevisionBuilder;
|
||||
use flowy_grid_data_model::revision::BuildGridContext;
|
||||
use flowy_sync::client_grid::GridBuilder;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
pub fn make_default_grid() -> BuildGridContext {
|
||||
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();
|
||||
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
|
||||
let work_option = SelectOptionPB::with_color("Work", SelectOptionColorPB::Aqua);
|
||||
let travel_option = SelectOptionPB::with_color("Travel", SelectOptionColorPB::Green);
|
||||
@ -152,102 +190,3 @@ pub fn make_default_board() -> BuildGridContext {
|
||||
|
||||
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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user