mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -228,6 +228,7 @@ class UngroupedItem extends StatelessWidget {
|
||||
text: cellBuilder.buildCell(
|
||||
cellContext: cellContext,
|
||||
renderHook: renderHook,
|
||||
hasNotes: false,
|
||||
),
|
||||
onTap: onPressed,
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>(
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user