Feat/calendar with backend data (#1930)

* chore: enable calendar view

* chore: update database group listener

* refactor: remove board data controller

* refactor: remove group backend service

* refactor: remove calendar controller

* chore: create default calendar setting

* chore: send calednar setting notifications

* refactor: rename the card in kanban board, prepare to reuse in calendar

* refactor: support custom card cell

* chore: return calendar events

* refactor: remove groupId in card, make card more generic

* chore: display cell

* chore: display three cards in calendar

* chore: create calendar card

* refactor: create row with data

* chore: support create event

* ci: fix tauri build

* chore: disable create calendar
This commit is contained in:
Nathan.fooo
2023-03-08 21:19:44 +08:00
committed by GitHub
parent 90da54d12f
commit 7106195d8a
92 changed files with 2407 additions and 1305 deletions

View File

@ -372,6 +372,7 @@
}, },
"calendar": { "calendar": {
"menuName": "Calendar", "menuName": "Calendar",
"defaultNewCalendarTitle": "Untitled",
"navigation": { "navigation": {
"today": "Today", "today": "Today",
"jumpToday": "Jump to Today", "jumpToday": "Jump to Today",

View File

@ -2,9 +2,9 @@ part of 'cell_service.dart';
typedef CellByFieldId = LinkedHashMap<String, CellIdentifier>; typedef CellByFieldId = LinkedHashMap<String, CellIdentifier>;
class GridBaseCell { class DatabaseCell {
dynamic object; dynamic object;
GridBaseCell({ DatabaseCell({
required this.object, required this.object,
}); });
} }
@ -44,7 +44,7 @@ class CellCache {
} }
} }
void insert<T extends GridBaseCell>(CellCacheKey key, T value) { void insert<T extends DatabaseCell>(CellCacheKey key, T value) {
var map = _cellDataByFieldId[key.fieldId]; var map = _cellDataByFieldId[key.fieldId];
if (map == null) { if (map == null) {
_cellDataByFieldId[key.fieldId] = {}; _cellDataByFieldId[key.fieldId] = {};

View File

@ -170,7 +170,7 @@ class CellController<T, D> extends Equatable {
_loadDataOperation = Timer(const Duration(milliseconds: 10), () { _loadDataOperation = Timer(const Duration(milliseconds: 10), () {
_cellDataLoader.loadData().then((data) { _cellDataLoader.loadData().then((data) {
if (data != null) { if (data != null) {
_cellCache.insert(_cacheKey, GridBaseCell(object: data)); _cellCache.insert(_cacheKey, DatabaseCell(object: data));
} else { } else {
_cellCache.remove(_cacheKey); _cellCache.remove(_cacheKey);
} }

View File

@ -0,0 +1,290 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database/calendar_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:collection/collection.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'database_service.dart';
import 'defines.dart';
import 'layout/layout_setting_listener.dart';
import 'row/row_cache.dart';
import 'group/group_listener.dart';
typedef OnRowsChanged = void Function(
List<RowInfo> rowInfos,
RowsChangedReason,
);
typedef OnGroupByField = void Function(List<GroupPB>);
typedef OnUpdateGroup = void Function(List<GroupPB>);
typedef OnDeleteGroup = void Function(List<String>);
typedef OnInsertGroup = void Function(InsertedGroupPB);
class GroupCallbacks {
final OnGroupByField? onGroupByField;
final OnUpdateGroup? onUpdateGroup;
final OnDeleteGroup? onDeleteGroup;
final OnInsertGroup? onInsertGroup;
GroupCallbacks({
this.onGroupByField,
this.onUpdateGroup,
this.onDeleteGroup,
this.onInsertGroup,
});
}
class LayoutCallbacks {
final void Function(LayoutSettingPB) onLayoutChanged;
final void Function(LayoutSettingPB) onLoadLayout;
LayoutCallbacks({
required this.onLayoutChanged,
required this.onLoadLayout,
});
}
class DatabaseCallbacks {
OnDatabaseChanged? onDatabaseChanged;
OnRowsChanged? onRowsChanged;
OnFieldsChanged? onFieldsChanged;
OnFiltersChanged? onFiltersChanged;
DatabaseCallbacks({
this.onDatabaseChanged,
this.onRowsChanged,
this.onFieldsChanged,
this.onFiltersChanged,
});
}
class DatabaseController {
final String viewId;
final DatabaseBackendService _databaseBackendSvc;
final FieldController fieldController;
late DatabaseViewCache _viewCache;
final LayoutTypePB layoutType;
// Callbacks
DatabaseCallbacks? _databaseCallbacks;
GroupCallbacks? _groupCallbacks;
LayoutCallbacks? _layoutCallbacks;
// Getters
List<RowInfo> get rowInfos => _viewCache.rowInfos;
RowCache get rowCache => _viewCache.rowCache;
// Listener
final DatabaseGroupListener groupListener;
final DatabaseLayoutListener layoutListener;
DatabaseController({required ViewPB view, required this.layoutType})
: viewId = view.id,
_databaseBackendSvc = DatabaseBackendService(viewId: view.id),
fieldController = FieldController(viewId: view.id),
groupListener = DatabaseGroupListener(view.id),
layoutListener = DatabaseLayoutListener(view.id) {
_viewCache = DatabaseViewCache(
viewId: viewId,
fieldController: fieldController,
);
_listenOnRowsChanged();
_listenOnFieldsChanged();
_listenOnGroupChanged();
_listenOnLayoutChanged();
}
void addListener({
DatabaseCallbacks? onDatabaseChanged,
LayoutCallbacks? onLayoutChanged,
GroupCallbacks? onGroupChanged,
}) {
_layoutCallbacks = onLayoutChanged;
_databaseCallbacks = onDatabaseChanged;
_groupCallbacks = onGroupChanged;
}
Future<Either<Unit, FlowyError>> open() async {
return _databaseBackendSvc.openGrid().then((result) {
return result.fold(
(database) async {
_databaseCallbacks?.onDatabaseChanged?.call(database);
_viewCache.rowCache.setInitialRows(database.rows);
return await fieldController
.loadFields(
fieldIds: database.fields,
)
.then(
(result) {
return result.fold(
(l) => Future(() async {
await _loadGroups();
await _loadLayoutSetting();
return left(l);
}),
(err) => right(err),
);
},
);
},
(err) => right(err),
);
});
}
Future<Either<RowPB, FlowyError>> createRow({
String? startRowId,
String? groupId,
void Function(RowDataBuilder builder)? withCells,
}) {
Map<String, String>? cellDataByFieldId;
if (withCells != null) {
final rowBuilder = RowDataBuilder();
withCells(rowBuilder);
cellDataByFieldId = rowBuilder.build();
}
return _databaseBackendSvc.createRow(
startRowId: startRowId,
groupId: groupId,
cellDataByFieldId: cellDataByFieldId,
);
}
Future<Either<Unit, FlowyError>> moveRow(RowPB fromRow,
{RowPB? toRow, String? groupId}) {
return _databaseBackendSvc.moveRow(
fromRowId: fromRow.id,
toGroupId: groupId,
toRowId: toRow?.id,
);
}
Future<Either<Unit, FlowyError>> moveGroup(
{required String fromGroupId, required String toGroupId}) {
return _databaseBackendSvc.moveGroup(
fromGroupId: fromGroupId,
toGroupId: toGroupId,
);
}
Future<void> updateCalenderLayoutSetting(
CalendarLayoutSettingsPB layoutSetting) async {
await _databaseBackendSvc
.updateLayoutSetting(calendarLayoutSetting: layoutSetting)
.then((result) {
result.fold((l) => null, (r) => Log.error(r));
});
}
Future<void> dispose() async {
await _databaseBackendSvc.closeView();
await fieldController.dispose();
await groupListener.stop();
}
Future<void> _loadGroups() async {
final result = await _databaseBackendSvc.loadGroups();
return Future(
() => result.fold(
(groups) {
_groupCallbacks?.onGroupByField?.call(groups.items);
},
(err) => Log.error(err),
),
);
}
Future<void> _loadLayoutSetting() async {
_databaseBackendSvc.getLayoutSetting(layoutType).then((result) {
result.fold(
(l) {
_layoutCallbacks?.onLoadLayout(l);
},
(r) => Log.error(r),
);
});
}
void _listenOnRowsChanged() {
_viewCache.addListener(onRowsChanged: (reason) {
_databaseCallbacks?.onRowsChanged?.call(rowInfos, reason);
});
}
void _listenOnFieldsChanged() {
fieldController.addListener(
onReceiveFields: (fields) {
_databaseCallbacks?.onFieldsChanged?.call(UnmodifiableListView(fields));
},
onFilters: (filters) {
_databaseCallbacks?.onFiltersChanged?.call(filters);
},
);
}
void _listenOnGroupChanged() {
groupListener.start(
onNumOfGroupsChanged: (result) {
result.fold((changeset) {
if (changeset.updateGroups.isNotEmpty) {
_groupCallbacks?.onUpdateGroup?.call(changeset.updateGroups);
}
if (changeset.deletedGroups.isNotEmpty) {
_groupCallbacks?.onDeleteGroup?.call(changeset.deletedGroups);
}
for (final insertedGroup in changeset.insertedGroups) {
_groupCallbacks?.onInsertGroup?.call(insertedGroup);
}
}, (r) => Log.error(r));
},
onGroupByNewField: (result) {
result.fold((groups) {
_groupCallbacks?.onGroupByField?.call(groups);
}, (r) => Log.error(r));
},
);
}
void _listenOnLayoutChanged() {
layoutListener.start(onLayoutChanged: (result) {
result.fold((l) {
_layoutCallbacks?.onLayoutChanged(l);
}, (r) => Log.error(r));
});
}
}
class RowDataBuilder {
final _cellDataByFieldId = <String, String>{};
void insertText(FieldInfo fieldInfo, String text) {
assert(fieldInfo.fieldType == FieldType.RichText);
_cellDataByFieldId[fieldInfo.field.id] = text;
}
void insertNumber(FieldInfo fieldInfo, int num) {
assert(fieldInfo.fieldType == FieldType.Number);
_cellDataByFieldId[fieldInfo.field.id] = num.toString();
}
void insertDate(FieldInfo fieldInfo, DateTime date) {
assert(fieldInfo.fieldType == FieldType.DateTime);
final timestamp = (date.millisecondsSinceEpoch ~/ 1000);
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
}
Map<String, String> build() {
return _cellDataByFieldId;
}
}

View File

@ -1,4 +1,7 @@
import 'package:appflowy_backend/protobuf/flowy-database/calendar_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -20,25 +23,56 @@ class DatabaseBackendService {
return DatabaseEventGetDatabase(payload).send(); return DatabaseEventGetDatabase(payload).send();
} }
Future<Either<RowPB, FlowyError>> createRow({Option<String>? startRowId}) { Future<Either<RowPB, FlowyError>> createRow({
var payload = CreateRowPayloadPB.create()..viewId = viewId;
startRowId?.fold(() => null, (id) => payload.startRowId = id);
return DatabaseEventCreateRow(payload).send();
}
Future<Either<RowPB, FlowyError>> createBoardCard(
String groupId,
String? startRowId, String? startRowId,
) { String? groupId,
CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create() Map<String, String>? cellDataByFieldId,
..viewId = viewId }) {
..groupId = groupId; var payload = CreateRowPayloadPB.create()..viewId = viewId;
if (startRowId != null) { if (startRowId != null) {
payload.startRowId = startRowId; payload.startRowId = startRowId;
} }
return DatabaseEventCreateBoardCard(payload).send(); if (groupId != null) {
payload.groupId = groupId;
}
if (cellDataByFieldId != null && cellDataByFieldId.isNotEmpty) {
payload.data = RowDataPB(cellDataByFieldId: cellDataByFieldId);
}
return DatabaseEventCreateRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveRow({
required String fromRowId,
required String? toGroupId,
required String? toRowId,
}) {
var payload = MoveGroupRowPayloadPB.create()
..viewId = viewId
..fromRowId = fromRowId;
if (toGroupId != null) {
payload.toGroupId = toGroupId;
}
if (toRowId != null) {
payload.toRowId = toRowId;
}
return DatabaseEventMoveGroupRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveGroup({
required String fromGroupId,
required String toGroupId,
}) {
final payload = MoveGroupPayloadPB.create()
..viewId = viewId
..fromGroupId = fromGroupId
..toGroupId = toGroupId;
return DatabaseEventMoveGroup(payload).send();
} }
Future<Either<List<FieldPB>, FlowyError>> getFields( Future<Either<List<FieldPB>, FlowyError>> getFields(
@ -53,6 +87,28 @@ class DatabaseBackendService {
}); });
} }
Future<Either<LayoutSettingPB, FlowyError>> getLayoutSetting(
LayoutTypePB layoutType) {
final payload = DatabaseLayoutIdPB.create()
..viewId = viewId
..layout = layoutType;
return DatabaseEventGetLayoutSetting(payload).send();
}
Future<Either<Unit, FlowyError>> updateLayoutSetting(
{CalendarLayoutSettingsPB? calendarLayoutSetting}) {
final layoutSetting = LayoutSettingPB.create();
if (calendarLayoutSetting != null) {
layoutSetting.calendar = calendarLayoutSetting;
}
final payload = UpdateLayoutSettingPB.create()
..viewId = viewId
..layoutSetting = layoutSetting;
return DatabaseEventSetLayoutSetting(payload).send();
}
Future<Either<Unit, FlowyError>> closeView() { Future<Either<Unit, FlowyError>> closeView() {
final request = ViewIdPB(value: viewId); final request = ViewIdPB(value: viewId);
return FolderEventCloseView(request).send(); return FolderEventCloseView(request).send();

View File

@ -11,20 +11,20 @@ import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart
typedef GroupUpdateValue = Either<GroupChangesetPB, FlowyError>; typedef GroupUpdateValue = Either<GroupChangesetPB, FlowyError>;
typedef GroupByNewFieldValue = Either<List<GroupPB>, FlowyError>; typedef GroupByNewFieldValue = Either<List<GroupPB>, FlowyError>;
class BoardListener { class DatabaseGroupListener {
final String viewId; final String viewId;
PublishNotifier<GroupUpdateValue>? _groupUpdateNotifier = PublishNotifier(); PublishNotifier<GroupUpdateValue>? _numOfGroupsNotifier = PublishNotifier();
PublishNotifier<GroupByNewFieldValue>? _groupByNewFieldNotifier = PublishNotifier<GroupByNewFieldValue>? _groupByFieldNotifier =
PublishNotifier(); PublishNotifier();
DatabaseNotificationListener? _listener; DatabaseNotificationListener? _listener;
BoardListener(this.viewId); DatabaseGroupListener(this.viewId);
void start({ void start({
required void Function(GroupUpdateValue) onBoardChanged, required void Function(GroupUpdateValue) onNumOfGroupsChanged,
required void Function(GroupByNewFieldValue) onGroupByNewField, required void Function(GroupByNewFieldValue) onGroupByNewField,
}) { }) {
_groupUpdateNotifier?.addPublishListener(onBoardChanged); _numOfGroupsNotifier?.addPublishListener(onNumOfGroupsChanged);
_groupByNewFieldNotifier?.addPublishListener(onGroupByNewField); _groupByFieldNotifier?.addPublishListener(onGroupByNewField);
_listener = DatabaseNotificationListener( _listener = DatabaseNotificationListener(
objectId: viewId, objectId: viewId,
handler: _handler, handler: _handler,
@ -38,16 +38,16 @@ class BoardListener {
switch (ty) { switch (ty) {
case DatabaseNotification.DidUpdateGroups: case DatabaseNotification.DidUpdateGroups:
result.fold( result.fold(
(payload) => _groupUpdateNotifier?.value = (payload) => _numOfGroupsNotifier?.value =
left(GroupChangesetPB.fromBuffer(payload)), left(GroupChangesetPB.fromBuffer(payload)),
(error) => _groupUpdateNotifier?.value = right(error), (error) => _numOfGroupsNotifier?.value = right(error),
); );
break; break;
case DatabaseNotification.DidGroupByField: case DatabaseNotification.DidGroupByField:
result.fold( result.fold(
(payload) => _groupByNewFieldNotifier?.value = (payload) => _groupByFieldNotifier?.value =
left(GroupChangesetPB.fromBuffer(payload).initialGroups), left(GroupChangesetPB.fromBuffer(payload).initialGroups),
(error) => _groupByNewFieldNotifier?.value = right(error), (error) => _groupByFieldNotifier?.value = right(error),
); );
break; break;
default: default:
@ -57,10 +57,10 @@ class BoardListener {
Future<void> stop() async { Future<void> stop() async {
await _listener?.stop(); await _listener?.stop();
_groupUpdateNotifier?.dispose(); _numOfGroupsNotifier?.dispose();
_groupUpdateNotifier = null; _numOfGroupsNotifier = null;
_groupByNewFieldNotifier?.dispose(); _groupByFieldNotifier?.dispose();
_groupByNewFieldNotifier = null; _groupByFieldNotifier = null;
} }
} }

View File

@ -0,0 +1,51 @@
import 'dart:typed_data';
import 'package:appflowy/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 LayoutSettingsValue<T> = Either<T, FlowyError>;
class DatabaseLayoutListener {
final String viewId;
PublishNotifier<LayoutSettingsValue<LayoutSettingPB>>? _settingNotifier =
PublishNotifier();
DatabaseNotificationListener? _listener;
DatabaseLayoutListener(this.viewId);
void start({
required void Function(LayoutSettingsValue<LayoutSettingPB>)
onLayoutChanged,
}) {
_settingNotifier?.addPublishListener(onLayoutChanged);
_listener = DatabaseNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void _handler(
DatabaseNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case DatabaseNotification.DidUpdateLayoutSettings:
result.fold(
(payload) => _settingNotifier?.value =
left(LayoutSettingPB.fromBuffer(payload)),
(error) => _settingNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_settingNotifier?.dispose();
_settingNotifier = null;
}
}

View File

@ -61,7 +61,7 @@ class RowCache {
}); });
} }
void initializeRows(List<RowPB> rows) { void setInitialRows(List<RowPB> rows) {
for (final row in rows) { for (final row in rows) {
final rowInfo = buildGridRow(row); final rowInfo = buildGridRow(row);
_rowList.add(rowInfo); _rowList.add(rowInfo);

View File

@ -5,24 +5,26 @@ import 'row_cache.dart';
typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason); typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason);
class RowDataController { class RowDataController {
final RowInfo rowInfo; final String rowId;
final String viewId;
final List<VoidCallback> _onRowChangedListeners = []; final List<VoidCallback> _onRowChangedListeners = [];
final RowCache _rowCache; final RowCache _rowCache;
get cellCache => _rowCache.cellCache; get cellCache => _rowCache.cellCache;
RowDataController({ RowDataController({
required this.rowInfo, required this.rowId,
required this.viewId,
required RowCache rowCache, required RowCache rowCache,
}) : _rowCache = rowCache; }) : _rowCache = rowCache;
CellByFieldId loadData() { CellByFieldId loadData() {
return _rowCache.loadGridCells(rowInfo.rowPB.id); return _rowCache.loadGridCells(rowId);
} }
void addListener({OnRowChanged? onRowChanged}) { void addListener({OnRowChanged? onRowChanged}) {
_onRowChangedListeners.add(_rowCache.addListener( _onRowChangedListeners.add(_rowCache.addListener(
rowId: rowInfo.rowPB.id, rowId: rowId,
onCellUpdated: onRowChanged, onCellUpdated: onRowChanged,
)); ));
} }

View File

@ -1,8 +1,6 @@
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
class RowBackendService { class RowBackendService {
@ -44,52 +42,3 @@ class RowBackendService {
return DatabaseEventDuplicateRow(payload).send(); return DatabaseEventDuplicateRow(payload).send();
} }
} }
class GroupBackendService {
final String viewId;
GroupBackendService({
required this.viewId,
});
Future<Either<Unit, FlowyError>> moveRow({
required String fromRowId,
required String toRowId,
}) {
var payload = MoveRowPayloadPB.create()
..viewId = viewId
..fromRowId = fromRowId
..toRowId = toRowId;
return DatabaseEventMoveRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveGroupRow({
required String fromRowId,
required String toGroupId,
required String? toRowId,
}) {
var payload = MoveGroupRowPayloadPB.create()
..viewId = viewId
..fromRowId = fromRowId
..toGroupId = toGroupId;
if (toRowId != null) {
payload.toRowId = toRowId;
}
return DatabaseEventMoveGroupRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveGroup({
required String fromGroupId,
required String toGroupId,
}) {
final payload = MoveGroupPayloadPB.create()
..viewId = viewId
..fromGroupId = fromGroupId
..toGroupId = toGroupId;
return DatabaseEventMoveGroup(payload).send();
}
}

View File

@ -13,25 +13,25 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import '../../application/field/field_controller.dart'; import '../../application/field/field_controller.dart';
import '../../application/row/row_cache.dart'; import '../../application/row/row_cache.dart';
import '../../application/row/row_service.dart'; import '../../application/database_controller.dart';
import 'board_data_controller.dart';
import 'group_controller.dart'; import 'group_controller.dart';
part 'board_bloc.freezed.dart'; part 'board_bloc.freezed.dart';
class BoardBloc extends Bloc<BoardEvent, BoardState> { class BoardBloc extends Bloc<BoardEvent, BoardState> {
final BoardDataController _boardDataController; final DatabaseController _databaseController;
late final AppFlowyBoardController boardController; late final AppFlowyBoardController boardController;
final GroupBackendService _groupBackendSvc;
final LinkedHashMap<String, GroupController> groupControllers = final LinkedHashMap<String, GroupController> groupControllers =
LinkedHashMap(); LinkedHashMap();
FieldController get fieldController => _boardDataController.fieldController; FieldController get fieldController => _databaseController.fieldController;
String get viewId => _boardDataController.viewId; String get viewId => _databaseController.viewId;
BoardBloc({required ViewPB view}) BoardBloc({required ViewPB view})
: _groupBackendSvc = GroupBackendService(viewId: view.id), : _databaseController = DatabaseController(
_boardDataController = BoardDataController(view: view), view: view,
layoutType: LayoutTypePB.Board,
),
super(BoardState.initial(view.id)) { super(BoardState.initial(view.id)) {
boardController = AppFlowyBoardController( boardController = AppFlowyBoardController(
onMoveGroup: ( onMoveGroup: (
@ -40,7 +40,10 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
toGroupId, toGroupId,
toIndex, toIndex,
) { ) {
_moveGroup(fromGroupId, toGroupId); _databaseController.moveGroup(
fromGroupId: fromGroupId,
toGroupId: toGroupId,
);
}, },
onMoveGroupItem: ( onMoveGroupItem: (
groupId, groupId,
@ -49,7 +52,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
) { ) {
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex); final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex); final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
_moveRow(fromRow, groupId, toRow); if (fromRow != null) {
_databaseController.moveRow(
fromRow,
toRow: toRow,
groupId: groupId,
);
}
}, },
onMoveGroupItemToGroup: ( onMoveGroupItemToGroup: (
fromGroupId, fromGroupId,
@ -59,7 +68,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
) { ) {
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex); final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex); final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
_moveRow(fromRow, toGroupId, toRow); if (fromRow != null) {
_databaseController.moveRow(
fromRow,
toRow: toRow,
groupId: toGroupId,
);
}
}, },
); );
@ -72,8 +87,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
}, },
createBottomRow: (groupId) async { createBottomRow: (groupId) async {
final startRowId = groupControllers[groupId]?.lastRow()?.id; final startRowId = groupControllers[groupId]?.lastRow()?.id;
final result = await _boardDataController.createBoardCard( final result = await _databaseController.createRow(
groupId, groupId: groupId,
startRowId: startRowId, startRowId: startRowId,
); );
result.fold( result.fold(
@ -82,7 +97,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
); );
}, },
createHeaderRow: (String groupId) async { createHeaderRow: (String groupId) async {
final result = await _boardDataController.createBoardCard(groupId); final result =
await _databaseController.createRow(groupId: groupId);
result.fold( result.fold(
(_) {}, (_) {},
(err) => Log.error(err), (err) => Log.error(err),
@ -141,44 +157,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
} }
boardController.enableGroupDragging(!isEdit); boardController.enableGroupDragging(!isEdit);
// boardController.updateGroupItem(
// group.groupId,
// GroupItem(
// row: row,
// fieldInfo: fieldInfo,
// isDraggable: !isEdit,
// ),
// );
}
void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
if (fromRow != null) {
_groupBackendSvc
.moveGroupRow(
fromRowId: fromRow.id,
toGroupId: columnId,
toRowId: toRow?.id,
)
.then((result) {
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
});
}
}
void _moveGroup(String fromGroupId, String toGroupId) {
_groupBackendSvc
.moveGroup(
fromGroupId: fromGroupId,
toGroupId: toGroupId,
)
.then((result) {
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
});
} }
@override @override
Future<void> close() async { Future<void> close() async {
await _boardDataController.dispose(); await _databaseController.dispose();
for (final controller in groupControllers.values) { for (final controller in groupControllers.values) {
controller.dispose(); controller.dispose();
} }
@ -204,34 +187,36 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
} }
RowCache? getRowCache(String blockId) { RowCache? getRowCache(String blockId) {
return _boardDataController.rowCache; return _databaseController.rowCache;
} }
void _startListening() { void _startListening() {
_boardDataController.addListener( final onDatabaseChanged = DatabaseCallbacks(
onDatabaseChanged: (grid) { onDatabaseChanged: (database) {
if (!isClosed) { if (!isClosed) {
add(BoardEvent.didReceiveGridUpdate(grid)); add(BoardEvent.didReceiveGridUpdate(database));
} }
}, },
didLoadGroups: (groups) { );
final onGroupChanged = GroupCallbacks(
onGroupByField: (groups) {
if (isClosed) return; if (isClosed) return;
initializeGroups(groups); initializeGroups(groups);
add(BoardEvent.didReceiveGroups(groups)); add(BoardEvent.didReceiveGroups(groups));
}, },
onDeletedGroup: (groupIds) { onDeleteGroup: (groupIds) {
if (isClosed) return; if (isClosed) return;
boardController.removeGroups(groupIds); boardController.removeGroups(groupIds);
}, },
onInsertedGroup: (insertedGroup) { onInsertGroup: (insertGroups) {
if (isClosed) return; if (isClosed) return;
final group = insertedGroup.group; final group = insertGroups.group;
final newGroup = initializeGroupData(group); final newGroup = initializeGroupData(group);
final controller = initializeGroupController(group); final controller = initializeGroupController(group);
groupControllers[controller.group.groupId] = (controller); groupControllers[controller.group.groupId] = (controller);
boardController.addGroup(newGroup); boardController.addGroup(newGroup);
}, },
onUpdatedGroup: (updatedGroups) { onUpdateGroup: (updatedGroups) {
if (isClosed) return; if (isClosed) return;
for (final group in updatedGroups) { for (final group in updatedGroups) {
final columnController = final columnController =
@ -239,15 +224,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
columnController?.updateGroupName(group.desc); columnController?.updateGroupName(group.desc);
} }
}, },
onError: (err) { );
Log.error(err);
},
onResetGroups: (groups) {
if (isClosed) return;
initializeGroups(groups); _databaseController.addListener(
add(BoardEvent.didReceiveGroups(groups)); onDatabaseChanged: onDatabaseChanged,
}, onGroupChanged: onGroupChanged,
); );
} }
@ -264,7 +245,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
} }
Future<void> _openGrid(Emitter<BoardState> emit) async { Future<void> _openGrid(Emitter<BoardState> emit) async {
final result = await _boardDataController.openGrid(); final result = await _databaseController.open();
result.fold( result.fold(
(grid) => emit( (grid) => emit(
state.copyWith(loadingState: GridLoadingState.finish(left(unit))), state.copyWith(loadingState: GridLoadingState.finish(left(unit))),

View File

@ -1,144 +0,0 @@
import 'dart:collection';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
import '../../application/database_service.dart';
import '../../application/defines.dart';
import '../../application/field/field_controller.dart';
import '../../application/row/row_cache.dart';
import '../../application/view/view_cache.dart';
import 'board_listener.dart';
typedef DidLoadGroups = void Function(List<GroupPB>);
typedef OnUpdatedGroup = void Function(List<GroupPB>);
typedef OnDeletedGroup = void Function(List<String>);
typedef OnInsertedGroup = void Function(InsertedGroupPB);
typedef OnResetGroups = void Function(List<GroupPB>);
class BoardDataController {
final String viewId;
final DatabaseBackendService _databaseSvc;
final FieldController fieldController;
final BoardListener _listener;
late DatabaseViewCache _viewCache;
OnFieldsChanged? _onFieldsChanged;
OnDatabaseChanged? _onDatabaseChanged;
DidLoadGroups? _didLoadGroup;
OnRowsChanged? _onRowsChanged;
OnError? _onError;
List<RowInfo> get rowInfos => _viewCache.rowInfos;
RowCache get rowCache => _viewCache.rowCache;
BoardDataController({required ViewPB view})
: viewId = view.id,
_listener = BoardListener(view.id),
_databaseSvc = DatabaseBackendService(viewId: view.id),
fieldController = FieldController(viewId: view.id) {
//
_viewCache = DatabaseViewCache(
viewId: view.id,
fieldController: fieldController,
);
_viewCache.addListener(onRowsChanged: (reason) {
_onRowsChanged?.call(rowInfos, reason);
});
}
void addListener({
required OnDatabaseChanged onDatabaseChanged,
OnFieldsChanged? onFieldsChanged,
required DidLoadGroups didLoadGroups,
OnRowsChanged? onRowsChanged,
required OnUpdatedGroup onUpdatedGroup,
required OnDeletedGroup onDeletedGroup,
required OnInsertedGroup onInsertedGroup,
required OnResetGroups onResetGroups,
required OnError? onError,
}) {
_onDatabaseChanged = onDatabaseChanged;
_onFieldsChanged = onFieldsChanged;
_didLoadGroup = didLoadGroups;
_onRowsChanged = onRowsChanged;
_onError = onError;
fieldController.addListener(onReceiveFields: (fields) {
_onFieldsChanged?.call(UnmodifiableListView(fields));
});
_listener.start(
onBoardChanged: (result) {
result.fold(
(changeset) {
if (changeset.updateGroups.isNotEmpty) {
onUpdatedGroup.call(changeset.updateGroups);
}
if (changeset.deletedGroups.isNotEmpty) {
onDeletedGroup.call(changeset.deletedGroups);
}
for (final insertedGroup in changeset.insertedGroups) {
onInsertedGroup.call(insertedGroup);
}
},
(e) => _onError?.call(e),
);
},
onGroupByNewField: (result) {
result.fold(
(groups) => onResetGroups(groups),
(e) => _onError?.call(e),
);
},
);
}
Future<Either<Unit, FlowyError>> openGrid() async {
final result = await _databaseSvc.openGrid();
return result.fold(
(grid) async {
_onDatabaseChanged?.call(grid);
return fieldController.loadFields(fieldIds: grid.fields).then((result) {
return result.fold(
(l) => Future(() async {
await _loadGroups();
_viewCache.rowCache.initializeRows(grid.rows);
return left(l);
}),
(err) => right(err),
);
});
},
(err) => right(err),
);
}
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId,
{String? startRowId}) {
return _databaseSvc.createBoardCard(groupId, startRowId);
}
Future<void> dispose() async {
await _viewCache.dispose();
await _databaseSvc.closeView();
await fieldController.dispose();
}
Future<void> _loadGroups() async {
final result = await _databaseSvc.loadGroups();
return Future(
() => result.fold(
(groups) {
_didLoadGroup?.call(groups.items);
},
(err) => _onError?.call(err),
),
);
}
}

View File

@ -1,39 +0,0 @@
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
import 'package:flutter/foundation.dart';
import '../../../application/cell/cell_service.dart';
import '../../../application/row/row_cache.dart';
import '../../presentation/card/card_cell_builder.dart';
typedef OnCardChanged = void Function(CellByFieldId, RowsChangedReason);
class CardDataController extends BoardCellBuilderDelegate {
final RowPB rowPB;
final RowCache _rowCache;
final List<VoidCallback> _onCardChangedListeners = [];
CardDataController({
required this.rowPB,
required RowCache rowCache,
}) : _rowCache = rowCache;
CellByFieldId loadData() {
return _rowCache.loadGridCells(rowPB.id);
}
void addListener({OnCardChanged? onRowChanged}) {
_onCardChangedListeners.add(_rowCache.addListener(
rowId: rowPB.id,
onCellUpdated: onRowChanged,
));
}
void dispose() {
for (final fn in _onCardChangedListeners) {
_rowCache.removeRowListener(fn);
}
}
@override
CellCache get cellCache => _rowCache.cellCache;
}

View File

@ -1,7 +1,11 @@
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
import 'group_listener.dart'; import 'dart:typed_data';
import 'package:appflowy/core/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:dartz/dartz.dart';
typedef OnGroupError = void Function(FlowyError); typedef OnGroupError = void Function(FlowyError);
@ -14,14 +18,14 @@ abstract class GroupControllerDelegate {
class GroupController { class GroupController {
final GroupPB group; final GroupPB group;
final GroupListener _listener; final SingleGroupListener _listener;
final GroupControllerDelegate delegate; final GroupControllerDelegate delegate;
GroupController({ GroupController({
required String viewId, required String viewId,
required this.group, required this.group,
required this.delegate, required this.delegate,
}) : _listener = GroupListener(group); }) : _listener = SingleGroupListener(group);
RowPB? rowAtIndex(int index) { RowPB? rowAtIndex(int index) {
if (index < group.rows.length) { if (index < group.rows.length) {
@ -81,3 +85,45 @@ class GroupController {
_listener.stop(); _listener.stop();
} }
} }
typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
class SingleGroupListener {
final GroupPB group;
PublishNotifier<UpdateGroupNotifiedValue>? _groupNotifier = PublishNotifier();
DatabaseNotificationListener? _listener;
SingleGroupListener(this.group);
void start({
required void Function(UpdateGroupNotifiedValue) onGroupChanged,
}) {
_groupNotifier?.addPublishListener(onGroupChanged);
_listener = DatabaseNotificationListener(
objectId: group.groupId,
handler: _handler,
);
}
void _handler(
DatabaseNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case DatabaseNotification.DidUpdateGroupRow:
result.fold(
(payload) => _groupNotifier?.value =
left(GroupRowsNotificationPB.fromBuffer(payload)),
(error) => _groupNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_groupNotifier?.dispose();
_groupNotifier = null;
}
}

View File

@ -1,51 +0,0 @@
import 'dart:typed_data';
import 'package:appflowy/core/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/notification.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
class GroupListener {
final GroupPB group;
PublishNotifier<UpdateGroupNotifiedValue>? _groupNotifier = PublishNotifier();
DatabaseNotificationListener? _listener;
GroupListener(this.group);
void start({
required void Function(UpdateGroupNotifiedValue) onGroupChanged,
}) {
_groupNotifier?.addPublishListener(onGroupChanged);
_listener = DatabaseNotificationListener(
objectId: group.groupId,
handler: _handler,
);
}
void _handler(
DatabaseNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case DatabaseNotification.DidUpdateGroupRow:
result.fold(
(payload) => _groupNotifier?.value =
left(GroupRowsNotificationPB.fromBuffer(payload)),
(error) => _groupNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_groupNotifier?.dispose();
_groupNotifier = null;
}
}

View File

@ -17,13 +17,13 @@ import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.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/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide Card;
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../widgets/card/cells/card_cell.dart';
import '../../widgets/card/card_cell_builder.dart';
import '../application/board_bloc.dart'; import '../application/board_bloc.dart';
import '../application/card/card_data_controller.dart'; import '../../widgets/card/card.dart';
import 'card/card.dart';
import 'card/card_cell_builder.dart';
import 'toolbar/board_toolbar.dart'; import 'toolbar/board_toolbar.dart';
class BoardPage extends StatelessWidget { class BoardPage extends StatelessWidget {
@ -78,6 +78,7 @@ class BoardContent extends StatefulWidget {
class _BoardContentState extends State<BoardContent> { class _BoardContentState extends State<BoardContent> {
late AppFlowyBoardScrollController scrollManager; late AppFlowyBoardScrollController scrollManager;
final cardConfiguration = CardConfiguration<String>();
final config = AppFlowyBoardConfig( final config = AppFlowyBoardConfig(
groupBackgroundColor: HexColor.fromHex('#F7F8FC'), groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
@ -86,6 +87,16 @@ class _BoardContentState extends State<BoardContent> {
@override @override
void initState() { void initState() {
scrollManager = AppFlowyBoardScrollController(); scrollManager = AppFlowyBoardScrollController();
cardConfiguration.addSelectOptionHook((options, groupId) {
// The cell should hide if the option id is equal to the groupId.
final isInGroup =
options.where((element) => element.id == groupId).isNotEmpty;
if (isInGroup || options.isEmpty) {
return const SizedBox();
}
return null;
});
super.initState(); super.initState();
} }
@ -225,15 +236,11 @@ class _BoardContentState extends State<BoardContent> {
/// Return placeholder widget if the rowCache is null. /// Return placeholder widget if the rowCache is null.
if (rowCache == null) return SizedBox(key: ObjectKey(groupItem)); if (rowCache == null) return SizedBox(key: ObjectKey(groupItem));
final cellCache = rowCache.cellCache;
final fieldController = context.read<BoardBloc>().fieldController; final fieldController = context.read<BoardBloc>().fieldController;
final viewId = context.read<BoardBloc>().viewId; final viewId = context.read<BoardBloc>().viewId;
final cardController = CardDataController(
rowCache: rowCache,
rowPB: rowPB,
);
final cellBuilder = BoardCellBuilder(cardController); final cellBuilder = CardCellBuilder<String>(cellCache);
bool isEditing = false; bool isEditing = false;
context.read<BoardBloc>().state.editingRow.fold( context.read<BoardBloc>().state.editingRow.fold(
() => null, () => null,
@ -247,13 +254,15 @@ class _BoardContentState extends State<BoardContent> {
key: ValueKey(groupItemId), key: ValueKey(groupItemId),
margin: config.cardPadding, margin: config.cardPadding,
decoration: _makeBoxDecoration(context), decoration: _makeBoxDecoration(context),
child: BoardCard( child: Card<String>(
row: rowPB,
viewId: viewId, viewId: viewId,
groupId: groupData.group.groupId, rowCache: rowCache,
cardData: groupData.group.groupId,
fieldId: groupItem.fieldInfo.id, fieldId: groupItem.fieldInfo.id,
isEditing: isEditing, isEditing: isEditing,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
dataController: cardController, configuration: cardConfiguration,
openCard: (context) => _openCard( openCard: (context) => _openCard(
viewId, viewId,
fieldController, fieldController,
@ -304,7 +313,8 @@ class _BoardContentState extends State<BoardContent> {
); );
final dataController = RowDataController( final dataController = RowDataController(
rowInfo: rowInfo, rowId: rowInfo.rowPB.id,
viewId: rowInfo.viewId,
rowCache: rowCache, rowCache: rowCache,
); );

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.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-folder/protobuf.dart';
@ -9,20 +10,24 @@ import 'package:dartz/dartz.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 'calendar_data_controller.dart'; import '../../application/database_controller.dart';
import '../../application/row/row_cache.dart';
part 'calendar_bloc.freezed.dart'; part 'calendar_bloc.freezed.dart';
class CalendarBloc extends Bloc<CalendarEvent, CalendarState> { class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
final CalendarDataController _databaseDataController; final DatabaseController _databaseController;
final EventController calendarEventsController = EventController();
FieldController get fieldController => // Getters
_databaseDataController.fieldController; String get viewId => _databaseController.viewId;
String get databaseId => _databaseDataController.databaseId; CellCache get cellCache => _databaseController.rowCache.cellCache;
RowCache get rowCache => _databaseController.rowCache;
CalendarBloc({required ViewPB view}) CalendarBloc({required ViewPB view})
: _databaseDataController = CalendarDataController(view: view), : _databaseController = DatabaseController(
view: view,
layoutType: LayoutTypePB.Calendar,
),
super(CalendarState.initial(view.id)) { super(CalendarState.initial(view.id)) {
on<CalendarEvent>( on<CalendarEvent>(
(event, emit) async { (event, emit) async {
@ -30,23 +35,57 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
initial: () async { initial: () async {
_startListening(); _startListening();
await _openDatabase(emit); await _openDatabase(emit);
_loadAllEvents();
}, },
didReceiveCalendarSettings: (CalendarSettingsPB settings) { didReceiveCalendarSettings: (CalendarLayoutSettingsPB settings) {
emit(state.copyWith(settings: Some(settings))); emit(state.copyWith(settings: Some(settings)));
}, },
didReceiveDatabaseUpdate: (DatabasePB database) { didReceiveDatabaseUpdate: (DatabasePB database) {
emit(state.copyWith(database: Some(database))); emit(state.copyWith(database: Some(database)));
}, },
didReceiveError: (FlowyError error) { didLoadAllEvents: (events) {
emit(state.copyWith(noneOrError: Some(error))); emit(state.copyWith(events: events));
},
createEvent: (DateTime date, String title) async {
await _createEvent(date, title);
},
didReceiveEvent: (CalendarEventData<CalendarCardData> newEvent) {
emit(state.copyWith(events: [...state.events, newEvent]));
},
didUpdateFieldInfos: (Map<String, FieldInfo> fieldInfoByFieldId) {
emit(state.copyWith(fieldInfoByFieldId: fieldInfoByFieldId));
}, },
); );
}, },
); );
} }
FieldInfo? _getCalendarFieldInfo(String fieldId) {
final fieldInfos = _databaseController.fieldController.fieldInfos;
final index = fieldInfos.indexWhere(
(element) => element.field.id == fieldId,
);
if (index != -1) {
return fieldInfos[index];
} else {
return null;
}
}
FieldInfo? _getTitleFieldInfo() {
final fieldInfos = _databaseController.fieldController.fieldInfos;
final index = fieldInfos.indexWhere(
(element) => element.field.isPrimary,
);
if (index != -1) {
return fieldInfos[index];
} else {
return null;
}
}
Future<void> _openDatabase(Emitter<CalendarState> emit) async { Future<void> _openDatabase(Emitter<CalendarState> emit) async {
final result = await _databaseDataController.openDatabase(); final result = await _databaseController.open();
result.fold( result.fold(
(database) => emit( (database) => emit(
state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))), state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))),
@ -57,60 +96,144 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
); );
} }
RowCache? getRowCache(String blockId) { Future<void> _createEvent(DateTime date, String title) async {
return _databaseDataController.rowCache; state.settings.fold(
() => null,
(settings) async {
final dateField = _getCalendarFieldInfo(settings.layoutFieldId);
final titleField = _getTitleFieldInfo();
if (dateField != null && titleField != null) {
final result = await _databaseController.createRow(
withCells: (builder) {
builder.insertDate(dateField, date);
builder.insertText(titleField, title);
},
);
result.fold(
(newRow) => _loadEvent(newRow.id),
(err) => Log.error(err),
);
} }
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) { Future<void> _loadEvent(String rowId) async {
calendarEventsController.removeWhere((element) => true); final payload = RowIdPB(viewId: viewId, rowId: rowId);
DatabaseEventGetCalendarEvent(payload).send().then((result) {
result.fold(
(eventPB) {
final calendarEvent = _calendarEventDataFromEventPB(eventPB);
if (calendarEvent != null) {
add(CalendarEvent.didReceiveEvent(calendarEvent));
}
},
(r) => Log.error(r),
);
});
}
const events = <CalendarEventData<CalendarData>>[]; Future<void> _loadAllEvents() async {
final payload = CalendarEventRequestPB.create()..viewId = viewId;
DatabaseEventGetAllCalendarEvents(payload).send().then((result) {
result.fold(
(events) {
if (!isClosed) {
final calendarEvents = <CalendarEventData<CalendarCardData>>[];
for (final eventPB in events.items) {
final calendarEvent = _calendarEventDataFromEventPB(eventPB);
if (calendarEvent != null) {
calendarEvents.add(calendarEvent);
}
}
// final List<CalendarEventData<CalendarData>> events = rows.map((row) { add(CalendarEvent.didLoadAllEvents(calendarEvents));
// final event = CalendarEventData( }
// title: "", },
// date: row -> dateField -> value, (r) => Log.error(r),
// event: row, );
// ); });
}
// return event; CalendarEventData<CalendarCardData>? _calendarEventDataFromEventPB(
// }).toList(); CalendarEventPB eventPB) {
final fieldInfo = state.fieldInfoByFieldId[eventPB.titleFieldId];
if (fieldInfo != null) {
final cellId = CellIdentifier(
viewId: viewId,
rowId: eventPB.rowId,
fieldInfo: fieldInfo,
);
calendarEventsController.addAll(events); final eventData = CalendarCardData(
event: eventPB,
cellId: cellId,
);
final date = DateTime.fromMillisecondsSinceEpoch(
eventPB.timestamp.toInt() * 1000,
);
return CalendarEventData(
title: eventPB.title,
date: date,
event: eventData,
);
} else {
return null;
}
}
void _startListening() {
final onDatabaseChanged = DatabaseCallbacks(
onDatabaseChanged: (database) {
if (isClosed) return;
},
onFieldsChanged: (fieldInfos) {
if (isClosed) return;
final fieldInfoByFieldId = {
for (var fieldInfo in fieldInfos) fieldInfo.field.id: fieldInfo
};
add(CalendarEvent.didUpdateFieldInfos(fieldInfoByFieldId));
},
);
final onLayoutChanged = LayoutCallbacks(
onLayoutChanged: _didReceiveLayoutSetting,
onLoadLayout: _didReceiveLayoutSetting,
);
_databaseController.addListener(
onDatabaseChanged: onDatabaseChanged,
onLayoutChanged: onLayoutChanged,
);
}
void _didReceiveLayoutSetting(LayoutSettingPB layoutSetting) {
if (layoutSetting.hasCalendar()) {
if (isClosed) return;
add(CalendarEvent.didReceiveCalendarSettings(layoutSetting.calendar));
}
} }
} }
typedef Events = List<CalendarEventData<CalendarCardData>>;
@freezed @freezed
class CalendarEvent with _$CalendarEvent { class CalendarEvent with _$CalendarEvent {
const factory CalendarEvent.initial() = _InitialCalendar; const factory CalendarEvent.initial() = _InitialCalendar;
const factory CalendarEvent.didReceiveCalendarSettings( const factory CalendarEvent.didReceiveCalendarSettings(
CalendarSettingsPB settings) = _DidReceiveCalendarSettings; CalendarLayoutSettingsPB settings) = _ReceiveCalendarSettings;
const factory CalendarEvent.didReceiveError(FlowyError error) = const factory CalendarEvent.didLoadAllEvents(Events events) =
_DidReceiveError; _ReceiveCalendarEvents;
const factory CalendarEvent.didReceiveEvent(
CalendarEventData<CalendarCardData> event) = _ReceiveEvent;
const factory CalendarEvent.didUpdateFieldInfos(
Map<String, FieldInfo> fieldInfoByFieldId) = _DidUpdateFieldInfos;
const factory CalendarEvent.createEvent(DateTime date, String title) =
_CreateEvent;
const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) = const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) =
_DidReceiveDatabaseUpdate; _ReceiveDatabaseUpdate;
} }
@freezed @freezed
@ -118,9 +241,9 @@ class CalendarState with _$CalendarState {
const factory CalendarState({ const factory CalendarState({
required String databaseId, required String databaseId,
required Option<DatabasePB> database, required Option<DatabasePB> database,
required Option<FieldPB> dateField, required Events events,
required Option<List<RowInfo>> unscheduledRows, required Map<String, FieldInfo> fieldInfoByFieldId,
required Option<CalendarSettingsPB> settings, required Option<CalendarLayoutSettingsPB> settings,
required DatabaseLoadingState loadingState, required DatabaseLoadingState loadingState,
required Option<FlowyError> noneOrError, required Option<FlowyError> noneOrError,
}) = _CalendarState; }) = _CalendarState;
@ -128,8 +251,8 @@ class CalendarState with _$CalendarState {
factory CalendarState.initial(String databaseId) => CalendarState( factory CalendarState.initial(String databaseId) => CalendarState(
database: none(), database: none(),
databaseId: databaseId, databaseId: databaseId,
dateField: none(), fieldInfoByFieldId: {},
unscheduledRows: none(), events: [],
settings: none(), settings: none(),
noneOrError: none(), noneOrError: none(),
loadingState: const _Loading(), loadingState: const _Loading(),
@ -153,7 +276,8 @@ class CalendarEditingRow {
}); });
} }
class CalendarData { class CalendarCardData {
final RowInfo rowInfo; final CalendarEventPB event;
CalendarData(this.rowInfo); final CellIdentifier cellId;
CalendarCardData({required this.cellId, required this.event});
} }

View File

@ -1,115 +0,0 @@
import 'dart:async';
import 'dart:collection';
import 'package:appflowy/plugins/database_view/application/database_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/view/view_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 DatabaseBackendService _databaseBackendSvc;
final FieldController 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;
RowCache get rowCache => _viewCache.rowCache;
CalendarDataController({required ViewPB view})
: databaseId = view.id,
_listener = CalendarListener(view.id),
_databaseBackendSvc = DatabaseBackendService(viewId: view.id),
fieldController = FieldController(viewId: view.id) {
_viewCache = DatabaseViewCache(
viewId: 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(onReceiveFields: (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 _databaseBackendSvc.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 _databaseBackendSvc.closeView();
await fieldController.dispose();
}
}

View File

@ -1,65 +0,0 @@
import 'dart:typed_data';
import 'package:appflowy/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;
}
}

View File

@ -77,8 +77,7 @@ class CalendarPluginDisplay extends PluginDisplay {
}); });
}); });
return CalendarPage(key: ValueKey(view.id)); return CalendarPage(key: ValueKey(view.id), view: view);
// return CalendarPage(key: ValueKey(view.id), view: view);
} }
@override @override

View File

@ -1,56 +1,85 @@
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:calendar_view/calendar_view.dart'; import 'package:calendar_view/calendar_view.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.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/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import '../../grid/presentation/layout/sizes.dart'; import '../../grid/presentation/layout/sizes.dart';
import '../../grid/presentation/widgets/row/row_detail.dart';
import 'layout/sizes.dart'; import 'layout/sizes.dart';
import 'toolbar/calendar_toolbar.dart'; import 'toolbar/calendar_toolbar.dart';
class CalendarPage extends StatelessWidget { class CalendarPage extends StatefulWidget {
const CalendarPage({super.key}); final ViewPB view;
const CalendarPage({required this.view, super.key});
@override @override
Widget build(BuildContext context) { State<CalendarPage> createState() => _CalendarPageState();
return const CalendarContent();
}
} }
class CalendarContent extends StatefulWidget { class _CalendarPageState extends State<CalendarPage> {
const CalendarContent({super.key}); final _eventController = EventController<CalendarCardData>();
@override
State<CalendarContent> createState() => _CalendarContentState();
}
class _CalendarContentState extends State<CalendarContent> {
late EventController _eventController;
GlobalKey<MonthViewState>? _calendarState; GlobalKey<MonthViewState>? _calendarState;
late CalendarBloc _calendarBloc;
@override @override
void initState() { void initState() {
_eventController = EventController();
_calendarState = GlobalKey<MonthViewState>(); _calendarState = GlobalKey<MonthViewState>();
_calendarBloc = CalendarBloc(view: widget.view)
..add(const CalendarEvent.initial());
super.initState(); super.initState();
} }
@override
void dispose() {
_calendarBloc.close();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CalendarControllerProvider( return CalendarControllerProvider(
controller: _eventController, controller: _eventController,
child: Column( child: MultiBlocProvider(
providers: [
BlocProvider<CalendarBloc>.value(
value: _calendarBloc,
)
],
child: BlocListener<CalendarBloc, CalendarState>(
listenWhen: (previous, current) => previous.events != current.events,
listener: (context, state) {
if (state.events.isNotEmpty) {
_eventController.removeWhere((element) => true);
_eventController.addAll(state.events);
}
},
child: BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
return Column(
children: [ children: [
// const _ToolbarBlocAdaptor(), // const _ToolbarBlocAdaptor(),
_toolbar(), _toolbar(),
_buildCalendar(_eventController), _buildCalendar(_eventController),
], ],
);
},
),
),
), ),
); );
} }
@ -125,9 +154,190 @@ class _CalendarContentState extends State<CalendarContent> {
); );
} }
Widget _calendarDayBuilder(date, event, isToday, isInMonth) { Widget _calendarDayBuilder(
DateTime date,
List<CalendarEventData<CalendarCardData>> calenderEvents,
isToday,
isInMonth,
) {
final builder = CardCellBuilder(_calendarBloc.cellCache);
final cells = calenderEvents.map((value) => value.event!).map((event) {
final child = builder.buildCell(cellId: event.cellId);
return FlowyHover(
child: GestureDetector(
onTap: () {
final dataController = RowDataController(
rowId: event.cellId.rowId,
viewId: widget.view.id,
rowCache: _calendarBloc.rowCache,
);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder:
GridCellBuilder(cellCache: _calendarBloc.cellCache),
dataController: dataController,
);
},
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: child,
),
),
);
}).toList();
return _CalendarCard(
isToday: isToday,
isInMonth: isInMonth,
date: date,
children: cells,
onCreateEvent: (date) {
_calendarBloc.add(
CalendarEvent.createEvent(
date,
LocaleKeys.calendar_defaultNewCalendarTitle.tr(),
),
);
},
);
}
}
class _CalendarCard extends StatelessWidget {
final bool isToday;
final bool isInMonth;
final DateTime date;
final List<Widget> children;
final void Function(DateTime) onCreateEvent;
const _CalendarCard({
required this.isToday,
required this.isInMonth,
required this.date,
required this.children,
required this.onCreateEvent,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Color backgroundColor = Theme.of(context).colorScheme.surface;
if (!isInMonth) {
backgroundColor = AFThemeExtension.of(context).lightGreyHover;
}
return ChangeNotifierProvider(
create: (_) => _CardEnterNotifier(),
builder: ((context, child) {
return Container(
color: backgroundColor,
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (p) => notifyEnter(context, true),
onExit: (p) => notifyEnter(context, false),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
_Header(
date: date,
isInMonth: isInMonth,
isToday: isToday,
onCreate: () => onCreateEvent(date),
),
...children
],
),
),
),
);
}),
);
}
notifyEnter(BuildContext context, bool isEnter) {
Provider.of<_CardEnterNotifier>(
context,
listen: false,
).onEnter = isEnter;
}
}
class _Header extends StatelessWidget {
final bool isToday;
final bool isInMonth;
final DateTime date;
final VoidCallback onCreate;
const _Header({
required this.isToday,
required this.isInMonth,
required this.date,
required this.onCreate,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Consumer<_CardEnterNotifier>(
builder: (context, notifier, _) {
final badge = _DayBadge(
isToday: isToday,
isInMonth: isInMonth,
date: date,
);
return Row(
children: [
if (notifier.onEnter) _NewEventButton(onClick: onCreate),
const Spacer(),
badge,
],
);
},
);
}
}
class _NewEventButton extends StatelessWidget {
final VoidCallback onClick;
const _NewEventButton({
required this.onClick,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FlowyIconButton(
onPressed: onClick,
iconPadding: EdgeInsets.zero,
icon: svgWidget(
"home/add",
color: Theme.of(context).colorScheme.onSurface,
),
width: 22,
);
}
}
class _DayBadge extends StatelessWidget {
final bool isToday;
final bool isInMonth;
final DateTime date;
const _DayBadge({
required this.isToday,
required this.isInMonth,
required this.date,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Color dayTextColor = Theme.of(context).colorScheme.onSurface; Color dayTextColor = Theme.of(context).colorScheme.onSurface;
Color cellBackgroundColor = Theme.of(context).colorScheme.surface;
String dayString = date.day == 1 String dayString = date.day == 1
? DateFormat('MMM d', context.locale.toLanguageTag()).format(date) ? DateFormat('MMM d', context.locale.toLanguageTag()).format(date)
: date.day.toString(); : date.day.toString();
@ -137,8 +347,8 @@ class _CalendarContentState extends State<CalendarContent> {
} }
if (!isInMonth) { if (!isInMonth) {
dayTextColor = Theme.of(context).disabledColor; dayTextColor = Theme.of(context).disabledColor;
cellBackgroundColor = AFThemeExtension.of(context).lightGreyHover;
} }
Widget day = Container( Widget day = Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isToday ? Theme.of(context).colorScheme.primary : null, color: isToday ? Theme.of(context).colorScheme.primary : null,
@ -151,12 +361,21 @@ class _CalendarContentState extends State<CalendarContent> {
), ),
); );
return Container( return day;
color: cellBackgroundColor,
child: Align(
alignment: Alignment.topRight,
child: day.padding(all: 6.0),
),
);
} }
} }
class _CardEnterNotifier extends ChangeNotifier {
bool _onEnter = false;
_CardEnterNotifier();
set onEnter(bool value) {
if (_onEnter != value) {
_onEnter = value;
notifyListeners();
}
}
bool get onEnter => _onEnter;
}

View File

@ -8,11 +8,12 @@ import 'checklist_cell_editor_bloc.dart';
import 'select_option_service.dart'; import 'select_option_service.dart';
part 'checklist_cell_bloc.freezed.dart'; part 'checklist_cell_bloc.freezed.dart';
class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> { class ChecklistCardCellBloc
extends Bloc<ChecklistCellEvent, ChecklistCellState> {
final ChecklistCellController cellController; final ChecklistCellController cellController;
final SelectOptionBackendService _selectOptionSvc; final SelectOptionBackendService _selectOptionSvc;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
ChecklistCellBloc({ ChecklistCardCellBloc({
required this.cellController, required this.cellController,
}) : _selectOptionSvc = }) : _selectOptionSvc =
SelectOptionBackendService(cellId: cellController.cellId), SelectOptionBackendService(cellId: cellController.cellId),

View File

@ -9,7 +9,7 @@ import 'package:appflowy_backend/protobuf/flowy-database/protobuf.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 '../../application/field/field_controller.dart'; import '../../application/field/field_controller.dart';
import 'grid_data_controller.dart'; import '../../application/database_controller.dart';
import 'dart:collection'; import 'dart:collection';
part 'grid_bloc.freezed.dart'; part 'grid_bloc.freezed.dart';
@ -66,10 +66,10 @@ class GridBloc extends Bloc<GridEvent, GridState> {
} }
void _startListening() { void _startListening() {
databaseController.addListener( final onDatabaseChanged = DatabaseCallbacks(
onGridChanged: (grid) { onDatabaseChanged: (database) {
if (!isClosed) { if (!isClosed) {
add(GridEvent.didReceiveGridUpdate(grid)); add(GridEvent.didReceiveGridUpdate(database));
} }
}, },
onRowsChanged: (rowInfos, reason) { onRowsChanged: (rowInfos, reason) {
@ -83,10 +83,11 @@ class GridBloc extends Bloc<GridEvent, GridState> {
} }
}, },
); );
databaseController.addListener(onDatabaseChanged: onDatabaseChanged);
} }
Future<void> _openGrid(Emitter<GridState> emit) async { Future<void> _openGrid(Emitter<GridState> emit) async {
final result = await databaseController.openGrid(); final result = await databaseController.open();
result.fold( result.fold(
(grid) { (grid) {
emit( emit(

View File

@ -1,83 +0,0 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:collection/collection.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import '../../application/database_service.dart';
import '../../application/defines.dart';
import '../../application/row/row_cache.dart';
typedef OnRowsChanged = void Function(
List<RowInfo> rowInfos,
RowsChangedReason,
);
typedef ListenOnRowChangedCondition = bool Function();
class DatabaseController {
final String viewId;
final DatabaseBackendService _databaseBackendSvc;
final FieldController fieldController;
late DatabaseViewCache _viewCache;
OnRowsChanged? _onRowChanged;
OnDatabaseChanged? _onGridChanged;
List<RowInfo> get rowInfos => _viewCache.rowInfos;
RowCache get rowCache => _viewCache.rowCache;
DatabaseController({required ViewPB view})
: viewId = view.id,
_databaseBackendSvc = DatabaseBackendService(viewId: view.id),
fieldController = FieldController(viewId: view.id) {
_viewCache = DatabaseViewCache(
viewId: viewId,
fieldController: fieldController,
);
_viewCache.addListener(onRowsChanged: (reason) {
_onRowChanged?.call(rowInfos, reason);
});
}
void addListener({
OnDatabaseChanged? onGridChanged,
OnRowsChanged? onRowsChanged,
OnFieldsChanged? onFieldsChanged,
OnFiltersChanged? onFiltersChanged,
}) {
_onGridChanged = onGridChanged;
_onRowChanged = onRowsChanged;
fieldController.addListener(
onReceiveFields: (fields) {
onFieldsChanged?.call(UnmodifiableListView(fields));
},
onFilters: onFiltersChanged,
);
}
Future<Either<Unit, FlowyError>> openGrid() async {
return _databaseBackendSvc.openGrid().then((result) {
return result.fold(
(grid) async {
_onGridChanged?.call(grid);
_viewCache.rowCache.initializeRows(grid.rows);
final result = await fieldController.loadFields(
fieldIds: grid.fields,
);
return result;
},
(err) => right(err),
);
});
}
Future<void> createRow() async {
await _databaseBackendSvc.createRow();
}
Future<void> dispose() async {
await _databaseBackendSvc.closeView();
await fieldController.dispose();
}
}

View File

@ -27,7 +27,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
}, },
deleteField: (_DeleteField value) { deleteField: (_DeleteField value) {
final fieldService = FieldBackendService( final fieldService = FieldBackendService(
viewId: dataController.rowInfo.viewId, viewId: dataController.viewId,
fieldId: value.fieldId, fieldId: value.fieldId,
); );
fieldService.deleteField(); fieldService.deleteField();

View File

@ -1,4 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
@ -16,7 +17,7 @@ import '../../application/row/row_data_controller.dart';
import '../../application/setting/setting_bloc.dart'; import '../../application/setting/setting_bloc.dart';
import '../application/filter/filter_menu_bloc.dart'; import '../application/filter/filter_menu_bloc.dart';
import '../application/grid_bloc.dart'; import '../application/grid_bloc.dart';
import '../application/grid_data_controller.dart'; import '../../application/database_controller.dart';
import '../application/sort/sort_menu_bloc.dart'; import '../application/sort/sort_menu_bloc.dart';
import 'grid_scroll.dart'; import 'grid_scroll.dart';
import 'layout/layout.dart'; import 'layout/layout.dart';
@ -35,7 +36,10 @@ class GridPage extends StatefulWidget {
required this.view, required this.view,
this.onDeleted, this.onDeleted,
Key? key, Key? key,
}) : databaseController = DatabaseController(view: view), }) : databaseController = DatabaseController(
view: view,
layoutType: LayoutTypePB.Grid,
),
super(key: key); super(key: key);
final ViewPB view; final ViewPB view;
@ -276,7 +280,8 @@ class _GridRowsState extends State<_GridRows> {
final fieldController = final fieldController =
context.read<GridBloc>().databaseController.fieldController; context.read<GridBloc>().databaseController.fieldController;
final dataController = RowDataController( final dataController = RowDataController(
rowInfo: rowInfo, rowId: rowInfo.rowPB.id,
viewId: rowInfo.viewId,
rowCache: rowCache, rowCache: rowCache,
); );
@ -308,7 +313,8 @@ class _GridRowsState extends State<_GridRows> {
GridCellBuilder cellBuilder, GridCellBuilder cellBuilder,
) { ) {
final dataController = RowDataController( final dataController = RowDataController(
rowInfo: rowInfo, viewId: rowInfo.viewId,
rowId: rowInfo.rowPB.id,
rowCache: rowCache, rowCache: rowCache,
); );

View File

@ -20,7 +20,7 @@ class GridChecklistCell extends GridCellWidget {
} }
class GridChecklistCellState extends GridCellState<GridChecklistCell> { class GridChecklistCellState extends GridCellState<GridChecklistCell> {
late ChecklistCellBloc _cellBloc; late ChecklistCardCellBloc _cellBloc;
late final PopoverController _popover; late final PopoverController _popover;
@override @override
@ -28,7 +28,7 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
_popover = PopoverController(); _popover = PopoverController();
final cellController = final cellController =
widget.cellControllerBuilder.build() as ChecklistCellController; widget.cellControllerBuilder.build() as ChecklistCellController;
_cellBloc = ChecklistCellBloc(cellController: cellController); _cellBloc = ChecklistCardCellBloc(cellController: cellController);
_cellBloc.add(const ChecklistCellEvent.initial()); _cellBloc.add(const ChecklistCellEvent.initial());
super.initState(); super.initState();
} }
@ -54,7 +54,7 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
onClose: () => widget.onCellEditing.value = false, onClose: () => widget.onCellEditing.value = false,
child: Padding( child: Padding(
padding: GridSize.cellContentInsets, padding: GridSize.cellContentInsets,
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>( child: BlocBuilder<ChecklistCardCellBloc, ChecklistCellState>(
builder: (context, state) => builder: (context, state) =>
ChecklistProgressBar(percent: state.percent), ChecklistProgressBar(percent: state.percent),
), ),

View File

@ -66,7 +66,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
Expanded( Expanded(
child: _PropertyList( child: _PropertyList(
cellBuilder: widget.cellBuilder, cellBuilder: widget.cellBuilder,
viewId: widget.dataController.rowInfo.viewId, viewId: widget.dataController.viewId,
), ),
), ),
], ],

View File

@ -3,16 +3,16 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import '../../../application/cell/cell_controller_builder.dart'; import '../../../application/cell/cell_controller_builder.dart';
part 'board_checkbox_cell_bloc.freezed.dart'; part 'checkbox_card_cell_bloc.freezed.dart';
class BoardCheckboxCellBloc class CheckboxCardCellBloc
extends Bloc<BoardCheckboxCellEvent, BoardCheckboxCellState> { extends Bloc<CheckboxCardCellEvent, CheckboxCardCellState> {
final CheckboxCellController cellController; final CheckboxCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
BoardCheckboxCellBloc({ CheckboxCardCellBloc({
required this.cellController, required this.cellController,
}) : super(BoardCheckboxCellState.initial(cellController)) { }) : super(CheckboxCardCellState.initial(cellController)) {
on<BoardCheckboxCellEvent>( on<CheckboxCardCellEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async { initial: () async {
@ -43,7 +43,7 @@ class BoardCheckboxCellBloc
_onCellChangedFn = cellController.startListening( _onCellChangedFn = cellController.startListening(
onCellChanged: ((cellContent) { onCellChanged: ((cellContent) {
if (!isClosed) { if (!isClosed) {
add(BoardCheckboxCellEvent.didReceiveCellUpdate(cellContent ?? "")); add(CheckboxCardCellEvent.didReceiveCellUpdate(cellContent ?? ""));
} }
}), }),
); );
@ -51,21 +51,21 @@ class BoardCheckboxCellBloc
} }
@freezed @freezed
class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent { class CheckboxCardCellEvent with _$CheckboxCardCellEvent {
const factory BoardCheckboxCellEvent.initial() = _InitialCell; const factory CheckboxCardCellEvent.initial() = _InitialCell;
const factory BoardCheckboxCellEvent.select() = _Selected; const factory CheckboxCardCellEvent.select() = _Selected;
const factory BoardCheckboxCellEvent.didReceiveCellUpdate( const factory CheckboxCardCellEvent.didReceiveCellUpdate(String cellContent) =
String cellContent) = _DidReceiveCellUpdate; _DidReceiveCellUpdate;
} }
@freezed @freezed
class BoardCheckboxCellState with _$BoardCheckboxCellState { class CheckboxCardCellState with _$CheckboxCardCellState {
const factory BoardCheckboxCellState({ const factory CheckboxCardCellState({
required bool isSelected, required bool isSelected,
}) = _CheckboxCellState; }) = _CheckboxCellState;
factory BoardCheckboxCellState.initial(TextCellController context) { factory CheckboxCardCellState.initial(TextCellController context) {
return BoardCheckboxCellState( return CheckboxCardCellState(
isSelected: _isSelected(context.getCellData())); isSelected: _isSelected(context.getCellData()));
} }
} }

View File

@ -5,15 +5,15 @@ import 'dart:async';
import '../../../application/cell/cell_controller_builder.dart'; import '../../../application/cell/cell_controller_builder.dart';
import '../../../application/field/field_controller.dart'; import '../../../application/field/field_controller.dart';
part 'board_date_cell_bloc.freezed.dart'; part 'date_card_cell_bloc.freezed.dart';
class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> { class DateCardCellBloc extends Bloc<DateCardCellEvent, DateCardCellState> {
final DateCellController cellController; final DateCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
BoardDateCellBloc({required this.cellController}) DateCardCellBloc({required this.cellController})
: super(BoardDateCellState.initial(cellController)) { : super(DateCardCellState.initial(cellController)) {
on<BoardDateCellEvent>( on<DateCardCellEvent>(
(event, emit) async { (event, emit) async {
event.when( event.when(
initial: () => _startListening(), initial: () => _startListening(),
@ -40,7 +40,7 @@ class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
_onCellChangedFn = cellController.startListening( _onCellChangedFn = cellController.startListening(
onCellChanged: ((data) { onCellChanged: ((data) {
if (!isClosed) { if (!isClosed) {
add(BoardDateCellEvent.didReceiveCellUpdate(data)); add(DateCardCellEvent.didReceiveCellUpdate(data));
} }
}), }),
); );
@ -48,24 +48,24 @@ class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
} }
@freezed @freezed
class BoardDateCellEvent with _$BoardDateCellEvent { class DateCardCellEvent with _$DateCardCellEvent {
const factory BoardDateCellEvent.initial() = _InitialCell; const factory DateCardCellEvent.initial() = _InitialCell;
const factory BoardDateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = const factory DateCardCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
_DidReceiveCellUpdate; _DidReceiveCellUpdate;
} }
@freezed @freezed
class BoardDateCellState with _$BoardDateCellState { class DateCardCellState with _$DateCardCellState {
const factory BoardDateCellState({ const factory DateCardCellState({
required DateCellDataPB? data, required DateCellDataPB? data,
required String dateStr, required String dateStr,
required FieldInfo fieldInfo, required FieldInfo fieldInfo,
}) = _BoardDateCellState; }) = _DateCardCellState;
factory BoardDateCellState.initial(DateCellController context) { factory DateCardCellState.initial(DateCellController context) {
final cellData = context.getCellData(); final cellData = context.getCellData();
return BoardDateCellState( return DateCardCellState(
fieldInfo: context.fieldInfo, fieldInfo: context.fieldInfo,
data: cellData, data: cellData,
dateStr: _dateStrFromCellData(cellData), dateStr: _dateStrFromCellData(cellData),

View File

@ -4,16 +4,16 @@ import 'dart:async';
import '../../../application/cell/cell_controller_builder.dart'; import '../../../application/cell/cell_controller_builder.dart';
part 'board_number_cell_bloc.freezed.dart'; part 'number_card_cell_bloc.freezed.dart';
class BoardNumberCellBloc class NumberCardCellBloc
extends Bloc<BoardNumberCellEvent, BoardNumberCellState> { extends Bloc<NumberCardCellEvent, NumberCardCellState> {
final NumberCellController cellController; final NumberCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
BoardNumberCellBloc({ NumberCardCellBloc({
required this.cellController, required this.cellController,
}) : super(BoardNumberCellState.initial(cellController)) { }) : super(NumberCardCellState.initial(cellController)) {
on<BoardNumberCellEvent>( on<NumberCardCellEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async { initial: () async {
@ -41,7 +41,7 @@ class BoardNumberCellBloc
_onCellChangedFn = cellController.startListening( _onCellChangedFn = cellController.startListening(
onCellChanged: ((cellContent) { onCellChanged: ((cellContent) {
if (!isClosed) { if (!isClosed) {
add(BoardNumberCellEvent.didReceiveCellUpdate(cellContent ?? "")); add(NumberCardCellEvent.didReceiveCellUpdate(cellContent ?? ""));
} }
}), }),
); );
@ -49,20 +49,20 @@ class BoardNumberCellBloc
} }
@freezed @freezed
class BoardNumberCellEvent with _$BoardNumberCellEvent { class NumberCardCellEvent with _$NumberCardCellEvent {
const factory BoardNumberCellEvent.initial() = _InitialCell; const factory NumberCardCellEvent.initial() = _InitialCell;
const factory BoardNumberCellEvent.didReceiveCellUpdate(String cellContent) = const factory NumberCardCellEvent.didReceiveCellUpdate(String cellContent) =
_DidReceiveCellUpdate; _DidReceiveCellUpdate;
} }
@freezed @freezed
class BoardNumberCellState with _$BoardNumberCellState { class NumberCardCellState with _$NumberCardCellState {
const factory BoardNumberCellState({ const factory NumberCardCellState({
required String content, required String content,
}) = _BoardNumberCellState; }) = _NumberCardCellState;
factory BoardNumberCellState.initial(TextCellController context) => factory NumberCardCellState.initial(TextCellController context) =>
BoardNumberCellState( NumberCardCellState(
content: context.getCellData() ?? "", content: context.getCellData() ?? "",
); );
} }

View File

@ -4,17 +4,17 @@ import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.d
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';
part 'board_select_option_cell_bloc.freezed.dart'; part 'select_option_card_cell_bloc.freezed.dart';
class BoardSelectOptionCellBloc class SelectOptionCardCellBloc
extends Bloc<BoardSelectOptionCellEvent, BoardSelectOptionCellState> { extends Bloc<SelectOptionCardCellEvent, SelectOptionCardCellState> {
final SelectOptionCellController cellController; final SelectOptionCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
BoardSelectOptionCellBloc({ SelectOptionCardCellBloc({
required this.cellController, required this.cellController,
}) : super(BoardSelectOptionCellState.initial(cellController)) { }) : super(SelectOptionCardCellState.initial(cellController)) {
on<BoardSelectOptionCellEvent>( on<SelectOptionCardCellEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async { initial: () async {
@ -42,7 +42,7 @@ class BoardSelectOptionCellBloc
_onCellChangedFn = cellController.startListening( _onCellChangedFn = cellController.startListening(
onCellChanged: ((selectOptionContext) { onCellChanged: ((selectOptionContext) {
if (!isClosed) { if (!isClosed) {
add(BoardSelectOptionCellEvent.didReceiveOptions( add(SelectOptionCardCellEvent.didReceiveOptions(
selectOptionContext?.selectOptions ?? [], selectOptionContext?.selectOptions ?? [],
)); ));
} }
@ -52,23 +52,23 @@ class BoardSelectOptionCellBloc
} }
@freezed @freezed
class BoardSelectOptionCellEvent with _$BoardSelectOptionCellEvent { class SelectOptionCardCellEvent with _$SelectOptionCardCellEvent {
const factory BoardSelectOptionCellEvent.initial() = _InitialCell; const factory SelectOptionCardCellEvent.initial() = _InitialCell;
const factory BoardSelectOptionCellEvent.didReceiveOptions( const factory SelectOptionCardCellEvent.didReceiveOptions(
List<SelectOptionPB> selectedOptions, List<SelectOptionPB> selectedOptions,
) = _DidReceiveOptions; ) = _DidReceiveOptions;
} }
@freezed @freezed
class BoardSelectOptionCellState with _$BoardSelectOptionCellState { class SelectOptionCardCellState with _$SelectOptionCardCellState {
const factory BoardSelectOptionCellState({ const factory SelectOptionCardCellState({
required List<SelectOptionPB> selectedOptions, required List<SelectOptionPB> selectedOptions,
}) = _BoardSelectOptionCellState; }) = _SelectOptionCardCellState;
factory BoardSelectOptionCellState.initial( factory SelectOptionCardCellState.initial(
SelectOptionCellController context) { SelectOptionCellController context) {
final data = context.getCellData(); final data = context.getCellData();
return BoardSelectOptionCellState( return SelectOptionCardCellState(
selectedOptions: data?.selectOptions ?? [], selectedOptions: data?.selectOptions ?? [],
); );
} }

View File

@ -3,15 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
part 'board_text_cell_bloc.freezed.dart'; part 'text_card_cell_bloc.freezed.dart';
class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> { class TextCardCellBloc extends Bloc<TextCardCellEvent, TextCardCellState> {
final TextCellController cellController; final TextCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
BoardTextCellBloc({ TextCardCellBloc({
required this.cellController, required this.cellController,
}) : super(BoardTextCellState.initial(cellController)) { }) : super(TextCardCellState.initial(cellController)) {
on<BoardTextCellEvent>( on<TextCardCellEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async { initial: () async {
@ -48,7 +48,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
_onCellChangedFn = cellController.startListening( _onCellChangedFn = cellController.startListening(
onCellChanged: ((cellContent) { onCellChanged: ((cellContent) {
if (!isClosed) { if (!isClosed) {
add(BoardTextCellEvent.didReceiveCellUpdate(cellContent ?? "")); add(TextCardCellEvent.didReceiveCellUpdate(cellContent ?? ""));
} }
}), }),
); );
@ -56,23 +56,23 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
} }
@freezed @freezed
class BoardTextCellEvent with _$BoardTextCellEvent { class TextCardCellEvent with _$TextCardCellEvent {
const factory BoardTextCellEvent.initial() = _InitialCell; const factory TextCardCellEvent.initial() = _InitialCell;
const factory BoardTextCellEvent.updateText(String text) = _UpdateContent; const factory TextCardCellEvent.updateText(String text) = _UpdateContent;
const factory BoardTextCellEvent.enableEdit(bool enabled) = _EnableEdit; const factory TextCardCellEvent.enableEdit(bool enabled) = _EnableEdit;
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) = const factory TextCardCellEvent.didReceiveCellUpdate(String cellContent) =
_DidReceiveCellUpdate; _DidReceiveCellUpdate;
} }
@freezed @freezed
class BoardTextCellState with _$BoardTextCellState { class TextCardCellState with _$TextCardCellState {
const factory BoardTextCellState({ const factory TextCardCellState({
required String content, required String content,
required bool enableEdit, required bool enableEdit,
}) = _BoardTextCellState; }) = _TextCardCellState;
factory BoardTextCellState.initial(TextCellController context) => factory TextCardCellState.initial(TextCellController context) =>
BoardTextCellState( TextCardCellState(
content: context.getCellData() ?? "", content: context.getCellData() ?? "",
enableEdit: false, enableEdit: false,
); );

View File

@ -4,15 +4,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
part 'board_url_cell_bloc.freezed.dart'; part 'url_card_cell_bloc.freezed.dart';
class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> { class URLCardCellBloc extends Bloc<URLCardCellEvent, URLCardCellState> {
final URLCellController cellController; final URLCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
BoardURLCellBloc({ URLCardCellBloc({
required this.cellController, required this.cellController,
}) : super(BoardURLCellState.initial(cellController)) { }) : super(URLCardCellState.initial(cellController)) {
on<BoardURLCellEvent>( on<URLCardCellEvent>(
(event, emit) async { (event, emit) async {
event.when( event.when(
initial: () { initial: () {
@ -46,7 +46,7 @@ class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
_onCellChangedFn = cellController.startListening( _onCellChangedFn = cellController.startListening(
onCellChanged: ((cellData) { onCellChanged: ((cellData) {
if (!isClosed) { if (!isClosed) {
add(BoardURLCellEvent.didReceiveCellUpdate(cellData)); add(URLCardCellEvent.didReceiveCellUpdate(cellData));
} }
}), }),
); );
@ -54,23 +54,23 @@ class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
} }
@freezed @freezed
class BoardURLCellEvent with _$BoardURLCellEvent { class URLCardCellEvent with _$URLCardCellEvent {
const factory BoardURLCellEvent.initial() = _InitialCell; const factory URLCardCellEvent.initial() = _InitialCell;
const factory BoardURLCellEvent.updateURL(String url) = _UpdateURL; const factory URLCardCellEvent.updateURL(String url) = _UpdateURL;
const factory BoardURLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = const factory URLCardCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
_DidReceiveCellUpdate; _DidReceiveCellUpdate;
} }
@freezed @freezed
class BoardURLCellState with _$BoardURLCellState { class URLCardCellState with _$URLCardCellState {
const factory BoardURLCellState({ const factory URLCardCellState({
required String content, required String content,
required String url, required String url,
}) = _BoardURLCellState; }) = _URLCardCellState;
factory BoardURLCellState.initial(URLCellController context) { factory URLCardCellState.initial(URLCellController context) {
final cellData = context.getCellData(); final cellData = context.getCellData();
return BoardURLCellState( return URLCardCellState(
content: cellData?.content ?? "", content: cellData?.content ?? "",
url: cellData?.url ?? "", url: cellData?.url ?? "",
); );

View File

@ -2,7 +2,7 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/card/board_number_cell_bloc.dart'; import 'bloc/number_card_cell_bloc.dart';
import 'define.dart'; import 'define.dart';
class BoardNumberCell extends StatefulWidget { class BoardNumberCell extends StatefulWidget {
@ -16,19 +16,19 @@ class BoardNumberCell extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
@override @override
State<BoardNumberCell> createState() => _BoardNumberCellState(); State<BoardNumberCell> createState() => _NumberCardCellState();
} }
class _BoardNumberCellState extends State<BoardNumberCell> { class _NumberCardCellState extends State<BoardNumberCell> {
late BoardNumberCellBloc _cellBloc; late NumberCardCellBloc _cellBloc;
@override @override
void initState() { void initState() {
final cellController = final cellController =
widget.cellControllerBuilder.build() as NumberCellController; widget.cellControllerBuilder.build() as NumberCellController;
_cellBloc = BoardNumberCellBloc(cellController: cellController) _cellBloc = NumberCardCellBloc(cellController: cellController)
..add(const BoardNumberCellEvent.initial()); ..add(const NumberCardCellEvent.initial());
super.initState(); super.initState();
} }
@ -36,7 +36,7 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>( child: BlocBuilder<NumberCardCellBloc, NumberCardCellState>(
buildWhen: (previous, current) => previous.content != current.content, buildWhen: (previous, current) => previous.content != current.content,
builder: (context, state) { builder: (context, state) {
if (state.content.isEmpty) { if (state.content.isEmpty) {
@ -46,7 +46,7 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: BoardSizes.cardCellVPadding, vertical: CardSizes.cardCellVPadding,
), ),
child: FlowyText.medium( child: FlowyText.medium(
state.content, state.content,

View File

@ -1,47 +1,52 @@
import 'package:appflowy/plugins/database_view/board/application/card/card_bloc.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/board/application/card/card_data_controller.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row_action_sheet.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row_action_sheet.dart';
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'board_cell.dart'; import 'card_bloc.dart';
import 'cells/card_cell.dart';
import 'card_cell_builder.dart'; import 'card_cell_builder.dart';
import 'container/accessory.dart'; import 'container/accessory.dart';
import 'container/card_container.dart'; import 'container/card_container.dart';
class BoardCard extends StatefulWidget { class Card<CustomCardData> extends StatefulWidget {
final RowPB row;
final String viewId; final String viewId;
final String groupId;
final String fieldId; final String fieldId;
final CustomCardData? cardData;
final bool isEditing; final bool isEditing;
final CardDataController dataController; final RowCache rowCache;
final BoardCellBuilder cellBuilder; final CardCellBuilder<CustomCardData> cellBuilder;
final void Function(BuildContext) openCard; final void Function(BuildContext) openCard;
final VoidCallback onStartEditing; final VoidCallback onStartEditing;
final VoidCallback onEndEditing; final VoidCallback onEndEditing;
final CardConfiguration<CustomCardData>? configuration;
const BoardCard({ const Card({
required this.row,
required this.viewId, required this.viewId,
required this.groupId,
required this.fieldId, required this.fieldId,
required this.isEditing, required this.isEditing,
required this.dataController, required this.rowCache,
required this.cellBuilder, required this.cellBuilder,
required this.openCard, required this.openCard,
required this.onStartEditing, required this.onStartEditing,
required this.onEndEditing, required this.onEndEditing,
this.cardData,
this.configuration,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
State<BoardCard> createState() => _BoardCardState(); State<Card<CustomCardData>> createState() => _CardState<CustomCardData>();
} }
class _BoardCardState extends State<BoardCard> { class _CardState<T> extends State<Card<T>> {
late BoardCardBloc _cardBloc; late CardBloc _cardBloc;
late EditableRowNotifier rowNotifier; late EditableRowNotifier rowNotifier;
late PopoverController popoverController; late PopoverController popoverController;
AccessoryType? accessoryType; AccessoryType? accessoryType;
@ -49,11 +54,12 @@ class _BoardCardState extends State<BoardCard> {
@override @override
void initState() { void initState() {
rowNotifier = EditableRowNotifier(isEditing: widget.isEditing); rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
_cardBloc = BoardCardBloc( _cardBloc = CardBloc(
viewId: widget.viewId, viewId: widget.viewId,
groupFieldId: widget.fieldId, groupFieldId: widget.fieldId,
dataController: widget.dataController,
isEditing: widget.isEditing, isEditing: widget.isEditing,
row: widget.row,
rowCache: widget.rowCache,
)..add(const BoardCardEvent.initial()); )..add(const BoardCardEvent.initial());
rowNotifier.isEditing.addListener(() { rowNotifier.isEditing.addListener(() {
@ -75,7 +81,7 @@ class _BoardCardState extends State<BoardCard> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cardBloc, value: _cardBloc,
child: BlocBuilder<BoardCardBloc, BoardCardState>( child: BlocBuilder<CardBloc, BoardCardState>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
// Rebuild when: // Rebuild when:
// 1.If the length of the cells is not the same // 1.If the length of the cells is not the same
@ -110,11 +116,12 @@ class _BoardCardState extends State<BoardCard> {
}, },
openAccessory: _handleOpenAccessory, openAccessory: _handleOpenAccessory,
openCard: (context) => widget.openCard(context), openCard: (context) => widget.openCard(context),
child: _CellColumn( child: _CardContent<T>(
groupId: widget.groupId,
rowNotifier: rowNotifier, rowNotifier: rowNotifier,
cellBuilder: widget.cellBuilder, cellBuilder: widget.cellBuilder,
cells: state.cells, cells: state.cells,
cardConfiguration: widget.configuration,
cardData: widget.cardData,
), ),
), ),
); );
@ -143,7 +150,7 @@ class _BoardCardState extends State<BoardCard> {
throw UnimplementedError(); throw UnimplementedError();
case AccessoryType.more: case AccessoryType.more:
return GridRowActionSheet( return GridRowActionSheet(
rowData: context.read<BoardCardBloc>().rowInfo(), rowData: context.read<CardBloc>().rowInfo(),
); );
} }
} }
@ -156,16 +163,18 @@ class _BoardCardState extends State<BoardCard> {
} }
} }
class _CellColumn extends StatelessWidget { class _CardContent<CustomCardData> extends StatelessWidget {
final String groupId; final CardCellBuilder<CustomCardData> cellBuilder;
final BoardCellBuilder cellBuilder;
final EditableRowNotifier rowNotifier; final EditableRowNotifier rowNotifier;
final List<BoardCellEquatable> cells; final List<BoardCellEquatable> cells;
const _CellColumn({ final CardConfiguration<CustomCardData>? cardConfiguration;
required this.groupId, final CustomCardData? cardData;
const _CardContent({
required this.rowNotifier, required this.rowNotifier,
required this.cellBuilder, required this.cellBuilder,
required this.cells, required this.cells,
required this.cardData,
this.cardConfiguration,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -188,7 +197,7 @@ class _CellColumn extends StatelessWidget {
cells.asMap().forEach( cells.asMap().forEach(
(int index, BoardCellEquatable cell) { (int index, BoardCellEquatable cell) {
final isEditing = index == 0 ? rowNotifier.isEditing.value : false; final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
final cellNotifier = EditableCellNotifier(isEditing: isEditing); final cellNotifier = EditableCardNotifier(isEditing: isEditing);
if (index == 0) { if (index == 0) {
// Only use the first cell to receive user's input when click the edit // Only use the first cell to receive user's input when click the edit
@ -200,9 +209,10 @@ class _CellColumn extends StatelessWidget {
key: cell.identifier.key(), key: cell.identifier.key(),
padding: const EdgeInsets.only(left: 4, right: 4), padding: const EdgeInsets.only(left: 4, right: 4),
child: cellBuilder.buildCell( child: cellBuilder.buildCell(
groupId, cellId: cell.identifier,
cell.identifier, cellNotifier: cellNotifier,
cellNotifier, cardConfiguration: cardConfiguration,
cardData: cardData,
), ),
); );

View File

@ -1,34 +1,36 @@
import 'dart:collection'; import 'dart:collection';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.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 'dart:async'; import 'dart:async';
import '../../../application/cell/cell_service.dart';
import '../../../application/row/row_cache.dart'; import '../../application/cell/cell_service.dart';
import '../../../application/row/row_service.dart'; import '../../application/row/row_cache.dart';
import 'card_data_controller.dart'; import '../../application/row/row_service.dart';
part 'card_bloc.freezed.dart'; part 'card_bloc.freezed.dart';
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> { class CardBloc extends Bloc<BoardCardEvent, BoardCardState> {
final RowPB row;
final String groupFieldId; final String groupFieldId;
final RowBackendService _rowBackendSvc; final RowBackendService _rowBackendSvc;
final CardDataController _dataController; final RowCache _rowCache;
VoidCallback? _rowCallback;
BoardCardBloc({ CardBloc({
required this.row,
required this.groupFieldId, required this.groupFieldId,
required String viewId, required String viewId,
required CardDataController dataController, required RowCache rowCache,
required bool isEditing, required bool isEditing,
}) : _rowBackendSvc = RowBackendService( }) : _rowBackendSvc = RowBackendService(viewId: viewId),
viewId: viewId, _rowCache = rowCache,
),
_dataController = dataController,
super( super(
BoardCardState.initial( BoardCardState.initial(
dataController.rowPB, row,
_makeCells(groupFieldId, dataController.loadData()), _makeCells(groupFieldId, rowCache.loadGridCells(row.id)),
isEditing, isEditing,
), ),
) { ) {
@ -54,7 +56,10 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
@override @override
Future<void> close() async { Future<void> close() async {
_dataController.dispose(); if (_rowCallback != null) {
_rowCache.removeRowListener(_rowCallback!);
_rowCallback = null;
}
return super.close(); return super.close();
} }
@ -69,8 +74,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
} }
Future<void> _startListening() async { Future<void> _startListening() async {
_dataController.addListener( _rowCallback = _rowCache.addListener(
onRowChanged: (cellMap, reason) { rowId: row.id,
onCellUpdated: (cellMap, reason) {
if (!isClosed) { if (!isClosed) {
final cells = _makeCells(groupFieldId, cellMap); final cells = _makeCells(groupFieldId, cellMap);
add(BoardCardEvent.didReceiveCells(cells, reason)); add(BoardCardEvent.didReceiveCells(cells, reason));

View File

@ -2,83 +2,78 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../application/cell/cell_service.dart'; import '../../application/cell/cell_service.dart';
import 'board_cell.dart'; import 'cells/card_cell.dart';
import 'board_checkbox_cell.dart'; import 'cells/checkbox_card_cell.dart';
import 'board_checklist_cell.dart'; import 'cells/checklist_card_cell.dart';
import 'board_date_cell.dart'; import 'cells/date_card_cell.dart';
import 'board_number_cell.dart'; import 'cells/number_card_cell.dart';
import 'board_select_option_cell.dart'; import 'cells/select_option_card_cell.dart';
import 'board_text_cell.dart'; import 'cells/text_card_cell.dart';
import 'board_url_cell.dart'; import 'cells/url_card_cell.dart';
abstract class BoardCellBuilderDelegate { // T represents as the Generic card data
CellCache get cellCache; class CardCellBuilder<CustomCardData> {
} final CellCache cellCache;
class BoardCellBuilder { CardCellBuilder(this.cellCache);
final BoardCellBuilderDelegate delegate;
BoardCellBuilder(this.delegate); Widget buildCell({
CustomCardData? cardData,
Widget buildCell( required CellIdentifier cellId,
String groupId, EditableCardNotifier? cellNotifier,
CellIdentifier cellId, CardConfiguration<CustomCardData>? cardConfiguration,
EditableCellNotifier cellNotifier, }) {
) {
final cellControllerBuilder = CellControllerBuilder( final cellControllerBuilder = CellControllerBuilder(
cellId: cellId, cellId: cellId,
cellCache: delegate.cellCache, cellCache: cellCache,
); );
final key = cellId.key(); final key = cellId.key();
switch (cellId.fieldType) { switch (cellId.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
return BoardCheckboxCell( return CheckboxCardCell(
groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
key: key, key: key,
); );
case FieldType.DateTime: case FieldType.DateTime:
return BoardDateCell( return DateCardCell(
groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
key: key, key: key,
); );
case FieldType.SingleSelect: case FieldType.SingleSelect:
return BoardSelectOptionCell( return SelectOptionCardCell<CustomCardData>(
groupId: groupId, renderHook: cardConfiguration?.renderHook[FieldType.SingleSelect],
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
cardData: cardData,
key: key, key: key,
); );
case FieldType.MultiSelect: case FieldType.MultiSelect:
return BoardSelectOptionCell( return SelectOptionCardCell<CustomCardData>(
groupId: groupId, renderHook: cardConfiguration?.renderHook[FieldType.MultiSelect],
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
cardData: cardData,
editableNotifier: cellNotifier, editableNotifier: cellNotifier,
key: key, key: key,
); );
case FieldType.Checklist: case FieldType.Checklist:
return BoardChecklistCell( return ChecklistCardCell(
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
key: key, key: key,
); );
case FieldType.Number: case FieldType.Number:
return BoardNumberCell( return NumberCardCell(
groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
key: key, key: key,
); );
case FieldType.RichText: case FieldType.RichText:
return BoardTextCell( return TextCardCell(
groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
editableNotifier: cellNotifier, editableNotifier: cellNotifier,
key: key, key: key,
); );
case FieldType.URL: case FieldType.URL:
return BoardUrlCell( return URLCardCell(
groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
key: key, key: key,
); );

View File

@ -1,14 +1,39 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
abstract class FocusableBoardCell { typedef CellRenderHook<C, T> = Widget? Function(C cellData, T cardData);
set becomeFocus(bool isFocus); typedef RenderHookByFieldType<C> = Map<FieldType, CellRenderHook<dynamic, C>>;
class CardConfiguration<CustomCardData> {
final RenderHookByFieldType<CustomCardData> renderHook = {};
CardConfiguration();
void addSelectOptionHook(
CellRenderHook<List<SelectOptionPB>, CustomCardData> hook,
) {
selectOptionHook(cellData, cardData) {
if (cellData is List<SelectOptionPB>) {
hook(cellData, cardData);
}
}
renderHook[FieldType.SingleSelect] = selectOptionHook;
renderHook[FieldType.MultiSelect] = selectOptionHook;
}
} }
class EditableCellNotifier { abstract class CardCell<T> extends StatefulWidget {
final T? cardData;
const CardCell({super.key, this.cardData});
}
class EditableCardNotifier {
final ValueNotifier<bool> isCellEditing; final ValueNotifier<bool> isCellEditing;
EditableCellNotifier({bool isEditing = false}) EditableCardNotifier({bool isEditing = false})
: isCellEditing = ValueNotifier(isEditing); : isCellEditing = ValueNotifier(isEditing);
void dispose() { void dispose() {
@ -17,7 +42,7 @@ class EditableCellNotifier {
} }
class EditableRowNotifier { class EditableRowNotifier {
final Map<EditableCellId, EditableCellNotifier> _cells = {}; final Map<EditableCellId, EditableCardNotifier> _cells = {};
final ValueNotifier<bool> isEditing; final ValueNotifier<bool> isEditing;
EditableRowNotifier({required bool isEditing}) EditableRowNotifier({required bool isEditing})
@ -25,7 +50,7 @@ class EditableRowNotifier {
void bindCell( void bindCell(
CellIdentifier cellIdentifier, CellIdentifier cellIdentifier,
EditableCellNotifier notifier, EditableCardNotifier notifier,
) { ) {
assert( assert(
_cells.values.isEmpty, _cells.values.isEmpty,
@ -80,7 +105,7 @@ abstract class EditableCell {
// the row notifier receive its cells event. For example: begin editing the // the row notifier receive its cells event. For example: begin editing the
// cell or end editing the cell. // cell or end editing the cell.
// //
EditableCellNotifier? get editableNotifier; EditableCardNotifier? get editableNotifier;
} }
class EditableCellId { class EditableCellId {

View File

@ -1,33 +1,33 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/board/application/card/board_checkbox_cell_bloc.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class BoardCheckboxCell extends StatefulWidget { import '../bloc/checkbox_card_cell_bloc.dart';
final String groupId; import 'card_cell.dart';
class CheckboxCardCell extends CardCell {
final CellControllerBuilder cellControllerBuilder; final CellControllerBuilder cellControllerBuilder;
const BoardCheckboxCell({ const CheckboxCardCell({
required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
State<BoardCheckboxCell> createState() => _BoardCheckboxCellState(); State<CheckboxCardCell> createState() => _CheckboxCardCellState();
} }
class _BoardCheckboxCellState extends State<BoardCheckboxCell> { class _CheckboxCardCellState extends State<CheckboxCardCell> {
late BoardCheckboxCellBloc _cellBloc; late CheckboxCardCellBloc _cellBloc;
@override @override
void initState() { void initState() {
final cellController = final cellController =
widget.cellControllerBuilder.build() as CheckboxCellController; widget.cellControllerBuilder.build() as CheckboxCellController;
_cellBloc = BoardCheckboxCellBloc(cellController: cellController); _cellBloc = CheckboxCardCellBloc(cellController: cellController);
_cellBloc.add(const BoardCheckboxCellEvent.initial()); _cellBloc.add(const CheckboxCardCellEvent.initial());
super.initState(); super.initState();
} }
@ -35,7 +35,7 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>( child: BlocBuilder<CheckboxCardCellBloc, CheckboxCardCellState>(
buildWhen: (previous, current) => buildWhen: (previous, current) =>
previous.isSelected != current.isSelected, previous.isSelected != current.isSelected,
builder: (context, state) { builder: (context, state) {
@ -49,8 +49,8 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
icon: icon, icon: icon,
width: 20, width: 20,
onPressed: () => context onPressed: () => context
.read<BoardCheckboxCellBloc>() .read<CheckboxCardCellBloc>()
.add(const BoardCheckboxCellEvent.select()), .add(const CheckboxCardCellEvent.select()),
), ),
); );
}, },

View File

@ -1,27 +1,28 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../grid/application/cell/checklist_cell_bloc.dart'; import '../../../grid/application/cell/checklist_cell_bloc.dart';
import '../../../grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart'; import 'card_cell.dart';
class BoardChecklistCell extends StatefulWidget { class ChecklistCardCell extends CardCell {
final CellControllerBuilder cellControllerBuilder; final CellControllerBuilder cellControllerBuilder;
const BoardChecklistCell({required this.cellControllerBuilder, Key? key}) const ChecklistCardCell({required this.cellControllerBuilder, Key? key})
: super(key: key); : super(key: key);
@override @override
State<BoardChecklistCell> createState() => _BoardChecklistCellState(); State<ChecklistCardCell> createState() => _ChecklistCardCellState();
} }
class _BoardChecklistCellState extends State<BoardChecklistCell> { class _ChecklistCardCellState extends State<ChecklistCardCell> {
late ChecklistCellBloc _cellBloc; late ChecklistCardCellBloc _cellBloc;
@override @override
void initState() { void initState() {
final cellController = final cellController =
widget.cellControllerBuilder.build() as ChecklistCellController; widget.cellControllerBuilder.build() as ChecklistCellController;
_cellBloc = ChecklistCellBloc(cellController: cellController); _cellBloc = ChecklistCardCellBloc(cellController: cellController);
_cellBloc.add(const ChecklistCellEvent.initial()); _cellBloc.add(const ChecklistCellEvent.initial());
super.initState(); super.initState();
} }
@ -30,7 +31,7 @@ class _BoardChecklistCellState extends State<BoardChecklistCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>( child: BlocBuilder<ChecklistCardCellBloc, ChecklistCellState>(
builder: (context, state) => builder: (context, state) =>
ChecklistProgressBar(percent: state.percent), ChecklistProgressBar(percent: state.percent),
), ),

View File

@ -1,35 +1,34 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/board/application/card/board_date_cell_bloc.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'define.dart'; import '../bloc/date_card_cell_bloc.dart';
import '../define.dart';
import 'card_cell.dart';
class BoardDateCell extends StatefulWidget { class DateCardCell extends CardCell {
final String groupId;
final CellControllerBuilder cellControllerBuilder; final CellControllerBuilder cellControllerBuilder;
const BoardDateCell({ const DateCardCell({
required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
State<BoardDateCell> createState() => _BoardDateCellState(); State<DateCardCell> createState() => _DateCardCellState();
} }
class _BoardDateCellState extends State<BoardDateCell> { class _DateCardCellState extends State<DateCardCell> {
late BoardDateCellBloc _cellBloc; late DateCardCellBloc _cellBloc;
@override @override
void initState() { void initState() {
final cellController = final cellController =
widget.cellControllerBuilder.build() as DateCellController; widget.cellControllerBuilder.build() as DateCellController;
_cellBloc = BoardDateCellBloc(cellController: cellController) _cellBloc = DateCardCellBloc(cellController: cellController)
..add(const BoardDateCellEvent.initial()); ..add(const DateCardCellEvent.initial());
super.initState(); super.initState();
} }
@ -37,7 +36,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>( child: BlocBuilder<DateCardCellBloc, DateCardCellState>(
buildWhen: (previous, current) => previous.dateStr != current.dateStr, buildWhen: (previous, current) => previous.dateStr != current.dateStr,
builder: (context, state) { builder: (context, state) {
if (state.dateStr.isEmpty) { if (state.dateStr.isEmpty) {
@ -47,7 +46,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: BoardSizes.cardCellVPadding, vertical: CardSizes.cardCellVPadding,
), ),
child: FlowyText.regular( child: FlowyText.regular(
state.dateStr, state.dateStr,

View File

@ -0,0 +1,68 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/number_card_cell_bloc.dart';
import '../define.dart';
import 'card_cell.dart';
class NumberCardCell extends CardCell {
final CellControllerBuilder cellControllerBuilder;
const NumberCardCell({
required this.cellControllerBuilder,
Key? key,
}) : super(key: key);
@override
State<NumberCardCell> createState() => _NumberCardCellState();
}
class _NumberCardCellState extends State<NumberCardCell> {
late NumberCardCellBloc _cellBloc;
@override
void initState() {
final cellController =
widget.cellControllerBuilder.build() as NumberCellController;
_cellBloc = NumberCardCellBloc(cellController: cellController)
..add(const NumberCardCellEvent.initial());
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<NumberCardCellBloc, NumberCardCellState>(
buildWhen: (previous, current) => previous.content != current.content,
builder: (context, state) {
if (state.content.isEmpty) {
return const SizedBox();
} else {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: CardSizes.cardCellVPadding,
),
child: FlowyText.medium(
state.content,
fontSize: 14,
),
),
);
}
},
),
);
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
}

View File

@ -1,32 +1,35 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/extension.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../grid/presentation/widgets/cell/select_option_cell/extension.dart'; import '../bloc/select_option_card_cell_bloc.dart';
import '../../../grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart'; import 'card_cell.dart';
import '../../application/card/board_select_option_cell_bloc.dart';
import 'board_cell.dart';
class BoardSelectOptionCell extends StatefulWidget with EditableCell { class SelectOptionCardCell<T> extends CardCell<T> with EditableCell {
final String groupId;
final CellControllerBuilder cellControllerBuilder; final CellControllerBuilder cellControllerBuilder;
@override final CellRenderHook<List<SelectOptionPB>, T>? renderHook;
final EditableCellNotifier? editableNotifier;
const BoardSelectOptionCell({ @override
required this.groupId, final EditableCardNotifier? editableNotifier;
SelectOptionCardCell({
required this.cellControllerBuilder, required this.cellControllerBuilder,
required T? cardData,
this.renderHook,
this.editableNotifier, this.editableNotifier,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key, cardData: cardData);
@override @override
State<BoardSelectOptionCell> createState() => _BoardSelectOptionCellState(); State<SelectOptionCardCell> createState() => _SelectOptionCardCellState();
} }
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> { class _SelectOptionCardCellState extends State<SelectOptionCardCell> {
late BoardSelectOptionCellBloc _cellBloc; late SelectOptionCardCellBloc _cellBloc;
late PopoverController _popover; late PopoverController _popover;
@override @override
@ -34,8 +37,8 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
_popover = PopoverController(); _popover = PopoverController();
final cellController = final cellController =
widget.cellControllerBuilder.build() as SelectOptionCellController; widget.cellControllerBuilder.build() as SelectOptionCellController;
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController) _cellBloc = SelectOptionCardCellBloc(cellController: cellController)
..add(const BoardSelectOptionCellEvent.initial()); ..add(const SelectOptionCardCellEvent.initial());
super.initState(); super.initState();
} }
@ -43,12 +46,17 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>( child: BlocBuilder<SelectOptionCardCellBloc, SelectOptionCardCellState>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
return previous.selectedOptions != current.selectedOptions; return previous.selectedOptions != current.selectedOptions;
}, builder: (context, state) { }, builder: (context, state) {
// Returns SizedBox if the content of the cell is empty Widget? custom = widget.renderHook?.call(
if (_isEmpty(state)) return const SizedBox(); state.selectedOptions,
widget.cardData,
);
if (custom != null) {
return custom;
}
final children = state.selectedOptions.map( final children = state.selectedOptions.map(
(option) { (option) {
@ -73,14 +81,6 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
); );
} }
bool _isEmpty(BoardSelectOptionCellState state) {
// The cell should hide if the option id is equal to the groupId.
final isInGroup = state.selectedOptions
.where((element) => element.id == widget.groupId)
.isNotEmpty;
return isInGroup || state.selectedOptions.isEmpty;
}
Widget _wrapPopover(Widget child) { Widget _wrapPopover(Widget child) {
final constraints = BoxConstraints.loose(Size( final constraints = BoxConstraints.loose(Size(
SelectOptionCellEditor.editorPanelWidth, SelectOptionCellEditor.editorPanelWidth,

View File

@ -5,29 +5,27 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart';
import '../../application/card/board_text_cell_bloc.dart'; import '../bloc/text_card_cell_bloc.dart';
import 'board_cell.dart'; import '../define.dart';
import 'define.dart'; import 'card_cell.dart';
class BoardTextCell extends StatefulWidget with EditableCell { class TextCardCell extends CardCell with EditableCell {
final String groupId;
@override @override
final EditableCellNotifier? editableNotifier; final EditableCardNotifier? editableNotifier;
final CellControllerBuilder cellControllerBuilder; final CellControllerBuilder cellControllerBuilder;
const BoardTextCell({ const TextCardCell({
required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
this.editableNotifier, this.editableNotifier,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
State<BoardTextCell> createState() => _BoardTextCellState(); State<TextCardCell> createState() => _TextCardCellState();
} }
class _BoardTextCellState extends State<BoardTextCell> { class _TextCardCellState extends State<TextCardCell> {
late BoardTextCellBloc _cellBloc; late TextCardCellBloc _cellBloc;
late TextEditingController _controller; late TextEditingController _controller;
bool focusWhenInit = false; bool focusWhenInit = false;
final focusNode = SingleListenerFocusNode(); final focusNode = SingleListenerFocusNode();
@ -36,8 +34,8 @@ class _BoardTextCellState extends State<BoardTextCell> {
void initState() { void initState() {
final cellController = final cellController =
widget.cellControllerBuilder.build() as TextCellController; widget.cellControllerBuilder.build() as TextCellController;
_cellBloc = BoardTextCellBloc(cellController: cellController) _cellBloc = TextCardCellBloc(cellController: cellController)
..add(const BoardTextCellEvent.initial()); ..add(const TextCardCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content); _controller = TextEditingController(text: _cellBloc.state.content);
focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false; focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false;
if (focusWhenInit) { if (focusWhenInit) {
@ -51,7 +49,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
if (!focusNode.hasFocus) { if (!focusNode.hasFocus) {
focusWhenInit = false; focusWhenInit = false;
widget.editableNotifier?.isCellEditing.value = false; widget.editableNotifier?.isCellEditing.value = false;
_cellBloc.add(const BoardTextCellEvent.enableEdit(false)); _cellBloc.add(const TextCardCellEvent.enableEdit(false));
} }
}); });
_bindEditableNotifier(); _bindEditableNotifier();
@ -68,12 +66,12 @@ class _BoardTextCellState extends State<BoardTextCell> {
focusNode.requestFocus(); focusNode.requestFocus();
}); });
} }
_cellBloc.add(BoardTextCellEvent.enableEdit(isEditing)); _cellBloc.add(TextCardCellEvent.enableEdit(isEditing));
}); });
} }
@override @override
void didUpdateWidget(covariant BoardTextCell oldWidget) { void didUpdateWidget(covariant TextCardCell oldWidget) {
_bindEditableNotifier(); _bindEditableNotifier();
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
@ -82,13 +80,13 @@ class _BoardTextCellState extends State<BoardTextCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocListener<BoardTextCellBloc, BoardTextCellState>( child: BlocListener<TextCardCellBloc, TextCardCellState>(
listener: (context, state) { listener: (context, state) {
if (_controller.text != state.content) { if (_controller.text != state.content) {
_controller.text = state.content; _controller.text = state.content;
} }
}, },
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>( child: BlocBuilder<TextCardCellBloc, TextCardCellState>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
if (previous.content != current.content && if (previous.content != current.content &&
_controller.text == current.content && _controller.text == current.content &&
@ -120,7 +118,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
} }
Future<void> focusChanged() async { Future<void> focusChanged() async {
_cellBloc.add(BoardTextCellEvent.updateText(_controller.text)); _cellBloc.add(TextCardCellEvent.updateText(_controller.text));
} }
@override @override
@ -131,10 +129,10 @@ class _BoardTextCellState extends State<BoardTextCell> {
super.dispose(); super.dispose();
} }
Widget _buildText(BoardTextCellState state) { Widget _buildText(TextCardCellState state) {
return Padding( return Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: BoardSizes.cardCellVPadding, vertical: CardSizes.cardCellVPadding,
), ),
child: FlowyText.medium( child: FlowyText.medium(
state.content, state.content,
@ -156,7 +154,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
decoration: InputDecoration( decoration: InputDecoration(
// Magic number 4 makes the textField take up the same space as FlowyText // Magic number 4 makes the textField take up the same space as FlowyText
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
vertical: BoardSizes.cardCellVPadding + 4, vertical: CardSizes.cardCellVPadding + 4,
), ),
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,

View File

@ -4,32 +4,31 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart';
import '../../application/card/board_url_cell_bloc.dart'; import '../bloc/url_card_cell_bloc.dart';
import 'define.dart'; import '../define.dart';
import 'card_cell.dart';
class BoardUrlCell extends StatefulWidget { class URLCardCell extends CardCell {
final String groupId;
final CellControllerBuilder cellControllerBuilder; final CellControllerBuilder cellControllerBuilder;
const BoardUrlCell({ const URLCardCell({
required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
State<BoardUrlCell> createState() => _BoardUrlCellState(); State<URLCardCell> createState() => _URLCardCellState();
} }
class _BoardUrlCellState extends State<BoardUrlCell> { class _URLCardCellState extends State<URLCardCell> {
late BoardURLCellBloc _cellBloc; late URLCardCellBloc _cellBloc;
@override @override
void initState() { void initState() {
final cellController = final cellController =
widget.cellControllerBuilder.build() as URLCellController; widget.cellControllerBuilder.build() as URLCellController;
_cellBloc = BoardURLCellBloc(cellController: cellController); _cellBloc = URLCardCellBloc(cellController: cellController);
_cellBloc.add(const BoardURLCellEvent.initial()); _cellBloc.add(const URLCardCellEvent.initial());
super.initState(); super.initState();
} }
@ -37,7 +36,7 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>( child: BlocBuilder<URLCardCellBloc, URLCardCellState>(
buildWhen: (previous, current) => previous.content != current.content, buildWhen: (previous, current) => previous.content != current.content,
builder: (context, state) { builder: (context, state) {
if (state.content.isEmpty) { if (state.content.isEmpty) {
@ -47,7 +46,7 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: BoardSizes.cardCellVPadding, vertical: CardSizes.cardCellVPadding,
), ),
child: RichText( child: RichText(
textAlign: TextAlign.left, textAlign: TextAlign.left,

View File

@ -1,3 +1,3 @@
class BoardSizes { class CardSizes {
static double get cardCellVPadding => 6; static double get cardCellVPadding => 6;
} }

View File

@ -36,7 +36,7 @@ class DocumentService {
} }
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) { Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
final request = ViewIdPB(value: docId); final payload = ViewIdPB(value: docId);
return FolderEventCloseView(request).send(); return FolderEventCloseView(payload).send();
} }
} }

View File

@ -416,6 +416,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = { buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@ -549,6 +550,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = { buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@ -573,6 +575,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = { buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;

View File

@ -6,10 +6,11 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
import 'package:appflowy/plugins/database_view/board/application/board_data_controller.dart';
import 'package:appflowy/plugins/database_view/board/board.dart'; import 'package:appflowy/plugins/database_view/board/board.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
import 'package:appflowy/workspace/application/app/app_service.dart'; import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
@ -38,9 +39,14 @@ class AppFlowyBoardTest {
.then((result) { .then((result) {
return result.fold( return result.fold(
(view) async { (view) async {
final context = final context = BoardTestContext(
BoardTestContext(view, BoardDataController(view: view)); view,
final result = await context._boardDataController.openGrid(); DatabaseController(
view: view,
layoutType: LayoutTypePB.Board,
),
);
final result = await context._boardDataController.open();
result.fold((l) => null, (r) => throw Exception(r)); result.fold((l) => null, (r) => throw Exception(r));
return context; return context;
}, },
@ -62,7 +68,7 @@ Duration boardResponseDuration({int milliseconds = 200}) {
class BoardTestContext { class BoardTestContext {
final ViewPB gridView; final ViewPB gridView;
final BoardDataController _boardDataController; final DatabaseController _boardDataController;
BoardTestContext(this.gridView, this._boardDataController); BoardTestContext(this.gridView, this._boardDataController);
@ -108,7 +114,8 @@ class BoardTestContext {
final rowCache = _boardDataController.rowCache; final rowCache = _boardDataController.rowCache;
final rowDataController = RowDataController( final rowDataController = RowDataController(
rowInfo: rowInfo, viewId: rowInfo.viewId,
rowId: rowInfo.rowPB.id,
rowCache: rowCache, rowCache: rowCache,
); );

View File

@ -1,7 +1,8 @@
import 'package:appflowy/plugins/database_view/application/filter/filter_service.dart'; import 'package:appflowy/plugins/database_view/application/filter/filter_service.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database/checkbox_filter.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database/checkbox_filter.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database/text_filter.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/text_filter.pb.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -50,7 +51,10 @@ void main() {
test('filter rows with condition: text is empty', () async { test('filter rows with condition: text is empty', () async {
final context = await gridTest.createTestGrid(); final context = await gridTest.createTestGrid();
final service = FilterBackendService(viewId: context.gridView.id); final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(
view: context.gridView,
layoutType: LayoutTypePB.Grid,
);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
databaseController: gridController, databaseController: gridController,
@ -71,7 +75,10 @@ void main() {
() async { () async {
final context = await gridTest.createTestGrid(); final context = await gridTest.createTestGrid();
final service = FilterBackendService(viewId: context.gridView.id); final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(
view: context.gridView,
layoutType: LayoutTypePB.Grid,
);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
databaseController: gridController, databaseController: gridController,
@ -112,7 +119,10 @@ void main() {
final context = await gridTest.createTestGrid(); final context = await gridTest.createTestGrid();
final checkboxField = context.checkboxFieldContext(); final checkboxField = context.checkboxFieldContext();
final service = FilterBackendService(viewId: context.gridView.id); final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(
view: context.gridView,
layoutType: LayoutTypePB.Grid,
);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
databaseController: gridController, databaseController: gridController,
@ -131,7 +141,10 @@ void main() {
final context = await gridTest.createTestGrid(); final context = await gridTest.createTestGrid();
final checkboxField = context.checkboxFieldContext(); final checkboxField = context.checkboxFieldContext();
final service = FilterBackendService(viewId: context.gridView.id); final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(
view: context.gridView,
layoutType: LayoutTypePB.Grid,
);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
databaseController: gridController, databaseController: gridController,

View File

@ -1,6 +1,7 @@
import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/grid/grid.dart'; import 'package:appflowy/plugins/database_view/grid/grid.dart';
import 'package:appflowy/workspace/application/app/app_service.dart'; import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
import '../util.dart'; import '../util.dart';
@ -16,8 +17,13 @@ Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
.then((result) { .then((result) {
return result.fold( return result.fold(
(view) async { (view) async {
final context = GridTestContext(view, DatabaseController(view: view)); final context = GridTestContext(
final result = await context.gridController.openGrid(); view,
DatabaseController(
view: view,
layoutType: LayoutTypePB.Grid,
));
final result = await context.gridController.open();
await editCells(context); await editCells(context);
await gridResponseFuture(milliseconds: 500); await gridResponseFuture(milliseconds: 500);

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'util.dart'; import 'util.dart';
@ -20,7 +21,10 @@ void main() {
"create a row", "create a row",
build: () => GridBloc( build: () => GridBloc(
view: context.gridView, view: context.gridView,
databaseController: DatabaseController(view: context.gridView)) databaseController: DatabaseController(
view: context.gridView,
layoutType: LayoutTypePB.Grid,
))
..add(const GridEvent.initial()), ..add(const GridEvent.initial()),
act: (bloc) => bloc.add(const GridEvent.createRow()), act: (bloc) => bloc.add(const GridEvent.createRow()),
wait: const Duration(milliseconds: 300), wait: const Duration(milliseconds: 300),
@ -33,7 +37,10 @@ void main() {
"delete the last row", "delete the last row",
build: () => GridBloc( build: () => GridBloc(
view: context.gridView, view: context.gridView,
databaseController: DatabaseController(view: context.gridView)) databaseController: DatabaseController(
view: context.gridView,
layoutType: LayoutTypePB.Grid,
))
..add(const GridEvent.initial()), ..add(const GridEvent.initial()),
act: (bloc) async { act: (bloc) async {
await gridResponseFuture(); await gridResponseFuture();

View File

@ -6,12 +6,16 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/grid.dart'; import 'package:appflowy/plugins/database_view/grid/grid.dart';
import 'package:appflowy/workspace/application/app/app_service.dart'; import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
import 'package:dartz/dartz.dart';
import '../../util.dart'; import '../../util.dart';
@ -31,7 +35,7 @@ class GridTestContext {
return gridController.fieldController; return gridController.fieldController;
} }
Future<void> createRow() async { Future<Either<RowPB, FlowyError>> createRow() async {
return gridController.createRow(); return gridController.createRow();
} }
@ -69,7 +73,8 @@ class GridTestContext {
final rowCache = gridController.rowCache; final rowCache = gridController.rowCache;
final rowDataController = RowDataController( final rowDataController = RowDataController(
rowInfo: rowInfo, rowId: rowInfo.rowPB.id,
viewId: rowInfo.viewId,
rowCache: rowCache, rowCache: rowCache,
); );
@ -169,8 +174,13 @@ class AppFlowyGridTest {
.then((result) { .then((result) {
return result.fold( return result.fold(
(view) async { (view) async {
final context = GridTestContext(view, DatabaseController(view: view)); final context = GridTestContext(
final result = await context.gridController.openGrid(); view,
DatabaseController(
view: view,
layoutType: LayoutTypePB.Grid,
));
final result = await context.gridController.open();
result.fold((l) => null, (r) => throw Exception(r)); result.fold((l) => null, (r) => throw Exception(r));
return context; return context;
}, },

View File

@ -1,6 +1,4 @@
import { import {
CreateBoardCardPayloadPB,
DatabaseEventCreateBoardCard,
DatabaseEventCreateRow, DatabaseEventCreateRow,
DatabaseEventGetDatabase, DatabaseEventGetDatabase,
DatabaseEventGetFields, DatabaseEventGetFields,
@ -41,17 +39,12 @@ export class DatabaseBackendService {
return FolderEventCloseView(payload); return FolderEventCloseView(payload);
}; };
createRow = async (rowId?: string) => { createRow = async (rowId?: string, groupId?: string) => {
const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId ?? undefined }); const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId ?? undefined });
return DatabaseEventCreateRow(payload); if (groupId !== undefined) {
}; payload.group_id = groupId;
createGroupRow = async (groupId: string, startRowId?: string) => {
const payload = CreateBoardCardPayloadPB.fromObject({ view_id: this.viewId, group_id: groupId });
if (startRowId !== undefined) {
payload.start_row_id = startRowId;
} }
return DatabaseEventCreateBoardCard(payload); return DatabaseEventCreateRow(payload);
}; };
moveRow = (rowId: string, groupId?: string) => { moveRow = (rowId: string, groupId?: string) => {

View File

@ -94,7 +94,7 @@ export class DatabaseGroupController {
}; };
createRow = async () => { createRow = async () => {
return this.databaseBackendSvc.createGroupRow(this.group.group_id); return this.databaseBackendSvc.createRow(this.group.group_id);
}; };
subscribe = (callbacks: GroupDataCallbacks) => { subscribe = (callbacks: GroupDataCallbacks) => {

View File

@ -1,7 +1,7 @@
use crate::errors::{SyncError, SyncResult}; use crate::errors::{SyncError, SyncResult};
use database_model::{ use database_model::{
BuildDatabaseContext, DatabaseBlockMetaRevision, DatabaseBlockRevision, FieldRevision, BuildDatabaseContext, DatabaseBlockMetaRevision, DatabaseBlockRevision, FieldRevision,
RowRevision, LayoutSetting, RowRevision,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -30,6 +30,7 @@ impl DatabaseBuilder {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn add_field(&mut self, field: FieldRevision) { pub fn add_field(&mut self, field: FieldRevision) {
self.build_context.field_revs.push(Arc::new(field)); self.build_context.field_revs.push(Arc::new(field));
} }
@ -54,6 +55,10 @@ impl DatabaseBuilder {
&self.build_context.block_metas.first().unwrap().block_id &self.build_context.block_metas.first().unwrap().block_id
} }
pub fn set_layout_setting(&mut self, layout_setting: LayoutSetting) {
self.build_context.layout_setting = layout_setting;
}
pub fn build(self) -> BuildDatabaseContext { pub fn build(self) -> BuildDatabaseContext {
self.build_context self.build_context
} }

View File

@ -176,8 +176,8 @@ impl DatabaseRevisionPad {
T: Into<FieldTypeRevision>, T: Into<FieldTypeRevision>,
{ {
let new_field_type = new_field_type.into(); let new_field_type = new_field_type.into();
self.modify_database(|grid_meta| { self.modify_database(|database_rev| {
match grid_meta match database_rev
.fields .fields
.iter_mut() .iter_mut()
.find(|field_rev| field_rev.id == field_id) .find(|field_rev| field_rev.id == field_id)

View File

@ -312,7 +312,7 @@ impl DatabaseViewRevisionPad {
} }
/// updates the settings for the given layout type /// updates the settings for the given layout type
pub fn update_layout_setting<T>( pub fn set_layout_setting<T>(
&mut self, &mut self,
layout: &LayoutRevision, layout: &LayoutRevision,
settings: &T, settings: &T,
@ -364,14 +364,18 @@ pub struct DatabaseViewRevisionChangeset {
pub md5: String, pub md5: String,
} }
pub fn make_database_view_rev_json_str(grid_revision: &DatabaseViewRevision) -> SyncResult<String> { pub fn make_database_view_rev_json_str(
let json = serde_json::to_string(grid_revision).map_err(|err| { database_view_rev: &DatabaseViewRevision,
) -> SyncResult<String> {
let json = serde_json::to_string(database_view_rev).map_err(|err| {
internal_sync_error(format!("Serialize grid view to json str failed. {:?}", err)) internal_sync_error(format!("Serialize grid view to json str failed. {:?}", err))
})?; })?;
Ok(json) Ok(json)
} }
pub fn make_database_view_operations(grid_view: &DatabaseViewRevision) -> DatabaseViewOperations { pub fn make_database_view_operations(
let json = serde_json::to_string(grid_view).unwrap(); database_view_rev: &DatabaseViewRevision,
) -> DatabaseViewOperations {
let json = serde_json::to_string(database_view_rev).unwrap();
DatabaseViewOperationsBuilder::new().insert(&json).build() DatabaseViewOperationsBuilder::new().insert(&json).build()
} }

View File

@ -1,17 +1,15 @@
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use database_model::{CalendarLayout, CalendarLayoutSetting};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode; 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)] #[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf)]
pub struct CalendarSettingsPB { pub struct CalendarLayoutSettingsPB {
#[pb(index = 1)] #[pb(index = 1)]
pub view_id: String, pub layout_field_id: String,
#[pb(index = 2)] #[pb(index = 2)]
pub layout_ty: CalendarLayout, pub layout_ty: CalendarLayoutPB,
#[pb(index = 3)] #[pb(index = 3)]
pub first_day_of_week: i32, pub first_day_of_week: i32,
@ -23,51 +21,23 @@ pub struct CalendarSettingsPB {
pub show_week_numbers: bool, pub show_week_numbers: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] impl std::convert::From<CalendarLayoutSettingsPB> for CalendarLayoutSetting {
pub struct CalendarSettingsParams { fn from(pb: CalendarLayoutSettingsPB) -> Self {
pub(crate) view_id: String, CalendarLayoutSetting {
layout_ty: CalendarLayout, layout_ty: pb.layout_ty.into(),
first_day_of_week: i32, first_day_of_week: pb.first_day_of_week,
show_weekends: bool, show_weekends: pb.show_weekends,
show_week_numbers: bool, show_week_numbers: pb.show_week_numbers,
} layout_field_id: pb.layout_field_id,
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,
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 { impl std::convert::From<CalendarLayoutSetting> for CalendarLayoutSettingsPB {
type Error = ErrorCode; fn from(params: CalendarLayoutSetting) -> Self {
CalendarLayoutSettingsPB {
fn try_into(self) -> Result<CalendarSettingsParams, Self::Error> { layout_field_id: params.layout_field_id,
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?; layout_ty: params.layout_ty.into(),
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, first_day_of_week: params.first_day_of_week,
show_weekends: params.show_weekends, show_weekends: params.show_weekends,
show_week_numbers: params.show_week_numbers, show_week_numbers: params.show_week_numbers,
@ -75,11 +45,92 @@ impl std::convert::From<CalendarSettingsParams> for CalendarSettingsPB {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf_Enum, Serialize_repr, Deserialize_repr)] #[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf_Enum)]
#[repr(u8)] #[repr(u8)]
pub enum CalendarLayout { pub enum CalendarLayoutPB {
#[default] #[default]
MonthLayout = 0, MonthLayout = 0,
WeekLayout = 1, WeekLayout = 1,
DayLayout = 2, DayLayout = 2,
} }
impl std::convert::From<CalendarLayoutPB> for CalendarLayout {
fn from(pb: CalendarLayoutPB) -> Self {
match pb {
CalendarLayoutPB::MonthLayout => CalendarLayout::MonthLayout,
CalendarLayoutPB::WeekLayout => CalendarLayout::WeekLayout,
CalendarLayoutPB::DayLayout => CalendarLayout::DayLayout,
}
}
}
impl std::convert::From<CalendarLayout> for CalendarLayoutPB {
fn from(layout: CalendarLayout) -> Self {
match layout {
CalendarLayout::MonthLayout => CalendarLayoutPB::MonthLayout,
CalendarLayout::WeekLayout => CalendarLayoutPB::WeekLayout,
CalendarLayout::DayLayout => CalendarLayoutPB::DayLayout,
}
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct CalendarEventRequestPB {
#[pb(index = 1)]
pub view_id: String,
// Currently, requesting the events within the specified month
// is not supported
#[pb(index = 2)]
pub month: String,
}
#[derive(Debug, Clone, Default)]
pub struct CalendarEventRequestParams {
pub view_id: String,
pub month: String,
}
impl TryInto<CalendarEventRequestParams> for CalendarEventRequestPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CalendarEventRequestParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
Ok(CalendarEventRequestParams {
view_id: view_id.0,
month: self.month,
})
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct CalendarEventPB {
#[pb(index = 1)]
pub row_id: String,
#[pb(index = 2)]
pub title_field_id: String,
#[pb(index = 3)]
pub title: String,
#[pb(index = 4)]
pub timestamp: i64,
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct RepeatedCalendarEventPB {
#[pb(index = 1)]
pub items: Vec<CalendarEventPB>,
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct MoveCalendarEventPB {
#[pb(index = 1)]
pub row_id: String,
#[pb(index = 2)]
pub field_id: String,
#[pb(index = 3)]
pub timestamp: i64,
}

View File

@ -1,5 +1,5 @@
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::{FieldIdPB, RowPB}; use crate::entities::{FieldIdPB, LayoutTypePB, RowPB};
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
@ -195,3 +195,11 @@ impl TryInto<DatabaseGroupIdParams> for DatabaseGroupIdPB {
}) })
} }
} }
#[derive(Clone, ProtoBuf, Default, Debug)]
pub struct DatabaseLayoutIdPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub layout: LayoutTypePB,
}

View File

@ -1,5 +1,5 @@
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::{CreateRowParams, FieldType, LayoutTypePB, RowPB}; use crate::entities::{FieldType, RowPB};
use crate::services::group::Group; use crate::services::group::Group;
use database_model::{FieldTypeRevision, GroupConfigurationRevision}; use database_model::{FieldTypeRevision, GroupConfigurationRevision};
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
@ -7,41 +7,6 @@ use flowy_error::ErrorCode;
use std::convert::TryInto; use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct CreateBoardCardPayloadPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub group_id: String,
#[pb(index = 3, one_of)]
pub start_row_id: Option<String>,
}
impl TryInto<CreateRowParams> for CreateBoardCardPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateRowParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
let start_row_id = match self.start_row_id {
None => None,
Some(start_row_id) => Some(
NotEmptyStr::parse(start_row_id)
.map_err(|_| ErrorCode::RowIdIsEmpty)?
.0,
),
};
Ok(CreateRowParams {
view_id: view_id.0,
start_row_id,
group_id: Some(group_id.0),
layout: LayoutTypePB::Board,
})
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GroupConfigurationPB { pub struct GroupConfigurationPB {
#[pb(index = 1)] #[pb(index = 1)]

View File

@ -1,8 +1,9 @@
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::LayoutTypePB;
use database_model::RowRevision; use database_model::RowRevision;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
/// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row. /// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row.
@ -177,6 +178,18 @@ pub struct CreateRowPayloadPB {
#[pb(index = 2, one_of)] #[pb(index = 2, one_of)]
pub start_row_id: Option<String>, pub start_row_id: Option<String>,
#[pb(index = 3, one_of)]
pub group_id: Option<String>,
#[pb(index = 4, one_of)]
pub data: Option<RowDataPB>,
}
#[derive(ProtoBuf, Default)]
pub struct RowDataPB {
#[pb(index = 1)]
pub cell_data_by_field_id: HashMap<String, String>,
} }
#[derive(Default)] #[derive(Default)]
@ -184,7 +197,7 @@ pub struct CreateRowParams {
pub view_id: String, pub view_id: String,
pub start_row_id: Option<String>, pub start_row_id: Option<String>,
pub group_id: Option<String>, pub group_id: Option<String>,
pub layout: LayoutTypePB, pub cell_data_by_field_id: Option<HashMap<String, String>>,
} }
impl TryInto<CreateRowParams> for CreateRowPayloadPB { impl TryInto<CreateRowParams> for CreateRowPayloadPB {
@ -192,12 +205,20 @@ impl TryInto<CreateRowParams> for CreateRowPayloadPB {
fn try_into(self) -> Result<CreateRowParams, Self::Error> { fn try_into(self) -> Result<CreateRowParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?; let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
let start_row_id = match self.start_row_id {
None => None,
Some(start_row_id) => Some(
NotEmptyStr::parse(start_row_id)
.map_err(|_| ErrorCode::RowIdIsEmpty)?
.0,
),
};
Ok(CreateRowParams { Ok(CreateRowParams {
view_id: view_id.0, view_id: view_id.0,
start_row_id: self.start_row_id, start_row_id,
group_id: None, group_id: self.group_id,
layout: LayoutTypePB::Grid, cell_data_by_field_id: self.data.map(|data| data.cell_data_by_field_id),
}) })
} }
} }

View File

@ -1,11 +1,11 @@
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::{ use crate::entities::{
AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, CalendarSettingsPB, AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB,
DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, CalendarLayoutSettingsPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams,
DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, DeleteGroupPayloadPB, DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams,
RepeatedGroupConfigurationPB, RepeatedSortPB, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGroupConfigurationPB, RepeatedSortPB,
}; };
use database_model::LayoutRevision; use database_model::{CalendarLayoutSetting, LayoutRevision};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use std::convert::TryInto; use std::convert::TryInto;
@ -159,10 +159,42 @@ impl DatabaseSettingChangesetParams {
} }
} }
#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
pub struct UpdateLayoutSettingPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub layout_setting: LayoutSettingPB,
}
#[derive(Debug)]
pub struct UpdateLayoutSettingParams {
pub view_id: String,
pub layout_setting: LayoutSettingParams,
}
impl TryInto<UpdateLayoutSettingParams> for UpdateLayoutSettingPB {
type Error = ErrorCode;
fn try_into(self) -> Result<UpdateLayoutSettingParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id)
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
.0;
let layout_setting: LayoutSettingParams = self.layout_setting.into();
Ok(UpdateLayoutSettingParams {
view_id,
layout_setting,
})
}
}
#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)] #[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
pub struct LayoutSettingPB { pub struct LayoutSettingPB {
#[pb(index = 1, one_of)] #[pb(index = 1, one_of)]
pub calendar: Option<CalendarSettingsPB>, pub calendar: Option<CalendarLayoutSettingsPB>,
} }
impl LayoutSettingPB { impl LayoutSettingPB {
@ -170,3 +202,24 @@ impl LayoutSettingPB {
Self::default() Self::default()
} }
} }
impl std::convert::From<LayoutSettingParams> for LayoutSettingPB {
fn from(params: LayoutSettingParams) -> Self {
Self {
calendar: params.calendar.map(|calender| calender.into()),
}
}
}
impl std::convert::From<LayoutSettingPB> for LayoutSettingParams {
fn from(params: LayoutSettingPB) -> Self {
Self {
calendar: params.calendar.map(|calender| calender.into()),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct LayoutSettingParams {
pub calendar: Option<CalendarLayoutSetting>,
}

View File

@ -329,7 +329,7 @@ pub(crate) async fn move_row_handler(
} }
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn create_table_row_handler( pub(crate) async fn create_row_handler(
data: AFPluginData<CreateRowPayloadPB>, data: AFPluginData<CreateRowPayloadPB>,
manager: AFPluginState<Arc<DatabaseManager>>, manager: AFPluginState<Arc<DatabaseManager>>,
) -> DataResult<RowPB, FlowyError> { ) -> DataResult<RowPB, FlowyError> {
@ -549,17 +549,6 @@ pub(crate) async fn get_group_handler(
data_result_ok(group) data_result_ok(group)
} }
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn create_board_card_handler(
data: AFPluginData<CreateBoardCardPayloadPB>,
manager: AFPluginState<Arc<DatabaseManager>>,
) -> DataResult<RowPB, FlowyError> {
let params: CreateRowParams = data.into_inner().try_into()?;
let editor = manager.get_database_editor(params.view_id.as_ref()).await?;
let row = editor.create_row(params).await?;
data_result_ok(row)
}
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn move_group_handler( pub(crate) async fn move_group_handler(
data: AFPluginData<MoveGroupPayloadPB>, data: AFPluginData<MoveGroupPayloadPB>,
@ -599,23 +588,56 @@ pub(crate) async fn get_databases_handler(
} }
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn set_calendar_setting_handler( pub(crate) async fn set_layout_setting_handler(
data: AFPluginData<CalendarSettingsPB>, data: AFPluginData<UpdateLayoutSettingPB>,
manager: AFPluginState<Arc<DatabaseManager>>, manager: AFPluginState<Arc<DatabaseManager>>,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
let params: CalendarSettingsParams = data.into_inner().try_into()?; let params: UpdateLayoutSettingParams = data.into_inner().try_into()?;
let _ = manager.get_database_editor(params.view_id.as_ref()).await?; let database_editor = manager.get_database_editor(params.view_id.as_ref()).await?;
//TODO(nathan): database_editor
todo!("nathan: depends on the main branch refactoring") .set_layout_setting(&params.view_id, params.layout_setting)
.await?;
Ok(())
} }
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_calendar_setting_handler( pub(crate) async fn get_layout_setting_handler(
data: AFPluginData<DatabaseViewIdPB>, data: AFPluginData<DatabaseLayoutIdPB>,
manager: AFPluginState<Arc<DatabaseManager>>, manager: AFPluginState<Arc<DatabaseManager>>,
) -> FlowyResult<()> { ) -> DataResult<LayoutSettingPB, FlowyError> {
let view_id = data.into_inner().value; let params = data.into_inner();
let _ = manager.get_database_editor(view_id.as_ref()).await?; let database_editor = manager.get_database_editor(&params.view_id).await?;
//TODO(nathan): let layout_setting = database_editor
todo!("nathan: depends on the main branch refactoring") .get_layout_setting(&params.view_id, params.layout)
.await?;
data_result_ok(layout_setting.into())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_calendar_events_handler(
data: AFPluginData<CalendarEventRequestPB>,
manager: AFPluginState<Arc<DatabaseManager>>,
) -> DataResult<RepeatedCalendarEventPB, FlowyError> {
let params: CalendarEventRequestParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_editor(&params.view_id).await?;
let events = database_editor
.get_all_calendar_events(&params.view_id)
.await;
data_result_ok(RepeatedCalendarEventPB { items: events })
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_calendar_event_handler(
data: AFPluginData<RowIdPB>,
manager: AFPluginState<Arc<DatabaseManager>>,
) -> DataResult<CalendarEventPB, FlowyError> {
let params: RowIdParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_editor(&params.view_id).await?;
let event = database_editor
.get_calendar_event(&params.view_id, &params.row_id)
.await;
match event {
None => Err(FlowyError::record_not_found()),
Some(event) => data_result_ok(event),
}
} }

View File

@ -28,7 +28,7 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler) .event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
.event(DatabaseEvent::CreateTypeOption, create_field_type_option_data_handler) .event(DatabaseEvent::CreateTypeOption, create_field_type_option_data_handler)
// Row // Row
.event(DatabaseEvent::CreateRow, create_table_row_handler) .event(DatabaseEvent::CreateRow, create_row_handler)
.event(DatabaseEvent::GetRow, get_row_handler) .event(DatabaseEvent::GetRow, get_row_handler)
.event(DatabaseEvent::DeleteRow, delete_row_handler) .event(DatabaseEvent::DeleteRow, delete_row_handler)
.event(DatabaseEvent::DuplicateRow, duplicate_row_handler) .event(DatabaseEvent::DuplicateRow, duplicate_row_handler)
@ -44,7 +44,6 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
// Date // Date
.event(DatabaseEvent::UpdateDateCell, update_date_cell_handler) .event(DatabaseEvent::UpdateDateCell, update_date_cell_handler)
// Group // Group
.event(DatabaseEvent::CreateBoardCard, create_board_card_handler)
.event(DatabaseEvent::MoveGroup, move_group_handler) .event(DatabaseEvent::MoveGroup, move_group_handler)
.event(DatabaseEvent::MoveGroupRow, move_group_row_handler) .event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
.event(DatabaseEvent::GetGroups, get_groups_handler) .event(DatabaseEvent::GetGroups, get_groups_handler)
@ -52,8 +51,11 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
// Database // Database
.event(DatabaseEvent::GetDatabases, get_databases_handler) .event(DatabaseEvent::GetDatabases, get_databases_handler)
// Calendar // Calendar
.event(DatabaseEvent::SetCalenderSetting, set_calendar_setting_handler) .event(DatabaseEvent::GetAllCalendarEvents, get_calendar_events_handler)
.event(DatabaseEvent::GetCalendarSetting, get_calendar_setting_handler); .event(DatabaseEvent::GetCalendarEvent, get_calendar_event_handler)
// Layout setting
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler);
plugin plugin
} }
@ -227,9 +229,6 @@ pub enum DatabaseEvent {
#[event(input = "DatabaseGroupIdPB", output = "GroupPB")] #[event(input = "DatabaseGroupIdPB", output = "GroupPB")]
GetGroup = 101, GetGroup = 101,
#[event(input = "CreateBoardCardPayloadPB", output = "RowPB")]
CreateBoardCard = 110,
#[event(input = "MoveGroupPayloadPB")] #[event(input = "MoveGroupPayloadPB")]
MoveGroup = 111, MoveGroup = 111,
@ -242,9 +241,18 @@ pub enum DatabaseEvent {
#[event(output = "RepeatedDatabaseDescPB")] #[event(output = "RepeatedDatabaseDescPB")]
GetDatabases = 114, GetDatabases = 114,
#[event(input = "CalendarSettingsPB")] #[event(input = "UpdateLayoutSettingPB")]
SetCalenderSetting = 115, SetLayoutSetting = 115,
#[event()] #[event(input = "DatabaseLayoutIdPB", output = "LayoutSettingPB")]
GetCalendarSetting = 116, GetLayoutSetting = 116,
#[event(input = "CalendarEventRequestPB", output = "RepeatedCalendarEventPB")]
GetAllCalendarEvents = 117,
#[event(input = "RowIdPB", output = "CalendarEventPB")]
GetCalendarEvent = 118,
#[event(input = "MoveCalendarEventPB")]
MoveCalendarEvent = 119,
} }

View File

@ -371,6 +371,7 @@ pub async fn create_new_database(
block_metas, block_metas,
blocks, blocks,
database_view_data, database_view_data,
layout_setting,
} = build_context; } = build_context;
for block_meta_data in &blocks { for block_meta_data in &blocks {
@ -405,11 +406,14 @@ pub async fn create_new_database(
// Create database view // Create database view
tracing::trace!("Create new database view: {}", view_id); tracing::trace!("Create new database view: {}", view_id);
let database_view_rev = if database_view_data.is_empty() { let mut database_view_rev = if database_view_data.is_empty() {
DatabaseViewRevision::new(database_id, view_id.to_owned(), true, name, layout.into()) DatabaseViewRevision::new(database_id, view_id.to_owned(), true, name, layout.into())
} else { } else {
DatabaseViewRevision::from_json(database_view_data)? DatabaseViewRevision::from_json(database_view_data)?
}; };
tracing::trace!("Initial calendar layout setting: {:?}", layout_setting);
database_view_rev.layout_settings = layout_setting;
let database_view_ops = make_database_view_operations(&database_view_rev); let database_view_ops = make_database_view_operations(&database_view_rev);
let database_view_bytes = database_view_ops.json_bytes(); let database_view_bytes = database_view_ops.json_bytes();
let revision = Revision::initial_revision(view_id, database_view_bytes); let revision = Revision::initial_revision(view_id, database_view_bytes);

View File

@ -31,8 +31,12 @@ pub enum DatabaseNotification {
DidReorderSingleRow = 66, DidReorderSingleRow = 66,
/// Trigger when the settings of the database are changed /// Trigger when the settings of the database are changed
DidUpdateSettings = 70, DidUpdateSettings = 70,
DidUpdateCalendarSettings = 80, // Trigger when the layout setting of the database is updated
DidArrangeCalendarWithNewField = 81, DidUpdateLayoutSettings = 80,
// Trigger when the layout field of the database is changed
DidSetNewLayoutField = 81,
DidArrangeCalendarWithNewField = 82,
} }
impl std::default::Default for DatabaseNotification { impl std::default::Default for DatabaseNotification {

View File

@ -425,7 +425,9 @@ impl DatabaseEditor {
} }
pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult<RowPB> { pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult<RowPB> {
let mut row_rev = self.create_row_rev().await?; let mut row_rev = self
.create_row_rev(params.cell_data_by_field_id.clone())
.await?;
self self
.database_views .database_views
@ -915,6 +917,7 @@ impl DatabaseEditor {
field_revs: duplicated_fields.into_iter().map(Arc::new).collect(), field_revs: duplicated_fields.into_iter().map(Arc::new).collect(),
block_metas: duplicated_blocks, block_metas: duplicated_blocks,
blocks: blocks_meta_data, blocks: blocks_meta_data,
layout_setting: Default::default(),
database_view_data, database_view_data,
}) })
} }
@ -929,12 +932,64 @@ impl DatabaseEditor {
self.database_views.get_group(view_id, group_id).await self.database_views.get_group(view_id, group_id).await
} }
async fn create_row_rev(&self) -> FlowyResult<RowRevision> { pub async fn get_layout_setting<T: Into<LayoutRevision>>(
&self,
view_id: &str,
layout_ty: T,
) -> FlowyResult<LayoutSettingParams> {
let layout_ty = layout_ty.into();
self
.database_views
.get_layout_setting(view_id, &layout_ty)
.await
}
pub async fn set_layout_setting(
&self,
view_id: &str,
layout_setting: LayoutSettingParams,
) -> FlowyResult<()> {
self
.database_views
.set_layout_setting(view_id, layout_setting)
.await
}
pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec<CalendarEventPB> {
match self.database_views.get_view_editor(view_id).await {
Ok(view_editor) => view_editor
.v_get_all_calendar_events()
.await
.unwrap_or_default(),
Err(err) => {
tracing::error!("Get calendar event failed: {}", err);
vec![]
},
}
}
#[tracing::instrument(level = "trace", skip(self))]
pub async fn get_calendar_event(&self, view_id: &str, row_id: &str) -> Option<CalendarEventPB> {
let view_editor = self.database_views.get_view_editor(view_id).await.ok()?;
view_editor.v_get_calendar_event(row_id).await
}
async fn create_row_rev(
&self,
cell_data_by_field_id: Option<HashMap<String, String>>,
) -> FlowyResult<RowRevision> {
let field_revs = self.database_pad.read().await.get_field_revs(None)?; let field_revs = self.database_pad.read().await.get_field_revs(None)?;
let block_id = self.block_id().await?; let block_id = self.block_id().await?;
// insert empty row below the row whose id is upper_row_id // insert empty row below the row whose id is upper_row_id
let row_rev = RowRevisionBuilder::new(&block_id, field_revs).build(); let builder = match cell_data_by_field_id {
None => RowRevisionBuilder::new(&block_id, field_revs),
Some(cell_data_by_field_id) => {
RowRevisionBuilder::new_with_data(&block_id, field_revs, cell_data_by_field_id)
},
};
let row_rev = builder.build();
Ok(row_rev) Ok(row_rev)
} }

View File

@ -43,6 +43,16 @@ impl DatabaseViewData for DatabaseViewDataImpl {
to_fut(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) }) to_fut(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) })
} }
fn get_primary_field_rev(&self) -> Fut<Option<Arc<FieldRevision>>> {
let pad = self.pad.clone();
to_fut(async move {
let field_revs = pad.read().await.get_field_revs(None).ok()?;
field_revs
.into_iter()
.find(|field_rev| field_rev.is_primary)
})
}
fn index_of_row(&self, row_id: &str) -> Fut<Option<usize>> { fn index_of_row(&self, row_id: &str) -> Fut<Option<usize>> {
let block_manager = self.blocks.clone(); let block_manager = self.blocks.clone();
let row_id = row_id.to_owned(); let row_id = row_id.to_owned();

View File

@ -18,8 +18,9 @@ use crate::services::sort::{
DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType, DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType,
}; };
use database_model::{ use database_model::{
gen_database_filter_id, gen_database_id, gen_database_sort_id, FieldRevision, FieldTypeRevision, gen_database_filter_id, gen_database_id, gen_database_sort_id, CalendarLayoutSetting,
FilterRevision, LayoutRevision, RowChangeset, RowRevision, SortRevision, FieldRevision, FieldTypeRevision, FilterRevision, LayoutRevision, RowChangeset, RowRevision,
SortRevision,
}; };
use flowy_client_sync::client_database::{ use flowy_client_sync::client_database::{
make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad, make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad,
@ -32,6 +33,7 @@ use lib_infra::future::Fut;
use nanoid::nanoid; use nanoid::nanoid;
use revision_model::Revision; use revision_model::Revision;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap;
use std::future::Future; use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::{broadcast, RwLock}; use tokio::sync::{broadcast, RwLock};
@ -43,6 +45,8 @@ pub trait DatabaseViewData: Send + Sync + 'static {
/// Returns the field with the field_id /// Returns the field with the field_id
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>>; fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>>;
fn get_primary_field_rev(&self) -> Fut<Option<Arc<FieldRevision>>>;
/// Returns the index of the row with row_id /// Returns the index of the row with row_id
fn index_of_row(&self, row_id: &str) -> Fut<Option<usize>>; fn index_of_row(&self, row_id: &str) -> Fut<Option<usize>>;
@ -661,25 +665,84 @@ impl DatabaseViewEditor {
} }
/// Returns the current calendar settings /// Returns the current calendar settings
pub async fn v_get_calendar_settings(&self) -> FlowyResult<CalendarSettingsParams> { #[tracing::instrument(level = "debug", skip(self), err)]
let settings = self pub async fn v_get_layout_settings(
&self,
layout_ty: &LayoutRevision,
) -> FlowyResult<LayoutSettingParams> {
let mut layout_setting = LayoutSettingParams::default();
match layout_ty {
LayoutRevision::Grid => {},
LayoutRevision::Board => {},
LayoutRevision::Calendar => {
if let Some(calendar) = self
.pad .pad
.read() .read()
.await .await
.get_layout_setting(&LayoutRevision::Calendar) .get_layout_setting::<CalendarLayoutSetting>(layout_ty)
.unwrap_or_else(|| CalendarSettingsParams::default_with(self.view_id.to_string())); {
Ok(settings) // Check the field exist or not
if let Some(field_rev) = self.delegate.get_field_rev(&calendar.layout_field_id).await {
let field_type: FieldType = field_rev.ty.into();
// Check the type of field is Datetime or not
if field_type == FieldType::DateTime {
layout_setting.calendar = Some(calendar);
}
}
}
},
}
tracing::debug!("{:?}", layout_setting);
Ok(layout_setting)
} }
/// Update the calendar settings and send the notification to refresh the UI /// Update the calendar settings and send the notification to refresh the UI
pub async fn v_update_calendar_settings( pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> {
&self,
params: CalendarSettingsParams,
) -> FlowyResult<()> {
// Maybe it needs no send notification to refresh the UI // Maybe it needs no send notification to refresh the UI
if let Some(new_calendar_setting) = params.calendar {
if let Some(field_rev) = self
.delegate
.get_field_rev(&new_calendar_setting.layout_field_id)
.await
{
let field_type: FieldType = field_rev.ty.into();
if field_type != FieldType::DateTime {
return Err(FlowyError::unexpect_calendar_field_type());
}
let layout_ty = LayoutRevision::Calendar;
let old_calender_setting = self.v_get_layout_settings(&layout_ty).await?.calendar;
self self
.modify(|pad| Ok(pad.update_layout_setting(&LayoutRevision::Calendar, &params)?)) .modify(|pad| Ok(pad.set_layout_setting(&layout_ty, &new_calendar_setting)?))
.await?; .await?;
let new_field_id = new_calendar_setting.layout_field_id.clone();
let layout_setting_pb: LayoutSettingPB = LayoutSettingParams {
calendar: Some(new_calendar_setting),
}
.into();
if let Some(old_calendar_setting) = old_calender_setting {
// compare the new layout field id is equal to old layout field id
// if not equal, send the DidSetNewLayoutField notification
// if equal, send the DidUpdateLayoutSettings notification
if old_calendar_setting.layout_field_id != new_field_id {
send_notification(&self.view_id, DatabaseNotification::DidSetNewLayoutField)
.payload(layout_setting_pb)
.send();
} else {
send_notification(&self.view_id, DatabaseNotification::DidUpdateLayoutSettings)
.payload(layout_setting_pb)
.send();
}
} else {
tracing::warn!("Calendar setting should not be empty")
}
}
}
Ok(()) Ok(())
} }
@ -772,6 +835,100 @@ impl DatabaseViewEditor {
get_cells_for_field(self.delegate.clone(), field_id).await get_cells_for_field(self.delegate.clone(), field_id).await
} }
pub async fn v_get_calendar_event(&self, row_id: &str) -> Option<CalendarEventPB> {
let layout_ty = LayoutRevision::Calendar;
let calendar_setting = self
.v_get_layout_settings(&layout_ty)
.await
.ok()?
.calendar?;
// Text
let primary_field = self.delegate.get_primary_field_rev().await?;
let text_cell = get_cell_for_row(self.delegate.clone(), &primary_field.id, row_id).await?;
// Date
let date_field = self
.delegate
.get_field_rev(&calendar_setting.layout_field_id)
.await?;
let date_cell = get_cell_for_row(self.delegate.clone(), &date_field.id, row_id).await?;
let title = text_cell
.into_text_field_cell_data()
.unwrap_or_default()
.into();
let timestamp = date_cell
.into_date_field_cell_data()
.unwrap_or_default()
.into();
Some(CalendarEventPB {
row_id: row_id.to_string(),
title_field_id: primary_field.id.clone(),
title,
timestamp,
})
}
pub async fn v_get_all_calendar_events(&self) -> Option<Vec<CalendarEventPB>> {
let layout_ty = LayoutRevision::Calendar;
let calendar_setting = self
.v_get_layout_settings(&layout_ty)
.await
.ok()?
.calendar?;
// Text
let primary_field = self.delegate.get_primary_field_rev().await?;
let text_cells = self.v_get_cells_for_field(&primary_field.id).await.ok()?;
// Date
let timestamp_by_row_id = self
.v_get_cells_for_field(&calendar_setting.layout_field_id)
.await
.ok()?
.into_iter()
.map(|date_cell| {
let row_id = date_cell.row_id.clone();
// timestamp
let timestamp = date_cell
.into_date_field_cell_data()
.map(|date_cell_data| date_cell_data.0.unwrap_or_default())
.unwrap_or_default();
(row_id, timestamp)
})
.collect::<HashMap<String, i64>>();
let mut events: Vec<CalendarEventPB> = vec![];
for text_cell in text_cells {
let title_field_id = text_cell.field_id.clone();
let row_id = text_cell.row_id.clone();
let timestamp = timestamp_by_row_id
.get(&row_id)
.cloned()
.unwrap_or_default();
let title = text_cell
.into_text_field_cell_data()
.unwrap_or_default()
.into();
let event = CalendarEventPB {
row_id,
title_field_id,
title,
timestamp,
};
events.push(event);
}
Some(events)
}
async fn notify_did_update_setting(&self) { async fn notify_did_update_setting(&self) {
let setting = self.v_get_setting().await; let setting = self.v_get_setting().await;
send_notification(&self.view_id, DatabaseNotification::DidUpdateSettings) send_notification(&self.view_id, DatabaseNotification::DidUpdateSettings)
@ -851,12 +1008,38 @@ impl DatabaseViewEditor {
} }
} }
} }
/// Returns the list of cells corresponding to the given field.
pub(crate) async fn get_cell_for_row(
delegate: Arc<dyn DatabaseViewData>,
field_id: &str,
row_id: &str,
) -> Option<RowSingleCellData> {
let (_, row_rev) = delegate.get_row_rev(row_id).await?;
let mut cells = get_cells_for_field_in_rows(delegate, field_id, vec![row_rev])
.await
.ok()?;
if cells.is_empty() {
None
} else {
assert_eq!(cells.len(), 1);
Some(cells.remove(0))
}
}
// Returns the list of cells corresponding to the given field.
pub(crate) async fn get_cells_for_field( pub(crate) async fn get_cells_for_field(
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewData>,
field_id: &str, field_id: &str,
) -> FlowyResult<Vec<RowSingleCellData>> { ) -> FlowyResult<Vec<RowSingleCellData>> {
let row_revs = delegate.get_row_revs(None).await; let row_revs = delegate.get_row_revs(None).await;
get_cells_for_field_in_rows(delegate, field_id, row_revs).await
}
pub(crate) async fn get_cells_for_field_in_rows(
delegate: Arc<dyn DatabaseViewData>,
field_id: &str,
row_revs: Vec<Arc<RowRevision>>,
) -> FlowyResult<Vec<RowSingleCellData>> {
let field_rev = delegate.get_field_rev(field_id).await.unwrap(); let field_rev = delegate.get_field_rev(field_id).await.unwrap();
let field_type: FieldType = field_rev.ty.into(); let field_type: FieldType = field_rev.ty.into();
let mut cells = vec![]; let mut cells = vec![];

View File

@ -1,8 +1,8 @@
#![allow(clippy::while_let_loop)] #![allow(clippy::while_let_loop)]
use crate::entities::{ use crate::entities::{
AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams, AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams,
DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, MoveGroupParams, DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, LayoutSettingParams,
RepeatedGroupPB, RowPB, MoveGroupParams, RepeatedGroupPB, RowPB,
}; };
use crate::manager::DatabaseUser; use crate::manager::DatabaseUser;
use crate::services::cell::AtomicCellDataCache; use crate::services::cell::AtomicCellDataCache;
@ -16,7 +16,9 @@ use crate::services::filter::FilterType;
use crate::services::persistence::rev_sqlite::{ use crate::services::persistence::rev_sqlite::{
SQLiteDatabaseRevisionSnapshotPersistence, SQLiteDatabaseViewRevisionPersistence, SQLiteDatabaseRevisionSnapshotPersistence, SQLiteDatabaseViewRevisionPersistence,
}; };
use database_model::{FieldRevision, FilterRevision, RowChangeset, RowRevision, SortRevision}; use database_model::{
FieldRevision, FilterRevision, LayoutRevision, RowChangeset, RowRevision, SortRevision,
};
use flowy_client_sync::client_database::DatabaseViewRevisionPad; use flowy_client_sync::client_database::DatabaseViewRevisionPad;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration}; use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration};
@ -207,6 +209,24 @@ impl DatabaseViews {
view_editor.v_get_group(group_id).await view_editor.v_get_group(group_id).await
} }
pub async fn get_layout_setting(
&self,
view_id: &str,
layout_ty: &LayoutRevision,
) -> FlowyResult<LayoutSettingParams> {
let view_editor = self.get_view_editor(view_id).await?;
view_editor.v_get_layout_settings(layout_ty).await
}
pub async fn set_layout_setting(
&self,
view_id: &str,
layout_setting: LayoutSettingParams,
) -> FlowyResult<()> {
let view_editor = self.get_view_editor(view_id).await?;
view_editor.v_set_layout_settings(layout_setting).await
}
pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
let view_editor = self.get_view_editor(&params.view_id).await?; let view_editor = self.get_view_editor(&params.view_id).await?;
view_editor.v_initialize_new_group(params).await view_editor.v_initialize_new_group(params).await

View File

@ -1,4 +1,4 @@
use crate::entities::{CalendarSettingsParams, DatabaseViewSettingPB, LayoutSettingPB}; use crate::entities::{DatabaseViewSettingPB, LayoutSettingPB};
use crate::services::database_view::{get_cells_for_field, DatabaseViewData}; use crate::services::database_view::{get_cells_for_field, DatabaseViewData};
use crate::services::field::RowSingleCellData; use crate::services::field::RowSingleCellData;
use crate::services::filter::{FilterController, FilterDelegate, FilterType}; use crate::services::filter::{FilterController, FilterDelegate, FilterType};
@ -7,8 +7,8 @@ use crate::services::row::DatabaseBlockRowRevision;
use crate::services::sort::{SortDelegate, SortType}; use crate::services::sort::{SortDelegate, SortType};
use bytes::Bytes; use bytes::Bytes;
use database_model::{ use database_model::{
FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, LayoutRevision, CalendarLayoutSetting, FieldRevision, FieldTypeRevision, FilterRevision,
RowRevision, SortRevision, GroupConfigurationRevision, LayoutRevision, RowRevision, SortRevision,
}; };
use flowy_client_sync::client_database::{DatabaseViewRevisionChangeset, DatabaseViewRevisionPad}; use flowy_client_sync::client_database::{DatabaseViewRevisionChangeset, DatabaseViewRevisionPad};
use flowy_client_sync::make_operations_from_revisions; use flowy_client_sync::make_operations_from_revisions;
@ -153,7 +153,7 @@ pub fn make_database_view_setting(
LayoutRevision::Board => {}, LayoutRevision::Board => {},
LayoutRevision::Calendar => { LayoutRevision::Calendar => {
layout_settings.calendar = view_pad layout_settings.calendar = view_pad
.get_layout_setting::<CalendarSettingsParams>(&layout_type) .get_layout_setting::<CalendarLayoutSetting>(&layout_type)
.map(|params| params.into()); .map(|params| params.into());
}, },
} }

View File

@ -11,6 +11,10 @@ pub const UNCHECK: &str = "No";
pub struct CheckboxCellData(String); pub struct CheckboxCellData(String);
impl CheckboxCellData { impl CheckboxCellData {
pub fn into_inner(self) -> bool {
self.is_check()
}
pub fn is_check(&self) -> bool { pub fn is_check(&self) -> bool {
self.0 == CHECK self.0 == CHECK
} }

View File

@ -1,8 +1,10 @@
use crate::services::cell::{ use crate::services::cell::{
insert_checkbox_cell, insert_date_cell, insert_number_cell, insert_select_option_cell, insert_checkbox_cell, insert_date_cell, insert_number_cell, insert_select_option_cell,
insert_text_cell, insert_url_cell, insert_text_cell, insert_url_cell, FromCellString,
}; };
use crate::entities::FieldType;
use crate::services::field::{CheckboxCellData, SelectOptionIds};
use database_model::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; use database_model::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
use indexmap::IndexMap; use indexmap::IndexMap;
use std::collections::HashMap; use std::collections::HashMap;
@ -16,7 +18,15 @@ pub struct RowRevisionBuilder {
impl RowRevisionBuilder { impl RowRevisionBuilder {
pub fn new(block_id: &str, fields: Vec<Arc<FieldRevision>>) -> Self { pub fn new(block_id: &str, fields: Vec<Arc<FieldRevision>>) -> Self {
let field_rev_map = fields Self::new_with_data(block_id, fields, Default::default())
}
pub fn new_with_data(
block_id: &str,
field_revs: Vec<Arc<FieldRevision>>,
cell_data_by_field_id: HashMap<String, String>,
) -> Self {
let field_rev_map = field_revs
.iter() .iter()
.map(|field| (field.id.clone(), field.clone())) .map(|field| (field.id.clone(), field.clone()))
.collect::<HashMap<String, Arc<FieldRevision>>>(); .collect::<HashMap<String, Arc<FieldRevision>>>();
@ -29,12 +39,49 @@ impl RowRevisionBuilder {
}; };
let block_id = block_id.to_string(); let block_id = block_id.to_string();
let mut builder = Self {
Self {
block_id, block_id,
field_rev_map, field_rev_map,
payload, payload,
};
for (field_id, cell_data) in cell_data_by_field_id {
if let Some(field_rev) = builder.field_rev_map.get(&field_id) {
let field_type: FieldType = field_rev.ty.into();
match field_type {
FieldType::RichText => builder.insert_text_cell(&field_id, cell_data),
FieldType::Number => {
if let Ok(num) = cell_data.parse::<i64>() {
builder.insert_date_cell(&field_id, num)
} }
},
FieldType::DateTime => {
if let Ok(timestamp) = cell_data.parse::<i64>() {
builder.insert_date_cell(&field_id, timestamp)
}
},
FieldType::MultiSelect | FieldType::SingleSelect => {
if let Ok(ids) = SelectOptionIds::from_cell_str(&cell_data) {
builder.insert_select_option_cell(&field_id, ids.into_inner());
}
},
FieldType::Checkbox => {
if let Ok(value) = CheckboxCellData::from_cell_str(&cell_data) {
builder.insert_checkbox_cell(&field_id, value.into_inner());
}
},
FieldType::URL => {
builder.insert_url_cell(&field_id, cell_data);
},
FieldType::Checklist => {
if let Ok(ids) = SelectOptionIds::from_cell_str(&cell_data) {
builder.insert_select_option_cell(&field_id, ids.into_inner());
}
},
}
}
}
builder
} }
pub fn insert_text_cell(&mut self, field_id: &str, data: String) { pub fn insert_text_cell(&mut self, field_id: &str, data: String) {

View File

@ -1,7 +1,7 @@
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::services::field::*; use crate::services::field::*;
use crate::services::row::RowRevisionBuilder; use crate::services::row::RowRevisionBuilder;
use database_model::BuildDatabaseContext; use database_model::{BuildDatabaseContext, CalendarLayoutSetting, LayoutRevision, LayoutSetting};
use flowy_client_sync::client_database::DatabaseBuilder; use flowy_client_sync::client_database::DatabaseBuilder;
pub fn make_default_grid() -> BuildDatabaseContext { pub fn make_default_grid() -> BuildDatabaseContext {
@ -80,7 +80,7 @@ pub fn make_default_calendar() -> BuildDatabaseContext {
let mut database_builder = DatabaseBuilder::new(); let mut database_builder = DatabaseBuilder::new();
// text // text
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Description") .name("Title")
.visibility(true) .visibility(true)
.primary(true) .primary(true)
.build(); .build();
@ -92,6 +92,7 @@ pub fn make_default_calendar() -> BuildDatabaseContext {
.name("Date") .name("Date")
.visibility(true) .visibility(true)
.build(); .build();
let date_field_id = date_field.id.clone();
database_builder.add_field(date_field); database_builder.add_field(date_field);
// single select // single select
@ -101,6 +102,12 @@ pub fn make_default_calendar() -> BuildDatabaseContext {
.visibility(true) .visibility(true)
.build(); .build();
database_builder.add_field(multi_select_field); database_builder.add_field(multi_select_field);
let calendar_layout_setting = CalendarLayoutSetting::new(date_field_id);
let mut layout_setting = LayoutSetting::new();
let calendar_setting = serde_json::to_string(&calendar_layout_setting).unwrap();
layout_setting.insert(LayoutRevision::Calendar, calendar_setting);
database_builder.set_layout_setting(layout_setting);
database_builder.build() database_builder.build()
} }

View File

@ -4,7 +4,7 @@ use crate::database::database_editor::DatabaseEditorTest;
use database_model::{ use database_model::{
DatabaseBlockMetaRevision, DatabaseBlockMetaRevisionChangeset, RowChangeset, RowRevision, DatabaseBlockMetaRevision, DatabaseBlockMetaRevisionChangeset, RowChangeset, RowRevision,
}; };
use flowy_database::entities::{CellIdParams, CreateRowParams, FieldType, LayoutTypePB, RowPB}; use flowy_database::entities::{CellIdParams, CreateRowParams, FieldType, RowPB};
use flowy_database::services::field::*; use flowy_database::services::field::*;
use flowy_database::services::row::DatabaseBlockRow; use flowy_database::services::row::DatabaseBlockRow;
use std::collections::HashMap; use std::collections::HashMap;
@ -81,7 +81,7 @@ impl DatabaseRowTest {
view_id: self.editor.database_id.clone(), view_id: self.editor.database_id.clone(),
start_row_id: None, start_row_id: None,
group_id: None, group_id: None,
layout: LayoutTypePB::Grid, cell_data_by_field_id: None,
}; };
let row_order = self.editor.create_row(params).await.unwrap(); let row_order = self.editor.create_row(params).await.unwrap();
self self

View File

@ -7,8 +7,6 @@ use flowy_database::services::field::{
use flowy_database::services::row::RowRevisionBuilder; use flowy_database::services::row::RowRevisionBuilder;
use std::sync::Arc; use std::sync::Arc;
use strum::EnumCount;
pub struct DatabaseRowTestBuilder { pub struct DatabaseRowTestBuilder {
field_revs: Vec<Arc<FieldRevision>>, field_revs: Vec<Arc<FieldRevision>>,
inner_builder: RowRevisionBuilder, inner_builder: RowRevisionBuilder,
@ -16,7 +14,6 @@ pub struct DatabaseRowTestBuilder {
impl DatabaseRowTestBuilder { impl DatabaseRowTestBuilder {
pub fn new(block_id: String, field_revs: Vec<Arc<FieldRevision>>) -> Self { pub fn new(block_id: String, field_revs: Vec<Arc<FieldRevision>>) -> Self {
assert_eq!(field_revs.len(), FieldType::COUNT);
let inner_builder = RowRevisionBuilder::new(&block_id, field_revs.clone()); let inner_builder = RowRevisionBuilder::new(&block_id, field_revs.clone());
Self { Self {
field_revs, field_revs,

View File

@ -33,6 +33,10 @@ impl DatabaseEditorTest {
Self::new(LayoutTypePB::Board).await Self::new(LayoutTypePB::Board).await
} }
pub async fn new_calendar() -> Self {
Self::new(LayoutTypePB::Calendar).await
}
pub async fn new(layout: LayoutTypePB) -> Self { pub async fn new(layout: LayoutTypePB) -> Self {
let sdk = FlowySDKTest::default(); let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
@ -64,10 +68,6 @@ impl DatabaseEditorTest {
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap(); let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
assert_eq!(block_meta_revs.len(), 1); assert_eq!(block_meta_revs.len(), 1);
// It seems like you should add the field in the make_test_grid() function.
// Because we assert the initialize count of the fields is equal to FieldType::COUNT.
assert_eq!(field_revs.len(), FieldType::COUNT);
let view_id = test.view.id; let view_id = test.view.id;
let app_id = test.app.id; let app_id = test.app.id;
Self { Self {

View File

@ -1,7 +1,7 @@
use crate::database::database_editor::DatabaseEditorTest; use crate::database::database_editor::DatabaseEditorTest;
use database_model::{FieldRevision, RowChangeset}; use database_model::{FieldRevision, RowChangeset};
use flowy_database::entities::{ use flowy_database::entities::{
CreateRowParams, FieldType, GroupPB, LayoutTypePB, MoveGroupParams, MoveGroupRowParams, RowPB, CreateRowParams, FieldType, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB,
}; };
use flowy_database::services::cell::{ use flowy_database::services::cell::{
delete_select_option_cell, insert_select_option_cell, insert_url_cell, delete_select_option_cell, insert_select_option_cell, insert_url_cell,
@ -130,7 +130,7 @@ impl DatabaseGroupTest {
view_id: self.view_id.clone(), view_id: self.view_id.clone(),
start_row_id: None, start_row_id: None,
group_id: Some(group.group_id.clone()), group_id: Some(group.group_id.clone()),
layout: LayoutTypePB::Board, cell_data_by_field_id: None,
}; };
let _ = self.editor.create_row(params).await.unwrap(); let _ = self.editor.create_row(params).await.unwrap();
}, },

View File

@ -0,0 +1,2 @@
mod script;
mod test;

View File

@ -0,0 +1,86 @@
use crate::database::database_editor::DatabaseEditorTest;
use database_model::{CalendarLayoutSetting, FieldRevision, LayoutRevision};
use flowy_database::entities::FieldType;
use std::sync::Arc;
pub enum LayoutScript {
AssertCalendarLayoutSetting { expected: CalendarLayoutSetting },
GetCalendarEvents,
}
pub struct DatabaseLayoutTest {
database_test: DatabaseEditorTest,
}
impl DatabaseLayoutTest {
pub async fn new_calendar() -> Self {
let database_test = DatabaseEditorTest::new_calendar().await;
Self { database_test }
}
pub async fn run_scripts(&mut self, scripts: Vec<LayoutScript>) {
for script in scripts {
self.run_script(script).await;
}
}
pub async fn get_first_date_field(&self) -> Arc<FieldRevision> {
self
.database_test
.get_first_field_rev(FieldType::DateTime)
.clone()
}
pub async fn run_script(&mut self, script: LayoutScript) {
match script {
LayoutScript::AssertCalendarLayoutSetting { expected } => {
let view_id = self.database_test.view_id.clone();
let layout_ty = LayoutRevision::Calendar;
let calendar_setting = self
.database_test
.editor
.get_layout_setting(&view_id, layout_ty)
.await
.unwrap()
.calendar
.unwrap();
assert_eq!(calendar_setting.layout_ty, expected.layout_ty);
assert_eq!(
calendar_setting.first_day_of_week,
expected.first_day_of_week
);
assert_eq!(calendar_setting.show_weekends, expected.show_weekends);
},
LayoutScript::GetCalendarEvents => {
let events = self
.database_test
.editor
.get_all_calendar_events(&self.database_test.view_id)
.await;
assert_eq!(events.len(), 5);
for (index, event) in events.into_iter().enumerate() {
if index == 0 {
assert_eq!(event.title, "A");
assert_eq!(event.timestamp, 1678090778);
}
if index == 1 {
assert_eq!(event.title, "B");
assert_eq!(event.timestamp, 1677917978);
}
if index == 2 {
assert_eq!(event.title, "C");
assert_eq!(event.timestamp, 1679213978);
}
if index == 4 {
assert_eq!(event.title, "E");
assert_eq!(event.timestamp, 1678695578);
}
}
},
}
}
}

View File

@ -0,0 +1,21 @@
use crate::database::layout_test::script::DatabaseLayoutTest;
use crate::database::layout_test::script::LayoutScript::*;
use database_model::CalendarLayoutSetting;
#[tokio::test]
async fn calendar_initial_layout_setting_test() {
let mut test = DatabaseLayoutTest::new_calendar().await;
let date_field = test.get_first_date_field().await;
let default_calendar_setting = CalendarLayoutSetting::new(date_field.id.clone());
let scripts = vec![AssertCalendarLayoutSetting {
expected: default_calendar_setting,
}];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn calendar_get_events_test() {
let mut test = DatabaseLayoutTest::new_calendar().await;
let scripts = vec![GetCalendarEvents];
test.run_scripts(scripts).await;
}

View File

@ -1,6 +1,104 @@
use database_model::BuildDatabaseContext; use crate::database::block_test::util::DatabaseRowTestBuilder;
use database_model::{BuildDatabaseContext, CalendarLayoutSetting, LayoutRevision, LayoutSetting};
use flowy_client_sync::client_database::DatabaseBuilder;
use flowy_database::entities::FieldType;
use flowy_database::services::field::{
DateTypeOptionBuilder, FieldBuilder, MultiSelectTypeOptionBuilder, RichTextTypeOptionBuilder,
};
use strum::IntoEnumIterator;
// Calendar unit test mock data // Calendar unit test mock data
pub fn make_test_calendar() -> BuildDatabaseContext { pub fn make_test_calendar() -> BuildDatabaseContext {
todo!() let mut database_builder = DatabaseBuilder::new();
// text
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Title")
.visibility(true)
.primary(true)
.build();
let _text_field_id = text_field.id.clone();
database_builder.add_field(text_field);
// date
let date_type_option = DateTypeOptionBuilder::default();
let date_field = FieldBuilder::new(date_type_option)
.name("Date")
.visibility(true)
.build();
let date_field_id = date_field.id.clone();
database_builder.add_field(date_field);
// single select
let multi_select_type_option = MultiSelectTypeOptionBuilder::default();
let multi_select_field = FieldBuilder::new(multi_select_type_option)
.name("Tags")
.visibility(true)
.build();
database_builder.add_field(multi_select_field);
let calendar_layout_setting = CalendarLayoutSetting::new(date_field_id);
let mut layout_setting = LayoutSetting::new();
let calendar_setting = serde_json::to_string(&calendar_layout_setting).unwrap();
layout_setting.insert(LayoutRevision::Calendar, calendar_setting);
database_builder.set_layout_setting(layout_setting);
for i in 0..5 {
let block_id = database_builder.block_id().to_owned();
let field_revs = database_builder.field_revs().clone();
let mut row_builder = DatabaseRowTestBuilder::new(block_id.clone(), field_revs);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::DateTime => row_builder.insert_date_cell("1678090778"),
_ => "".to_owned(),
};
}
},
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("B"),
FieldType::DateTime => row_builder.insert_date_cell("1677917978"),
_ => "".to_owned(),
};
}
},
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::DateTime => row_builder.insert_date_cell("1679213978"),
_ => "".to_owned(),
};
}
},
3 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("D"),
FieldType::DateTime => row_builder.insert_date_cell("1678695578"),
_ => "".to_owned(),
};
}
},
4 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("E"),
FieldType::DateTime => row_builder.insert_date_cell("1678695578"),
_ => "".to_owned(),
};
}
},
_ => {},
}
let row_rev = row_builder.build();
database_builder.add_row(row_rev);
}
database_builder.build()
} }

View File

@ -5,6 +5,7 @@ mod database_ref_test;
mod field_test; mod field_test;
mod filter_test; mod filter_test;
mod group_test; mod group_test;
mod layout_test;
mod snapshot_test; mod snapshot_test;
mod sort_test; mod sort_test;

View File

@ -186,6 +186,9 @@ pub enum ErrorCode {
#[error("Payload should not be empty")] #[error("Payload should not be empty")]
UnexpectedEmptyPayload = 60, UnexpectedEmptyPayload = 60,
#[error("Only the date type can be used in calendar")]
UnexpectedCalendarFieldType = 61,
} }
impl ErrorCode { impl ErrorCode {

View File

@ -82,6 +82,10 @@ impl FlowyError {
static_flowy_error!(field_record_not_found, ErrorCode::FieldRecordNotFound); static_flowy_error!(field_record_not_found, ErrorCode::FieldRecordNotFound);
static_flowy_error!(payload_none, ErrorCode::UnexpectedEmptyPayload); static_flowy_error!(payload_none, ErrorCode::UnexpectedEmptyPayload);
static_flowy_error!(http, ErrorCode::HttpError); static_flowy_error!(http, ErrorCode::HttpError);
static_flowy_error!(
unexpect_calendar_field_type,
ErrorCode::UnexpectedCalendarFieldType
);
} }
impl std::convert::From<ErrorCode> for FlowyError { impl std::convert::From<ErrorCode> for FlowyError {

View File

@ -1,8 +1,9 @@
use crate::DatabaseBlockRevision; use crate::{DatabaseBlockRevision, LayoutSetting};
use bytes::Bytes; use bytes::Bytes;
use indexmap::IndexMap; use indexmap::IndexMap;
use nanoid::nanoid; use nanoid::nanoid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::sync::Arc; use std::sync::Arc;
pub fn gen_database_id() -> String { pub fn gen_database_id() -> String {
@ -195,6 +196,7 @@ pub struct BuildDatabaseContext {
pub field_revs: Vec<Arc<FieldRevision>>, pub field_revs: Vec<Arc<FieldRevision>>,
pub block_metas: Vec<DatabaseBlockMetaRevision>, pub block_metas: Vec<DatabaseBlockMetaRevision>,
pub blocks: Vec<DatabaseBlockRevision>, pub blocks: Vec<DatabaseBlockRevision>,
pub layout_setting: LayoutSetting,
// String in JSON format. It can be deserialized into [GridViewRevision] // String in JSON format. It can be deserialized into [GridViewRevision]
pub database_view_data: String, pub database_view_data: String,
@ -223,3 +225,36 @@ impl std::convert::TryFrom<Bytes> for BuildDatabaseContext {
} }
pub type FieldTypeRevision = u8; pub type FieldTypeRevision = u8;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalendarLayoutSetting {
pub layout_ty: CalendarLayout,
pub first_day_of_week: i32,
pub show_weekends: bool,
pub show_week_numbers: bool,
pub layout_field_id: String,
}
impl CalendarLayoutSetting {
pub fn new(layout_field_id: String) -> Self {
CalendarLayoutSetting {
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,
layout_field_id,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum CalendarLayout {
#[default]
MonthLayout = 0,
WeekLayout = 1,
DayLayout = 2,
}
pub const DEFAULT_FIRST_DAY_OF_WEEK: i32 = 0;
pub const DEFAULT_SHOW_WEEKENDS: bool = true;
pub const DEFAULT_SHOW_WEEK_NUMBERS: bool = true;

View File

@ -47,8 +47,8 @@ pub struct DatabaseViewRevision {
pub layout: LayoutRevision, pub layout: LayoutRevision,
#[serde(default)] #[serde(default)]
#[serde(skip_serializing_if = "LayoutSettings::is_empty")] #[serde(skip_serializing_if = "LayoutSetting::is_empty")]
pub layout_settings: LayoutSettings, pub layout_settings: LayoutSetting,
#[serde(default)] #[serde(default)]
pub filters: FilterConfiguration, pub filters: FilterConfiguration,
@ -90,18 +90,23 @@ impl DatabaseViewRevision {
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct LayoutSettings { pub struct LayoutSetting {
#[serde(with = "indexmap::serde_seq")] #[serde(with = "indexmap::serde_seq")]
inner: IndexMap<LayoutRevision, String>, inner: IndexMap<LayoutRevision, String>,
} }
impl LayoutSettings { impl LayoutSetting {
pub fn new() -> Self {
Self {
inner: Default::default(),
}
}
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.inner.is_empty() self.inner.is_empty()
} }
} }
impl std::ops::Deref for LayoutSettings { impl std::ops::Deref for LayoutSetting {
type Target = IndexMap<LayoutRevision, String>; type Target = IndexMap<LayoutRevision, String>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -109,7 +114,7 @@ impl std::ops::Deref for LayoutSettings {
} }
} }
impl std::ops::DerefMut for LayoutSettings { impl std::ops::DerefMut for LayoutSetting {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner &mut self.inner
} }

View File

@ -62,15 +62,15 @@ impl fmt::Display for SyncError {
pub enum ErrorCode { pub enum ErrorCode {
DocumentIdInvalid = 0, DocumentIdInvalid = 0,
DocumentNotfound = 1, DocumentNotfound = 1,
UndoFail = 200, UndoFail = 2,
RedoFail = 201, RedoFail = 3,
OutOfBound = 202, OutOfBound = 4,
RevisionConflict = 203, RevisionConflict = 5,
RecordNotFound = 300, RecordNotFound = 6,
CannotDeleteThePrimaryField = 301, CannotDeleteThePrimaryField = 7,
UnexpectedEmptyRevision = 302, UnexpectedEmptyRevision = 8,
SerdeError = 999, SerdeError = 100,
InternalError = 1000, InternalError = 101,
} }
impl std::convert::From<lib_ot::errors::OTError> for SyncError { impl std::convert::From<lib_ot::errors::OTError> for SyncError {