浏览代码

chore: calendar plugin backend database data (#1884)

* chore: implement CalendarBloc

* chore: enable save and read the calendar setting

* style: more precise variable name

* chore: backend calendar settings

* chore: protobuf for layout settings

* chore: update test

* chore: Enumerate the LayoutTypePB enum type to get the supported layout types

* fix: deserialize object type is not the same as serialize object type

* chore: add set/get calendar settings event

* ci: fix wanrings

---------

Co-authored-by: nathan <[email protected]>
Co-authored-by: vedon <[email protected]>
Richard Shiue 2 年之前
父节点
当前提交
6877607c5e

+ 159 - 0
frontend/app_flowy/lib/plugins/calendar/application/calendar_bloc.dart

@@ -0,0 +1,159 @@
+import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
+import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
+import 'package:calendar_view/calendar_view.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+import 'calendar_data_controller.dart';
+
+part 'calendar_bloc.freezed.dart';
+
+class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
+  final CalendarDataController _databaseDataController;
+  final EventController calendarEventsController = EventController();
+
+  GridFieldController get fieldController =>
+      _databaseDataController.fieldController;
+  String get databaseId => _databaseDataController.databaseId;
+
+  CalendarBloc({required ViewPB view})
+      : _databaseDataController = CalendarDataController(view: view),
+        super(CalendarState.initial(view.id)) {
+    on<CalendarEvent>(
+      (event, emit) async {
+        await event.when(
+          initial: () async {
+            _startListening();
+            await _openDatabase(emit);
+          },
+          didReceiveCalendarSettings: (CalendarSettingsPB settings) {
+            emit(state.copyWith(settings: Some(settings)));
+          },
+          didReceiveDatabaseUpdate: (DatabasePB database) {
+            emit(state.copyWith(database: Some(database)));
+          },
+          didReceiveError: (FlowyError error) {
+            emit(state.copyWith(noneOrError: Some(error)));
+          },
+        );
+      },
+    );
+  }
+
+  Future<void> _openDatabase(Emitter<CalendarState> emit) async {
+    final result = await _databaseDataController.openDatabase();
+    result.fold(
+      (database) => emit(
+        state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))),
+      ),
+      (err) => emit(
+        state.copyWith(loadingState: DatabaseLoadingState.finish(right(err))),
+      ),
+    );
+  }
+
+  GridRowCache? getRowCache(String blockId) {
+    return _databaseDataController.rowCache;
+  }
+
+  void _startListening() {
+    _databaseDataController.addListener(
+      onDatabaseChanged: (database) {
+        if (!isClosed) return;
+
+        add(CalendarEvent.didReceiveDatabaseUpdate(database));
+      },
+      onSettingsChanged: (CalendarSettingsPB settings) {
+        if (isClosed) return;
+        add(CalendarEvent.didReceiveCalendarSettings(settings));
+      },
+      onArrangeWithNewField: (field) {
+        if (isClosed) return;
+        _initializeEvents(field);
+        // add(CalendarEvent.)
+      },
+      onError: (err) {
+        Log.error(err);
+      },
+    );
+  }
+
+  void _initializeEvents(FieldPB dateField) {
+    calendarEventsController.removeWhere((element) => true);
+
+    const events = <CalendarEventData<CalendarData>>[];
+
+    // final List<CalendarEventData<CalendarData>> events = rows.map((row) {
+    // final event = CalendarEventData(
+    //   title: "",
+    //   date: row -> dateField -> value,
+    //   event: row,
+    // );
+
+    // return event;
+    // }).toList();
+
+    calendarEventsController.addAll(events);
+  }
+}
+
+@freezed
+class CalendarEvent with _$CalendarEvent {
+  const factory CalendarEvent.initial() = _InitialCalendar;
+  const factory CalendarEvent.didReceiveCalendarSettings(
+      CalendarSettingsPB settings) = _DidReceiveCalendarSettings;
+  const factory CalendarEvent.didReceiveError(FlowyError error) =
+      _DidReceiveError;
+  const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) =
+      _DidReceiveDatabaseUpdate;
+}
+
+@freezed
+class CalendarState with _$CalendarState {
+  const factory CalendarState({
+    required String databaseId,
+    required Option<DatabasePB> database,
+    required Option<FieldPB> dateField,
+    required Option<List<RowInfo>> unscheduledRows,
+    required Option<CalendarSettingsPB> settings,
+    required DatabaseLoadingState loadingState,
+    required Option<FlowyError> noneOrError,
+  }) = _CalendarState;
+
+  factory CalendarState.initial(String databaseId) => CalendarState(
+        database: none(),
+        databaseId: databaseId,
+        dateField: none(),
+        unscheduledRows: none(),
+        settings: none(),
+        noneOrError: none(),
+        loadingState: const _Loading(),
+      );
+}
+
+@freezed
+class DatabaseLoadingState with _$DatabaseLoadingState {
+  const factory DatabaseLoadingState.loading() = _Loading;
+  const factory DatabaseLoadingState.finish(
+      Either<Unit, FlowyError> successOrFail) = _Finish;
+}
+
+class CalendarEditingRow {
+  RowPB row;
+  int? index;
+
+  CalendarEditingRow({
+    required this.row,
+    required this.index,
+  });
+}
+
+class CalendarData {
+  final RowInfo rowInfo;
+  CalendarData(this.rowInfo);
+}

+ 115 - 0
frontend/app_flowy/lib/plugins/calendar/application/calendar_data_controller.dart

@@ -0,0 +1,115 @@
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:app_flowy/plugins/grid/application/view/grid_view_cache.dart';
+import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
+import 'package:app_flowy/plugins/grid/application/grid_service.dart';
+import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
+import 'package:dartz/dartz.dart';
+
+import 'calendar_listener.dart';
+
+typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldInfo>);
+typedef OnDatabaseChanged = void Function(DatabasePB);
+typedef OnSettingsChanged = void Function(CalendarSettingsPB);
+typedef OnArrangeWithNewField = void Function(FieldPB);
+
+typedef OnRowsChanged = void Function(List<RowInfo>, RowsChangedReason);
+typedef OnError = void Function(FlowyError);
+
+class CalendarDataController {
+  final String databaseId;
+  final DatabaseFFIService _databaseFFIService;
+  final GridFieldController fieldController;
+  final CalendarListener _listener;
+  late DatabaseViewCache _viewCache;
+
+  OnFieldsChanged? _onFieldsChanged;
+  OnDatabaseChanged? _onDatabaseChanged;
+  OnRowsChanged? _onRowsChanged;
+  OnSettingsChanged? _onSettingsChanged;
+  OnArrangeWithNewField? _onArrangeWithNewField;
+  OnError? _onError;
+
+  List<RowInfo> get rowInfos => _viewCache.rowInfos;
+  GridRowCache get rowCache => _viewCache.rowCache;
+
+  CalendarDataController({required ViewPB view})
+      : databaseId = view.id,
+        _listener = CalendarListener(view.id),
+        _databaseFFIService = DatabaseFFIService(viewId: view.id),
+        fieldController = GridFieldController(databaseId: view.id) {
+    _viewCache = DatabaseViewCache(
+      databaseId: view.id,
+      fieldController: fieldController,
+    );
+    _viewCache.addListener(onRowsChanged: (reason) {
+      _onRowsChanged?.call(rowInfos, reason);
+    });
+  }
+
+  void addListener({
+    required OnDatabaseChanged onDatabaseChanged,
+    OnFieldsChanged? onFieldsChanged,
+    OnRowsChanged? onRowsChanged,
+    required OnSettingsChanged? onSettingsChanged,
+    required OnArrangeWithNewField? onArrangeWithNewField,
+    required OnError? onError,
+  }) {
+    _onDatabaseChanged = onDatabaseChanged;
+    _onFieldsChanged = onFieldsChanged;
+    _onRowsChanged = onRowsChanged;
+    _onSettingsChanged = onSettingsChanged;
+    _onArrangeWithNewField = onArrangeWithNewField;
+    _onError = onError;
+
+    fieldController.addListener(onFields: (fields) {
+      _onFieldsChanged?.call(UnmodifiableListView(fields));
+    });
+
+    _listener.start(
+      onCalendarSettingsChanged: (result) {
+        result.fold(
+          (settings) => _onSettingsChanged?.call(settings),
+          (e) => _onError?.call(e),
+        );
+      },
+      onArrangeWithNewField: (result) {
+        result.fold(
+          (settings) => _onArrangeWithNewField?.call(settings),
+          (e) => _onError?.call(e),
+        );
+      },
+    );
+  }
+
+  Future<Either<Unit, FlowyError>> openDatabase() async {
+    final result = await _databaseFFIService.openGrid();
+    return result.fold(
+      (database) async {
+        _onDatabaseChanged?.call(database);
+        return fieldController
+            .loadFields(fieldIds: database.fields)
+            .then((result) {
+          return result.fold(
+            (l) => Future(() async {
+              _viewCache.rowCache.initializeRows(database.rows);
+              return left(l);
+            }),
+            (err) => right(err),
+          );
+        });
+      },
+      (err) => right(err),
+    );
+  }
+
+  Future<void> dispose() async {
+    await _viewCache.dispose();
+    await _databaseFFIService.closeGrid();
+    await fieldController.dispose();
+  }
+}

+ 65 - 0
frontend/app_flowy/lib/plugins/calendar/application/calendar_listener.dart

@@ -0,0 +1,65 @@
+import 'dart:typed_data';
+
+import 'package:app_flowy/core/grid_notification.dart';
+import 'package:flowy_infra/notifier.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
+import 'package:dartz/dartz.dart';
+
+typedef CalendarSettingsValue = Either<CalendarSettingsPB, FlowyError>;
+typedef ArrangeWithNewField = Either<FieldPB, FlowyError>;
+
+class CalendarListener {
+  final String viewId;
+  PublishNotifier<CalendarSettingsValue>? _calendarSettingsNotifier =
+      PublishNotifier();
+  PublishNotifier<ArrangeWithNewField>? _arrangeWithNewFieldNotifier =
+      PublishNotifier();
+  DatabaseNotificationListener? _listener;
+  CalendarListener(this.viewId);
+
+  void start({
+    required void Function(CalendarSettingsValue) onCalendarSettingsChanged,
+    required void Function(ArrangeWithNewField) onArrangeWithNewField,
+  }) {
+    _calendarSettingsNotifier?.addPublishListener(onCalendarSettingsChanged);
+    _arrangeWithNewFieldNotifier?.addPublishListener(onArrangeWithNewField);
+    _listener = DatabaseNotificationListener(
+      objectId: viewId,
+      handler: _handler,
+    );
+  }
+
+  void _handler(
+    DatabaseNotification ty,
+    Either<Uint8List, FlowyError> result,
+  ) {
+    switch (ty) {
+      case DatabaseNotification.DidUpdateCalendarSettings:
+        result.fold(
+          (payload) => _calendarSettingsNotifier?.value =
+              left(CalendarSettingsPB.fromBuffer(payload)),
+          (error) => _calendarSettingsNotifier?.value = right(error),
+        );
+        break;
+      case DatabaseNotification.DidArrangeCalendarWithNewField:
+        result.fold(
+          (payload) => _arrangeWithNewFieldNotifier?.value =
+              left(FieldPB.fromBuffer(payload)),
+          (error) => _arrangeWithNewFieldNotifier?.value = right(error),
+        );
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _listener?.stop();
+    _calendarSettingsNotifier?.dispose();
+    _calendarSettingsNotifier = null;
+
+    _arrangeWithNewFieldNotifier?.dispose();
+    _arrangeWithNewFieldNotifier = null;
+  }
+}

+ 27 - 0
frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs

@@ -294,6 +294,33 @@ impl DatabaseViewRevisionPad {
     })
   }
 
+  /// Returns the settings for the given layout. If it's not exists then will return the
+  /// default settings for the given layout.
+  /// Each [database view](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/database-view) has its own settings.
+  pub fn get_layout_setting<T>(&self, layout: &LayoutRevision) -> Option<T>
+  where
+    T: serde::de::DeserializeOwned,
+  {
+    let settings_str = self.view.layout_settings.get(layout)?;
+    serde_json::from_str::<T>(settings_str).ok()
+  }
+
+  /// updates the settings for the given layout type
+  pub fn update_layout_setting<T>(
+    &mut self,
+    layout: &LayoutRevision,
+    settings: &T,
+  ) -> SyncResult<Option<GridViewRevisionChangeset>>
+  where
+    T: serde::Serialize,
+  {
+    let settings_str = serde_json::to_string(settings).map_err(internal_sync_error)?;
+    self.modify(|view| {
+      view.layout_settings.insert(layout.clone(), settings_str);
+      Ok(Some(()))
+    })
+  }
+
   pub fn json_str(&self) -> SyncResult<String> {
     make_grid_view_rev_json_str(&self.view)
   }

+ 85 - 0
frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs

@@ -0,0 +1,85 @@
+use crate::entities::parser::NotEmptyStr;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use serde::{Deserialize, Serialize};
+use serde_repr::{Deserialize_repr, Serialize_repr};
+use std::convert::TryInto;
+
+#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf)]
+pub struct CalendarSettingsPB {
+  #[pb(index = 1)]
+  pub view_id: String,
+
+  #[pb(index = 2)]
+  pub layout_ty: CalendarLayout,
+
+  #[pb(index = 3)]
+  pub first_day_of_week: i32,
+
+  #[pb(index = 4)]
+  pub show_weekends: bool,
+
+  #[pb(index = 5)]
+  pub show_week_numbers: bool,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct CalendarSettingsParams {
+  pub(crate) view_id: String,
+  layout_ty: CalendarLayout,
+  first_day_of_week: i32,
+  show_weekends: bool,
+  show_week_numbers: bool,
+}
+
+const DEFAULT_FIRST_DAY_OF_WEEK: i32 = 0;
+const DEFAULT_SHOW_WEEKENDS: bool = true;
+const DEFAULT_SHOW_WEEK_NUMBERS: bool = true;
+
+impl CalendarSettingsParams {
+  pub fn default_with(view_id: String) -> Self {
+    CalendarSettingsParams {
+      view_id: view_id.to_string(),
+      layout_ty: CalendarLayout::default(),
+      first_day_of_week: DEFAULT_FIRST_DAY_OF_WEEK,
+      show_weekends: DEFAULT_SHOW_WEEKENDS,
+      show_week_numbers: DEFAULT_SHOW_WEEK_NUMBERS,
+    }
+  }
+}
+
+impl TryInto<CalendarSettingsParams> for CalendarSettingsPB {
+  type Error = ErrorCode;
+
+  fn try_into(self) -> Result<CalendarSettingsParams, Self::Error> {
+    let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdInvalid)?;
+    Ok(CalendarSettingsParams {
+      view_id: view_id.0,
+      layout_ty: self.layout_ty,
+      first_day_of_week: self.first_day_of_week,
+      show_weekends: self.show_weekends,
+      show_week_numbers: self.show_week_numbers,
+    })
+  }
+}
+
+impl std::convert::From<CalendarSettingsParams> for CalendarSettingsPB {
+  fn from(params: CalendarSettingsParams) -> Self {
+    CalendarSettingsPB {
+      view_id: params.view_id,
+      layout_ty: params.layout_ty,
+      first_day_of_week: params.first_day_of_week,
+      show_weekends: params.show_weekends,
+      show_week_numbers: params.show_week_numbers,
+    }
+  }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf_Enum, Serialize_repr, Deserialize_repr)]
+#[repr(u8)]
+pub enum CalendarLayout {
+  #[default]
+  MonthLayout = 0,
+  WeekLayout = 1,
+  DayLayout = 2,
+}

+ 2 - 0
frontend/rust-lib/flowy-database/src/entities/mod.rs

@@ -1,3 +1,4 @@
+mod calendar_entities;
 mod cell_entities;
 mod field_entities;
 pub mod filter_entities;
@@ -9,6 +10,7 @@ pub mod setting_entities;
 mod sort_entities;
 mod view_entities;
 
+pub use calendar_entities::*;
 pub use cell_entities::*;
 pub use field_entities::*;
 pub use filter_entities::*;

+ 17 - 23
frontend/rust-lib/flowy-database/src/entities/setting_entities.rs

@@ -1,25 +1,24 @@
 use crate::entities::parser::NotEmptyStr;
 use crate::entities::{
-  AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, DeleteFilterParams,
-  DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, DeleteSortParams,
-  DeleteSortPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB,
+  AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, CalendarSettingsPB,
+  DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB,
+  DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB,
   RepeatedGroupConfigurationPB, RepeatedSortPB,
 };
 use database_model::LayoutRevision;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use std::convert::TryInto;
-use strum::IntoEnumIterator;
 use strum_macros::EnumIter;
 
 /// [DatabaseViewSettingPB] defines the setting options for the grid. Such as the filter, group, and sort.
 #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct DatabaseViewSettingPB {
   #[pb(index = 1)]
-  pub support_layouts: Vec<ViewLayoutPB>,
+  pub current_layout: LayoutTypePB,
 
   #[pb(index = 2)]
-  pub current_layout: LayoutTypePB,
+  pub layout_setting: LayoutSettingPB,
 
   #[pb(index = 3)]
   pub filters: RepeatedFilterPB,
@@ -31,23 +30,6 @@ pub struct DatabaseViewSettingPB {
   pub sorts: RepeatedSortPB,
 }
 
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct ViewLayoutPB {
-  #[pb(index = 1)]
-  ty: LayoutTypePB,
-}
-
-impl ViewLayoutPB {
-  pub fn all() -> Vec<ViewLayoutPB> {
-    let mut layouts = vec![];
-    for layout_ty in LayoutTypePB::iter() {
-      layouts.push(ViewLayoutPB { ty: layout_ty })
-    }
-
-    layouts
-  }
-}
-
 #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumIter)]
 #[repr(u8)]
 pub enum LayoutTypePB {
@@ -176,3 +158,15 @@ impl DatabaseSettingChangesetParams {
     self.insert_filter.is_some() || self.delete_filter.is_some()
   }
 }
+
+#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
+pub struct LayoutSettingPB {
+  #[pb(index = 1, one_of)]
+  pub calendar: Option<CalendarSettingsPB>,
+}
+
+impl LayoutSettingPB {
+  pub fn new() -> Self {
+    Self::default()
+  }
+}

+ 20 - 0
frontend/rust-lib/flowy-database/src/event_handler.rs

@@ -560,3 +560,23 @@ pub(crate) async fn move_group_row_handler(
   editor.move_group_row(params).await?;
   Ok(())
 }
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn set_calendar_setting_handler(
+  data: AFPluginData<CalendarSettingsPB>,
+  manager: AFPluginState<Arc<DatabaseManager>>,
+) -> FlowyResult<()> {
+  let params: CalendarSettingsParams = data.into_inner().try_into()?;
+  let _ = manager.get_database_editor(params.view_id.as_ref()).await?;
+  todo!("nathan: depends on the main branch refactoring")
+}
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn get_calendar_setting_handler(
+  data: AFPluginData<DatabaseViewIdPB>,
+  manager: AFPluginState<Arc<DatabaseManager>>,
+) -> FlowyResult<()> {
+  let view_id = data.into_inner().value;
+  let _ = manager.get_database_editor(view_id.as_ref()).await?;
+  todo!("nathan: depends on the main branch refactoring")
+}

+ 10 - 1
frontend/rust-lib/flowy-database/src/event_map.rs

@@ -47,7 +47,10 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
         .event(DatabaseEvent::CreateBoardCard, create_board_card_handler)
         .event(DatabaseEvent::MoveGroup, move_group_handler)
         .event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
-        .event(DatabaseEvent::GetGroup, get_groups_handler);
+        .event(DatabaseEvent::GetGroup, get_groups_handler)
+        // Calendar
+      .event(DatabaseEvent::SetCalenderSetting, set_calendar_setting_handler)
+        .event(DatabaseEvent::GetCalendarSetting, get_calendar_setting_handler);
 
   plugin
 }
@@ -229,4 +232,10 @@ pub enum DatabaseEvent {
 
   #[event(input = "MoveGroupRowPayloadPB")]
   GroupByField = 113,
+
+  #[event(input = "CalendarSettingsPB")]
+  SetCalenderSetting = 114,
+
+  #[event()]
+  GetCalendarSetting = 115,
 }

+ 2 - 0
frontend/rust-lib/flowy-database/src/notification.rs

@@ -31,6 +31,8 @@ pub enum DatabaseNotification {
   DidReorderSingleRow = 66,
   /// Trigger when the settings of the database are changed
   DidUpdateSettings = 70,
+  DidUpdateCalendarSettings = 80,
+  DidArrangeCalendarWithNewField = 81,
 }
 
 impl std::default::Default for DatabaseNotification {

+ 21 - 1
frontend/rust-lib/flowy-database/src/services/database_view/editor.rs

@@ -635,6 +635,26 @@ impl DatabaseViewRevisionEditor {
     Ok(())
   }
 
+  /// Returns the current calendar settings
+  pub async fn get_calendar_settings(&self) -> FlowyResult<CalendarSettingsParams> {
+    let settings = self
+      .pad
+      .read()
+      .await
+      .get_layout_setting(&LayoutRevision::Calendar)
+      .unwrap_or_else(|| CalendarSettingsParams::default_with(self.view_id.to_string()));
+    Ok(settings)
+  }
+
+  /// Update the calendar settings and send the notification to refresh the UI
+  pub async fn update_calendar_settings(&self, params: CalendarSettingsParams) -> FlowyResult<()> {
+    // Maybe it needs no send notification to refresh the UI
+    self
+      .modify(|pad| Ok(pad.update_layout_setting(&LayoutRevision::Calendar, &params)?))
+      .await?;
+    Ok(())
+  }
+
   #[tracing::instrument(level = "trace", skip_all, err)]
   pub async fn did_update_view_field_type_option(
     &self,
@@ -878,7 +898,7 @@ async fn new_group_controller(
   .await
 }
 
-/// Returns a [GroupController]  
+/// Returns a [GroupController]
 ///
 async fn new_group_controller_with_field_rev(
   user_id: String,

+ 17 - 6
frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs

@@ -1,4 +1,4 @@
-use crate::entities::{DatabaseViewSettingPB, LayoutTypePB, ViewLayoutPB};
+use crate::entities::{CalendarSettingsParams, DatabaseViewSettingPB, LayoutSettingPB};
 use crate::services::database_view::{get_cells_for_field, DatabaseViewEditorDelegate};
 use crate::services::field::RowSingleCellData;
 use crate::services::filter::{FilterController, FilterDelegate, FilterType};
@@ -7,8 +7,8 @@ use crate::services::row::DatabaseBlockRowRevision;
 use crate::services::sort::{SortDelegate, SortType};
 use bytes::Bytes;
 use database_model::{
-  FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, RowRevision,
-  SortRevision,
+  FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, LayoutRevision,
+  RowRevision, SortRevision,
 };
 use flowy_client_sync::client_database::{DatabaseViewRevisionPad, GridViewRevisionChangeset};
 use flowy_client_sync::make_operations_from_revisions;
@@ -146,13 +146,24 @@ pub fn make_grid_setting(
   view_pad: &DatabaseViewRevisionPad,
   field_revs: &[Arc<FieldRevision>],
 ) -> DatabaseViewSettingPB {
-  let layout_type: LayoutTypePB = view_pad.layout.clone().into();
+  let layout_type: LayoutRevision = view_pad.layout.clone();
+  let mut layout_settings = LayoutSettingPB::new();
+  match layout_type {
+    LayoutRevision::Grid => {},
+    LayoutRevision::Board => {},
+    LayoutRevision::Calendar => {
+      layout_settings.calendar = view_pad
+        .get_layout_setting::<CalendarSettingsParams>(&layout_type)
+        .map(|params| params.into());
+    },
+  }
+
   let filters = view_pad.get_all_filters(field_revs);
   let group_configurations = view_pad.get_groups_by_field_revs(field_revs);
   let sorts = view_pad.get_all_sorts(field_revs);
   DatabaseViewSettingPB {
-    support_layouts: ViewLayoutPB::all(),
-    current_layout: layout_type,
+    current_layout: layout_type.into(),
+    layout_setting: layout_settings,
     filters: filters.into(),
     sorts: sorts.into(),
     group_configurations: group_configurations.into(),

+ 28 - 1
shared-lib/database-model/src/view_rev.rs

@@ -1,4 +1,5 @@
 use crate::{FilterConfiguration, GroupConfiguration, SortConfiguration};
+use indexmap::IndexMap;
 use nanoid::nanoid;
 use serde::{Deserialize, Serialize};
 use serde_repr::*;
@@ -38,6 +39,9 @@ pub struct DatabaseViewRevision {
 
   pub layout: LayoutRevision,
 
+  #[serde(default)]
+  pub layout_settings: LayoutSettings,
+
   #[serde(default)]
   pub filters: FilterConfiguration,
 
@@ -54,6 +58,7 @@ impl DatabaseViewRevision {
       view_id,
       database_id,
       layout,
+      layout_settings: Default::default(),
       filters: Default::default(),
       groups: Default::default(),
       sorts: Default::default(),
@@ -65,6 +70,27 @@ impl DatabaseViewRevision {
   }
 }
 
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct LayoutSettings {
+  #[serde(with = "indexmap::serde_seq")]
+  inner: IndexMap<LayoutRevision, String>,
+}
+
+impl std::ops::Deref for LayoutSettings {
+  type Target = IndexMap<LayoutRevision, String>;
+
+  fn deref(&self) -> &Self::Target {
+    &self.inner
+  }
+}
+
+impl std::ops::DerefMut for LayoutSettings {
+  fn deref_mut(&mut self) -> &mut Self::Target {
+    &mut self.inner
+  }
+}
+
 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 pub struct RowOrderRevision {
   pub row_id: String,
@@ -80,6 +106,7 @@ mod tests {
       view_id: "1".to_string(),
       database_id: "1".to_string(),
       layout: Default::default(),
+      layout_settings: Default::default(),
       filters: Default::default(),
       groups: Default::default(),
       sorts: Default::default(),
@@ -87,7 +114,7 @@ mod tests {
     let s = serde_json::to_string(&grid_view_revision).unwrap();
     assert_eq!(
       s,
-      r#"{"view_id":"1","grid_id":"1","layout":0,"filters":[],"groups":[],"sorts":[]}"#
+      r#"{"view_id":"1","grid_id":"1","layout":0,"layout_settings":[],"filters":[],"groups":[],"sorts":[]}"#
     );
   }
 }