mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #944 from AppFlowy-IO/feat/edit_card_button
chore: add edit card button
This commit is contained in:
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,18 +66,26 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
|||||||
_controller.text = state.content;
|
_controller.text = state.content;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: TextField(
|
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||||
controller: _controller,
|
buildWhen: (previous, current) =>
|
||||||
focusNode: focusNode,
|
previous.enableEdit != current.enableEdit,
|
||||||
onChanged: (value) => focusChanged(),
|
builder: (context, state) {
|
||||||
onEditingComplete: () => focusNode.unfocus(),
|
return TextField(
|
||||||
maxLines: 1,
|
// autofocus: true,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
// enabled: state.enableEdit,
|
||||||
decoration: const InputDecoration(
|
controller: _controller,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 6),
|
focusNode: focusNode,
|
||||||
border: InputBorder.none,
|
onChanged: (value) => focusChanged(),
|
||||||
isDense: true,
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
),
|
maxLines: 1,
|
||||||
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 6),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user