feat: show notes icon when notes is not empty (#3893)

* feat: show notes icon when notes is not empty

* fix: redundant clone

* chore: update collab and fix after merging main
This commit is contained in:
Mathias Mogensen
2023-11-09 00:30:50 +01:00
committed by GitHub
parent 73f1c211c2
commit 17651bf64c
19 changed files with 249 additions and 156 deletions

View File

@ -40,6 +40,7 @@ class RowBackendService {
required String rowId,
String? iconURL,
String? coverURL,
bool? isDocumentEmpty,
}) {
final payload = UpdateRowMetaChangesetPB.create()
..viewId = viewId
@ -52,6 +53,10 @@ class RowBackendService {
payload.coverUrl = coverURL;
}
if (isDocumentEmpty != null) {
payload.isDocumentEmpty = isDocumentEmpty;
}
return DatabaseEventUpdateRowMeta(payload).send();
}

View File

@ -115,9 +115,9 @@ class BoardPage extends StatelessWidget {
class BoardContent extends StatefulWidget {
const BoardContent({
Key? key,
super.key,
this.onEditStateChanged,
}) : super(key: key);
});
final VoidCallback? onEditStateChanged;
@ -275,6 +275,7 @@ class _BoardContentState extends State<BoardContent> {
boardBloc.state.editingRow?.row.id == groupItem.row.id;
final groupItemId = groupItem.row.id + groupData.group.groupId;
return AppFlowyGroupCard(
key: ValueKey(groupItemId),
margin: config.cardPadding,

View File

@ -228,6 +228,7 @@ class UngroupedItem extends StatelessWidget {
text: cellBuilder.buildCell(
cellContext: cellContext,
renderHook: renderHook,
hasNotes: false,
),
onTap: onPressed,
),

View File

@ -5,6 +5,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -43,6 +44,14 @@ class RowDocumentBloc extends Bloc<RowDocumentEvent, RowDocumentState> {
),
);
},
updateIsEmpty: (isEmpty) async {
final unitOrFailure = await _rowBackendSvc.updateMeta(
rowId: rowId,
isDocumentEmpty: isEmpty,
);
unitOrFailure.fold((l) => null, (err) => Log.error(err));
},
);
},
);
@ -104,6 +113,8 @@ class RowDocumentEvent with _$RowDocumentEvent {
_DidReceiveRowDocument;
const factory RowDocumentEvent.didReceiveError(FlowyError error) =
_DidReceiveError;
const factory RowDocumentEvent.updateIsEmpty(bool isDocumentEmpty) =
_UpdateIsEmpty;
}
@freezed

View File

@ -48,23 +48,23 @@ class RowCard<CustomCardData> extends StatefulWidget {
final RowCardStyleConfiguration styleConfiguration;
const RowCard({
super.key,
required this.rowMeta,
required this.viewId,
this.groupingFieldId,
this.groupId,
required this.isEditing,
required this.rowCache,
required this.cellBuilder,
required this.openCard,
required this.onStartEditing,
required this.onEndEditing,
this.groupingFieldId,
this.groupId,
this.cardData,
this.styleConfiguration = const RowCardStyleConfiguration(
showAccessory: true,
),
this.renderHook,
Key? key,
}) : super(key: key);
});
@override
State<RowCard<CustomCardData>> createState() =>
@ -79,6 +79,7 @@ class _RowCardState<T> extends State<RowCard<T>> {
@override
void initState() {
super.initState();
rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
_cardBloc = CardBloc(
viewId: widget.viewId,
@ -100,7 +101,6 @@ class _RowCardState<T> extends State<RowCard<T>> {
});
popoverController = PopoverController();
super.initState();
}
@override
@ -197,21 +197,22 @@ class _RowCardState<T> extends State<RowCard<T>> {
}
class _CardContent<CustomCardData> extends StatelessWidget {
final CardCellBuilder<CustomCardData> cellBuilder;
final EditableRowNotifier rowNotifier;
final List<DatabaseCellContext> cells;
final RowCardRenderHook<CustomCardData>? renderHook;
final CustomCardData? cardData;
final RowCardStyleConfiguration styleConfiguration;
const _CardContent({
super.key,
required this.rowNotifier,
required this.cellBuilder,
required this.cells,
required this.cardData,
required this.styleConfiguration,
this.renderHook,
Key? key,
}) : super(key: key);
});
final CardCellBuilder<CustomCardData> cellBuilder;
final EditableRowNotifier rowNotifier;
final List<DatabaseCellContext> cells;
final RowCardRenderHook<CustomCardData>? renderHook;
final CustomCardData? cardData;
final RowCardStyleConfiguration styleConfiguration;
@override
Widget build(BuildContext context) {
@ -244,31 +245,30 @@ class _CardContent<CustomCardData> extends StatelessWidget {
// Remove all the cell listeners.
rowNotifier.unbind();
cells.asMap().forEach(
(int index, DatabaseCellContext cellContext) {
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
final cellNotifier = EditableCardNotifier(isEditing: isEditing);
cells.asMap().forEach((int index, DatabaseCellContext cellContext) {
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
final cellNotifier = EditableCardNotifier(isEditing: isEditing);
if (index == 0) {
// Only use the first cell to receive user's input when click the edit
// button
rowNotifier.bindCell(cellContext, cellNotifier);
}
if (index == 0) {
// Only use the first cell to receive user's input when click the edit
// button
rowNotifier.bindCell(cellContext, cellNotifier);
}
final child = Padding(
key: cellContext.key(),
padding: styleConfiguration.cellPadding,
child: cellBuilder.buildCell(
cellContext: cellContext,
cellNotifier: cellNotifier,
renderHook: renderHook,
cardData: cardData,
),
);
final child = Padding(
key: cellContext.key(),
padding: styleConfiguration.cellPadding,
child: cellBuilder.buildCell(
cellContext: cellContext,
cellNotifier: cellNotifier,
renderHook: renderHook,
cardData: cardData,
hasNotes: !cellContext.rowMeta.isDocumentEmpty,
),
);
children.add(child);
},
);
children.add(child);
});
return children;
}
}

View File

@ -1,4 +1,5 @@
import 'dart:collection';
import 'package:appflowy/plugins/database_view/application/row/row_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -16,8 +17,10 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
final String? groupFieldId;
final RowBackendService _rowBackendSvc;
final RowCache _rowCache;
VoidCallback? _rowCallback;
final String viewId;
final RowListener _rowListener;
VoidCallback? _rowCallback;
CardBloc({
required this.rowMeta,
@ -26,6 +29,7 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
required RowCache rowCache,
required bool isEditing,
}) : _rowBackendSvc = RowBackendService(viewId: viewId),
_rowListener = RowListener(rowMeta.id),
_rowCache = rowCache,
super(
RowCardState.initial(
@ -50,6 +54,16 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
setIsEditing: (bool isEditing) {
emit(state.copyWith(isEditing: isEditing));
},
didReceiveRowMeta: (rowMeta) {
final cells = state.cells
.map(
(cell) => cell.rowMeta.id == rowMeta.id
? cell.copyWith(rowMeta: rowMeta)
: cell,
)
.toList();
emit(state.copyWith(cells: cells));
},
);
},
);
@ -85,6 +99,14 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
}
},
);
_rowListener.start(
onMetaChanged: (meta) {
if (!isClosed) {
add(RowCardEvent.didReceiveRowMeta(meta));
}
},
);
}
}
@ -116,6 +138,9 @@ class RowCardEvent with _$RowCardEvent {
List<DatabaseCellContext> cells,
ChangedReason reason,
) = _DidReceiveCells;
const factory RowCardEvent.didReceiveRowMeta(
RowMetaPB meta,
) = _DidReceiveRowMeta;
}
@freezed

View File

@ -25,6 +25,7 @@ class CardCellBuilder<CustomCardData> {
required DatabaseCellContext cellContext,
EditableCardNotifier? cellNotifier,
RowCardRenderHook<CustomCardData>? renderHook,
required bool hasNotes,
}) {
final cellControllerBuilder = CellControllerBuilder(
cellContext: cellContext,
@ -86,12 +87,13 @@ class CardCellBuilder<CustomCardData> {
);
case FieldType.RichText:
return TextCardCell<CustomCardData>(
key: key,
style: isStyleOrNull<TextCardCellStyle>(style),
cardData: cardData,
renderHook: renderHook?.renderHook[FieldType.RichText],
cellControllerBuilder: cellControllerBuilder,
editableNotifier: cellNotifier,
cardData: cardData,
style: isStyleOrNull<TextCardCellStyle>(style),
key: key,
showNotes: cellContext.fieldInfo.isPrimary && hasNotes,
);
case FieldType.URL:
return URLCardCell<CustomCardData>(

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../row/cell_builder.dart';
@ -15,19 +17,21 @@ class TextCardCellStyle extends CardCellStyle {
class TextCardCell<CustomCardData>
extends CardCell<CustomCardData, TextCardCellStyle> with EditableCell {
const TextCardCell({
super.key,
super.cardData,
super.style,
required this.cellControllerBuilder,
this.editableNotifier,
this.renderHook,
this.showNotes = false,
});
@override
final EditableCardNotifier? editableNotifier;
final CellControllerBuilder cellControllerBuilder;
final CellRenderHook<String, CustomCardData>? renderHook;
const TextCardCell({
required this.cellControllerBuilder,
required CustomCardData? cardData,
this.editableNotifier,
this.renderHook,
TextCardCellStyle? style,
Key? key,
}) : super(key: key, style: style, cardData: cardData);
final bool showNotes;
@override
State<TextCardCell> createState() => _TextCellState();
@ -122,14 +126,19 @@ class _TextCellState extends State<TextCardCell> {
return const SizedBox();
}
//
Widget child;
if (state.enableEdit || focusWhenInit) {
child = _buildTextField();
} else {
child = _buildText(state);
}
return Align(alignment: Alignment.centerLeft, child: child);
final child = state.enableEdit || focusWhenInit
? _buildTextField()
: _buildText(state);
return Row(
children: [
if (widget.showNotes) ...[
const FlowySvg(FlowySvgs.notes_s),
const HSpace(4),
],
Expanded(child: child),
],
);
},
),
),
@ -151,9 +160,9 @@ class _TextCellState extends State<TextCardCell> {
double _fontSize() {
if (widget.style != null) {
return widget.style!.fontSize;
} else {
return 14;
}
return 14;
}
Widget _buildText(TextCellState state) {

View File

@ -15,10 +15,10 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
final GridCellBuilder cellBuilder;
const RowDetailPage({
super.key,
required this.rowController,
required this.cellBuilder,
Key? key,
}) : super(key: key);
});
@override
State<RowDetailPage> createState() => _RowDetailPageState();

View File

@ -24,12 +24,8 @@ class RowDocument extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<RowDocumentBloc>(
create: (context) => RowDocumentBloc(
viewId: viewId,
rowId: rowId,
)..add(
const RowDocumentEvent.initial(),
),
create: (context) => RowDocumentBloc(viewId: viewId, rowId: rowId)
..add(const RowDocumentEvent.initial()),
child: BlocBuilder<RowDocumentBloc, RowDocumentState>(
builder: (context, state) {
return state.loadingState.when(
@ -43,6 +39,9 @@ class RowDocument extends StatelessWidget {
finish: () => RowEditor(
viewPB: state.viewPB!,
scrollController: scrollController,
onIsEmptyChanged: (isEmpty) => context
.read<RowDocumentBloc>()
.add(RowDocumentEvent.updateIsEmpty(isEmpty)),
),
);
},
@ -56,10 +55,12 @@ class RowEditor extends StatefulWidget {
super.key,
required this.viewPB,
required this.scrollController,
this.onIsEmptyChanged,
});
final ViewPB viewPB;
final ScrollController scrollController;
final void Function(bool)? onIsEmptyChanged;
@override
State<RowEditor> createState() => _RowEditorState();
@ -87,47 +88,56 @@ class _RowEditorState extends State<RowEditor> {
providers: [
BlocProvider.value(value: documentBloc),
],
child: BlocBuilder<DocumentBloc, DocumentState>(
builder: (context, state) {
return state.loadingState.when(
loading: () => const Center(
child: CircularProgressIndicator.adaptive(),
),
finish: (result) {
return result.fold(
(error) => FlowyErrorPage.message(
error.toString(),
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
),
(_) {
final editorState = documentBloc.editorState;
if (editorState == null) {
return const SizedBox.shrink();
}
return IntrinsicHeight(
child: Container(
constraints: const BoxConstraints(minHeight: 300),
child: AppFlowyEditorPage(
shrinkWrap: true,
autoFocus: false,
editorState: editorState,
scrollController: widget.scrollController,
styleCustomizer: EditorStyleCustomizer(
context: context,
padding: const EdgeInsets.symmetric(horizontal: 10),
),
showParagraphPlaceholder: (editorState, node) =>
editorState.document.isEmpty,
placeholderText: (node) =>
LocaleKeys.cardDetails_notesPlaceholder.tr(),
),
),
);
},
);
},
);
child: BlocListener<DocumentBloc, DocumentState>(
listenWhen: (previous, current) =>
previous.isDocumentEmpty != current.isDocumentEmpty,
listener: (context, state) {
if (state.isDocumentEmpty != null) {
widget.onIsEmptyChanged?.call(state.isDocumentEmpty!);
}
},
child: BlocBuilder<DocumentBloc, DocumentState>(
builder: (context, state) {
return state.loadingState.when(
loading: () => const Center(
child: CircularProgressIndicator.adaptive(),
),
finish: (result) {
return result.fold(
(error) => FlowyErrorPage.message(
error.toString(),
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
),
(_) {
final editorState = documentBloc.editorState;
if (editorState == null) {
return const SizedBox.shrink();
}
return IntrinsicHeight(
child: Container(
constraints: const BoxConstraints(minHeight: 300),
child: AppFlowyEditorPage(
shrinkWrap: true,
autoFocus: false,
editorState: editorState,
scrollController: widget.scrollController,
styleCustomizer: EditorStyleCustomizer(
context: context,
padding: const EdgeInsets.symmetric(horizontal: 10),
),
showParagraphPlaceholder: (editorState, node) =>
editorState.document.isEmpty,
placeholderText: (node) =>
LocaleKeys.cardDetails_notesPlaceholder.tr(),
),
),
);
},
);
},
);
},
),
),
);
}