chore: adjust board card UI (#3954)

* chore: adjust board card UI

* chore: fix accessory popover and add title placeholder

* chore: add pubspec.lock

* chore: fix integration test

* chore: apply suggestions from code review

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

* chore: apply suggestions from Mathias

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
This commit is contained in:
Richard Shiue 2023-11-24 00:25:12 +08:00 committed by GitHub
parent bddaac05ae
commit e18e031710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 294 additions and 279 deletions

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database_view/board/presentation/widgets/board_column_header.dart'; import 'package:appflowy/plugins/database_view/board/presentation/widgets/board_column_header.dart';
import 'package:appflowy/plugins/database_view/widgets/card/container/card_container.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_board/appflowy_board.dart'; import 'package:appflowy_board/appflowy_board.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
@ -15,8 +16,8 @@ const defaultLastCardName = 'Card 3';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('board add row test', () { group('board add row test:', () {
testWidgets('Add card from header', (tester) async { testWidgets('from header', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();
@ -45,7 +46,7 @@ void main() {
const newCardName = 'Card 4'; const newCardName = 'Card 4';
await tester.enterText( await tester.enterText(
find.descendant( find.descendant(
of: find.byType(IntrinsicHeight), of: find.byType(RowCardContainer),
matching: find.byType(TextField), matching: find.byType(TextField),
), ),
newCardName, newCardName,
@ -59,7 +60,7 @@ void main() {
expect(firstCardText.text, newCardName); expect(firstCardText.text, newCardName);
}); });
testWidgets('Add card from footer', (tester) async { testWidgets('from footer', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();
@ -87,7 +88,7 @@ void main() {
const newCardName = 'Card 4'; const newCardName = 'Card 4';
await tester.enterText( await tester.enterText(
find.descendant( find.descendant(
of: find.byType(IntrinsicHeight), of: find.byType(RowCardContainer),
matching: find.byType(TextField), matching: find.byType(TextField),
), ),
newCardName, newCardName,

View File

@ -17,6 +17,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_board/appflowy_board.dart'; import 'package:appflowy_board/appflowy_board.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart' hide Card; import 'package:flutter/material.dart' hide Card;
@ -159,10 +160,12 @@ class _BoardContentState extends State<BoardContent> {
boardScrollController: scrollManager, boardScrollController: scrollManager,
scrollController: scrollController, scrollController: scrollController,
controller: context.read<BoardBloc>().boardController, controller: context.read<BoardBloc>().boardController,
groupConstraints: const BoxConstraints.tightFor(width: 300), groupConstraints: const BoxConstraints.tightFor(width: 256),
config: const AppFlowyBoardConfig( config: const AppFlowyBoardConfig(
groupPadding: EdgeInsets.symmetric(horizontal: 4), groupPadding: EdgeInsets.symmetric(horizontal: 4),
groupItemPadding: EdgeInsets.symmetric(horizontal: 4), groupItemPadding: EdgeInsets.symmetric(horizontal: 4),
footerPadding: EdgeInsets.fromLTRB(4, 14, 4, 4),
stretchGroupHeight: false,
), ),
leading: HiddenGroupsColumn(margin: config.headerPadding), leading: HiddenGroupsColumn(margin: config.headerPadding),
trailing: showCreateGroupButton trailing: showCreateGroupButton
@ -200,20 +203,15 @@ class _BoardContentState extends State<BoardContent> {
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) { Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
return AppFlowyGroupFooter( return AppFlowyGroupFooter(
height: 50, height: 36,
margin: config.footerPadding, margin: config.footerPadding,
icon: SizedBox( icon: FlowySvg(
height: 20, FlowySvgs.add_s,
width: 20, color: Theme.of(context).hintColor,
child: FlowySvg(
FlowySvgs.add_s,
color: Theme.of(context).hintColor,
),
), ),
title: FlowyText.medium( title: FlowyText.medium(
LocaleKeys.board_column_createNewCard.tr(), LocaleKeys.board_column_createNewCard.tr(),
color: Theme.of(context).hintColor, color: Theme.of(context).hintColor,
fontSize: 14,
), ),
onAddButtonClick: () => context onAddButtonClick: () => context
.read<BoardBloc>() .read<BoardBloc>()
@ -266,6 +264,14 @@ class _BoardContentState extends State<BoardContent> {
rowMeta: rowMeta, rowMeta: rowMeta,
rowCache: rowCache, rowCache: rowCache,
), ),
styleConfiguration: RowCardStyleConfiguration(
hoverStyle: HoverStyle(
hoverColor: Theme.of(context).brightness == Brightness.light
? const Color(0x0F1F2329)
: const Color(0x0FEFF4FB),
foregroundColorOnHover: Theme.of(context).colorScheme.onBackground,
),
),
onStartEditing: () => boardBloc onStartEditing: () => boardBloc
.add(BoardEvent.startEditingRow(groupData.group, groupItem.row)), .add(BoardEvent.startEditingRow(groupData.group, groupItem.row)),
onEndEditing: () => onEndEditing: () =>
@ -280,8 +286,10 @@ class _BoardContentState extends State<BoardContent> {
borderRadius: const BorderRadius.all(Radius.circular(6)), borderRadius: const BorderRadius.all(Radius.circular(6)),
border: Border.fromBorderSide( border: Border.fromBorderSide(
BorderSide( BorderSide(
color: Theme.of(context).dividerColor, color: Theme.of(context).brightness == Brightness.light
width: 1.4, ? const Color(0xFF1F2329).withOpacity(0.12)
: const Color(0xFF59647A),
width: 1.0,
), ),
), ),
boxShadow: [ boxShadow: [

View File

@ -3,7 +3,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart'; import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:appflowy/plugins/database_view/widgets/card/define.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
import 'package:appflowy_board/appflowy_board.dart'; import 'package:appflowy_board/appflowy_board.dart';
@ -158,10 +157,8 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
filled: true, filled: true,
fillColor: Theme.of(context).colorScheme.surface, fillColor: Theme.of(context).colorScheme.surface,
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
contentPadding: EdgeInsets.symmetric( contentPadding:
vertical: CardSizes.cardCellVPadding + 4, const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
horizontal: 8,
),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,

View File

@ -73,9 +73,9 @@ class RowCard<CustomCardData> extends StatefulWidget {
} }
class _RowCardState<T> extends State<RowCard<T>> { class _RowCardState<T> extends State<RowCard<T>> {
final popoverController = PopoverController();
late final CardBloc _cardBloc; late final CardBloc _cardBloc;
late final EditableRowNotifier rowNotifier; late final EditableRowNotifier rowNotifier;
late final PopoverController popoverController;
AccessoryType? accessoryType; AccessoryType? accessoryType;
@override @override
@ -100,8 +100,6 @@ class _RowCardState<T> extends State<RowCard<T>> {
widget.onEndEditing(); widget.onEndEditing();
} }
}); });
popoverController = PopoverController();
} }
@override @override
@ -111,26 +109,21 @@ class _RowCardState<T> extends State<RowCard<T>> {
child: BlocBuilder<CardBloc, RowCardState>( child: BlocBuilder<CardBloc, RowCardState>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
// Rebuild when: // Rebuild when:
// 1.If the length of the cells is not the same // 1. If the length of the cells is not the same or isEditing changed
// 2.isEditing changed
if (previous.cells.length != current.cells.length || if (previous.cells.length != current.cells.length ||
previous.isEditing != current.isEditing) { previous.isEditing != current.isEditing) {
return true; return true;
} }
// 3.Compare the content of the cells. The cells consists of // 2. the content of the cells changed
// list of [BoardCellEquatable] that extends the [Equatable].
return !listEquals(previous.cells, current.cells); return !listEquals(previous.cells, current.cells);
}, },
builder: (context, state) { builder: (context, state) {
// mobile
if (PlatformExtension.isMobile) { if (PlatformExtension.isMobile) {
// TODO(yijing): refactor it in mobile to display card in database view // TODO(yijing): refactor it in mobile to display card in database view
return RowCardContainer( return RowCardContainer(
buildAccessoryWhen: () => state.isEditing == false, buildAccessoryWhen: () => state.isEditing == false,
accessoryBuilder: (context) { accessories: const [],
return [];
},
openAccessory: (p0) {}, openAccessory: (p0) {},
openCard: (context) => widget.openCard(context), openCard: (context) => widget.openCard(context),
child: _CardContent<T>( child: _CardContent<T>(
@ -143,29 +136,27 @@ class _RowCardState<T> extends State<RowCard<T>> {
), ),
); );
} }
// desktop
return AppFlowyPopover( return AppFlowyPopover(
controller: popoverController, controller: popoverController,
triggerActions: PopoverTriggerFlags.none, triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(140, 200)), constraints: BoxConstraints.loose(const Size(140, 200)),
margin: const EdgeInsets.all(6),
direction: PopoverDirection.rightWithCenterAligned, direction: PopoverDirection.rightWithCenterAligned,
popupBuilder: (popoverContext) => _handlePopoverBuilder( popupBuilder: (popoverContext) {
context, return RowActions(
popoverContext, viewId: _cardBloc.viewId,
), rowId: _cardBloc.rowMeta.id,
groupId: widget.groupId,
);
},
child: RowCardContainer( child: RowCardContainer(
buildAccessoryWhen: () => state.isEditing == false, buildAccessoryWhen: () => state.isEditing == false,
accessoryBuilder: (context) { accessories: [
if (widget.styleConfiguration.showAccessory == false) { if (widget.styleConfiguration.showAccessory) ...[
return []; _CardEditOption(rowNotifier: rowNotifier),
} else { const CardMoreOption(),
return [ ],
_CardEditOption(rowNotifier: rowNotifier), ],
CardMoreOption(),
];
}
},
openAccessory: _handleOpenAccessory, openAccessory: _handleOpenAccessory,
openCard: (context) => widget.openCard(context), openCard: (context) => widget.openCard(context),
child: _CardContent<T>( child: _CardContent<T>(
@ -194,22 +185,6 @@ class _RowCardState<T> extends State<RowCard<T>> {
} }
} }
Widget _handlePopoverBuilder(
BuildContext context,
BuildContext popoverContext,
) {
switch (accessoryType!) {
case AccessoryType.edit:
throw UnimplementedError();
case AccessoryType.more:
return RowActions(
viewId: context.read<CardBloc>().viewId,
rowId: context.read<CardBloc>().rowMeta.id,
groupId: widget.groupId,
);
}
}
@override @override
Future<void> dispose() async { Future<void> dispose() async {
rowNotifier.dispose(); rowNotifier.dispose();
@ -241,6 +216,7 @@ class _CardContent<CustomCardData> extends StatelessWidget {
if (styleConfiguration.hoverStyle != null) { if (styleConfiguration.hoverStyle != null) {
return FlowyHover( return FlowyHover(
style: styleConfiguration.hoverStyle, style: styleConfiguration.hoverStyle,
buildWhenOnHover: () => !rowNotifier.isEditing.value,
child: Padding( child: Padding(
padding: styleConfiguration.cardPadding, padding: styleConfiguration.cardPadding,
child: Column( child: Column(
@ -296,21 +272,21 @@ class _CardContent<CustomCardData> extends StatelessWidget {
} }
class CardMoreOption extends StatelessWidget with CardAccessory { class CardMoreOption extends StatelessWidget with CardAccessory {
CardMoreOption({Key? key}) : super(key: key); const CardMoreOption({super.key});
@override
AccessoryType get type => AccessoryType.more;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(3.0), padding: const EdgeInsets.all(3.0),
child: FlowySvg( child: FlowySvg(
FlowySvgs.details_s, FlowySvgs.three_dots_s,
color: Theme.of(context).iconTheme.color, color: Theme.of(context).hintColor,
), ),
); );
} }
@override
AccessoryType get type => AccessoryType.more;
} }
class _CardEditOption extends StatelessWidget with CardAccessory { class _CardEditOption extends StatelessWidget with CardAccessory {
@ -326,7 +302,7 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
padding: const EdgeInsets.all(3.0), padding: const EdgeInsets.all(3.0),
child: FlowySvg( child: FlowySvg(
FlowySvgs.edit_s, FlowySvgs.edit_s,
color: Theme.of(context).iconTheme.color, color: Theme.of(context).hintColor,
), ),
); );
} }
@ -346,7 +322,7 @@ class RowCardStyleConfiguration {
const RowCardStyleConfiguration({ const RowCardStyleConfiguration({
this.showAccessory = true, this.showAccessory = true,
this.cellPadding = const EdgeInsets.only(left: 4, right: 4), this.cellPadding = EdgeInsets.zero,
this.cardPadding = const EdgeInsets.all(8), this.cardPadding = const EdgeInsets.all(8),
this.hoverStyle, this.hoverStyle,
}); });

View File

@ -5,6 +5,7 @@ import 'package:flowy_infra_ui/style_widget/icon_button.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 '../define.dart';
import 'card_cell.dart'; import 'card_cell.dart';
class CheckboxCardCell extends CardCell { class CheckboxCardCell extends CardCell {
@ -24,11 +25,11 @@ class _CheckboxCellState extends State<CheckboxCardCell> {
@override @override
void initState() { void initState() {
super.initState();
final cellController = final cellController =
widget.cellControllerBuilder.build() as CheckboxCellController; widget.cellControllerBuilder.build() as CheckboxCellController;
_cellBloc = CheckboxCellBloc(cellController: cellController); _cellBloc = CheckboxCellBloc(cellController: cellController)
_cellBloc.add(const CheckboxCellEvent.initial()); ..add(const CheckboxCellEvent.initial());
super.initState();
} }
@override @override
@ -47,7 +48,7 @@ class _CheckboxCellState extends State<CheckboxCardCell> {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2), padding: CardSizes.cardCellPadding,
child: FlowyIconButton( child: FlowyIconButton(
iconPadding: EdgeInsets.zero, iconPadding: EdgeInsets.zero,
icon: icon, icon: icon,

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../row/cells/checklist_cell/checklist_cell_bloc.dart'; import '../../row/cells/checklist_cell/checklist_cell_bloc.dart';
import '../define.dart';
import 'card_cell.dart'; import 'card_cell.dart';
class ChecklistCardCell extends CardCell { class ChecklistCardCell extends CardCell {
@ -37,7 +38,7 @@ class _ChecklistCellState extends State<ChecklistCardCell> {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 4), padding: CardSizes.cardCellPadding,
child: ChecklistProgressBar( child: ChecklistProgressBar(
tasks: state.tasks, tasks: state.tasks,
percent: state.percent, percent: state.percent,

View File

@ -42,31 +42,28 @@ class _DateCellState extends State<DateCardCell> {
buildWhen: (previous, current) => previous.dateStr != current.dateStr, buildWhen: (previous, current) => previous.dateStr != current.dateStr,
builder: (context, state) { builder: (context, state) {
if (state.dateStr.isEmpty) { if (state.dateStr.isEmpty) {
return const SizedBox(); return const SizedBox.shrink();
} else {
final Widget? custom = widget.renderHook?.call(
state.data,
widget.cardData,
context,
);
if (custom != null) {
return custom;
}
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: CardSizes.cardCellVPadding,
),
child: FlowyText.regular(
state.dateStr,
fontSize: 13,
color: Theme.of(context).hintColor,
),
),
);
} }
final Widget? custom = widget.renderHook?.call(
state.data,
widget.cardData,
context,
);
if (custom != null) {
return custom;
}
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: CardSizes.cardCellPadding,
child: FlowyText.regular(
state.dateStr,
fontSize: 11,
color: Theme.of(context).hintColor,
),
),
);
}, },
), ),
); );

View File

@ -52,30 +52,28 @@ class _NumberCellState extends State<NumberCardCell> {
previous.cellContent != current.cellContent, previous.cellContent != current.cellContent,
builder: (context, state) { builder: (context, state) {
if (state.cellContent.isEmpty) { if (state.cellContent.isEmpty) {
return const SizedBox(); return const SizedBox.shrink();
} else {
final Widget? custom = widget.renderHook?.call(
state.cellContent,
widget.cardData,
context,
);
if (custom != null) {
return custom;
}
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: CardSizes.cardCellVPadding,
),
child: FlowyText.medium(
state.cellContent,
fontSize: widget.style?.fontSize ?? 14,
),
),
);
} }
final Widget? custom = widget.renderHook?.call(
state.cellContent,
widget.cardData,
context,
);
if (custom != null) {
return custom;
}
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: CardSizes.cardCellPadding,
child: FlowyText.regular(
state.cellContent,
fontSize: widget.style?.fontSize ?? 11,
color: Theme.of(context).hintColor,
),
),
);
}, },
), ),
); );

View File

@ -5,6 +5,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.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 '../define.dart';
import 'card_cell.dart'; import 'card_cell.dart';
class SelectOptionCardCellStyle extends CardCellStyle {} class SelectOptionCardCellStyle extends CardCellStyle {}
@ -69,12 +70,11 @@ class _SelectOptionCellState extends State<SelectOptionCardCell> {
) )
.toList(); .toList();
return IntrinsicHeight( return Align(
alignment: AlignmentDirectional.topStart,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6), padding: CardSizes.cardCellPadding,
child: SizedBox.expand( child: Wrap(spacing: 4, runSpacing: 2, children: children),
child: Wrap(spacing: 4, runSpacing: 2, children: children),
),
), ),
); );
}, },

View File

@ -1,10 +1,13 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.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:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.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 '../../row/cell_builder.dart'; import '../../row/cell_builder.dart';
import '../define.dart'; import '../define.dart';
import 'card_cell.dart'; import 'card_cell.dart';
@ -120,24 +123,30 @@ class _TextCellState extends State<TextCardCell> {
return custom; return custom;
} }
final isTitle =
context.read<TextCellBloc>().cellController.fieldInfo.isPrimary;
if (state.content.isEmpty && if (state.content.isEmpty &&
state.enableEdit == false && state.enableEdit == false &&
focusWhenInit == false) { focusWhenInit == false &&
return const SizedBox(); !isTitle) {
return const SizedBox.shrink();
} }
final child = state.enableEdit || focusWhenInit final child = state.enableEdit || focusWhenInit
? _buildTextField() ? _buildTextField()
: _buildText(state); : _buildText(state, isTitle);
return Row( return Padding(
children: [ padding: CardSizes.cardCellPadding,
if (widget.showNotes) ...[ child: Row(
const FlowySvg(FlowySvgs.notes_s), children: [
const HSpace(4), if (widget.showNotes) ...[
const FlowySvg(FlowySvgs.notes_s),
const HSpace(4),
],
Expanded(child: child),
], ],
Expanded(child: child), ),
],
); );
}, },
), ),
@ -157,47 +166,46 @@ class _TextCellState extends State<TextCardCell> {
super.dispose(); super.dispose();
} }
double _fontSize() { Widget _buildText(TextCellState state, bool isTitle) {
if (widget.style != null) { final text = state.content.isEmpty
return widget.style!.fontSize; ? LocaleKeys.grid_row_titlePlaceholder.tr()
} : state.content;
final color = state.content.isEmpty ? Theme.of(context).hintColor : null;
return 14; return FlowyText(
} text,
fontSize: _fontSize(isTitle),
Widget _buildText(TextCellState state) { fontWeight: _fontWeight(isTitle),
return Padding( color: color,
padding: EdgeInsets.symmetric( maxLines: null, // Enable multiple lines
vertical: CardSizes.cardCellVPadding,
),
child: FlowyText.medium(
state.content,
fontSize: _fontSize(),
maxLines: null, // Enable multiple lines
),
); );
} }
double _fontSize(bool isTitle) {
return widget.style?.fontSize ?? (isTitle ? 12 : 11);
}
FontWeight _fontWeight(bool isTitle) {
return isTitle ? FontWeight.w500 : FontWeight.w400;
}
Widget _buildTextField() { Widget _buildTextField() {
return IntrinsicHeight( return TextField(
child: TextField( controller: _controller,
controller: _controller, focusNode: focusNode,
focusNode: focusNode, onChanged: (value) => focusChanged(),
onChanged: (value) => focusChanged(), onEditingComplete: () => focusNode.unfocus(),
onEditingComplete: () => focusNode.unfocus(), maxLines: null,
maxLines: null, style: Theme.of(context)
style: Theme.of(context) .textTheme
.textTheme .bodyMedium!
.bodyMedium! .copyWith(fontSize: _fontSize(true)),
.copyWith(fontSize: _fontSize()), decoration: InputDecoration(
decoration: InputDecoration( contentPadding:
// Magic number 4 makes the textField take up the same space as FlowyText EdgeInsets.symmetric(vertical: CardSizes.cardCellPadding.top),
contentPadding: EdgeInsets.symmetric( border: InputBorder.none,
vertical: CardSizes.cardCellVPadding + 4, isDense: true,
), isCollapsed: true,
border: InputBorder.none, hintText: LocaleKeys.grid_row_titlePlaceholder.tr(),
isDense: true,
),
), ),
); );
} }

View File

@ -56,12 +56,10 @@ class _TimestampCellState extends State<TimestampCardCell> {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: CardSizes.cardCellPadding,
vertical: CardSizes.cardCellVPadding,
),
child: FlowyText.regular( child: FlowyText.regular(
state.dateStr, state.dateStr,
fontSize: 13, fontSize: 11,
color: Theme.of(context).hintColor, color: Theme.of(context).hintColor,
), ),
), ),

View File

@ -1,6 +1,5 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/url_cell/url_cell_bloc.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/url_cell/url_cell_bloc.dart';
import 'package:flowy_infra/size.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';
@ -48,27 +47,24 @@ class _URLCellState extends State<URLCardCell> {
builder: (context, state) { builder: (context, state) {
if (state.content.isEmpty) { if (state.content.isEmpty) {
return const SizedBox(); return const SizedBox();
} else { }
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: CardSizes.cardCellPadding,
vertical: CardSizes.cardCellVPadding, child: RichText(
), textAlign: TextAlign.left,
child: RichText( text: TextSpan(
textAlign: TextAlign.left, text: state.content,
text: TextSpan( style: Theme.of(context).textTheme.bodySmall!.copyWith(
text: state.content, fontSize: widget.style?.fontSize ?? 11,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.primary,
fontSize: widget.style?.fontSize ?? FontSizes.s14, decoration: TextDecoration.underline,
color: Theme.of(context).colorScheme.primary, ),
decoration: TextDecoration.underline,
),
),
), ),
), ),
); ),
} );
}, },
), ),
); );

View File

@ -26,7 +26,7 @@ class CardAccessoryContainer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final children = accessories.map((accessory) { final children = accessories.map<Widget>((accessory) {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
@ -36,32 +36,62 @@ class CardAccessoryContainer extends StatelessWidget {
child: _wrapHover(context, accessory), child: _wrapHover(context, accessory),
); );
}).toList(); }).toList();
return _wrapDecoration(context, Row(children: children));
children.insert(
1,
VerticalDivider(
width: 1,
thickness: 1,
color: Theme.of(context).brightness == Brightness.light
? const Color(0xFF1F2329).withOpacity(0.12)
: const Color(0xff59647a),
),
);
return _wrapDecoration(
context,
IntrinsicHeight(child: Row(children: children)),
);
} }
FlowyHover _wrapHover(BuildContext context, CardAccessory accessory) { Widget _wrapHover(BuildContext context, CardAccessory accessory) {
return FlowyHover( return SizedBox(
style: HoverStyle( width: 24,
backgroundColor: Theme.of(context).colorScheme.surface, height: 22,
borderRadius: BorderRadius.zero, child: FlowyHover(
), style: HoverStyle(
builder: (_, onHover) => SizedBox( backgroundColor: Theme.of(context).colorScheme.surface,
width: 24, borderRadius: BorderRadius.zero,
height: 24, ),
child: accessory, child: accessory,
), ),
); );
} }
Widget _wrapDecoration(BuildContext context, Widget child) { Widget _wrapDecoration(BuildContext context, Widget child) {
final borderSide = BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
);
final decoration = BoxDecoration( final decoration = BoxDecoration(
color: Colors.transparent, color: Theme.of(context).colorScheme.surface,
border: Border.fromBorderSide(borderSide),
borderRadius: const BorderRadius.all(Radius.circular(4)), borderRadius: const BorderRadius.all(Radius.circular(4)),
border: Border.fromBorderSide(
BorderSide(
color: Theme.of(context).brightness == Brightness.light
? const Color(0xFF1F2329).withOpacity(0.12)
: const Color(0xff59647a),
width: 1.0,
),
),
boxShadow: [
BoxShadow(
blurRadius: 4,
spreadRadius: 0,
color: const Color(0xFF1F2329).withOpacity(0.02),
),
BoxShadow(
blurRadius: 4,
spreadRadius: -2,
color: const Color(0xFF1F2329).withOpacity(0.02),
),
],
); );
return Container( return Container(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,

View File

@ -5,7 +5,7 @@ import 'accessory.dart';
class RowCardContainer extends StatelessWidget { class RowCardContainer extends StatelessWidget {
final Widget child; final Widget child;
final CardAccessoryBuilder? accessoryBuilder; final List<CardAccessory> accessories;
final bool Function()? buildAccessoryWhen; final bool Function()? buildAccessoryWhen;
final void Function(BuildContext) openCard; final void Function(BuildContext) openCard;
final void Function(AccessoryType) openAccessory; final void Function(AccessoryType) openAccessory;
@ -13,7 +13,7 @@ class RowCardContainer extends StatelessWidget {
required this.child, required this.child,
required this.openCard, required this.openCard,
required this.openAccessory, required this.openAccessory,
this.accessoryBuilder, required this.accessories,
this.buildAccessoryWhen, this.buildAccessoryWhen,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -30,15 +30,12 @@ class RowCardContainer extends StatelessWidget {
shouldBuildAccessory = buildAccessoryWhen!.call(); shouldBuildAccessory = buildAccessoryWhen!.call();
} }
if (accessoryBuilder != null && shouldBuildAccessory) { if (shouldBuildAccessory && accessories.isNotEmpty) {
final accessories = accessoryBuilder!(context); container = _CardEnterRegion(
if (accessories.isNotEmpty) { accessories: accessories,
container = _CardEnterRegion( onTapAccessory: openAccessory,
accessories: accessories, child: container,
onTapAccessory: openAccessory, );
child: container,
);
}
} }
return GestureDetector( return GestureDetector(
@ -75,8 +72,8 @@ class _CardEnterRegion extends StatelessWidget {
if (onEnter) { if (onEnter) {
children.add( children.add(
Positioned( Positioned(
top: 8.0, top: 10.0,
right: 8.0, right: 10.0,
child: CardAccessoryContainer( child: CardAccessoryContainer(
accessories: accessories, accessories: accessories,
onTapAccessory: onTapAccessory, onTapAccessory: onTapAccessory,

View File

@ -1,3 +1,5 @@
import 'package:flutter/widgets.dart';
class CardSizes { class CardSizes {
static double get cardCellVPadding => 6; static EdgeInsets get cardCellPadding => const EdgeInsets.all(4);
} }

View File

@ -113,7 +113,7 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
: LocaleKeys.grid_checklist_hideComplete.tr(), : LocaleKeys.grid_checklist_hideComplete.tr(),
width: 32, width: 32,
iconColorOnHover: iconColorOnHover:
Theme.of(context).colorScheme.onPrimary, Theme.of(context).colorScheme.onSurface,
icon: FlowySvg( icon: FlowySvg(
showIncompleteOnly showIncompleteOnly
? FlowySvgs.show_m ? FlowySvgs.show_m

View File

@ -106,7 +106,7 @@ class SelectOptionTag extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Flexible( Flexible(
child: FlowyText.medium( child: FlowyText.regular(
name, name,
fontSize: FontSizes.s11, fontSize: FontSizes.s11,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -135,11 +135,11 @@ class SelectOptionTagCell extends StatelessWidget {
final void Function(SelectOptionPB) onSelected; final void Function(SelectOptionPB) onSelected;
final SelectOptionPB option; final SelectOptionPB option;
const SelectOptionTagCell({ const SelectOptionTagCell({
super.key,
required this.option, required this.option,
required this.onSelected, required this.onSelected,
this.children = const [], this.children = const [],
Key? key, });
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -17,5 +17,6 @@ export 'style_widget/button.dart';
export 'style_widget/icon_button.dart'; export 'style_widget/icon_button.dart';
export 'style_widget/scrolling/styled_scroll_bar.dart'; export 'style_widget/scrolling/styled_scroll_bar.dart';
export '/widget/spacing.dart'; export '/widget/spacing.dart';
export '/widget/separated_flex.dart';
export 'style_widget/scrolling/styled_list.dart'; export 'style_widget/scrolling/styled_list.dart';
export 'style_widget/color_picker.dart'; export 'style_widget/color_picker.dart';

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
typedef SeparatorBuilder = Widget Function();
class SeparatedColumn extends Column {
SeparatedColumn({
super.key,
super.mainAxisAlignment,
super.crossAxisAlignment,
super.mainAxisSize,
super.textBaseline,
super.textDirection,
super.verticalDirection,
required SeparatorBuilder separatorBuilder,
required List<Widget> children,
}) : super(children: _insertSeparators(children, separatorBuilder));
}
class SeparatedRow extends Row {
SeparatedRow({
super.key,
super.mainAxisAlignment,
super.crossAxisAlignment,
super.mainAxisSize,
super.textBaseline,
super.textDirection,
super.verticalDirection,
required SeparatorBuilder separatorBuilder,
required List<Widget> children,
}) : super(children: _insertSeparators(children, separatorBuilder));
}
List<Widget> _insertSeparators(
List<Widget> children,
SeparatorBuilder separatorBuilder,
) {
if (children.length < 2) {
return children;
}
List<Widget> newChildren = [];
for (int i = 0; i < children.length - 1; i++) {
newChildren.add(children[i]);
newChildren.add(separatorBuilder());
}
return newChildren..add(children.last);
}

View File

@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
typedef SeparatorBuilder = Widget Function();
class SeparatedColumn extends StatelessWidget {
final List<Widget> children;
final SeparatorBuilder? separatorBuilder;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
final MainAxisSize mainAxisSize;
final TextBaseline? textBaseline;
final TextDirection? textDirection;
final VerticalDirection verticalDirection;
const SeparatedColumn({
Key? key,
required this.children,
this.separatorBuilder,
this.mainAxisAlignment = MainAxisAlignment.start,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.mainAxisSize = MainAxisSize.max,
this.verticalDirection = VerticalDirection.down,
this.textBaseline,
this.textDirection,
}) : super(key: key);
@override
Widget build(BuildContext context) {
var c = children.toList();
for (var i = c.length; i-- > 0;) {
if (i > 0 && separatorBuilder != null) c.insert(i, separatorBuilder!());
}
return Column(
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
mainAxisSize: mainAxisSize,
textBaseline: textBaseline,
textDirection: textDirection,
verticalDirection: verticalDirection,
children: c,
);
}
}