mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-09-11 19:17:43 +03:00
fix: kanban UX bugs (#5227)
* chore: improve title editing behavior * chore: fix editable text field * chore: fix autoscroll
This commit is contained in:
parent
33802fa62d
commit
f3544375c9
@ -11,6 +11,8 @@ const uint8_t *sync_event(const uint8_t *input, uintptr_t len);
|
|||||||
|
|
||||||
int32_t set_stream_port(int64_t port);
|
int32_t set_stream_port(int64_t port);
|
||||||
|
|
||||||
|
int32_t set_log_stream_port(int64_t port);
|
||||||
|
|
||||||
void link_me_please(void);
|
void link_me_please(void);
|
||||||
|
|
||||||
void rust_log(int64_t level, const char *data);
|
void rust_log(int64_t level, const char *data);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart';
|
import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/card/container/card_container.dart';
|
import 'package:appflowy/plugins/database/widgets/card/card.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -22,13 +22,15 @@ void main() {
|
|||||||
|
|
||||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||||
|
|
||||||
final findFirstCard = find.descendant(
|
final firstCard = find.byType(RowCard).first;
|
||||||
of: find.byType(AppFlowyGroupCard),
|
|
||||||
matching: find.byType(Text),
|
|
||||||
);
|
|
||||||
|
|
||||||
Text firstCardText = tester.firstWidget(findFirstCard);
|
expect(
|
||||||
expect(firstCardText.data, defaultFirstCardName);
|
find.descendant(
|
||||||
|
of: firstCard,
|
||||||
|
matching: find.text(defaultFirstCardName),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
|
||||||
await tester.tap(
|
await tester.tap(
|
||||||
find
|
find
|
||||||
@ -45,7 +47,7 @@ void main() {
|
|||||||
const newCardName = 'Card 4';
|
const newCardName = 'Card 4';
|
||||||
await tester.enterText(
|
await tester.enterText(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byType(RowCardContainer),
|
of: firstCard,
|
||||||
matching: find.byType(TextField),
|
matching: find.byType(TextField),
|
||||||
),
|
),
|
||||||
newCardName,
|
newCardName,
|
||||||
@ -55,8 +57,13 @@ void main() {
|
|||||||
await tester.tap(find.byType(AppFlowyBoard));
|
await tester.tap(find.byType(AppFlowyBoard));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
firstCardText = tester.firstWidget(findFirstCard);
|
expect(
|
||||||
expect(firstCardText.data, newCardName);
|
find.descendant(
|
||||||
|
of: find.byType(RowCard).first,
|
||||||
|
matching: find.text(newCardName),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('from footer', (tester) async {
|
testWidgets('from footer', (tester) async {
|
||||||
@ -65,13 +72,15 @@ void main() {
|
|||||||
|
|
||||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||||
|
|
||||||
final findLastCard = find.descendant(
|
final lastCard = find.byType(RowCard).last;
|
||||||
of: find.byType(AppFlowyGroupCard),
|
|
||||||
matching: find.byType(Text),
|
|
||||||
);
|
|
||||||
|
|
||||||
Text? lastCardText = tester.widgetList(findLastCard).last as Text;
|
expect(
|
||||||
expect(lastCardText.data, defaultLastCardName);
|
find.descendant(
|
||||||
|
of: lastCard,
|
||||||
|
matching: find.text(defaultLastCardName),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
|
||||||
await tester.tap(
|
await tester.tap(
|
||||||
find
|
find
|
||||||
@ -81,12 +90,11 @@ void main() {
|
|||||||
)
|
)
|
||||||
.at(1),
|
.at(1),
|
||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
const newCardName = 'Card 4';
|
const newCardName = 'Card 4';
|
||||||
await tester.enterText(
|
await tester.enterText(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byType(RowCardContainer),
|
of: lastCard,
|
||||||
matching: find.byType(TextField),
|
matching: find.byType(TextField),
|
||||||
),
|
),
|
||||||
newCardName,
|
newCardName,
|
||||||
@ -96,8 +104,13 @@ void main() {
|
|||||||
await tester.tap(find.byType(AppFlowyBoard));
|
await tester.tap(find.byType(AppFlowyBoard));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
lastCardText = tester.widgetList(findLastCard).last as Text;
|
expect(
|
||||||
expect(lastCardText.data, newCardName);
|
find.descendant(
|
||||||
|
of: find.byType(RowCard).last,
|
||||||
|
matching: find.text(newCardName),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import 'package:appflowy/plugins/database/widgets/card/card.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
|
import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
|
||||||
|
|
||||||
import '../../shared/util.dart';
|
import '../../shared/util.dart';
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ void main() {
|
|||||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||||
final card1 = find.ancestor(
|
final card1 = find.ancestor(
|
||||||
of: find.text(card1Name),
|
of: find.text(card1Name),
|
||||||
matching: find.byType(AppFlowyGroupCard),
|
matching: find.byType(RowCard),
|
||||||
);
|
);
|
||||||
final doingGroup = find.text('Doing');
|
final doingGroup = find.text('Doing');
|
||||||
final doingGroupCenter = tester.getCenter(doingGroup);
|
final doingGroupCenter = tester.getCenter(doingGroup);
|
||||||
|
@ -68,6 +68,8 @@ class CellController<T, D> {
|
|||||||
Timer? _loadDataOperation;
|
Timer? _loadDataOperation;
|
||||||
Timer? _saveDataOperation;
|
Timer? _saveDataOperation;
|
||||||
|
|
||||||
|
Completer? _completer;
|
||||||
|
|
||||||
RowId get rowId => _cellContext.rowId;
|
RowId get rowId => _cellContext.rowId;
|
||||||
String get fieldId => _cellContext.fieldId;
|
String get fieldId => _cellContext.fieldId;
|
||||||
FieldInfo get fieldInfo => _fieldController.getField(_cellContext.fieldId)!;
|
FieldInfo get fieldInfo => _fieldController.getField(_cellContext.fieldId)!;
|
||||||
@ -192,6 +194,7 @@ class CellController<T, D> {
|
|||||||
_loadDataOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
if (debounce) {
|
if (debounce) {
|
||||||
_saveDataOperation?.cancel();
|
_saveDataOperation?.cancel();
|
||||||
|
_completer = Completer();
|
||||||
_saveDataOperation = Timer(const Duration(milliseconds: 300), () async {
|
_saveDataOperation = Timer(const Duration(milliseconds: 300), () async {
|
||||||
final result = await _cellDataPersistence.save(
|
final result = await _cellDataPersistence.save(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
@ -199,6 +202,7 @@ class CellController<T, D> {
|
|||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
onFinish?.call(result);
|
onFinish?.call(result);
|
||||||
|
_completer?.complete();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
final result = await _cellDataPersistence.save(
|
final result = await _cellDataPersistence.save(
|
||||||
@ -241,6 +245,7 @@ class CellController<T, D> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
_loadDataOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
|
await _completer?.future;
|
||||||
_saveDataOperation?.cancel();
|
_saveDataOperation?.cancel();
|
||||||
_cellDataNotifier?.dispose();
|
_cellDataNotifier?.dispose();
|
||||||
_cellDataNotifier = null;
|
_cellDataNotifier = null;
|
||||||
|
@ -59,7 +59,7 @@ class FieldCellState with _$FieldCellState {
|
|||||||
factory FieldCellState.initial(FieldInfo fieldInfo) => FieldCellState(
|
factory FieldCellState.initial(FieldInfo fieldInfo) => FieldCellState(
|
||||||
fieldInfo: fieldInfo,
|
fieldInfo: fieldInfo,
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
width: fieldInfo.fieldSettings!.width.toDouble(),
|
width: fieldInfo.width!.toDouble(),
|
||||||
resizeStart: 0,
|
resizeStart: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -102,11 +102,12 @@ class BoardPage extends StatelessWidget {
|
|||||||
)..add(const BoardEvent.initial()),
|
)..add(const BoardEvent.initial()),
|
||||||
child: BlocBuilder<BoardBloc, BoardState>(
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
buildWhen: (p, c) => p.loadingState != c.loadingState,
|
buildWhen: (p, c) => p.loadingState != c.loadingState,
|
||||||
builder: (context, state) => state.loadingState.map(
|
builder: (context, state) => state.loadingState.when(
|
||||||
loading: (_) => const Center(
|
loading: () => const Center(
|
||||||
child: CircularProgressIndicator.adaptive(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
),
|
),
|
||||||
finish: (result) => result.successOrFail.fold(
|
idle: () => const SizedBox.shrink(),
|
||||||
|
finish: (result) => result.fold(
|
||||||
(_) => PlatformExtension.isMobile
|
(_) => PlatformExtension.isMobile
|
||||||
? const MobileBoardContent()
|
? const MobileBoardContent()
|
||||||
: DesktopBoardContent(onEditStateChanged: onEditStateChanged),
|
: DesktopBoardContent(onEditStateChanged: onEditStateChanged),
|
||||||
@ -121,7 +122,6 @@ class BoardPage extends StatelessWidget {
|
|||||||
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
idle: (_) => const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -154,6 +154,10 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
|
|||||||
stretchGroupHeight: false,
|
stretchGroupHeight: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
late final cellBuilder = CardCellBuilder(
|
||||||
|
databaseController: context.read<BoardBloc>().databaseController,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
@ -164,7 +168,6 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<BoardBloc, BoardState>(
|
return BlocListener<BoardBloc, BoardState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_handleEditStateChanged(state, context);
|
|
||||||
widget.onEditStateChanged?.call();
|
widget.onEditStateChanged?.call();
|
||||||
},
|
},
|
||||||
child: BlocBuilder<BoardBloc, BoardState>(
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
@ -182,7 +185,7 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
|
|||||||
leading: HiddenGroupsColumn(margin: config.groupHeaderPadding),
|
leading: HiddenGroupsColumn(margin: config.groupHeaderPadding),
|
||||||
trailing: showCreateGroupButton
|
trailing: showCreateGroupButton
|
||||||
? BoardTrailing(scrollController: scrollController)
|
? BoardTrailing(scrollController: scrollController)
|
||||||
: null,
|
: const HSpace(40),
|
||||||
headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value(
|
headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value(
|
||||||
value: context.read<BoardBloc>(),
|
value: context.read<BoardBloc>(),
|
||||||
child: BoardColumnHeader(
|
child: BoardColumnHeader(
|
||||||
@ -203,16 +206,6 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleEditStateChanged(BoardState state, BuildContext context) {
|
|
||||||
if (state.isEditingRow && state.editingRow != null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (state.editingRow!.index == null) {
|
|
||||||
scrollManager.scrollToBottom(state.editingRow!.group.groupId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
|
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: config.groupFooterPadding,
|
padding: config.groupFooterPadding,
|
||||||
@ -257,14 +250,13 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
|
|||||||
final databaseController = boardBloc.databaseController;
|
final databaseController = boardBloc.databaseController;
|
||||||
final viewId = boardBloc.viewId;
|
final viewId = boardBloc.viewId;
|
||||||
|
|
||||||
final cellBuilder = CardCellBuilder(databaseController: databaseController);
|
|
||||||
final isEditing = boardBloc.state.isEditingRow &&
|
final isEditing = boardBloc.state.isEditingRow &&
|
||||||
boardBloc.state.editingRow?.row.id == groupItem.row.id;
|
boardBloc.state.editingRow?.row.id == groupItem.row.id;
|
||||||
|
|
||||||
final groupItemId = "${groupData.group.groupId}${groupItem.row.id}";
|
final groupItemId = "${groupData.group.groupId}${groupItem.row.id}";
|
||||||
final rowMeta = rowInfo?.rowMeta ?? groupItem.row;
|
final rowMeta = rowInfo?.rowMeta ?? groupItem.row;
|
||||||
|
|
||||||
return AppFlowyGroupCard(
|
return Container(
|
||||||
key: ValueKey(groupItemId),
|
key: ValueKey(groupItemId),
|
||||||
margin: config.cardMargin,
|
margin: config.cardMargin,
|
||||||
decoration: _makeBoxDecoration(context),
|
decoration: _makeBoxDecoration(context),
|
||||||
@ -412,52 +404,50 @@ class _BoardTrailingState extends State<BoardTrailing> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Padding(
|
return Container(
|
||||||
padding: const EdgeInsets.only(left: 8.0, top: 12),
|
padding: const EdgeInsets.only(left: 8.0, top: 12, right: 40),
|
||||||
child: Align(
|
alignment: AlignmentDirectional.topStart,
|
||||||
alignment: AlignmentDirectional.topStart,
|
child: AnimatedSwitcher(
|
||||||
child: AnimatedSwitcher(
|
duration: const Duration(milliseconds: 300),
|
||||||
duration: const Duration(milliseconds: 300),
|
child: isEditing
|
||||||
child: isEditing
|
? SizedBox(
|
||||||
? SizedBox(
|
width: 256,
|
||||||
width: 256,
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(8.0),
|
||||||
padding: const EdgeInsets.all(8.0),
|
child: TextField(
|
||||||
child: TextField(
|
controller: _textController,
|
||||||
controller: _textController,
|
focusNode: _focusNode,
|
||||||
focusNode: _focusNode,
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
suffixIcon: Padding(
|
||||||
suffixIcon: Padding(
|
padding: const EdgeInsets.only(left: 4, bottom: 8.0),
|
||||||
padding: const EdgeInsets.only(left: 4, bottom: 8.0),
|
child: FlowyIconButton(
|
||||||
child: FlowyIconButton(
|
icon: const FlowySvg(FlowySvgs.close_filled_m),
|
||||||
icon: const FlowySvg(FlowySvgs.close_filled_m),
|
hoverColor: Colors.transparent,
|
||||||
hoverColor: Colors.transparent,
|
onPressed: () => _textController.clear(),
|
||||||
onPressed: () => _textController.clear(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
suffixIconConstraints:
|
|
||||||
BoxConstraints.loose(const Size(20, 24)),
|
|
||||||
border: const UnderlineInputBorder(),
|
|
||||||
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
|
|
||||||
isDense: true,
|
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
suffixIconConstraints:
|
||||||
onSubmitted: (groupName) => context
|
BoxConstraints.loose(const Size(20, 24)),
|
||||||
.read<BoardBloc>()
|
border: const UnderlineInputBorder(),
|
||||||
.add(BoardEvent.createGroup(groupName)),
|
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
|
||||||
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
)
|
onSubmitted: (groupName) => context
|
||||||
: FlowyTooltip(
|
.read<BoardBloc>()
|
||||||
message: LocaleKeys.board_column_createNewColumn.tr(),
|
.add(BoardEvent.createGroup(groupName)),
|
||||||
child: FlowyIconButton(
|
|
||||||
width: 26,
|
|
||||||
icon: const FlowySvg(FlowySvgs.add_s),
|
|
||||||
iconColorOnHover: Theme.of(context).colorScheme.onSurface,
|
|
||||||
onPressed: () => setState(() => isEditing = true),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
: FlowyTooltip(
|
||||||
|
message: LocaleKeys.board_column_createNewColumn.tr(),
|
||||||
|
child: FlowyIconButton(
|
||||||
|
width: 26,
|
||||||
|
icon: const FlowySvg(FlowySvgs.add_s),
|
||||||
|
iconColorOnHover: Theme.of(context).colorScheme.onSurface,
|
||||||
|
onPressed: () => setState(() => isEditing = true),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,14 @@ class HiddenGroupsColumn extends StatelessWidget {
|
|||||||
? SizedBox(
|
? SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 40, right: 8),
|
padding: const EdgeInsets.only(left: 80, right: 8),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: _collapseExpandIcon(context, isCollapsed),
|
child: _collapseExpandIcon(context, isCollapsed),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: SizedBox(
|
: SizedBox(
|
||||||
width: 234,
|
width: 274,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -60,7 +60,7 @@ class HiddenGroupsColumn extends StatelessWidget {
|
|||||||
height: 50,
|
height: 50,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: 40 + margin.left,
|
left: 80 + margin.left,
|
||||||
right: margin.right + 4,
|
right: margin.right + 4,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -167,10 +167,13 @@ class _EventCardState extends State<EventCard> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Material(
|
||||||
padding: widget.padding,
|
color: Colors.transparent,
|
||||||
decoration: decoration,
|
child: Container(
|
||||||
child: card,
|
padding: widget.padding,
|
||||||
|
decoration: decoration,
|
||||||
|
child: card,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -188,7 +188,8 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: PlatformExtension.isMobile
|
padding: PlatformExtension.isMobile
|
||||||
? CalendarSize.contentInsetsMobile
|
? CalendarSize.contentInsetsMobile
|
||||||
: CalendarSize.contentInsets,
|
: CalendarSize.contentInsets +
|
||||||
|
const EdgeInsets.symmetric(horizontal: 40),
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior:
|
behavior:
|
||||||
ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||||
|
@ -154,7 +154,11 @@ class _GridPageState extends State<GridPage> {
|
|||||||
loading: (_) =>
|
loading: (_) =>
|
||||||
const Center(child: CircularProgressIndicator.adaptive()),
|
const Center(child: CircularProgressIndicator.adaptive()),
|
||||||
finish: (result) => result.successOrFail.fold(
|
finish: (result) => result.successOrFail.fold(
|
||||||
(_) => GridShortcuts(child: GridPageContent(view: widget.view)),
|
(_) => GridShortcuts(
|
||||||
|
child: GridPageContent(
|
||||||
|
view: widget.view,
|
||||||
|
),
|
||||||
|
),
|
||||||
(err) => FlowyErrorPage.message(
|
(err) => FlowyErrorPage.message(
|
||||||
err.toString(),
|
err.toString(),
|
||||||
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
@ -234,7 +238,9 @@ class _GridPageContentState extends State<GridPageContent> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_GridHeader(headerScrollController: headerScrollController),
|
_GridHeader(
|
||||||
|
headerScrollController: headerScrollController,
|
||||||
|
),
|
||||||
_GridRows(
|
_GridRows(
|
||||||
viewId: widget.view.id,
|
viewId: widget.view.id,
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
@ -498,7 +504,7 @@ class _PositionedCalculationsRowState
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.only(left: GridSize.horizontalHeaderPadding),
|
margin: EdgeInsets.only(left: GridSize.horizontalHeaderPadding + 40),
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).canvasColor,
|
color: Theme.of(context).canvasColor,
|
||||||
|
@ -12,11 +12,12 @@ class GridLayout {
|
|||||||
element.visibility != null &&
|
element.visibility != null &&
|
||||||
element.visibility != FieldVisibility.AlwaysHidden,
|
element.visibility != FieldVisibility.AlwaysHidden,
|
||||||
)
|
)
|
||||||
.map((fieldInfo) => fieldInfo.fieldSettings!.width.toDouble())
|
.map((fieldInfo) => fieldInfo.width!.toDouble())
|
||||||
.reduce((value, element) => value + element);
|
.reduce((value, element) => value + element);
|
||||||
|
|
||||||
return fieldsWidth +
|
return fieldsWidth +
|
||||||
GridSize.horizontalHeaderPadding +
|
GridSize.horizontalHeaderPadding +
|
||||||
|
40 +
|
||||||
GridSize.trailHeaderPadding;
|
GridSize.trailHeaderPadding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ class GridCalculationsRow extends StatelessWidget {
|
|||||||
key: Key(
|
key: Key(
|
||||||
'${field.id}-${state.calculationsByFieldId[field.id]?.id}',
|
'${field.id}-${state.calculationsByFieldId[field.id]?.id}',
|
||||||
),
|
),
|
||||||
width: field.fieldSettings!.width.toDouble(),
|
width: field.width!.toDouble(),
|
||||||
fieldInfo: field,
|
fieldInfo: field,
|
||||||
calculation: state.calculationsByFieldId[field.id],
|
calculation: state.calculationsByFieldId[field.id],
|
||||||
),
|
),
|
||||||
|
@ -42,9 +42,8 @@ class GridRowBottomBar extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: GridSize.footerContentInsets,
|
padding: GridSize.footerContentInsets + const EdgeInsets.only(left: 40),
|
||||||
height: GridSize.footerHeight,
|
height: GridSize.footerHeight,
|
||||||
// margin: const EdgeInsets.only(bottom: 8, top: 8),
|
|
||||||
child: const GridAddRowButton(),
|
child: const GridAddRowButton(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _cellLeading() {
|
Widget _cellLeading() {
|
||||||
return SizedBox(width: GridSize.horizontalHeaderPadding);
|
return SizedBox(width: GridSize.horizontalHeaderPadding + 40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ class _RowLeadingState extends State<_RowLeading> {
|
|||||||
child: Consumer<RegionStateNotifier>(
|
child: Consumer<RegionStateNotifier>(
|
||||||
builder: (context, state, _) {
|
builder: (context, state, _) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: GridSize.horizontalHeaderPadding,
|
width: GridSize.horizontalHeaderPadding + 40,
|
||||||
child: state.onEnter ? _activeWidget() : null,
|
child: state.onEnter ? _activeWidget() : null,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -122,7 +122,7 @@ class _RowLeadingState extends State<_RowLeading> {
|
|||||||
|
|
||||||
Widget _activeWidget() {
|
Widget _activeWidget() {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
const InsertRowButton(),
|
const InsertRowButton(),
|
||||||
if (isDraggable)
|
if (isDraggable)
|
||||||
@ -246,7 +246,7 @@ class RowContent extends StatelessWidget {
|
|||||||
EditableCellStyle.desktopGrid,
|
EditableCellStyle.desktopGrid,
|
||||||
);
|
);
|
||||||
return CellContainer(
|
return CellContainer(
|
||||||
width: fieldInfo.fieldSettings!.width.toDouble(),
|
width: fieldInfo.width!.toDouble(),
|
||||||
isPrimary: fieldInfo.field.isPrimary,
|
isPrimary: fieldInfo.field.isPrimary,
|
||||||
accessoryBuilder: (buildContext) {
|
accessoryBuilder: (buildContext) {
|
||||||
final builder = child.accessoryBuilder;
|
final builder = child.accessoryBuilder;
|
||||||
|
@ -24,7 +24,7 @@ class TabBarHeader extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
height: 30,
|
height: 30,
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: GridSize.horizontalHeaderPadding,
|
horizontal: GridSize.horizontalHeaderPadding + 40,
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
@ -286,4 +286,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
EdgeInsets get contentPadding => const EdgeInsets.only(top: 28);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,9 @@ class _TextCellState extends State<TextCardCell> {
|
|||||||
|
|
||||||
void _bindEditableNotifier() {
|
void _bindEditableNotifier() {
|
||||||
widget.editableNotifier?.isCellEditing.addListener(() {
|
widget.editableNotifier?.isCellEditing.addListener(() {
|
||||||
if (!mounted) return;
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final isEditing = widget.editableNotifier?.isCellEditing.value ?? false;
|
final isEditing = widget.editableNotifier?.isCellEditing.value ?? false;
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@ -106,15 +108,14 @@ class _TextCellState extends State<TextCardCell> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: cellBloc,
|
value: cellBloc,
|
||||||
child: BlocConsumer<TextCellBloc, TextCellState>(
|
child: BlocConsumer<TextCellBloc, TextCellState>(
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.content != current.content && !current.enableEdit,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (_textEditingController.text != state.content) {
|
_textEditingController.text = state.content;
|
||||||
_textEditingController.text = state.content;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
buildWhen: (previous, current) {
|
buildWhen: (previous, current) {
|
||||||
if (previous.content != current.content &&
|
if (previous.content != current.content &&
|
||||||
_textEditingController.text == current.content &&
|
_textEditingController.text == current.content) {
|
||||||
current.enableEdit) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,10 +130,10 @@ class _TextCellState extends State<TextCardCell> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final icon = _buildIcon(state, isTitle);
|
final icon = isTitle ? _buildIcon(state) : null;
|
||||||
final child = state.enableEdit || focusWhenInit
|
final child = isTitle
|
||||||
? _buildTextField()
|
? _buildTextField(state.enableEdit || focusWhenInit)
|
||||||
: _buildText(state, isTitle);
|
: _buildText(state.content);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
@ -156,10 +157,7 @@ class _TextCellState extends State<TextCardCell> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? _buildIcon(TextCellState state, bool isTitle) {
|
Widget? _buildIcon(TextCellState state) {
|
||||||
if (!isTitle) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (state.emoji.isNotEmpty) {
|
if (state.emoji.isNotEmpty) {
|
||||||
return Text(
|
return Text(
|
||||||
state.emoji,
|
state.emoji,
|
||||||
@ -178,43 +176,52 @@ class _TextCellState extends State<TextCardCell> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildText(TextCellState state, bool isTitle) {
|
Widget _buildText(String content) {
|
||||||
final text = state.content.isEmpty
|
final text =
|
||||||
? isTitle
|
content.isEmpty ? LocaleKeys.grid_row_textPlaceholder.tr() : content;
|
||||||
? LocaleKeys.grid_row_titlePlaceholder.tr()
|
final color = content.isEmpty ? Theme.of(context).hintColor : null;
|
||||||
: LocaleKeys.grid_row_textPlaceholder.tr()
|
|
||||||
: state.content;
|
|
||||||
final color = state.content.isEmpty ? Theme.of(context).hintColor : null;
|
|
||||||
final textStyle =
|
|
||||||
isTitle ? widget.style.titleTextStyle : widget.style.textStyle;
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: widget.style.padding,
|
padding: widget.style.padding,
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: textStyle.copyWith(color: color),
|
style: widget.style.textStyle.copyWith(color: color),
|
||||||
maxLines: widget.style.maxLines,
|
maxLines: widget.style.maxLines,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextField() {
|
Widget _buildTextField(bool isEditing) {
|
||||||
final padding =
|
final padding =
|
||||||
widget.style.padding.add(const EdgeInsets.symmetric(vertical: 4.0));
|
widget.style.padding.add(const EdgeInsets.symmetric(vertical: 4.0));
|
||||||
return TextField(
|
return IgnorePointer(
|
||||||
controller: _textEditingController,
|
ignoring: !isEditing,
|
||||||
focusNode: focusNode,
|
child: TextField(
|
||||||
onChanged: (_) =>
|
controller: _textEditingController,
|
||||||
cellBloc.add(TextCellEvent.updateText(_textEditingController.text)),
|
focusNode: focusNode,
|
||||||
onEditingComplete: () => focusNode.unfocus(),
|
onChanged: (_) {
|
||||||
maxLines: null,
|
if (_textEditingController.value.composing.isCollapsed) {
|
||||||
style: widget.style.titleTextStyle,
|
cellBloc.add(TextCellEvent.updateText(_textEditingController.text));
|
||||||
decoration: InputDecoration(
|
}
|
||||||
contentPadding: padding,
|
},
|
||||||
border: InputBorder.none,
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
isDense: true,
|
maxLines: isEditing ? null : 2,
|
||||||
isCollapsed: true,
|
minLines: 1,
|
||||||
hintText: LocaleKeys.grid_row_titlePlaceholder.tr(),
|
textInputAction: TextInputAction.done,
|
||||||
|
readOnly: !isEditing,
|
||||||
|
enableInteractiveSelection: isEditing,
|
||||||
|
style: widget.style.titleTextStyle,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
contentPadding: padding,
|
||||||
|
border: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
isCollapsed: true,
|
||||||
|
hintText: LocaleKeys.grid_row_titlePlaceholder.tr(),
|
||||||
|
hintStyle: widget.style.titleTextStyle.copyWith(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ CardCellStyleMap desktopBoardCardCellStyleMap(BuildContext context) {
|
|||||||
FieldType.RichText: TextCardCellStyle(
|
FieldType.RichText: TextCardCellStyle(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
textStyle: textStyle,
|
textStyle: textStyle,
|
||||||
maxLines: null,
|
maxLines: 2,
|
||||||
titleTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
titleTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -180,8 +180,7 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newVisiblity =
|
final newVisiblity = widget.fieldInfo.visibility!.toggle();
|
||||||
widget.fieldInfo.fieldSettings!.visibility.toggle();
|
|
||||||
context.read<DatabasePropertyBloc>().add(
|
context.read<DatabasePropertyBloc>().add(
|
||||||
DatabasePropertyEvent.setFieldVisibility(
|
DatabasePropertyEvent.setFieldVisibility(
|
||||||
widget.fieldInfo.id,
|
widget.fieldInfo.id,
|
||||||
|
@ -44,11 +44,11 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "15a3a50"
|
ref: f88b4ce01d2728c05125cbe9170013f4a7c85a31
|
||||||
resolved-ref: "15a3a5071ffdb002ffaefda9df343b6800844d8d"
|
resolved-ref: f88b4ce01d2728c05125cbe9170013f4a7c85a31
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-board.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-board.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.1"
|
version: "0.1.2"
|
||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1405,10 +1405,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.1"
|
version: "6.1.2"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -41,9 +41,10 @@ dependencies:
|
|||||||
flowy_svg:
|
flowy_svg:
|
||||||
path: packages/flowy_svg
|
path: packages/flowy_svg
|
||||||
appflowy_board:
|
appflowy_board:
|
||||||
|
# path: ../../../appflowy-board
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
||||||
ref: 15a3a50
|
ref: f88b4ce01d2728c05125cbe9170013f4a7c85a31
|
||||||
appflowy_result:
|
appflowy_result:
|
||||||
path: packages/appflowy_result
|
path: packages/appflowy_result
|
||||||
appflowy_editor_plugins: ^0.0.2
|
appflowy_editor_plugins: ^0.0.2
|
||||||
|
Loading…
Reference in New Issue
Block a user