Merge pull request #944 from AppFlowy-IO/feat/edit_card_button

chore: add edit card button
This commit is contained in:
Nathan.fooo
2022-08-30 23:08:31 +08:00
committed by GitHub
10 changed files with 178 additions and 34 deletions

View File

@ -27,6 +27,9 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
emit(state.copyWith(content: text)); emit(state.copyWith(content: text));
} }
}, },
enableEdit: (bool enabled) {
emit(state.copyWith(enableEdit: enabled));
},
); );
}, },
); );
@ -57,6 +60,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
class BoardTextCellEvent with _$BoardTextCellEvent { class BoardTextCellEvent with _$BoardTextCellEvent {
const factory BoardTextCellEvent.initial() = _InitialCell; const factory BoardTextCellEvent.initial() = _InitialCell;
const factory BoardTextCellEvent.updateText(String text) = _UpdateContent; const factory BoardTextCellEvent.updateText(String text) = _UpdateContent;
const factory BoardTextCellEvent.enableEdit(bool enabled) = _EnableEdit;
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) = const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
_DidReceiveCellUpdate; _DidReceiveCellUpdate;
} }
@ -65,10 +69,12 @@ class BoardTextCellEvent with _$BoardTextCellEvent {
class BoardTextCellState with _$BoardTextCellState { class BoardTextCellState with _$BoardTextCellState {
const factory BoardTextCellState({ const factory BoardTextCellState({
required String content, required String content,
required bool enableEdit,
}) = _BoardTextCellState; }) = _BoardTextCellState;
factory BoardTextCellState.initial(GridCellController context) => factory BoardTextCellState.initial(GridCellController context) =>
BoardTextCellState( BoardTextCellState(
content: context.getCellData() ?? "", content: context.getCellData() ?? "",
enableEdit: false,
); );
} }

View File

@ -107,7 +107,10 @@ class BoardCardState with _$BoardCardState {
factory BoardCardState.initial( factory BoardCardState.initial(
RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) => RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) =>
BoardCardState(rowPB: rowPB, cells: cells); BoardCardState(
rowPB: rowPB,
cells: cells,
);
} }
class BoardCellEquatable extends Equatable { class BoardCellEquatable extends Equatable {

View File

@ -1,3 +1,54 @@
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:flowy_infra/notifier.dart';
abstract class FocusableBoardCell { abstract class FocusableBoardCell {
set becomeFocus(bool isFocus); set becomeFocus(bool isFocus);
} }
class EditableCellNotifier {
final Notifier becomeFirstResponder = Notifier();
final Notifier resignFirstResponder = Notifier();
EditableCellNotifier();
}
class EditableRowNotifier {
Map<EditableCellId, EditableCellNotifier> cells = {};
void insertCell(
GridCellIdentifier cellIdentifier,
EditableCellNotifier notifier,
) {
cells[EditableCellId.from(cellIdentifier)] = notifier;
}
void becomeFirstResponder() {
for (final notifier in cells.values) {
notifier.becomeFirstResponder.notify();
}
}
void resignFirstResponder() {
for (final notifier in cells.values) {
notifier.resignFirstResponder.notify();
}
}
}
abstract class EditableCell {
EditableCellNotifier? get editableNotifier;
}
class EditableCellId {
String fieldId;
String rowId;
EditableCellId(this.rowId, this.fieldId);
factory EditableCellId.from(GridCellIdentifier cellIdentifier) =>
EditableCellId(
cellIdentifier.rowId,
cellIdentifier.fieldId,
);
}

View File

@ -5,13 +5,18 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_c
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class BoardSelectOptionCell extends StatefulWidget { import 'board_cell.dart';
class BoardSelectOptionCell extends StatefulWidget with EditableCell {
final String groupId; final String groupId;
final GridCellControllerBuilder cellControllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
@override
final EditableCellNotifier? editableNotifier;
const BoardSelectOptionCell({ const BoardSelectOptionCell({
required this.groupId, required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
this.editableNotifier,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);

View File

@ -4,15 +4,19 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.da
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class BoardTextCell extends StatefulWidget { import 'board_cell.dart';
class BoardTextCell extends StatefulWidget with EditableCell {
final String groupId; final String groupId;
final bool isFocus; final bool isFocus;
@override
final EditableCellNotifier? editableNotifier;
final GridCellControllerBuilder cellControllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
const BoardTextCell({ const BoardTextCell({
required this.groupId, required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
this.editableNotifier,
this.isFocus = false, this.isFocus = false,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -37,6 +41,18 @@ class _BoardTextCellState extends State<BoardTextCell> {
if (widget.isFocus) { if (widget.isFocus) {
focusNode.requestFocus(); focusNode.requestFocus();
} }
widget.editableNotifier?.becomeFirstResponder.addListener(() {
if (!mounted) return;
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();
} }
@ -50,7 +66,13 @@ class _BoardTextCellState extends State<BoardTextCell> {
_controller.text = state.content; _controller.text = state.content;
} }
}, },
child: TextField( child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
buildWhen: (previous, current) =>
previous.enableEdit != current.enableEdit,
builder: (context, state) {
return TextField(
// autofocus: true,
// enabled: state.enableEdit,
controller: _controller, controller: _controller,
focusNode: focusNode, focusNode: focusNode,
onChanged: (value) => focusChanged(), onChanged: (value) => focusChanged(),
@ -62,6 +84,8 @@ class _BoardTextCellState extends State<BoardTextCell> {
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
), ),
);
},
), ),
), ),
); );

View File

@ -7,6 +7,7 @@ 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/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 'card_cell_builder.dart'; import 'card_cell_builder.dart';
import 'card_container.dart'; import 'card_container.dart';
@ -36,9 +37,11 @@ class BoardCard extends StatefulWidget {
class _BoardCardState extends State<BoardCard> { class _BoardCardState extends State<BoardCard> {
late BoardCardBloc _cardBloc; late BoardCardBloc _cardBloc;
late EditableRowNotifier rowNotifier;
@override @override
void initState() { void initState() {
rowNotifier = EditableRowNotifier();
_cardBloc = BoardCardBloc( _cardBloc = BoardCardBloc(
gridId: widget.gridId, gridId: widget.gridId,
fieldId: widget.fieldId, fieldId: widget.fieldId,
@ -58,7 +61,12 @@ class _BoardCardState extends State<BoardCard> {
builder: (context, state) { builder: (context, state) {
return BoardCardContainer( return BoardCardContainer(
accessoryBuilder: (context) { accessoryBuilder: (context) {
return [const _CardMoreOption()]; return [
_CardEditOption(
startEditing: () => rowNotifier.becomeFirstResponder(),
),
const _CardMoreOption(),
];
}, },
onTap: (context) { onTap: (context) {
widget.openCard(context); widget.openCard(context);
@ -83,11 +91,14 @@ class _BoardCardState extends State<BoardCard> {
final List<Widget> children = []; final List<Widget> children = [];
cells.asMap().forEach( cells.asMap().forEach(
(int index, GridCellIdentifier cellId) { (int index, GridCellIdentifier cellId) {
final cellNotifier = EditableCellNotifier();
Widget child = widget.cellBuilder.buildCell( Widget child = widget.cellBuilder.buildCell(
widget.groupId, widget.groupId,
cellId, cellId,
widget.isEditing, widget.isEditing,
cellNotifier,
); );
rowNotifier.insertCell(cellId, cellNotifier);
if (index != 0) { if (index != 0) {
child = Padding( child = Padding(
@ -121,7 +132,11 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return svgWidget('grid/details', color: context.read<AppTheme>().iconColor); return Padding(
padding: const EdgeInsets.all(3.0),
child:
svgWidget('grid/details', color: context.read<AppTheme>().iconColor),
);
} }
@override @override
@ -131,3 +146,27 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
).show(context, direction: AnchorDirection.bottomWithCenterAligned); ).show(context, direction: AnchorDirection.bottomWithCenterAligned);
} }
} }
class _CardEditOption extends StatelessWidget with CardAccessory {
final VoidCallback startEditing;
const _CardEditOption({
required this.startEditing,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(3.0),
child: svgWidget(
'editor/edit',
color: context.read<AppTheme>().iconColor,
),
);
}
@override
void onTap(BuildContext context) {
startEditing();
}
}

View File

@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'board_cell.dart';
import 'board_checkbox_cell.dart'; import 'board_checkbox_cell.dart';
import 'board_date_cell.dart'; import 'board_date_cell.dart';
import 'board_number_cell.dart'; import 'board_number_cell.dart';
@ -23,6 +24,7 @@ class BoardCellBuilder {
String groupId, String groupId,
GridCellIdentifier cellId, GridCellIdentifier cellId,
bool isEditing, bool isEditing,
EditableCellNotifier cellNotifier,
) { ) {
final cellControllerBuilder = GridCellControllerBuilder( final cellControllerBuilder = GridCellControllerBuilder(
delegate: delegate, delegate: delegate,
@ -54,6 +56,7 @@ class BoardCellBuilder {
return BoardSelectOptionCell( return BoardSelectOptionCell(
groupId: groupId, groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
editableNotifier: cellNotifier,
key: key, key: key,
); );
case FieldType.Number: case FieldType.Number:
@ -67,6 +70,7 @@ class BoardCellBuilder {
groupId: groupId, groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
isFocus: isEditing, isFocus: isEditing,
editableNotifier: cellNotifier,
key: key, key: key,
); );
case FieldType.URL: case FieldType.URL:

View File

@ -69,12 +69,11 @@ class CardAccessoryContainer extends StatelessWidget {
style: HoverStyle( style: HoverStyle(
hoverColor: theme.hover, hoverColor: theme.hover,
backgroundColor: theme.surface, backgroundColor: theme.surface,
borderRadius: BorderRadius.zero,
), ),
builder: (_, onHover) => Container( builder: (_, onHover) => SizedBox(
width: 26, width: 24,
height: 26, height: 24,
padding: const EdgeInsets.all(3),
decoration: _makeBoxDecoration(context),
child: accessory, child: accessory,
), ),
); );
@ -85,7 +84,11 @@ class CardAccessoryContainer extends StatelessWidget {
); );
}).toList(); }).toList();
return Wrap(children: children, spacing: 6); return Container(
clipBehavior: Clip.hardEdge,
decoration: _makeBoxDecoration(context),
child: Row(children: children),
);
} }
} }
@ -95,15 +98,16 @@ BoxDecoration _makeBoxDecoration(BuildContext context) {
return BoxDecoration( return BoxDecoration(
color: Colors.transparent, color: Colors.transparent,
border: Border.fromBorderSide(borderSide), border: Border.fromBorderSide(borderSide),
boxShadow: const [ // boxShadow: const [
BoxShadow( // BoxShadow(
color: Colors.transparent, // color: Colors.transparent,
spreadRadius: 0, // spreadRadius: 0,
blurRadius: 2, // blurRadius: 5,
offset: Offset.zero, // offset: Offset.zero,
) // )
], // ],
borderRadius: const BorderRadius.all(Radius.circular(6)),
borderRadius: const BorderRadius.all(Radius.circular(4)),
); );
} }

View File

@ -31,7 +31,8 @@ class PublishNotifier<T> extends ChangeNotifier {
T? get currentValue => _value; T? get currentValue => _value;
void addPublishListener(void Function(T) callback, {bool Function()? listenWhen}) { void addPublishListener(void Function(T) callback,
{bool Function()? listenWhen}) {
super.addListener( super.addListener(
() { () {
if (_value == null) { if (_value == null) {
@ -47,3 +48,9 @@ class PublishNotifier<T> extends ChangeNotifier {
); );
} }
} }
class Notifier extends ChangeNotifier {
void notify() {
notifyListeners();
}
}

View File

@ -154,6 +154,7 @@ pub fn select_option_color_from_index(index: usize) -> SelectOptionColorPB {
_ => SelectOptionColorPB::Purple, _ => SelectOptionColorPB::Purple,
} }
} }
pub struct SelectOptionIds(Vec<String>); pub struct SelectOptionIds(Vec<String>);
impl SelectOptionIds { impl SelectOptionIds {