feat: enable group condition (#5248)

* feat: enable group condition

* style: added i18n for date field group conditions

* fix: flutter analyze

* fix: test use i18n

* fix: more localization

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
This commit is contained in:
Mohammad Zolfaghari
2024-06-13 01:32:16 +03:30
committed by GitHub
parent f1a4deb459
commit 4f4be7eac7
16 changed files with 421 additions and 70 deletions

View File

@ -137,6 +137,8 @@ class FieldController {
List<FieldInfo> get fieldInfos => [..._fieldNotifier.fieldInfos]; List<FieldInfo> get fieldInfos => [..._fieldNotifier.fieldInfos];
List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []]; List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []]; List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []];
List<GroupSettingPB> get groupSettings =>
_groupConfigurationByFieldId.entries.map((e) => e.value).toList();
FieldInfo? getField(String fieldId) { FieldInfo? getField(String fieldId) {
return _fieldNotifier.fieldInfos return _fieldNotifier.fieldInfos

View File

@ -1,5 +1,6 @@
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:protobuf/protobuf.dart';
part 'field_info.freezed.dart'; part 'field_info.freezed.dart';
@ -89,4 +90,13 @@ class FieldInfo with _$FieldInfo {
return false; return false;
} }
} }
List<ProtobufEnum> get groupConditions {
switch (field.fieldType) {
case FieldType.DateTime:
return DateConditionPB.values;
default:
return [];
}
}
} }

View File

@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database/domain/group_service.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -22,6 +23,7 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
viewId, viewId,
databaseController.fieldController.fieldInfos, databaseController.fieldController.fieldInfos,
databaseController.databaseLayoutSetting!.board, databaseController.databaseLayoutSetting!.board,
databaseController.fieldController.groupSettings,
), ),
) { ) {
_dispatch(); _dispatch();
@ -51,11 +53,22 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
_startListening(); _startListening();
}, },
didReceiveFieldUpdate: (fieldInfos) { didReceiveFieldUpdate: (fieldInfos) {
emit(state.copyWith(fieldInfos: fieldInfos)); emit(
state.copyWith(
fieldInfos: fieldInfos,
groupSettings:
_databaseController.fieldController.groupSettings,
),
);
}, },
setGroupByField: (String fieldId, FieldType fieldType) async { setGroupByField: (
String fieldId,
FieldType fieldType, [
List<int>? settingContent,
]) async {
final result = await _groupBackendSvc.groupByField( final result = await _groupBackendSvc.groupByField(
fieldId: fieldId, fieldId: fieldId,
settingContent: settingContent ?? [],
); );
result.fold((l) => null, (err) => Log.error(err)); result.fold((l) => null, (err) => Log.error(err));
}, },
@ -96,8 +109,9 @@ class DatabaseGroupEvent with _$DatabaseGroupEvent {
const factory DatabaseGroupEvent.initial() = _Initial; const factory DatabaseGroupEvent.initial() = _Initial;
const factory DatabaseGroupEvent.setGroupByField( const factory DatabaseGroupEvent.setGroupByField(
String fieldId, String fieldId,
FieldType fieldType, FieldType fieldType, [
) = _DatabaseGroupEvent; @Default([]) List<int> settingContent,
]) = _DatabaseGroupEvent;
const factory DatabaseGroupEvent.didReceiveFieldUpdate( const factory DatabaseGroupEvent.didReceiveFieldUpdate(
List<FieldInfo> fields, List<FieldInfo> fields,
) = _DidReceiveFieldUpdate; ) = _DidReceiveFieldUpdate;
@ -112,16 +126,19 @@ class DatabaseGroupState with _$DatabaseGroupState {
required String viewId, required String viewId,
required List<FieldInfo> fieldInfos, required List<FieldInfo> fieldInfos,
required BoardLayoutSettingPB layoutSettings, required BoardLayoutSettingPB layoutSettings,
required List<GroupSettingPB> groupSettings,
}) = _DatabaseGroupState; }) = _DatabaseGroupState;
factory DatabaseGroupState.initial( factory DatabaseGroupState.initial(
String viewId, String viewId,
List<FieldInfo> fieldInfos, List<FieldInfo> fieldInfos,
BoardLayoutSettingPB layoutSettings, BoardLayoutSettingPB layoutSettings,
List<GroupSettingPB> groupSettings,
) => ) =>
DatabaseGroupState( DatabaseGroupState(
viewId: viewId, viewId: viewId,
fieldInfos: fieldInfos, fieldInfos: fieldInfos,
layoutSettings: layoutSettings, layoutSettings: layoutSettings,
groupSettings: groupSettings,
); );
} }

View File

@ -16,8 +16,10 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:intl/intl.dart';
import 'package:protobuf/protobuf.dart' hide FieldInfo; import 'package:protobuf/protobuf.dart' hide FieldInfo;
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:calendar_view/calendar_view.dart';
import '../../application/database_controller.dart'; import '../../application/database_controller.dart';
import '../../application/field/field_controller.dart'; import '../../application/field/field_controller.dart';
@ -527,6 +529,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
return "No ${field.name}"; return "No ${field.name}";
} }
final groupSettings = databaseController.fieldController.groupSettings
.firstWhereOrNull((gs) => gs.fieldId == field.id);
switch (field.fieldType) { switch (field.fieldType) {
case FieldType.SingleSelect: case FieldType.SingleSelect:
final options = final options =
@ -547,33 +552,61 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
case FieldType.URL: case FieldType.URL:
return group.groupId; return group.groupId;
case FieldType.DateTime: case FieldType.DateTime:
// Assume DateCondition::Relative as there isn't an option for this final config = groupSettings?.content != null
// right now. ? DateGroupConfigurationPB.fromBuffer(groupSettings!.content)
: DateGroupConfigurationPB();
final dateFormat = DateFormat("y/MM/dd"); final dateFormat = DateFormat("y/MM/dd");
try { try {
final targetDateTime = dateFormat.parseLoose(group.groupId); final targetDateTime = dateFormat.parseLoose(group.groupId);
final targetDateTimeDay = DateTime( switch (config.condition) {
targetDateTime.year, case DateConditionPB.Day:
targetDateTime.month, return DateFormat("MMM dd, y").format(targetDateTime);
targetDateTime.day, case DateConditionPB.Week:
); final beginningOfWeek = targetDateTime
final now = DateTime.now(); .subtract(Duration(days: targetDateTime.weekday - 1));
final nowDay = DateTime( final endOfWeek = targetDateTime.add(
now.year, Duration(days: DateTime.daysPerWeek - targetDateTime.weekday),
now.month, );
now.day,
); final beginningOfWeekFormat =
final diff = targetDateTimeDay.difference(nowDay).inDays; beginningOfWeek.year != endOfWeek.year
return switch (diff) { ? "MMM dd y"
0 => "Today", : "MMM dd";
-1 => "Yesterday", final endOfWeekFormat = beginningOfWeek.month != endOfWeek.month
1 => "Tomorrow", ? "MMM dd y"
-7 => "Last 7 days", : "dd y";
2 => "Next 7 days",
-30 => "Last 30 days", return LocaleKeys.board_dateCondition_weekOf.tr(
8 => "Next 30 days", args: [
_ => DateFormat("MMM y").format(targetDateTimeDay) DateFormat(beginningOfWeekFormat).format(beginningOfWeek),
}; DateFormat(endOfWeekFormat).format(endOfWeek),
],
);
case DateConditionPB.Month:
return DateFormat("MMM y").format(targetDateTime);
case DateConditionPB.Year:
return DateFormat("y").format(targetDateTime);
case DateConditionPB.Relative:
final targetDateTimeDay = DateTime(
targetDateTime.year,
targetDateTime.month,
targetDateTime.day,
);
final nowDay = DateTime.now().withoutTime;
final diff = targetDateTimeDay.difference(nowDay).inDays;
return switch (diff) {
0 => LocaleKeys.board_dateCondition_today.tr(),
-1 => LocaleKeys.board_dateCondition_yesterday.tr(),
1 => LocaleKeys.board_dateCondition_tomorrow.tr(),
-7 => LocaleKeys.board_dateCondition_lastSevenDays.tr(),
2 => LocaleKeys.board_dateCondition_nextSevenDays.tr(),
-30 => LocaleKeys.board_dateCondition_lastThirtyDays.tr(),
8 => LocaleKeys.board_dateCondition_nextThirtyDays.tr(),
_ => DateFormat("MMM y").format(targetDateTimeDay)
};
default:
return "";
}
} on FormatException { } on FormatException {
return ""; return "";
} }

View File

@ -1,5 +1,5 @@
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_result/appflowy_result.dart'; import 'package:appflowy_result/appflowy_result.dart';
@ -10,10 +10,12 @@ class GroupBackendService {
Future<FlowyResult<void, FlowyError>> groupByField({ Future<FlowyResult<void, FlowyError>> groupByField({
required String fieldId, required String fieldId,
required List<int> settingContent,
}) { }) {
final payload = GroupByFieldPayloadPB.create() final payload = GroupByFieldPayloadPB.create()
..viewId = viewId ..viewId = viewId
..fieldId = fieldId; ..fieldId = fieldId
..settingContent = settingContent;
return DatabaseEventSetGroupByField(payload).send(); return DatabaseEventSetGroupByField(payload).send();
} }

View File

@ -8,8 +8,7 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_
import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
@ -17,6 +16,7 @@ import 'package:flowy_infra_ui/style_widget/button.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:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:protobuf/protobuf.dart' hide FieldInfo; import 'package:protobuf/protobuf.dart' hide FieldInfo;
@ -42,12 +42,21 @@ class DatabaseGroupList extends StatelessWidget {
)..add(const DatabaseGroupEvent.initial()), )..add(const DatabaseGroupEvent.initial()),
child: BlocBuilder<DatabaseGroupBloc, DatabaseGroupState>( child: BlocBuilder<DatabaseGroupBloc, DatabaseGroupState>(
builder: (context, state) { builder: (context, state) {
final showHideUngroupedToggle = state.fieldInfos.any( final field = state.fieldInfos.firstWhereOrNull(
(field) => (field) => field.canBeGroup && field.isGroupField,
field.canBeGroup &&
field.isGroupField &&
field.fieldType != FieldType.Checkbox,
); );
final showHideUngroupedToggle =
field?.fieldType != FieldType.Checkbox;
DateGroupConfigurationPB? config;
if (field != null) {
final gs = state.groupSettings
.firstWhereOrNull((gs) => gs.fieldId == field.id);
config = gs != null
? DateGroupConfigurationPB.fromBuffer(gs.content)
: null;
}
final children = [ final children = [
if (showHideUngroupedToggle) ...[ if (showHideUngroupedToggle) ...[
SizedBox( SizedBox(
@ -90,10 +99,37 @@ class DatabaseGroupList extends StatelessWidget {
...state.fieldInfos.where((fieldInfo) => fieldInfo.canBeGroup).map( ...state.fieldInfos.where((fieldInfo) => fieldInfo.canBeGroup).map(
(fieldInfo) => _GridGroupCell( (fieldInfo) => _GridGroupCell(
fieldInfo: fieldInfo, fieldInfo: fieldInfo,
name: fieldInfo.name,
icon: fieldInfo.fieldType.svgData,
checked: fieldInfo.isGroupField,
onSelected: onDismissed, onSelected: onDismissed,
key: ValueKey(fieldInfo.id), key: ValueKey(fieldInfo.id),
), ),
), ),
if (field?.groupConditions.isNotEmpty ?? false) ...[
const TypeOptionSeparator(spacing: 0),
SizedBox(
height: GridSize.popoverItemHeight,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: FlowyText.medium(
LocaleKeys.board_groupCondition.tr(),
textAlign: TextAlign.left,
color: Theme.of(context).hintColor,
),
),
),
...field!.groupConditions.map(
(condition) => _GridGroupCell(
fieldInfo: field,
name: condition.name,
condition: condition.value,
onSelected: onDismissed,
checked: config?.condition == condition,
),
),
],
]; ];
return ListView.separated( return ListView.separated(
@ -128,15 +164,23 @@ class _GridGroupCell extends StatelessWidget {
super.key, super.key,
required this.fieldInfo, required this.fieldInfo,
required this.onSelected, required this.onSelected,
required this.checked,
required this.name,
this.condition = 0,
this.icon,
}); });
final FieldInfo fieldInfo; final FieldInfo fieldInfo;
final VoidCallback onSelected; final VoidCallback onSelected;
final bool checked;
final int condition;
final String name;
final FlowySvgData? icon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget? rightIcon; Widget? rightIcon;
if (fieldInfo.isGroupField) { if (checked) {
rightIcon = const Padding( rightIcon = const Padding(
padding: EdgeInsets.all(2.0), padding: EdgeInsets.all(2.0),
child: FlowySvg(FlowySvgs.check_s), child: FlowySvg(FlowySvgs.check_s),
@ -150,19 +194,31 @@ class _GridGroupCell extends StatelessWidget {
child: FlowyButton( child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover, hoverColor: AFThemeExtension.of(context).lightGreyHover,
text: FlowyText.medium( text: FlowyText.medium(
fieldInfo.name, name,
color: AFThemeExtension.of(context).textColor, color: AFThemeExtension.of(context).textColor,
), ),
leftIcon: FlowySvg( leftIcon: icon != null
fieldInfo.fieldType.svgData, ? FlowySvg(
color: Theme.of(context).iconTheme.color, icon!,
), color: Theme.of(context).iconTheme.color,
)
: null,
rightIcon: rightIcon, rightIcon: rightIcon,
onTap: () { onTap: () {
List<int> settingContent = [];
switch (fieldInfo.fieldType) {
case FieldType.DateTime:
final config = DateGroupConfigurationPB()
..condition = DateConditionPB.values[condition];
settingContent = config.writeToBuffer();
break;
default:
}
context.read<DatabaseGroupBloc>().add( context.read<DatabaseGroupBloc>().add(
DatabaseGroupEvent.setGroupByField( DatabaseGroupEvent.setGroupByField(
fieldInfo.id, fieldInfo.id,
fieldInfo.fieldType, fieldInfo.fieldType,
settingContent,
), ),
); );
onSelected(); onSelected();

View File

@ -0,0 +1,115 @@
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/setting/group_bloc.dart';
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'util.dart';
void main() {
late AppFlowyBoardTest boardTest;
setUpAll(() async {
boardTest = await AppFlowyBoardTest.ensureInitialized();
});
test('group by date field test', () async {
final context = await boardTest.createTestBoard();
final boardBloc = BoardBloc(
databaseController: DatabaseController(view: context.gridView),
)..add(const BoardEvent.initial());
await boardResponseFuture();
// assert the initial values
assert(boardBloc.groupControllers.values.length == 4);
assert(context.fieldContexts.length == 2);
await context.createField(FieldType.DateTime);
await boardResponseFuture();
assert(context.fieldContexts.length == 3);
final dateField = context.fieldContexts.last.field;
final cellController = context.makeCellControllerFromFieldId(dateField.id)
as DateCellController;
final bloc = DateCellEditorBloc(
cellController: cellController,
reminderBloc: getIt<ReminderBloc>(),
);
await boardResponseFuture();
bloc.add(DateCellEditorEvent.selectDay(DateTime.now()));
await boardResponseFuture();
final gridGroupBloc = DatabaseGroupBloc(
viewId: context.gridView.id,
databaseController: context.databaseController,
)..add(const DatabaseGroupEvent.initial());
gridGroupBloc.add(
DatabaseGroupEvent.setGroupByField(
dateField.id,
dateField.fieldType,
),
);
await boardResponseFuture();
assert(boardBloc.groupControllers.values.length == 2);
assert(
boardBloc.boardController.groupDatas.last.headerData.groupName ==
LocaleKeys.board_dateCondition_today.tr(),
);
});
test('group by date field with condition', () async {
final context = await boardTest.createTestBoard();
final boardBloc = BoardBloc(
databaseController: DatabaseController(view: context.gridView),
)..add(const BoardEvent.initial());
await boardResponseFuture();
// assert the initial values
assert(boardBloc.groupControllers.values.length == 4);
assert(context.fieldContexts.length == 2);
await context.createField(FieldType.DateTime);
await boardResponseFuture();
assert(context.fieldContexts.length == 3);
final dateField = context.fieldContexts.last.field;
final cellController = context.makeCellControllerFromFieldId(dateField.id)
as DateCellController;
final bloc = DateCellEditorBloc(
cellController: cellController,
reminderBloc: getIt<ReminderBloc>(),
);
await boardResponseFuture();
bloc.add(DateCellEditorEvent.selectDay(DateTime.now()));
await boardResponseFuture();
final gridGroupBloc = DatabaseGroupBloc(
viewId: context.gridView.id,
databaseController: context.databaseController,
)..add(const DatabaseGroupEvent.initial());
final settingContent = DateGroupConfigurationPB()
..condition = DateConditionPB.Year;
gridGroupBloc.add(
DatabaseGroupEvent.setGroupByField(
dateField.id,
dateField.fieldType,
settingContent.writeToBuffer(),
),
);
await boardResponseFuture();
assert(boardBloc.groupControllers.values.length == 2);
assert(
boardBloc.boardController.groupDatas.last.headerData.groupName == "2024",
);
});
}

View File

@ -1436,6 +1436,7 @@
"ungroupedButtonTooltip": "Contains cards that don't belong in any group", "ungroupedButtonTooltip": "Contains cards that don't belong in any group",
"ungroupedItemsTitle": "Click to add to the board", "ungroupedItemsTitle": "Click to add to the board",
"groupBy": "Group by", "groupBy": "Group by",
"groupCondition": "Group condition",
"referencedBoardPrefix": "View of", "referencedBoardPrefix": "View of",
"notesTooltip": "Notes inside", "notesTooltip": "Notes inside",
"mobile": { "mobile": {
@ -1444,6 +1445,16 @@
"showGroupContent": "Are you sure you want to show this group on the board?", "showGroupContent": "Are you sure you want to show this group on the board?",
"failedToLoad": "Failed to load board view" "failedToLoad": "Failed to load board view"
}, },
"dateCondition": {
"weekOf": "Week of {} - {}",
"today": "Today",
"yesterday": "Yesterday",
"tomorrow": "Tomorrow",
"lastSevenDays": "Last 7 days",
"nextSevenDays": "Next 7 days",
"lastThirtyDays": "Last 30 days",
"nextThirtyDays": "Next 30 days"
},
"noGroup": "No group by property", "noGroup": "No group by property",
"noGroupDesc": "Board views require a property to group by in order to display" "noGroupDesc": "Board views require a property to group by in order to display"
}, },

View File

@ -444,12 +444,18 @@ impl EventIntegrationTest {
.error() .error()
} }
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> { pub async fn set_group_by_field(
&self,
view_id: &str,
field_id: &str,
setting_content: Vec<u8>,
) -> Option<FlowyError> {
EventBuilder::new(self.clone()) EventBuilder::new(self.clone())
.event(DatabaseEvent::SetGroupByField) .event(DatabaseEvent::SetGroupByField)
.payload(GroupByFieldPayloadPB { .payload(GroupByFieldPayloadPB {
field_id: field_id.to_string(), field_id: field_id.to_string(),
view_id: view_id.to_string(), view_id: view_id.to_string(),
setting_content,
}) })
.async_send() .async_send()
.await .await

View File

@ -697,7 +697,7 @@ async fn update_database_layout_event_test2() {
.find(|field| field.field_type == FieldType::Checkbox) .find(|field| field.field_type == FieldType::Checkbox)
.unwrap(); .unwrap();
test test
.set_group_by_field(&grid_view.id, &checkbox_field.id) .set_group_by_field(&grid_view.id, &checkbox_field.id, vec![])
.await; .await;
let error = test let error = test
@ -725,7 +725,7 @@ async fn set_group_by_checkbox_field_test() {
let checkbox_field = test.create_field(&board_view.id, FieldType::Checkbox).await; let checkbox_field = test.create_field(&board_view.id, FieldType::Checkbox).await;
test test
.set_group_by_field(&board_view.id, &checkbox_field.id) .set_group_by_field(&board_view.id, &checkbox_field.id, vec![])
.await; .await;
let groups = test.get_groups(&board_view.id).await; let groups = test.get_groups(&board_view.id).await;

View File

@ -1,5 +1,10 @@
use crate::services::group::Group; use crate::{
entities::FieldType,
services::group::{DateCondition, DateGroupConfiguration, Group},
};
use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::FlowyResult;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct URLGroupConfigurationPB { pub struct URLGroupConfigurationPB {
@ -46,16 +51,33 @@ pub struct NumberGroupConfigurationPB {
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct DateGroupConfigurationPB { pub struct DateGroupConfigurationPB {
#[pb(index = 1)] #[pb(index = 1)]
pub condition: DateCondition, pub condition: DateConditionPB,
#[pb(index = 2)] #[pb(index = 2)]
hide_empty: bool, hide_empty: bool,
} }
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] impl From<DateGroupConfigurationPB> for DateGroupConfiguration {
fn from(data: DateGroupConfigurationPB) -> Self {
Self {
condition: data.condition.into(),
hide_empty: data.hide_empty,
}
}
}
impl From<DateGroupConfiguration> for DateGroupConfigurationPB {
fn from(data: DateGroupConfiguration) -> Self {
Self {
condition: data.condition.into(),
hide_empty: data.hide_empty,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, Default)]
#[repr(u8)] #[repr(u8)]
#[derive(Default)] pub enum DateConditionPB {
pub enum DateCondition {
#[default] #[default]
Relative = 0, Relative = 0,
Day = 1, Day = 1,
@ -64,8 +86,56 @@ pub enum DateCondition {
Year = 4, Year = 4,
} }
impl From<DateConditionPB> for DateCondition {
fn from(data: DateConditionPB) -> Self {
match data {
DateConditionPB::Relative => DateCondition::Relative,
DateConditionPB::Day => DateCondition::Day,
DateConditionPB::Week => DateCondition::Week,
DateConditionPB::Month => DateCondition::Month,
DateConditionPB::Year => DateCondition::Year,
}
}
}
impl From<DateCondition> for DateConditionPB {
fn from(data: DateCondition) -> Self {
match data {
DateCondition::Relative => DateConditionPB::Relative,
DateCondition::Day => DateConditionPB::Day,
DateCondition::Week => DateConditionPB::Week,
DateCondition::Month => DateConditionPB::Month,
DateCondition::Year => DateConditionPB::Year,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct CheckboxGroupConfigurationPB { pub struct CheckboxGroupConfigurationPB {
#[pb(index = 1)] #[pb(index = 1)]
pub(crate) hide_empty: bool, pub(crate) hide_empty: bool,
} }
pub fn group_config_pb_to_json_str<T: Into<Bytes>>(
bytes: T,
field_type: &FieldType,
) -> FlowyResult<String> {
let bytes = bytes.into();
match field_type {
FieldType::DateTime => DateGroupConfigurationPB::try_from(bytes)
.map(|pb| DateGroupConfiguration::from(pb).to_json())?,
_ => Ok("".to_string()),
}
}
pub fn group_config_json_to_pb(setting_content: String, field_type: &FieldType) -> Bytes {
match field_type {
FieldType::DateTime => {
let date_group_config = DateGroupConfiguration::from_json(setting_content.as_ref()).unwrap();
DateGroupConfigurationPB::from(date_group_config)
.try_into()
.unwrap()
},
_ => Bytes::new(),
}
}

View File

@ -5,9 +5,11 @@ use flowy_error::ErrorCode;
use validator::Validate; use validator::Validate;
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::RowMetaPB; use crate::entities::{FieldType, RowMetaPB};
use crate::services::group::{GroupChangeset, GroupData, GroupSetting}; use crate::services::group::{GroupChangeset, GroupData, GroupSetting};
use super::group_config_json_to_pb;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GroupSettingPB { pub struct GroupSettingPB {
#[pb(index = 1)] #[pb(index = 1)]
@ -15,13 +17,18 @@ pub struct GroupSettingPB {
#[pb(index = 2)] #[pb(index = 2)]
pub field_id: String, pub field_id: String,
#[pb(index = 3)]
pub content: Vec<u8>,
} }
impl std::convert::From<&GroupSetting> for GroupSettingPB { impl std::convert::From<&GroupSetting> for GroupSettingPB {
fn from(rev: &GroupSetting) -> Self { fn from(rev: &GroupSetting) -> Self {
let field_type = FieldType::from(rev.field_type);
GroupSettingPB { GroupSettingPB {
id: rev.id.clone(), id: rev.id.clone(),
field_id: rev.field_id.clone(), field_id: rev.field_id.clone(),
content: group_config_json_to_pb(rev.content.clone(), &field_type).to_vec(),
} }
} }
} }
@ -105,6 +112,9 @@ pub struct GroupByFieldPayloadPB {
#[pb(index = 2)] #[pb(index = 2)]
pub view_id: String, pub view_id: String,
#[pb(index = 3)]
pub setting_content: Vec<u8>,
} }
impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB { impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB {
@ -118,13 +128,18 @@ impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB {
.map_err(|_| ErrorCode::ViewIdIsInvalid)? .map_err(|_| ErrorCode::ViewIdIsInvalid)?
.0; .0;
Ok(GroupByFieldParams { field_id, view_id }) Ok(GroupByFieldParams {
field_id,
view_id,
setting_content: self.setting_content,
})
} }
} }
pub struct GroupByFieldParams { pub struct GroupByFieldParams {
pub field_id: String, pub field_id: String,
pub view_id: String, pub view_id: String,
pub setting_content: Vec<u8>,
} }
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone, Validate)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone, Validate)]

View File

@ -656,7 +656,7 @@ pub(crate) async fn set_group_by_field_handler(
let params: GroupByFieldParams = data.into_inner().try_into()?; let params: GroupByFieldParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?; let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor database_editor
.set_group_by_field(&params.view_id, &params.field_id) .set_group_by_field(&params.view_id, &params.field_id, params.setting_content)
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -136,20 +136,37 @@ impl DatabaseEditor {
self.database.lock().fields.get_field(field_id) self.database.lock().fields.get_field(field_id)
} }
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> { pub async fn set_group_by_field(
&self,
view_id: &str,
field_id: &str,
data: Vec<u8>,
) -> FlowyResult<()> {
let old_group_settings: Vec<GroupSetting>;
let mut setting_content = "".to_string();
{ {
let database = self.database.lock(); let database = self.database.lock();
let field = database.fields.get_field(field_id); let field = database.fields.get_field(field_id);
old_group_settings = database.get_all_group_setting(view_id);
if let Some(field) = field { if let Some(field) = field {
let group_setting = default_group_setting(&field); let field_type = FieldType::from(field.field_type);
setting_content = group_config_pb_to_json_str(data, &field_type)?;
let mut group_setting = default_group_setting(&field);
group_setting.content = setting_content.clone();
database.views.update_database_view(view_id, |view| { database.views.update_database_view(view_id, |view| {
view.set_groups(vec![group_setting.into()]); view.set_groups(vec![group_setting.into()]);
}); });
} }
} }
let old_group_setting = old_group_settings.iter().find(|g| g.field_id == field_id);
let has_same_content =
old_group_setting.is_some() && old_group_setting.unwrap().content == setting_content;
let view_editor = self.database_views.get_view_editor(view_id).await?; let view_editor = self.database_views.get_view_editor(view_id).await?;
view_editor.v_initialize_new_group(field_id).await?; if !view_editor.is_grouping_field(field_id).await || !has_same_content {
view_editor.v_initialize_new_group(field_id).await?;
}
Ok(()) Ok(())
} }

View File

@ -401,15 +401,12 @@ impl DatabaseViewEditor {
/// Called when the user changes the grouping field /// Called when the user changes the grouping field
pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> { pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> {
let is_grouping_field = self.is_grouping_field(field_id).await; if let Some(view) = self.delegate.get_view(&self.view_id).await {
if !is_grouping_field { let setting = database_view_setting_pb_from_view(view);
self.v_group_by_field(field_id).await?; notify_did_update_setting(&self.view_id, setting).await;
if let Some(view) = self.delegate.get_view(&self.view_id).await {
let setting = database_view_setting_pb_from_view(view);
notify_did_update_setting(&self.view_id, setting).await;
}
} }
self.v_group_by_field(field_id).await?;
Ok(()) Ok(())
} }

View File

@ -6,7 +6,7 @@ use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use flowy_error::FlowyResult; use flowy_error::{internal_error, FlowyResult};
use crate::entities::{ use crate::entities::{
FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB, FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB,
@ -27,13 +27,13 @@ pub struct DateGroupConfiguration {
} }
impl DateGroupConfiguration { impl DateGroupConfiguration {
fn from_json(s: &str) -> Result<Self, serde_json::Error> { pub fn from_json(s: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(s) serde_json::from_str(s)
} }
#[allow(dead_code)] #[allow(dead_code)]
fn to_json(&self) -> Result<String, serde_json::Error> { pub fn to_json(&self) -> FlowyResult<String> {
serde_json::to_string(self) serde_json::to_string(self).map_err(internal_error)
} }
} }