mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: launch review 0.5.1 (#4855)
* feat: floating calculations row * fix: calculate cell overflow + tooltip * fix: empty text cell should be counted as empty * fix: empty text cell sohuld not be counted as not empty * fix: conversion of some field types for calculations * fix: tooltip + size of duplicate event button * fix: minor view meta info changes * fix: apply number format on word/char count * fix: dart format * fix: hide arrow for calc values --------- Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
This commit is contained in:
parent
7afae69fe4
commit
c48001bd74
@ -20,6 +20,7 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.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/widget/flowy_tooltip.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class CalendarEventEditor extends StatelessWidget {
|
class CalendarEventEditor extends StatelessWidget {
|
||||||
@ -86,16 +87,22 @@ class EventEditorControls extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
FlowyIconButton(
|
FlowyTooltip(
|
||||||
width: 20,
|
message: LocaleKeys.calendar_duplicateEvent.tr(),
|
||||||
icon: const FlowySvg(FlowySvgs.m_duplicate_s),
|
child: FlowyIconButton(
|
||||||
iconColorOnHover: Theme.of(context).colorScheme.onSecondary,
|
width: 20,
|
||||||
onPressed: () => context.read<CalendarBloc>().add(
|
icon: const FlowySvg(
|
||||||
CalendarEvent.duplicateEvent(
|
FlowySvgs.m_duplicate_s,
|
||||||
rowController.viewId,
|
size: Size.square(17),
|
||||||
rowController.rowId,
|
),
|
||||||
|
iconColorOnHover: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
onPressed: () => context.read<CalendarBloc>().add(
|
||||||
|
CalendarEvent.duplicateEvent(
|
||||||
|
rowController.viewId,
|
||||||
|
rowController.rowId,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const HSpace(8.0),
|
const HSpace(8.0),
|
||||||
FlowyIconButton(
|
FlowyIconButton(
|
||||||
|
@ -6,9 +6,15 @@ extension AvailableCalculations on FieldType {
|
|||||||
CalculationType.Count,
|
CalculationType.Count,
|
||||||
];
|
];
|
||||||
|
|
||||||
// These FieldTypes cannot be empty, no need to count empty/non-empty
|
// These FieldTypes cannot be empty, or might hold secondary
|
||||||
if (![FieldType.Checkbox, FieldType.LastEditedTime, FieldType.CreatedTime]
|
// data causing them to be seen as not empty when in fact they
|
||||||
.contains(this)) {
|
// are empty.
|
||||||
|
if (![
|
||||||
|
FieldType.URL,
|
||||||
|
FieldType.Checkbox,
|
||||||
|
FieldType.LastEditedTime,
|
||||||
|
FieldType.CreatedTime,
|
||||||
|
].contains(this)) {
|
||||||
calculationTypes.addAll([
|
calculationTypes.addAll([
|
||||||
CalculationType.CountEmpty,
|
CalculationType.CountEmpty,
|
||||||
CalculationType.CountNonEmpty,
|
CalculationType.CountNonEmpty,
|
||||||
|
@ -19,7 +19,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||||
|
|
||||||
import '../../application/database_controller.dart';
|
import '../../application/database_controller.dart';
|
||||||
import '../../application/row/row_cache.dart';
|
|
||||||
import '../../application/row/row_controller.dart';
|
import '../../application/row/row_controller.dart';
|
||||||
import '../../tab_bar/tab_bar_view.dart';
|
import '../../tab_bar/tab_bar_view.dart';
|
||||||
import '../../widgets/row/row_detail.dart';
|
import '../../widgets/row/row_detail.dart';
|
||||||
@ -259,7 +258,7 @@ class _GridHeader extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridRows extends StatelessWidget {
|
class _GridRows extends StatefulWidget {
|
||||||
const _GridRows({
|
const _GridRows({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
@ -268,6 +267,30 @@ class _GridRows extends StatelessWidget {
|
|||||||
final String viewId;
|
final String viewId;
|
||||||
final GridScrollController scrollController;
|
final GridScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_GridRows> createState() => _GridRowsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GridRowsState extends State<_GridRows> {
|
||||||
|
bool showFloatingCalculations = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_evaluateFloatingCalculations();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _evaluateFloatingCalculations() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
setState(() {
|
||||||
|
// maxScrollExtent is 0.0 if scrolling is not possible
|
||||||
|
showFloatingCalculations = widget
|
||||||
|
.scrollController.verticalController.position.maxScrollExtent >
|
||||||
|
0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<GridBloc, GridState>(
|
return BlocBuilder<GridBloc, GridState>(
|
||||||
@ -275,24 +298,18 @@ class _GridRows extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Flexible(
|
return Flexible(
|
||||||
child: _WrapScrollView(
|
child: _WrapScrollView(
|
||||||
scrollController: scrollController,
|
scrollController: widget.scrollController,
|
||||||
contentWidth: GridLayout.headerWidth(state.fields),
|
contentWidth: GridLayout.headerWidth(state.fields),
|
||||||
child: BlocBuilder<GridBloc, GridState>(
|
child: BlocConsumer<GridBloc, GridState>(
|
||||||
buildWhen: (previous, current) => current.reason.maybeWhen(
|
listenWhen: (previous, current) =>
|
||||||
reorderRows: () => true,
|
previous.rowCount != current.rowCount,
|
||||||
reorderSingleRow: (reorderRow, rowInfo) => true,
|
listener: (context, state) => _evaluateFloatingCalculations(),
|
||||||
delete: (item) => true,
|
|
||||||
insert: (item) => true,
|
|
||||||
orElse: () => true,
|
|
||||||
),
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final rowInfos = state.rowInfos;
|
|
||||||
final behavior = ScrollConfiguration.of(context).copyWith(
|
|
||||||
scrollbars: false,
|
|
||||||
);
|
|
||||||
return ScrollConfiguration(
|
return ScrollConfiguration(
|
||||||
behavior: behavior,
|
behavior: ScrollConfiguration.of(context).copyWith(
|
||||||
child: _renderList(context, state, rowInfos),
|
scrollbars: false,
|
||||||
|
),
|
||||||
|
child: _renderList(context, state),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -305,9 +322,8 @@ class _GridRows extends StatelessWidget {
|
|||||||
Widget _renderList(
|
Widget _renderList(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GridState state,
|
GridState state,
|
||||||
List<RowInfo> rowInfos,
|
|
||||||
) {
|
) {
|
||||||
final children = rowInfos.mapIndexed((index, rowInfo) {
|
final children = state.rowInfos.mapIndexed((index, rowInfo) {
|
||||||
return _renderRow(
|
return _renderRow(
|
||||||
context,
|
context,
|
||||||
rowInfo.rowId,
|
rowInfo.rowId,
|
||||||
@ -315,32 +331,56 @@ class _GridRows extends StatelessWidget {
|
|||||||
index: index,
|
index: index,
|
||||||
);
|
);
|
||||||
}).toList()
|
}).toList()
|
||||||
..add(const GridRowBottomBar(key: Key('grid_footer')))
|
..add(const GridRowBottomBar(key: Key('grid_footer')));
|
||||||
..add(
|
|
||||||
GridCalculationsRow(
|
if (showFloatingCalculations) {
|
||||||
key: const Key('grid_calculations'),
|
children.add(
|
||||||
viewId: viewId,
|
const SizedBox(
|
||||||
|
key: Key('calculations_bottom_padding'),
|
||||||
|
height: 36,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
children.add(
|
||||||
|
GridCalculationsRow(
|
||||||
|
key: const Key('grid_calculations'),
|
||||||
|
viewId: widget.viewId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return ReorderableListView.builder(
|
children.add(const SizedBox(key: Key('footer_padding'), height: 10));
|
||||||
/// This is a workaround related to
|
|
||||||
/// https://github.com/flutter/flutter/issues/25652
|
return Stack(
|
||||||
cacheExtent: 5000,
|
children: [
|
||||||
scrollController: scrollController.verticalController,
|
Positioned.fill(
|
||||||
buildDefaultDragHandles: false,
|
child: ReorderableListView.builder(
|
||||||
proxyDecorator: (child, index, animation) => Material(
|
/// This is a workaround related to
|
||||||
color: Colors.white.withOpacity(.1),
|
/// https://github.com/flutter/flutter/issues/25652
|
||||||
child: Opacity(opacity: .5, child: child),
|
cacheExtent: 5000,
|
||||||
),
|
scrollController: widget.scrollController.verticalController,
|
||||||
onReorder: (fromIndex, newIndex) {
|
physics: const ClampingScrollPhysics(),
|
||||||
final toIndex = newIndex > fromIndex ? newIndex - 1 : newIndex;
|
buildDefaultDragHandles: false,
|
||||||
if (fromIndex != toIndex) {
|
proxyDecorator: (child, index, animation) => Material(
|
||||||
context.read<GridBloc>().add(GridEvent.moveRow(fromIndex, toIndex));
|
color: Colors.white.withOpacity(.1),
|
||||||
}
|
child: Opacity(opacity: .5, child: child),
|
||||||
},
|
),
|
||||||
itemCount: children.length,
|
onReorder: (fromIndex, newIndex) {
|
||||||
itemBuilder: (context, index) => children[index],
|
final toIndex = newIndex > fromIndex ? newIndex - 1 : newIndex;
|
||||||
|
if (fromIndex != toIndex) {
|
||||||
|
context
|
||||||
|
.read<GridBloc>()
|
||||||
|
.add(GridEvent.moveRow(fromIndex, toIndex));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemCount: children.length,
|
||||||
|
itemBuilder: (context, index) => children[index],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showFloatingCalculations) ...[
|
||||||
|
_PositionedCalculationsRow(viewId: widget.viewId),
|
||||||
|
],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,21 +418,16 @@ class _GridRows extends StatelessWidget {
|
|||||||
openDetailPage: (context, cellBuilder) {
|
openDetailPage: (context, cellBuilder) {
|
||||||
FlowyOverlay.show(
|
FlowyOverlay.show(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (_) => RowDetailPage(
|
||||||
return RowDetailPage(
|
rowController: rowController,
|
||||||
rowController: rowController,
|
databaseController: databaseController,
|
||||||
databaseController: databaseController,
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (animation != null) {
|
if (animation != null) {
|
||||||
return SizeTransition(
|
return SizeTransition(sizeFactor: animation, child: child);
|
||||||
sizeFactor: animation,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
@ -413,12 +448,14 @@ class _WrapScrollView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScrollbarListStack(
|
return ScrollbarListStack(
|
||||||
|
includeInsets: false,
|
||||||
axis: Axis.vertical,
|
axis: Axis.vertical,
|
||||||
controller: scrollController.verticalController,
|
controller: scrollController.verticalController,
|
||||||
barSize: GridSize.scrollBarSize,
|
barSize: GridSize.scrollBarSize,
|
||||||
autoHideScrollbar: false,
|
autoHideScrollbar: false,
|
||||||
child: StyledSingleChildScrollView(
|
child: StyledSingleChildScrollView(
|
||||||
autoHideScrollbar: false,
|
autoHideScrollbar: false,
|
||||||
|
includeInsets: false,
|
||||||
controller: scrollController.horizontalController,
|
controller: scrollController.horizontalController,
|
||||||
axis: Axis.horizontal,
|
axis: Axis.horizontal,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@ -429,3 +466,49 @@ class _WrapScrollView extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This Widget is used to show the Calculations Row at the bottom of the Grids ScrollView
|
||||||
|
/// when the ScrollView is scrollable.
|
||||||
|
///
|
||||||
|
class _PositionedCalculationsRow extends StatefulWidget {
|
||||||
|
const _PositionedCalculationsRow({
|
||||||
|
required this.viewId,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String viewId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_PositionedCalculationsRow> createState() =>
|
||||||
|
_PositionedCalculationsRowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PositionedCalculationsRowState
|
||||||
|
extends State<_PositionedCalculationsRow> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.only(left: GridSize.horizontalHeaderPadding),
|
||||||
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).canvasColor,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: Theme.of(context).dividerColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 36,
|
||||||
|
width: double.infinity,
|
||||||
|
child: GridCalculationsRow(
|
||||||
|
key: const Key('floating_grid_calculations'),
|
||||||
|
viewId: widget.viewId,
|
||||||
|
includeDefaultInsets: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|
||||||
import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart';
|
import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart';
|
||||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||||
import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart';
|
import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart';
|
||||||
@ -16,6 +15,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dar
|
|||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class CalculateCell extends StatefulWidget {
|
class CalculateCell extends StatefulWidget {
|
||||||
@ -35,7 +35,38 @@ class CalculateCell extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CalculateCellState extends State<CalculateCell> {
|
class _CalculateCellState extends State<CalculateCell> {
|
||||||
|
final _cellScrollController = ScrollController();
|
||||||
bool isSelected = false;
|
bool isSelected = false;
|
||||||
|
bool isScrollable = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkScrollable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant CalculateCell oldWidget) {
|
||||||
|
_checkScrollable();
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkScrollable() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_cellScrollController.hasClients) {
|
||||||
|
setState(
|
||||||
|
() =>
|
||||||
|
isScrollable = _cellScrollController.position.maxScrollExtent > 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_cellScrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void setIsSelected(bool selected) => setState(() => isSelected = selected);
|
void setIsSelected(bool selected) => setState(() => isSelected = selected);
|
||||||
|
|
||||||
@ -98,36 +129,62 @@ class _CalculateCellState extends State<CalculateCell> {
|
|||||||
|
|
||||||
Widget _showCalculateValue(BuildContext context, String? prefix) {
|
Widget _showCalculateValue(BuildContext context, String? prefix) {
|
||||||
prefix = prefix != null ? '$prefix ' : '';
|
prefix = prefix != null ? '$prefix ' : '';
|
||||||
|
final calculateValue =
|
||||||
|
'$prefix${_withoutTrailingZeros(widget.calculation!.value)}';
|
||||||
|
|
||||||
return FlowyButton(
|
return FlowyTooltip(
|
||||||
radius: BorderRadius.zero,
|
message: !isScrollable ? "" : null,
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
richMessage: !isScrollable
|
||||||
text: Row(
|
? null
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
TextSpan(
|
||||||
child: FlowyText(
|
text: widget.calculation!.calculationType.shortLabel
|
||||||
widget.calculation!.calculationType.shortLabel,
|
.toUpperCase(),
|
||||||
color: Theme.of(context).hintColor,
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
const TextSpan(text: ' '),
|
||||||
|
TextSpan(
|
||||||
|
text: calculateValue,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
child: FlowyButton(
|
||||||
if (widget.calculation!.value.isNotEmpty) ...[
|
radius: BorderRadius.zero,
|
||||||
const HSpace(8),
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
Flexible(
|
text: Row(
|
||||||
child: FlowyText(
|
children: [
|
||||||
'$prefix${_withoutTrailingZeros(widget.calculation!.value)}',
|
Expanded(
|
||||||
color: AFThemeExtension.of(context).textColor,
|
child: SingleChildScrollView(
|
||||||
overflow: TextOverflow.ellipsis,
|
controller: _cellScrollController,
|
||||||
|
key: ValueKey(widget.calculation!.id),
|
||||||
|
reverse: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FlowyText(
|
||||||
|
widget.calculation!.calculationType.shortLabel
|
||||||
|
.toUpperCase(),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
if (widget.calculation!.value.isNotEmpty) ...[
|
||||||
|
const HSpace(8),
|
||||||
|
FlowyText(
|
||||||
|
calculateValue,
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const HSpace(8),
|
),
|
||||||
FlowySvg(
|
|
||||||
FlowySvgs.arrow_down_s,
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,14 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class GridCalculationsRow extends StatelessWidget {
|
class GridCalculationsRow extends StatelessWidget {
|
||||||
const GridCalculationsRow({super.key, required this.viewId});
|
const GridCalculationsRow({
|
||||||
|
super.key,
|
||||||
|
required this.viewId,
|
||||||
|
this.includeDefaultInsets = true,
|
||||||
|
});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
final bool includeDefaultInsets;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -23,7 +28,8 @@ class GridCalculationsRow extends StatelessWidget {
|
|||||||
child: BlocBuilder<CalculationsBloc, CalculationsState>(
|
child: BlocBuilder<CalculationsBloc, CalculationsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: GridSize.contentInsets,
|
padding:
|
||||||
|
includeDefaultInsets ? GridSize.contentInsets : EdgeInsets.zero,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
...state.fields.map(
|
...state.fields.map(
|
||||||
|
@ -66,7 +66,7 @@ class _MoreViewActionsState extends State<MoreViewActions> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
mutex: popoverMutex,
|
mutex: popoverMutex,
|
||||||
constraints: BoxConstraints.loose(const Size(210, 400)),
|
constraints: BoxConstraints.loose(const Size(215, 400)),
|
||||||
offset: const Offset(0, 30),
|
offset: const Offset(0, 30),
|
||||||
popupBuilder: (_) {
|
popupBuilder: (_) {
|
||||||
final actions = [
|
final actions = [
|
||||||
|
@ -24,6 +24,8 @@ class ViewMetaInfo extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final numberFormat = NumberFormat();
|
||||||
|
|
||||||
// If more info is added to this Widget, use a separated ListView
|
// If more info is added to this Widget, use a separated ListView
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
@ -33,15 +35,21 @@ class ViewMetaInfo extends StatelessWidget {
|
|||||||
if (documentCounters != null) ...[
|
if (documentCounters != null) ...[
|
||||||
FlowyText.regular(
|
FlowyText.regular(
|
||||||
LocaleKeys.moreAction_wordCount.tr(
|
LocaleKeys.moreAction_wordCount.tr(
|
||||||
args: [documentCounters!.wordCount.toString()],
|
args: [
|
||||||
|
numberFormat.format(documentCounters!.wordCount).toString(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
fontSize: 11,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
const VSpace(2),
|
const VSpace(2),
|
||||||
FlowyText.regular(
|
FlowyText.regular(
|
||||||
LocaleKeys.moreAction_charCount.tr(
|
LocaleKeys.moreAction_charCount.tr(
|
||||||
args: [documentCounters!.charCount.toString()],
|
args: [
|
||||||
|
numberFormat.format(documentCounters!.charCount).toString(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
fontSize: 11,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -51,6 +59,7 @@ class ViewMetaInfo extends StatelessWidget {
|
|||||||
LocaleKeys.moreAction_createdAt.tr(
|
LocaleKeys.moreAction_createdAt.tr(
|
||||||
args: [dateFormat.formatDate(createdAt!, true, timeFormat)],
|
args: [dateFormat.formatDate(createdAt!, true, timeFormat)],
|
||||||
),
|
),
|
||||||
|
fontSize: 11,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
|
@ -1,13 +1,28 @@
|
|||||||
import 'dart:math';
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:async/async.dart';
|
import 'dart:math';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
|
||||||
import 'package:flowy_infra/size.dart';
|
|
||||||
import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:async/async.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
class StyledScrollbar extends StatefulWidget {
|
class StyledScrollbar extends StatefulWidget {
|
||||||
|
const StyledScrollbar({
|
||||||
|
super.key,
|
||||||
|
this.size,
|
||||||
|
required this.axis,
|
||||||
|
required this.controller,
|
||||||
|
this.onDrag,
|
||||||
|
this.contentSize,
|
||||||
|
this.showTrack = false,
|
||||||
|
this.autoHideScrollbar = true,
|
||||||
|
this.handleColor,
|
||||||
|
this.trackColor,
|
||||||
|
});
|
||||||
|
|
||||||
final double? size;
|
final double? size;
|
||||||
final Axis axis;
|
final Axis axis;
|
||||||
final ScrollController controller;
|
final ScrollController controller;
|
||||||
@ -22,18 +37,6 @@ class StyledScrollbar extends StatefulWidget {
|
|||||||
// https://stackoverflow.com/questions/60855712/flutter-how-to-force-scrollcontroller-to-recalculate-position-maxextents
|
// https://stackoverflow.com/questions/60855712/flutter-how-to-force-scrollcontroller-to-recalculate-position-maxextents
|
||||||
final double? contentSize;
|
final double? contentSize;
|
||||||
|
|
||||||
const StyledScrollbar(
|
|
||||||
{super.key,
|
|
||||||
this.size,
|
|
||||||
required this.axis,
|
|
||||||
required this.controller,
|
|
||||||
this.onDrag,
|
|
||||||
this.contentSize,
|
|
||||||
this.showTrack = false,
|
|
||||||
this.autoHideScrollbar = true,
|
|
||||||
this.handleColor,
|
|
||||||
this.trackColor});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ScrollbarState createState() => ScrollbarState();
|
ScrollbarState createState() => ScrollbarState();
|
||||||
}
|
}
|
||||||
@ -46,25 +49,29 @@ class ScrollbarState extends State<StyledScrollbar> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
widget.controller.addListener(() => setState(() {}));
|
widget.controller.addListener(_onScrollChanged);
|
||||||
|
widget.controller.position.isScrollingNotifier
|
||||||
widget.controller.position.isScrollingNotifier.addListener(
|
.addListener(_hideScrollbarInTime);
|
||||||
_hideScrollbarInTime,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(StyledScrollbar oldWidget) {
|
void dispose() {
|
||||||
if (oldWidget.contentSize != widget.contentSize) setState(() {});
|
if (widget.controller.hasClients) {
|
||||||
super.didUpdateWidget(oldWidget);
|
widget.controller.removeListener(_onScrollChanged);
|
||||||
|
widget.controller.position.isScrollingNotifier
|
||||||
|
.removeListener(_hideScrollbarInTime);
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onScrollChanged() => setState(() {});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (_, BoxConstraints constraints) {
|
builder: (_, BoxConstraints constraints) {
|
||||||
double maxExtent;
|
double maxExtent;
|
||||||
final contentSize = widget.contentSize;
|
final double? contentSize = widget.contentSize;
|
||||||
|
|
||||||
switch (widget.axis) {
|
switch (widget.axis) {
|
||||||
case Axis.vertical:
|
case Axis.vertical:
|
||||||
@ -109,11 +116,9 @@ class ScrollbarState extends State<StyledScrollbar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide the handle if content is < the viewExtent
|
// Hide the handle if content is < the viewExtent
|
||||||
var showHandle = contentExtent > _viewExtent && contentExtent > 0;
|
var showHandle = hideHandler
|
||||||
|
? false
|
||||||
if (hideHandler) {
|
: contentExtent > _viewExtent && contentExtent > 0;
|
||||||
showHandle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle color
|
// Handle color
|
||||||
var handleColor = widget.handleColor ??
|
var handleColor = widget.handleColor ??
|
||||||
@ -184,7 +189,7 @@ class ScrollbarState extends State<StyledScrollbar> {
|
|||||||
|
|
||||||
if (!widget.controller.position.isScrollingNotifier.value) {
|
if (!widget.controller.position.isScrollingNotifier.value) {
|
||||||
_hideScrollbarOperation = CancelableOperation.fromFuture(
|
_hideScrollbarOperation = CancelableOperation.fromFuture(
|
||||||
Future.delayed(const Duration(seconds: 2), () {}),
|
Future.delayed(const Duration(seconds: 2)),
|
||||||
).then((_) {
|
).then((_) {
|
||||||
hideHandler = true;
|
hideHandler = true;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -216,17 +221,6 @@ class ScrollbarState extends State<StyledScrollbar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ScrollbarListStack extends StatelessWidget {
|
class ScrollbarListStack extends StatelessWidget {
|
||||||
final double barSize;
|
|
||||||
final Axis axis;
|
|
||||||
final Widget child;
|
|
||||||
final ScrollController controller;
|
|
||||||
final double? contentSize;
|
|
||||||
final EdgeInsets? scrollbarPadding;
|
|
||||||
final Color? handleColor;
|
|
||||||
final Color? trackColor;
|
|
||||||
final bool showTrack;
|
|
||||||
final bool autoHideScrollbar;
|
|
||||||
|
|
||||||
const ScrollbarListStack({
|
const ScrollbarListStack({
|
||||||
super.key,
|
super.key,
|
||||||
required this.barSize,
|
required this.barSize,
|
||||||
@ -239,20 +233,37 @@ class ScrollbarListStack extends StatelessWidget {
|
|||||||
this.autoHideScrollbar = true,
|
this.autoHideScrollbar = true,
|
||||||
this.trackColor,
|
this.trackColor,
|
||||||
this.showTrack = false,
|
this.showTrack = false,
|
||||||
|
this.includeInsets = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final double barSize;
|
||||||
|
final Axis axis;
|
||||||
|
final Widget child;
|
||||||
|
final ScrollController controller;
|
||||||
|
final double? contentSize;
|
||||||
|
final EdgeInsets? scrollbarPadding;
|
||||||
|
final Color? handleColor;
|
||||||
|
final Color? trackColor;
|
||||||
|
final bool showTrack;
|
||||||
|
final bool autoHideScrollbar;
|
||||||
|
final bool includeInsets;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
/// LIST
|
/// Wrap with a bit of padding on the right or bottom to make room for the scrollbar
|
||||||
/// Wrap with a bit of padding on the right
|
Padding(
|
||||||
child.padding(
|
padding: !includeInsets
|
||||||
right: axis == Axis.vertical ? barSize + Insets.m : 0,
|
? EdgeInsets.zero
|
||||||
bottom: axis == Axis.horizontal ? barSize + Insets.m : 0,
|
: EdgeInsets.only(
|
||||||
|
right: axis == Axis.vertical ? barSize + Insets.m : 0,
|
||||||
|
bottom: axis == Axis.horizontal ? barSize + Insets.m : 0,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
|
|
||||||
/// SCROLLBAR
|
/// Display the scrollbar
|
||||||
Padding(
|
Padding(
|
||||||
padding: scrollbarPadding ?? EdgeInsets.zero,
|
padding: scrollbarPadding ?? EdgeInsets.zero,
|
||||||
child: StyledScrollbar(
|
child: StyledScrollbar(
|
||||||
@ -266,7 +277,7 @@ class ScrollbarListStack extends StatelessWidget {
|
|||||||
showTrack: showTrack,
|
showTrack: showTrack,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// The animate will be used by the children that using styled_widget.
|
// The animate will be used by the children that are using styled_widget.
|
||||||
.animate(const Duration(milliseconds: 250), Curves.easeOut),
|
.animate(const Duration(milliseconds: 250), Curves.easeOut),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -4,17 +4,6 @@ import 'styled_list.dart';
|
|||||||
import 'styled_scroll_bar.dart';
|
import 'styled_scroll_bar.dart';
|
||||||
|
|
||||||
class StyledSingleChildScrollView extends StatefulWidget {
|
class StyledSingleChildScrollView extends StatefulWidget {
|
||||||
final double? contentSize;
|
|
||||||
final Axis axis;
|
|
||||||
final Color? trackColor;
|
|
||||||
final Color? handleColor;
|
|
||||||
final ScrollController? controller;
|
|
||||||
final EdgeInsets? scrollbarPadding;
|
|
||||||
final double barSize;
|
|
||||||
final bool autoHideScrollbar;
|
|
||||||
|
|
||||||
final Widget? child;
|
|
||||||
|
|
||||||
const StyledSingleChildScrollView({
|
const StyledSingleChildScrollView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
@ -26,8 +15,20 @@ class StyledSingleChildScrollView extends StatefulWidget {
|
|||||||
this.scrollbarPadding,
|
this.scrollbarPadding,
|
||||||
this.barSize = 8,
|
this.barSize = 8,
|
||||||
this.autoHideScrollbar = true,
|
this.autoHideScrollbar = true,
|
||||||
|
this.includeInsets = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final Widget? child;
|
||||||
|
final double? contentSize;
|
||||||
|
final Axis axis;
|
||||||
|
final Color? trackColor;
|
||||||
|
final Color? handleColor;
|
||||||
|
final ScrollController? controller;
|
||||||
|
final EdgeInsets? scrollbarPadding;
|
||||||
|
final double barSize;
|
||||||
|
final bool autoHideScrollbar;
|
||||||
|
final bool includeInsets;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StyledSingleChildScrollView> createState() =>
|
State<StyledSingleChildScrollView> createState() =>
|
||||||
StyledSingleChildScrollViewState();
|
StyledSingleChildScrollViewState();
|
||||||
@ -35,13 +36,8 @@ class StyledSingleChildScrollView extends StatefulWidget {
|
|||||||
|
|
||||||
class StyledSingleChildScrollViewState
|
class StyledSingleChildScrollViewState
|
||||||
extends State<StyledSingleChildScrollView> {
|
extends State<StyledSingleChildScrollView> {
|
||||||
late ScrollController scrollController;
|
late final ScrollController scrollController =
|
||||||
|
widget.controller ?? ScrollController();
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
scrollController = widget.controller ?? ScrollController();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -51,14 +47,6 @@ class StyledSingleChildScrollViewState
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(StyledSingleChildScrollView oldWidget) {
|
|
||||||
if (oldWidget.child != widget.child) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScrollbarListStack(
|
return ScrollbarListStack(
|
||||||
@ -70,6 +58,7 @@ class StyledSingleChildScrollViewState
|
|||||||
barSize: widget.barSize,
|
barSize: widget.barSize,
|
||||||
trackColor: widget.trackColor,
|
trackColor: widget.trackColor,
|
||||||
handleColor: widget.handleColor,
|
handleColor: widget.handleColor,
|
||||||
|
includeInsets: widget.includeInsets,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: widget.axis,
|
scrollDirection: widget.axis,
|
||||||
physics: StyledScrollPhysics(),
|
physics: StyledScrollPhysics(),
|
||||||
@ -81,13 +70,6 @@ class StyledSingleChildScrollViewState
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StyledCustomScrollView extends StatefulWidget {
|
class StyledCustomScrollView extends StatefulWidget {
|
||||||
final Axis axis;
|
|
||||||
final Color? trackColor;
|
|
||||||
final Color? handleColor;
|
|
||||||
final ScrollController? verticalController;
|
|
||||||
final List<Widget> slivers;
|
|
||||||
final double barSize;
|
|
||||||
|
|
||||||
const StyledCustomScrollView({
|
const StyledCustomScrollView({
|
||||||
super.key,
|
super.key,
|
||||||
this.axis = Axis.vertical,
|
this.axis = Axis.vertical,
|
||||||
@ -98,32 +80,20 @@ class StyledCustomScrollView extends StatefulWidget {
|
|||||||
this.barSize = 8,
|
this.barSize = 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final Axis axis;
|
||||||
|
final Color? trackColor;
|
||||||
|
final Color? handleColor;
|
||||||
|
final ScrollController? verticalController;
|
||||||
|
final List<Widget> slivers;
|
||||||
|
final double barSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
StyledCustomScrollViewState createState() => StyledCustomScrollViewState();
|
StyledCustomScrollViewState createState() => StyledCustomScrollViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class StyledCustomScrollViewState extends State<StyledCustomScrollView> {
|
class StyledCustomScrollViewState extends State<StyledCustomScrollView> {
|
||||||
late ScrollController controller;
|
late final ScrollController controller =
|
||||||
|
widget.verticalController ?? ScrollController();
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
controller = widget.verticalController ?? ScrollController();
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(StyledCustomScrollView oldWidget) {
|
|
||||||
if (oldWidget.slivers != widget.slivers) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -93,7 +93,7 @@
|
|||||||
"moreOptions": "More options",
|
"moreOptions": "More options",
|
||||||
"wordCount": "Word count: {}",
|
"wordCount": "Word count: {}",
|
||||||
"charCount": "Character count: {}",
|
"charCount": "Character count: {}",
|
||||||
"createdAt": "Created at: {}",
|
"createdAt": "Created: {}",
|
||||||
"deleteView": "Delete",
|
"deleteView": "Delete",
|
||||||
"duplicateView": "Duplicate"
|
"duplicateView": "Duplicate"
|
||||||
},
|
},
|
||||||
@ -743,9 +743,9 @@
|
|||||||
"sum": "Sum",
|
"sum": "Sum",
|
||||||
"count": "Count",
|
"count": "Count",
|
||||||
"countEmpty": "Count empty",
|
"countEmpty": "Count empty",
|
||||||
"countEmptyShort": "Empty",
|
"countEmptyShort": "EMPTY",
|
||||||
"countNonEmpty": "Count non empty",
|
"countNonEmpty": "Count not empty",
|
||||||
"countNonEmptyShort": "Not empty"
|
"countNonEmptyShort": "FILLED"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"document": {
|
"document": {
|
||||||
@ -1051,7 +1051,8 @@
|
|||||||
"name": "Calendar settings"
|
"name": "Calendar settings"
|
||||||
},
|
},
|
||||||
"referencedCalendarPrefix": "View of",
|
"referencedCalendarPrefix": "View of",
|
||||||
"quickJumpYear": "Jump to"
|
"quickJumpYear": "Jump to",
|
||||||
|
"duplicateEvent": "Duplicate event"
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "AppFlowy Error",
|
"title": "AppFlowy Error",
|
||||||
|
@ -116,8 +116,13 @@ impl CalculationType {
|
|||||||
| CalculationType::Sum => {
|
| CalculationType::Sum => {
|
||||||
matches!(field_type, FieldType::Number)
|
matches!(field_type, FieldType::Number)
|
||||||
},
|
},
|
||||||
|
// Exclude some fields from CountNotEmpty & CountEmpty
|
||||||
|
CalculationType::CountEmpty | CalculationType::CountNonEmpty => !matches!(
|
||||||
|
field_type,
|
||||||
|
FieldType::URL | FieldType::Checkbox | FieldType::CreatedTime | FieldType::LastEditedTime
|
||||||
|
),
|
||||||
// All fields
|
// All fields
|
||||||
CalculationType::Count | CalculationType::CountEmpty | CalculationType::CountNonEmpty => true,
|
CalculationType::Count => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ impl CalculationsService {
|
|||||||
CalculationType::Min => self.calculate_min(field, row_cells),
|
CalculationType::Min => self.calculate_min(field, row_cells),
|
||||||
CalculationType::Sum => self.calculate_sum(field, row_cells),
|
CalculationType::Sum => self.calculate_sum(field, row_cells),
|
||||||
CalculationType::Count => self.calculate_count(row_cells),
|
CalculationType::Count => self.calculate_count(row_cells),
|
||||||
CalculationType::CountEmpty => self.calculate_count_empty(row_cells),
|
CalculationType::CountEmpty => self.calculate_count_empty(field, row_cells),
|
||||||
CalculationType::CountNonEmpty => self.calculate_count_non_empty(row_cells),
|
CalculationType::CountNonEmpty => self.calculate_count_non_empty(field, row_cells),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,34 +129,51 @@ impl CalculationsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_count_empty(&self, row_cells: Vec<Arc<RowCell>>) -> String {
|
fn calculate_count_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
|
||||||
if !row_cells.is_empty() {
|
let field_type = FieldType::from(field.field_type);
|
||||||
format!(
|
if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None)
|
||||||
"{}",
|
.get_type_option_cell_data_handler(&field_type)
|
||||||
row_cells
|
{
|
||||||
.iter()
|
if !row_cells.is_empty() {
|
||||||
.filter(|c| c.is_none())
|
return format!(
|
||||||
.collect::<Vec<_>>()
|
"{}",
|
||||||
.len()
|
row_cells
|
||||||
)
|
.iter()
|
||||||
} else {
|
.filter(|c| c.is_none()
|
||||||
String::new()
|
|| handler
|
||||||
|
.handle_stringify_cell(&c.cell.clone().unwrap_or_default(), &field_type, field)
|
||||||
|
.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_count_non_empty(&self, row_cells: Vec<Arc<RowCell>>) -> String {
|
fn calculate_count_non_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
|
||||||
if !row_cells.is_empty() {
|
let field_type = FieldType::from(field.field_type);
|
||||||
format!(
|
if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None)
|
||||||
"{}",
|
.get_type_option_cell_data_handler(&field_type)
|
||||||
row_cells
|
{
|
||||||
.iter()
|
if !row_cells.is_empty() {
|
||||||
.filter(|c| c.is_some())
|
return format!(
|
||||||
.collect::<Vec<_>>()
|
"{}",
|
||||||
.len()
|
row_cells
|
||||||
)
|
.iter()
|
||||||
} else {
|
// Check the Cell has data and that the stringified version is not empty
|
||||||
String::new()
|
.filter(|c| c.is_some()
|
||||||
|
&& !handler
|
||||||
|
.handle_stringify_cell(&c.cell.clone().unwrap_or_default(), &field_type, field)
|
||||||
|
.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce_values_f64<F, T>(&self, field: &Field, row_cells: Vec<Arc<RowCell>>, f: F) -> T
|
fn reduce_values_f64<F, T>(&self, field: &Field, row_cells: Vec<Arc<RowCell>>, f: F) -> T
|
||||||
|
Loading…
Reference in New Issue
Block a user