mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
90da54d12f
commit
7106195d8a
@ -372,6 +372,7 @@
|
||||
},
|
||||
"calendar": {
|
||||
"menuName": "Calendar",
|
||||
"defaultNewCalendarTitle": "Untitled",
|
||||
"navigation": {
|
||||
"today": "Today",
|
||||
"jumpToday": "Jump to Today",
|
||||
@ -379,4 +380,4 @@
|
||||
"nextMonth": "Next Month"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,9 +2,9 @@ part of 'cell_service.dart';
|
||||
|
||||
typedef CellByFieldId = LinkedHashMap<String, CellIdentifier>;
|
||||
|
||||
class GridBaseCell {
|
||||
class DatabaseCell {
|
||||
dynamic object;
|
||||
GridBaseCell({
|
||||
DatabaseCell({
|
||||
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];
|
||||
if (map == null) {
|
||||
_cellDataByFieldId[key.fieldId] = {};
|
||||
|
@ -170,7 +170,7 @@ class CellController<T, D> extends Equatable {
|
||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
_cellDataLoader.loadData().then((data) {
|
||||
if (data != null) {
|
||||
_cellCache.insert(_cacheKey, GridBaseCell(object: data));
|
||||
_cellCache.insert(_cacheKey, DatabaseCell(object: data));
|
||||
} else {
|
||||
_cellCache.remove(_cacheKey);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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/group_changeset.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -20,25 +23,56 @@ class DatabaseBackendService {
|
||||
return DatabaseEventGetDatabase(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<RowPB, FlowyError>> createRow({Option<String>? startRowId}) {
|
||||
var payload = CreateRowPayloadPB.create()..viewId = viewId;
|
||||
startRowId?.fold(() => null, (id) => payload.startRowId = id);
|
||||
return DatabaseEventCreateRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(
|
||||
String groupId,
|
||||
Future<Either<RowPB, FlowyError>> createRow({
|
||||
String? startRowId,
|
||||
) {
|
||||
CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create()
|
||||
..viewId = viewId
|
||||
..groupId = groupId;
|
||||
|
||||
String? groupId,
|
||||
Map<String, String>? cellDataByFieldId,
|
||||
}) {
|
||||
var payload = CreateRowPayloadPB.create()..viewId = viewId;
|
||||
if (startRowId != null) {
|
||||
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(
|
||||
@ -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() {
|
||||
final request = ViewIdPB(value: viewId);
|
||||
return FolderEventCloseView(request).send();
|
||||
|
@ -11,20 +11,20 @@ import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart
|
||||
typedef GroupUpdateValue = Either<GroupChangesetPB, FlowyError>;
|
||||
typedef GroupByNewFieldValue = Either<List<GroupPB>, FlowyError>;
|
||||
|
||||
class BoardListener {
|
||||
class DatabaseGroupListener {
|
||||
final String viewId;
|
||||
PublishNotifier<GroupUpdateValue>? _groupUpdateNotifier = PublishNotifier();
|
||||
PublishNotifier<GroupByNewFieldValue>? _groupByNewFieldNotifier =
|
||||
PublishNotifier<GroupUpdateValue>? _numOfGroupsNotifier = PublishNotifier();
|
||||
PublishNotifier<GroupByNewFieldValue>? _groupByFieldNotifier =
|
||||
PublishNotifier();
|
||||
DatabaseNotificationListener? _listener;
|
||||
BoardListener(this.viewId);
|
||||
DatabaseGroupListener(this.viewId);
|
||||
|
||||
void start({
|
||||
required void Function(GroupUpdateValue) onBoardChanged,
|
||||
required void Function(GroupUpdateValue) onNumOfGroupsChanged,
|
||||
required void Function(GroupByNewFieldValue) onGroupByNewField,
|
||||
}) {
|
||||
_groupUpdateNotifier?.addPublishListener(onBoardChanged);
|
||||
_groupByNewFieldNotifier?.addPublishListener(onGroupByNewField);
|
||||
_numOfGroupsNotifier?.addPublishListener(onNumOfGroupsChanged);
|
||||
_groupByFieldNotifier?.addPublishListener(onGroupByNewField);
|
||||
_listener = DatabaseNotificationListener(
|
||||
objectId: viewId,
|
||||
handler: _handler,
|
||||
@ -38,16 +38,16 @@ class BoardListener {
|
||||
switch (ty) {
|
||||
case DatabaseNotification.DidUpdateGroups:
|
||||
result.fold(
|
||||
(payload) => _groupUpdateNotifier?.value =
|
||||
(payload) => _numOfGroupsNotifier?.value =
|
||||
left(GroupChangesetPB.fromBuffer(payload)),
|
||||
(error) => _groupUpdateNotifier?.value = right(error),
|
||||
(error) => _numOfGroupsNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
case DatabaseNotification.DidGroupByField:
|
||||
result.fold(
|
||||
(payload) => _groupByNewFieldNotifier?.value =
|
||||
(payload) => _groupByFieldNotifier?.value =
|
||||
left(GroupChangesetPB.fromBuffer(payload).initialGroups),
|
||||
(error) => _groupByNewFieldNotifier?.value = right(error),
|
||||
(error) => _groupByFieldNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
@ -57,10 +57,10 @@ class BoardListener {
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
_groupUpdateNotifier?.dispose();
|
||||
_groupUpdateNotifier = null;
|
||||
_numOfGroupsNotifier?.dispose();
|
||||
_numOfGroupsNotifier = null;
|
||||
|
||||
_groupByNewFieldNotifier?.dispose();
|
||||
_groupByNewFieldNotifier = null;
|
||||
_groupByFieldNotifier?.dispose();
|
||||
_groupByFieldNotifier = null;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ class RowCache {
|
||||
});
|
||||
}
|
||||
|
||||
void initializeRows(List<RowPB> rows) {
|
||||
void setInitialRows(List<RowPB> rows) {
|
||||
for (final row in rows) {
|
||||
final rowInfo = buildGridRow(row);
|
||||
_rowList.add(rowInfo);
|
||||
|
@ -5,24 +5,26 @@ import 'row_cache.dart';
|
||||
typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason);
|
||||
|
||||
class RowDataController {
|
||||
final RowInfo rowInfo;
|
||||
final String rowId;
|
||||
final String viewId;
|
||||
final List<VoidCallback> _onRowChangedListeners = [];
|
||||
final RowCache _rowCache;
|
||||
|
||||
get cellCache => _rowCache.cellCache;
|
||||
|
||||
RowDataController({
|
||||
required this.rowInfo,
|
||||
required this.rowId,
|
||||
required this.viewId,
|
||||
required RowCache rowCache,
|
||||
}) : _rowCache = rowCache;
|
||||
|
||||
CellByFieldId loadData() {
|
||||
return _rowCache.loadGridCells(rowInfo.rowPB.id);
|
||||
return _rowCache.loadGridCells(rowId);
|
||||
}
|
||||
|
||||
void addListener({OnRowChanged? onRowChanged}) {
|
||||
_onRowChangedListeners.add(_rowCache.addListener(
|
||||
rowId: rowInfo.rowPB.id,
|
||||
rowId: rowId,
|
||||
onCellUpdated: onRowChanged,
|
||||
));
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.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';
|
||||
|
||||
class RowBackendService {
|
||||
@ -44,52 +42,3 @@ class RowBackendService {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -13,25 +13,25 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../application/field/field_controller.dart';
|
||||
import '../../application/row/row_cache.dart';
|
||||
import '../../application/row/row_service.dart';
|
||||
import 'board_data_controller.dart';
|
||||
import '../../application/database_controller.dart';
|
||||
import 'group_controller.dart';
|
||||
|
||||
part 'board_bloc.freezed.dart';
|
||||
|
||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final BoardDataController _boardDataController;
|
||||
final DatabaseController _databaseController;
|
||||
late final AppFlowyBoardController boardController;
|
||||
final GroupBackendService _groupBackendSvc;
|
||||
final LinkedHashMap<String, GroupController> groupControllers =
|
||||
LinkedHashMap();
|
||||
|
||||
FieldController get fieldController => _boardDataController.fieldController;
|
||||
String get viewId => _boardDataController.viewId;
|
||||
FieldController get fieldController => _databaseController.fieldController;
|
||||
String get viewId => _databaseController.viewId;
|
||||
|
||||
BoardBloc({required ViewPB view})
|
||||
: _groupBackendSvc = GroupBackendService(viewId: view.id),
|
||||
_boardDataController = BoardDataController(view: view),
|
||||
: _databaseController = DatabaseController(
|
||||
view: view,
|
||||
layoutType: LayoutTypePB.Board,
|
||||
),
|
||||
super(BoardState.initial(view.id)) {
|
||||
boardController = AppFlowyBoardController(
|
||||
onMoveGroup: (
|
||||
@ -40,7 +40,10 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
toGroupId,
|
||||
toIndex,
|
||||
) {
|
||||
_moveGroup(fromGroupId, toGroupId);
|
||||
_databaseController.moveGroup(
|
||||
fromGroupId: fromGroupId,
|
||||
toGroupId: toGroupId,
|
||||
);
|
||||
},
|
||||
onMoveGroupItem: (
|
||||
groupId,
|
||||
@ -49,7 +52,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
) {
|
||||
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
|
||||
_moveRow(fromRow, groupId, toRow);
|
||||
if (fromRow != null) {
|
||||
_databaseController.moveRow(
|
||||
fromRow,
|
||||
toRow: toRow,
|
||||
groupId: groupId,
|
||||
);
|
||||
}
|
||||
},
|
||||
onMoveGroupItemToGroup: (
|
||||
fromGroupId,
|
||||
@ -59,7 +68,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
) {
|
||||
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
|
||||
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 {
|
||||
final startRowId = groupControllers[groupId]?.lastRow()?.id;
|
||||
final result = await _boardDataController.createBoardCard(
|
||||
groupId,
|
||||
final result = await _databaseController.createRow(
|
||||
groupId: groupId,
|
||||
startRowId: startRowId,
|
||||
);
|
||||
result.fold(
|
||||
@ -82,7 +97,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
},
|
||||
createHeaderRow: (String groupId) async {
|
||||
final result = await _boardDataController.createBoardCard(groupId);
|
||||
final result =
|
||||
await _databaseController.createRow(groupId: groupId);
|
||||
result.fold(
|
||||
(_) {},
|
||||
(err) => Log.error(err),
|
||||
@ -141,44 +157,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
|
||||
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
|
||||
Future<void> close() async {
|
||||
await _boardDataController.dispose();
|
||||
await _databaseController.dispose();
|
||||
for (final controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
@ -204,34 +187,36 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
|
||||
RowCache? getRowCache(String blockId) {
|
||||
return _boardDataController.rowCache;
|
||||
return _databaseController.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_boardDataController.addListener(
|
||||
onDatabaseChanged: (grid) {
|
||||
final onDatabaseChanged = DatabaseCallbacks(
|
||||
onDatabaseChanged: (database) {
|
||||
if (!isClosed) {
|
||||
add(BoardEvent.didReceiveGridUpdate(grid));
|
||||
add(BoardEvent.didReceiveGridUpdate(database));
|
||||
}
|
||||
},
|
||||
didLoadGroups: (groups) {
|
||||
);
|
||||
final onGroupChanged = GroupCallbacks(
|
||||
onGroupByField: (groups) {
|
||||
if (isClosed) return;
|
||||
initializeGroups(groups);
|
||||
add(BoardEvent.didReceiveGroups(groups));
|
||||
},
|
||||
onDeletedGroup: (groupIds) {
|
||||
onDeleteGroup: (groupIds) {
|
||||
if (isClosed) return;
|
||||
boardController.removeGroups(groupIds);
|
||||
},
|
||||
onInsertedGroup: (insertedGroup) {
|
||||
onInsertGroup: (insertGroups) {
|
||||
if (isClosed) return;
|
||||
final group = insertedGroup.group;
|
||||
final group = insertGroups.group;
|
||||
final newGroup = initializeGroupData(group);
|
||||
final controller = initializeGroupController(group);
|
||||
groupControllers[controller.group.groupId] = (controller);
|
||||
boardController.addGroup(newGroup);
|
||||
},
|
||||
onUpdatedGroup: (updatedGroups) {
|
||||
onUpdateGroup: (updatedGroups) {
|
||||
if (isClosed) return;
|
||||
for (final group in updatedGroups) {
|
||||
final columnController =
|
||||
@ -239,15 +224,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
columnController?.updateGroupName(group.desc);
|
||||
}
|
||||
},
|
||||
onError: (err) {
|
||||
Log.error(err);
|
||||
},
|
||||
onResetGroups: (groups) {
|
||||
if (isClosed) return;
|
||||
);
|
||||
|
||||
initializeGroups(groups);
|
||||
add(BoardEvent.didReceiveGroups(groups));
|
||||
},
|
||||
_databaseController.addListener(
|
||||
onDatabaseChanged: onDatabaseChanged,
|
||||
onGroupChanged: onGroupChanged,
|
||||
);
|
||||
}
|
||||
|
||||
@ -264,7 +245,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
|
||||
Future<void> _openGrid(Emitter<BoardState> emit) async {
|
||||
final result = await _boardDataController.openGrid();
|
||||
final result = await _databaseController.open();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.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);
|
||||
|
||||
@ -14,14 +18,14 @@ abstract class GroupControllerDelegate {
|
||||
|
||||
class GroupController {
|
||||
final GroupPB group;
|
||||
final GroupListener _listener;
|
||||
final SingleGroupListener _listener;
|
||||
final GroupControllerDelegate delegate;
|
||||
|
||||
GroupController({
|
||||
required String viewId,
|
||||
required this.group,
|
||||
required this.delegate,
|
||||
}) : _listener = GroupListener(group);
|
||||
}) : _listener = SingleGroupListener(group);
|
||||
|
||||
RowPB? rowAtIndex(int index) {
|
||||
if (index < group.rows.length) {
|
||||
@ -81,3 +85,45 @@ class GroupController {
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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/style_widget/text.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 '../../widgets/card/cells/card_cell.dart';
|
||||
import '../../widgets/card/card_cell_builder.dart';
|
||||
import '../application/board_bloc.dart';
|
||||
import '../application/card/card_data_controller.dart';
|
||||
import 'card/card.dart';
|
||||
import 'card/card_cell_builder.dart';
|
||||
import '../../widgets/card/card.dart';
|
||||
import 'toolbar/board_toolbar.dart';
|
||||
|
||||
class BoardPage extends StatelessWidget {
|
||||
@ -78,6 +78,7 @@ class BoardContent extends StatefulWidget {
|
||||
|
||||
class _BoardContentState extends State<BoardContent> {
|
||||
late AppFlowyBoardScrollController scrollManager;
|
||||
final cardConfiguration = CardConfiguration<String>();
|
||||
|
||||
final config = AppFlowyBoardConfig(
|
||||
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
@ -86,6 +87,16 @@ class _BoardContentState extends State<BoardContent> {
|
||||
@override
|
||||
void initState() {
|
||||
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();
|
||||
}
|
||||
|
||||
@ -225,15 +236,11 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(groupItem));
|
||||
|
||||
final cellCache = rowCache.cellCache;
|
||||
final fieldController = context.read<BoardBloc>().fieldController;
|
||||
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;
|
||||
context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => null,
|
||||
@ -247,13 +254,15 @@ class _BoardContentState extends State<BoardContent> {
|
||||
key: ValueKey(groupItemId),
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: BoardCard(
|
||||
child: Card<String>(
|
||||
row: rowPB,
|
||||
viewId: viewId,
|
||||
groupId: groupData.group.groupId,
|
||||
rowCache: rowCache,
|
||||
cardData: groupData.group.groupId,
|
||||
fieldId: groupItem.fieldInfo.id,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: cardController,
|
||||
configuration: cardConfiguration,
|
||||
openCard: (context) => _openCard(
|
||||
viewId,
|
||||
fieldController,
|
||||
@ -304,7 +313,8 @@ class _BoardContentState extends State<BoardContent> {
|
||||
);
|
||||
|
||||
final dataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
rowId: rowInfo.rowPB.id,
|
||||
viewId: rowInfo.viewId,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
|
@ -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/row/row_cache.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
@ -9,20 +10,24 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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';
|
||||
|
||||
class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
final CalendarDataController _databaseDataController;
|
||||
final EventController calendarEventsController = EventController();
|
||||
final DatabaseController _databaseController;
|
||||
|
||||
FieldController get fieldController =>
|
||||
_databaseDataController.fieldController;
|
||||
String get databaseId => _databaseDataController.databaseId;
|
||||
// Getters
|
||||
String get viewId => _databaseController.viewId;
|
||||
CellCache get cellCache => _databaseController.rowCache.cellCache;
|
||||
RowCache get rowCache => _databaseController.rowCache;
|
||||
|
||||
CalendarBloc({required ViewPB view})
|
||||
: _databaseDataController = CalendarDataController(view: view),
|
||||
: _databaseController = DatabaseController(
|
||||
view: view,
|
||||
layoutType: LayoutTypePB.Calendar,
|
||||
),
|
||||
super(CalendarState.initial(view.id)) {
|
||||
on<CalendarEvent>(
|
||||
(event, emit) async {
|
||||
@ -30,23 +35,57 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
initial: () async {
|
||||
_startListening();
|
||||
await _openDatabase(emit);
|
||||
_loadAllEvents();
|
||||
},
|
||||
didReceiveCalendarSettings: (CalendarSettingsPB settings) {
|
||||
didReceiveCalendarSettings: (CalendarLayoutSettingsPB settings) {
|
||||
emit(state.copyWith(settings: Some(settings)));
|
||||
},
|
||||
didReceiveDatabaseUpdate: (DatabasePB database) {
|
||||
emit(state.copyWith(database: Some(database)));
|
||||
},
|
||||
didReceiveError: (FlowyError error) {
|
||||
emit(state.copyWith(noneOrError: Some(error)));
|
||||
didLoadAllEvents: (events) {
|
||||
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 {
|
||||
final result = await _databaseDataController.openDatabase();
|
||||
final result = await _databaseController.open();
|
||||
result.fold(
|
||||
(database) => emit(
|
||||
state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))),
|
||||
@ -57,60 +96,144 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
);
|
||||
}
|
||||
|
||||
RowCache? getRowCache(String blockId) {
|
||||
return _databaseDataController.rowCache;
|
||||
}
|
||||
Future<void> _createEvent(DateTime date, String title) async {
|
||||
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);
|
||||
},
|
||||
);
|
||||
|
||||
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);
|
||||
result.fold(
|
||||
(newRow) => _loadEvent(newRow.id),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _initializeEvents(FieldPB dateField) {
|
||||
calendarEventsController.removeWhere((element) => true);
|
||||
Future<void> _loadEvent(String rowId) async {
|
||||
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) {
|
||||
// final event = CalendarEventData(
|
||||
// title: "",
|
||||
// date: row -> dateField -> value,
|
||||
// event: row,
|
||||
// );
|
||||
add(CalendarEvent.didLoadAllEvents(calendarEvents));
|
||||
}
|
||||
},
|
||||
(r) => Log.error(r),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// return event;
|
||||
// }).toList();
|
||||
CalendarEventData<CalendarCardData>? _calendarEventDataFromEventPB(
|
||||
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
|
||||
class CalendarEvent with _$CalendarEvent {
|
||||
const factory CalendarEvent.initial() = _InitialCalendar;
|
||||
const factory CalendarEvent.didReceiveCalendarSettings(
|
||||
CalendarSettingsPB settings) = _DidReceiveCalendarSettings;
|
||||
const factory CalendarEvent.didReceiveError(FlowyError error) =
|
||||
_DidReceiveError;
|
||||
CalendarLayoutSettingsPB settings) = _ReceiveCalendarSettings;
|
||||
const factory CalendarEvent.didLoadAllEvents(Events events) =
|
||||
_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) =
|
||||
_DidReceiveDatabaseUpdate;
|
||||
_ReceiveDatabaseUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -118,9 +241,9 @@ class CalendarState with _$CalendarState {
|
||||
const factory CalendarState({
|
||||
required String databaseId,
|
||||
required Option<DatabasePB> database,
|
||||
required Option<FieldPB> dateField,
|
||||
required Option<List<RowInfo>> unscheduledRows,
|
||||
required Option<CalendarSettingsPB> settings,
|
||||
required Events events,
|
||||
required Map<String, FieldInfo> fieldInfoByFieldId,
|
||||
required Option<CalendarLayoutSettingsPB> settings,
|
||||
required DatabaseLoadingState loadingState,
|
||||
required Option<FlowyError> noneOrError,
|
||||
}) = _CalendarState;
|
||||
@ -128,8 +251,8 @@ class CalendarState with _$CalendarState {
|
||||
factory CalendarState.initial(String databaseId) => CalendarState(
|
||||
database: none(),
|
||||
databaseId: databaseId,
|
||||
dateField: none(),
|
||||
unscheduledRows: none(),
|
||||
fieldInfoByFieldId: {},
|
||||
events: [],
|
||||
settings: none(),
|
||||
noneOrError: none(),
|
||||
loadingState: const _Loading(),
|
||||
@ -153,7 +276,8 @@ class CalendarEditingRow {
|
||||
});
|
||||
}
|
||||
|
||||
class CalendarData {
|
||||
final RowInfo rowInfo;
|
||||
CalendarData(this.rowInfo);
|
||||
class CalendarCardData {
|
||||
final CalendarEventPB event;
|
||||
final CellIdentifier cellId;
|
||||
CalendarCardData({required this.cellId, required this.event});
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -1,56 +1,85 @@
|
||||
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:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.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/hover.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: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/widgets/row/row_detail.dart';
|
||||
import 'layout/sizes.dart';
|
||||
import 'toolbar/calendar_toolbar.dart';
|
||||
|
||||
class CalendarPage extends StatelessWidget {
|
||||
const CalendarPage({super.key});
|
||||
class CalendarPage extends StatefulWidget {
|
||||
final ViewPB view;
|
||||
const CalendarPage({required this.view, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const CalendarContent();
|
||||
}
|
||||
State<CalendarPage> createState() => _CalendarPageState();
|
||||
}
|
||||
|
||||
class CalendarContent extends StatefulWidget {
|
||||
const CalendarContent({super.key});
|
||||
|
||||
@override
|
||||
State<CalendarContent> createState() => _CalendarContentState();
|
||||
}
|
||||
|
||||
class _CalendarContentState extends State<CalendarContent> {
|
||||
late EventController _eventController;
|
||||
class _CalendarPageState extends State<CalendarPage> {
|
||||
final _eventController = EventController<CalendarCardData>();
|
||||
GlobalKey<MonthViewState>? _calendarState;
|
||||
late CalendarBloc _calendarBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_eventController = EventController();
|
||||
_calendarState = GlobalKey<MonthViewState>();
|
||||
_calendarBloc = CalendarBloc(view: widget.view)
|
||||
..add(const CalendarEvent.initial());
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_calendarBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CalendarControllerProvider(
|
||||
controller: _eventController,
|
||||
child: Column(
|
||||
children: [
|
||||
// const _ToolbarBlocAdaptor(),
|
||||
_toolbar(),
|
||||
_buildCalendar(_eventController),
|
||||
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: [
|
||||
// const _ToolbarBlocAdaptor(),
|
||||
_toolbar(),
|
||||
_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 cellBackgroundColor = Theme.of(context).colorScheme.surface;
|
||||
String dayString = date.day == 1
|
||||
? DateFormat('MMM d', context.locale.toLanguageTag()).format(date)
|
||||
: date.day.toString();
|
||||
@ -137,8 +347,8 @@ class _CalendarContentState extends State<CalendarContent> {
|
||||
}
|
||||
if (!isInMonth) {
|
||||
dayTextColor = Theme.of(context).disabledColor;
|
||||
cellBackgroundColor = AFThemeExtension.of(context).lightGreyHover;
|
||||
}
|
||||
|
||||
Widget day = Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isToday ? Theme.of(context).colorScheme.primary : null,
|
||||
@ -151,12 +361,21 @@ class _CalendarContentState extends State<CalendarContent> {
|
||||
),
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: cellBackgroundColor,
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: day.padding(all: 6.0),
|
||||
),
|
||||
);
|
||||
return day;
|
||||
}
|
||||
}
|
||||
|
||||
class _CardEnterNotifier extends ChangeNotifier {
|
||||
bool _onEnter = false;
|
||||
|
||||
_CardEnterNotifier();
|
||||
|
||||
set onEnter(bool value) {
|
||||
if (_onEnter != value) {
|
||||
_onEnter = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
||||
|
@ -8,11 +8,12 @@ import 'checklist_cell_editor_bloc.dart';
|
||||
import 'select_option_service.dart';
|
||||
part 'checklist_cell_bloc.freezed.dart';
|
||||
|
||||
class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
class ChecklistCardCellBloc
|
||||
extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
final ChecklistCellController cellController;
|
||||
final SelectOptionBackendService _selectOptionSvc;
|
||||
void Function()? _onCellChangedFn;
|
||||
ChecklistCellBloc({
|
||||
ChecklistCardCellBloc({
|
||||
required this.cellController,
|
||||
}) : _selectOptionSvc =
|
||||
SelectOptionBackendService(cellId: cellController.cellId),
|
||||
|
@ -9,7 +9,7 @@ import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import '../../application/field/field_controller.dart';
|
||||
import 'grid_data_controller.dart';
|
||||
import '../../application/database_controller.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
part 'grid_bloc.freezed.dart';
|
||||
@ -66,10 +66,10 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
databaseController.addListener(
|
||||
onGridChanged: (grid) {
|
||||
final onDatabaseChanged = DatabaseCallbacks(
|
||||
onDatabaseChanged: (database) {
|
||||
if (!isClosed) {
|
||||
add(GridEvent.didReceiveGridUpdate(grid));
|
||||
add(GridEvent.didReceiveGridUpdate(database));
|
||||
}
|
||||
},
|
||||
onRowsChanged: (rowInfos, reason) {
|
||||
@ -83,10 +83,11 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
}
|
||||
},
|
||||
);
|
||||
databaseController.addListener(onDatabaseChanged: onDatabaseChanged);
|
||||
}
|
||||
|
||||
Future<void> _openGrid(Emitter<GridState> emit) async {
|
||||
final result = await databaseController.openGrid();
|
||||
final result = await databaseController.open();
|
||||
result.fold(
|
||||
(grid) {
|
||||
emit(
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
},
|
||||
deleteField: (_DeleteField value) {
|
||||
final fieldService = FieldBackendService(
|
||||
viewId: dataController.rowInfo.viewId,
|
||||
viewId: dataController.viewId,
|
||||
fieldId: value.fieldId,
|
||||
);
|
||||
fieldService.deleteField();
|
||||
|
@ -1,4 +1,5 @@
|
||||
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:flowy_infra_ui/flowy_infra_ui_web.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/filter/filter_menu_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 'grid_scroll.dart';
|
||||
import 'layout/layout.dart';
|
||||
@ -35,7 +36,10 @@ class GridPage extends StatefulWidget {
|
||||
required this.view,
|
||||
this.onDeleted,
|
||||
Key? key,
|
||||
}) : databaseController = DatabaseController(view: view),
|
||||
}) : databaseController = DatabaseController(
|
||||
view: view,
|
||||
layoutType: LayoutTypePB.Grid,
|
||||
),
|
||||
super(key: key);
|
||||
|
||||
final ViewPB view;
|
||||
@ -276,7 +280,8 @@ class _GridRowsState extends State<_GridRows> {
|
||||
final fieldController =
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
final dataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
rowId: rowInfo.rowPB.id,
|
||||
viewId: rowInfo.viewId,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
@ -308,7 +313,8 @@ class _GridRowsState extends State<_GridRows> {
|
||||
GridCellBuilder cellBuilder,
|
||||
) {
|
||||
final dataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
viewId: rowInfo.viewId,
|
||||
rowId: rowInfo.rowPB.id,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
|
@ -20,7 +20,7 @@ class GridChecklistCell extends GridCellWidget {
|
||||
}
|
||||
|
||||
class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
||||
late ChecklistCellBloc _cellBloc;
|
||||
late ChecklistCardCellBloc _cellBloc;
|
||||
late final PopoverController _popover;
|
||||
|
||||
@override
|
||||
@ -28,7 +28,7 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
||||
_popover = PopoverController();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as ChecklistCellController;
|
||||
_cellBloc = ChecklistCellBloc(cellController: cellController);
|
||||
_cellBloc = ChecklistCardCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const ChecklistCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
@ -54,7 +54,7 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
||||
onClose: () => widget.onCellEditing.value = false,
|
||||
child: Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
child: BlocBuilder<ChecklistCardCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) =>
|
||||
ChecklistProgressBar(percent: state.percent),
|
||||
),
|
||||
|
@ -66,7 +66,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
||||
Expanded(
|
||||
child: _PropertyList(
|
||||
cellBuilder: widget.cellBuilder,
|
||||
viewId: widget.dataController.rowInfo.viewId,
|
||||
viewId: widget.dataController.viewId,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -3,16 +3,16 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import '../../../application/cell/cell_controller_builder.dart';
|
||||
|
||||
part 'board_checkbox_cell_bloc.freezed.dart';
|
||||
part 'checkbox_card_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardCheckboxCellBloc
|
||||
extends Bloc<BoardCheckboxCellEvent, BoardCheckboxCellState> {
|
||||
class CheckboxCardCellBloc
|
||||
extends Bloc<CheckboxCardCellEvent, CheckboxCardCellState> {
|
||||
final CheckboxCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardCheckboxCellBloc({
|
||||
CheckboxCardCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardCheckboxCellState.initial(cellController)) {
|
||||
on<BoardCheckboxCellEvent>(
|
||||
}) : super(CheckboxCardCellState.initial(cellController)) {
|
||||
on<CheckboxCardCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
@ -43,7 +43,7 @@ class BoardCheckboxCellBloc
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardCheckboxCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
add(CheckboxCardCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -51,21 +51,21 @@ class BoardCheckboxCellBloc
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent {
|
||||
const factory BoardCheckboxCellEvent.initial() = _InitialCell;
|
||||
const factory BoardCheckboxCellEvent.select() = _Selected;
|
||||
const factory BoardCheckboxCellEvent.didReceiveCellUpdate(
|
||||
String cellContent) = _DidReceiveCellUpdate;
|
||||
class CheckboxCardCellEvent with _$CheckboxCardCellEvent {
|
||||
const factory CheckboxCardCellEvent.initial() = _InitialCell;
|
||||
const factory CheckboxCardCellEvent.select() = _Selected;
|
||||
const factory CheckboxCardCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellState with _$BoardCheckboxCellState {
|
||||
const factory BoardCheckboxCellState({
|
||||
class CheckboxCardCellState with _$CheckboxCardCellState {
|
||||
const factory CheckboxCardCellState({
|
||||
required bool isSelected,
|
||||
}) = _CheckboxCellState;
|
||||
|
||||
factory BoardCheckboxCellState.initial(TextCellController context) {
|
||||
return BoardCheckboxCellState(
|
||||
factory CheckboxCardCellState.initial(TextCellController context) {
|
||||
return CheckboxCardCellState(
|
||||
isSelected: _isSelected(context.getCellData()));
|
||||
}
|
||||
}
|
@ -5,15 +5,15 @@ import 'dart:async';
|
||||
|
||||
import '../../../application/cell/cell_controller_builder.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;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
BoardDateCellBloc({required this.cellController})
|
||||
: super(BoardDateCellState.initial(cellController)) {
|
||||
on<BoardDateCellEvent>(
|
||||
DateCardCellBloc({required this.cellController})
|
||||
: super(DateCardCellState.initial(cellController)) {
|
||||
on<DateCardCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () => _startListening(),
|
||||
@ -40,7 +40,7 @@ class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((data) {
|
||||
if (!isClosed) {
|
||||
add(BoardDateCellEvent.didReceiveCellUpdate(data));
|
||||
add(DateCardCellEvent.didReceiveCellUpdate(data));
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -48,24 +48,24 @@ class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardDateCellEvent with _$BoardDateCellEvent {
|
||||
const factory BoardDateCellEvent.initial() = _InitialCell;
|
||||
const factory BoardDateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
|
||||
class DateCardCellEvent with _$DateCardCellEvent {
|
||||
const factory DateCardCellEvent.initial() = _InitialCell;
|
||||
const factory DateCardCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardDateCellState with _$BoardDateCellState {
|
||||
const factory BoardDateCellState({
|
||||
class DateCardCellState with _$DateCardCellState {
|
||||
const factory DateCardCellState({
|
||||
required DateCellDataPB? data,
|
||||
required String dateStr,
|
||||
required FieldInfo fieldInfo,
|
||||
}) = _BoardDateCellState;
|
||||
}) = _DateCardCellState;
|
||||
|
||||
factory BoardDateCellState.initial(DateCellController context) {
|
||||
factory DateCardCellState.initial(DateCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
|
||||
return BoardDateCellState(
|
||||
return DateCardCellState(
|
||||
fieldInfo: context.fieldInfo,
|
||||
data: cellData,
|
||||
dateStr: _dateStrFromCellData(cellData),
|
@ -4,16 +4,16 @@ import 'dart:async';
|
||||
|
||||
import '../../../application/cell/cell_controller_builder.dart';
|
||||
|
||||
part 'board_number_cell_bloc.freezed.dart';
|
||||
part 'number_card_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardNumberCellBloc
|
||||
extends Bloc<BoardNumberCellEvent, BoardNumberCellState> {
|
||||
class NumberCardCellBloc
|
||||
extends Bloc<NumberCardCellEvent, NumberCardCellState> {
|
||||
final NumberCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardNumberCellBloc({
|
||||
NumberCardCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardNumberCellState.initial(cellController)) {
|
||||
on<BoardNumberCellEvent>(
|
||||
}) : super(NumberCardCellState.initial(cellController)) {
|
||||
on<NumberCardCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
@ -41,7 +41,7 @@ class BoardNumberCellBloc
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardNumberCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
add(NumberCardCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -49,20 +49,20 @@ class BoardNumberCellBloc
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardNumberCellEvent with _$BoardNumberCellEvent {
|
||||
const factory BoardNumberCellEvent.initial() = _InitialCell;
|
||||
const factory BoardNumberCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
class NumberCardCellEvent with _$NumberCardCellEvent {
|
||||
const factory NumberCardCellEvent.initial() = _InitialCell;
|
||||
const factory NumberCardCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardNumberCellState with _$BoardNumberCellState {
|
||||
const factory BoardNumberCellState({
|
||||
class NumberCardCellState with _$NumberCardCellState {
|
||||
const factory NumberCardCellState({
|
||||
required String content,
|
||||
}) = _BoardNumberCellState;
|
||||
}) = _NumberCardCellState;
|
||||
|
||||
factory BoardNumberCellState.initial(TextCellController context) =>
|
||||
BoardNumberCellState(
|
||||
factory NumberCardCellState.initial(TextCellController context) =>
|
||||
NumberCardCellState(
|
||||
content: context.getCellData() ?? "",
|
||||
);
|
||||
}
|
@ -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:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'board_select_option_cell_bloc.freezed.dart';
|
||||
part 'select_option_card_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardSelectOptionCellBloc
|
||||
extends Bloc<BoardSelectOptionCellEvent, BoardSelectOptionCellState> {
|
||||
class SelectOptionCardCellBloc
|
||||
extends Bloc<SelectOptionCardCellEvent, SelectOptionCardCellState> {
|
||||
final SelectOptionCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
BoardSelectOptionCellBloc({
|
||||
SelectOptionCardCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardSelectOptionCellState.initial(cellController)) {
|
||||
on<BoardSelectOptionCellEvent>(
|
||||
}) : super(SelectOptionCardCellState.initial(cellController)) {
|
||||
on<SelectOptionCardCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
@ -42,7 +42,7 @@ class BoardSelectOptionCellBloc
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((selectOptionContext) {
|
||||
if (!isClosed) {
|
||||
add(BoardSelectOptionCellEvent.didReceiveOptions(
|
||||
add(SelectOptionCardCellEvent.didReceiveOptions(
|
||||
selectOptionContext?.selectOptions ?? [],
|
||||
));
|
||||
}
|
||||
@ -52,23 +52,23 @@ class BoardSelectOptionCellBloc
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardSelectOptionCellEvent with _$BoardSelectOptionCellEvent {
|
||||
const factory BoardSelectOptionCellEvent.initial() = _InitialCell;
|
||||
const factory BoardSelectOptionCellEvent.didReceiveOptions(
|
||||
class SelectOptionCardCellEvent with _$SelectOptionCardCellEvent {
|
||||
const factory SelectOptionCardCellEvent.initial() = _InitialCell;
|
||||
const factory SelectOptionCardCellEvent.didReceiveOptions(
|
||||
List<SelectOptionPB> selectedOptions,
|
||||
) = _DidReceiveOptions;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardSelectOptionCellState with _$BoardSelectOptionCellState {
|
||||
const factory BoardSelectOptionCellState({
|
||||
class SelectOptionCardCellState with _$SelectOptionCardCellState {
|
||||
const factory SelectOptionCardCellState({
|
||||
required List<SelectOptionPB> selectedOptions,
|
||||
}) = _BoardSelectOptionCellState;
|
||||
}) = _SelectOptionCardCellState;
|
||||
|
||||
factory BoardSelectOptionCellState.initial(
|
||||
factory SelectOptionCardCellState.initial(
|
||||
SelectOptionCellController context) {
|
||||
final data = context.getCellData();
|
||||
return BoardSelectOptionCellState(
|
||||
return SelectOptionCardCellState(
|
||||
selectedOptions: data?.selectOptions ?? [],
|
||||
);
|
||||
}
|
@ -3,15 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
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;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardTextCellBloc({
|
||||
TextCardCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardTextCellState.initial(cellController)) {
|
||||
on<BoardTextCellEvent>(
|
||||
}) : super(TextCardCellState.initial(cellController)) {
|
||||
on<TextCardCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
@ -48,7 +48,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardTextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
add(TextCardCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -56,23 +56,23 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardTextCellEvent with _$BoardTextCellEvent {
|
||||
const factory BoardTextCellEvent.initial() = _InitialCell;
|
||||
const factory BoardTextCellEvent.updateText(String text) = _UpdateContent;
|
||||
const factory BoardTextCellEvent.enableEdit(bool enabled) = _EnableEdit;
|
||||
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
class TextCardCellEvent with _$TextCardCellEvent {
|
||||
const factory TextCardCellEvent.initial() = _InitialCell;
|
||||
const factory TextCardCellEvent.updateText(String text) = _UpdateContent;
|
||||
const factory TextCardCellEvent.enableEdit(bool enabled) = _EnableEdit;
|
||||
const factory TextCardCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardTextCellState with _$BoardTextCellState {
|
||||
const factory BoardTextCellState({
|
||||
class TextCardCellState with _$TextCardCellState {
|
||||
const factory TextCardCellState({
|
||||
required String content,
|
||||
required bool enableEdit,
|
||||
}) = _BoardTextCellState;
|
||||
}) = _TextCardCellState;
|
||||
|
||||
factory BoardTextCellState.initial(TextCellController context) =>
|
||||
BoardTextCellState(
|
||||
factory TextCardCellState.initial(TextCellController context) =>
|
||||
TextCardCellState(
|
||||
content: context.getCellData() ?? "",
|
||||
enableEdit: false,
|
||||
);
|
@ -4,15 +4,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
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;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardURLCellBloc({
|
||||
URLCardCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardURLCellState.initial(cellController)) {
|
||||
on<BoardURLCellEvent>(
|
||||
}) : super(URLCardCellState.initial(cellController)) {
|
||||
on<URLCardCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
@ -46,7 +46,7 @@ class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(BoardURLCellEvent.didReceiveCellUpdate(cellData));
|
||||
add(URLCardCellEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -54,23 +54,23 @@ class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellEvent with _$BoardURLCellEvent {
|
||||
const factory BoardURLCellEvent.initial() = _InitialCell;
|
||||
const factory BoardURLCellEvent.updateURL(String url) = _UpdateURL;
|
||||
const factory BoardURLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
class URLCardCellEvent with _$URLCardCellEvent {
|
||||
const factory URLCardCellEvent.initial() = _InitialCell;
|
||||
const factory URLCardCellEvent.updateURL(String url) = _UpdateURL;
|
||||
const factory URLCardCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellState with _$BoardURLCellState {
|
||||
const factory BoardURLCellState({
|
||||
class URLCardCellState with _$URLCardCellState {
|
||||
const factory URLCardCellState({
|
||||
required String content,
|
||||
required String url,
|
||||
}) = _BoardURLCellState;
|
||||
}) = _URLCardCellState;
|
||||
|
||||
factory BoardURLCellState.initial(URLCellController context) {
|
||||
factory URLCardCellState.initial(URLCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
return BoardURLCellState(
|
||||
return URLCardCellState(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
);
|
@ -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:flutter/material.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';
|
||||
|
||||
class BoardNumberCell extends StatefulWidget {
|
||||
@ -16,19 +16,19 @@ class BoardNumberCell extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardNumberCell> createState() => _BoardNumberCellState();
|
||||
State<BoardNumberCell> createState() => _NumberCardCellState();
|
||||
}
|
||||
|
||||
class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
late BoardNumberCellBloc _cellBloc;
|
||||
class _NumberCardCellState extends State<BoardNumberCell> {
|
||||
late NumberCardCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as NumberCellController;
|
||||
|
||||
_cellBloc = BoardNumberCellBloc(cellController: cellController)
|
||||
..add(const BoardNumberCellEvent.initial());
|
||||
_cellBloc = NumberCardCellBloc(cellController: cellController)
|
||||
..add(const NumberCardCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>(
|
||||
child: BlocBuilder<NumberCardCellBloc, NumberCardCellState>(
|
||||
buildWhen: (previous, current) => previous.content != current.content,
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
@ -46,7 +46,7 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
vertical: CardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
@ -1,47 +1,52 @@
|
||||
import 'package:appflowy/plugins/database_view/board/application/card/card_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/board/application/card/card_data_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.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:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.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 'container/accessory.dart';
|
||||
import 'container/card_container.dart';
|
||||
|
||||
class BoardCard extends StatefulWidget {
|
||||
class Card<CustomCardData> extends StatefulWidget {
|
||||
final RowPB row;
|
||||
final String viewId;
|
||||
final String groupId;
|
||||
final String fieldId;
|
||||
final CustomCardData? cardData;
|
||||
final bool isEditing;
|
||||
final CardDataController dataController;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
final RowCache rowCache;
|
||||
final CardCellBuilder<CustomCardData> cellBuilder;
|
||||
final void Function(BuildContext) openCard;
|
||||
final VoidCallback onStartEditing;
|
||||
final VoidCallback onEndEditing;
|
||||
final CardConfiguration<CustomCardData>? configuration;
|
||||
|
||||
const BoardCard({
|
||||
const Card({
|
||||
required this.row,
|
||||
required this.viewId,
|
||||
required this.groupId,
|
||||
required this.fieldId,
|
||||
required this.isEditing,
|
||||
required this.dataController,
|
||||
required this.rowCache,
|
||||
required this.cellBuilder,
|
||||
required this.openCard,
|
||||
required this.onStartEditing,
|
||||
required this.onEndEditing,
|
||||
this.cardData,
|
||||
this.configuration,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardCard> createState() => _BoardCardState();
|
||||
State<Card<CustomCardData>> createState() => _CardState<CustomCardData>();
|
||||
}
|
||||
|
||||
class _BoardCardState extends State<BoardCard> {
|
||||
late BoardCardBloc _cardBloc;
|
||||
class _CardState<T> extends State<Card<T>> {
|
||||
late CardBloc _cardBloc;
|
||||
late EditableRowNotifier rowNotifier;
|
||||
late PopoverController popoverController;
|
||||
AccessoryType? accessoryType;
|
||||
@ -49,11 +54,12 @@ class _BoardCardState extends State<BoardCard> {
|
||||
@override
|
||||
void initState() {
|
||||
rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
|
||||
_cardBloc = BoardCardBloc(
|
||||
_cardBloc = CardBloc(
|
||||
viewId: widget.viewId,
|
||||
groupFieldId: widget.fieldId,
|
||||
dataController: widget.dataController,
|
||||
isEditing: widget.isEditing,
|
||||
row: widget.row,
|
||||
rowCache: widget.rowCache,
|
||||
)..add(const BoardCardEvent.initial());
|
||||
|
||||
rowNotifier.isEditing.addListener(() {
|
||||
@ -75,7 +81,7 @@ class _BoardCardState extends State<BoardCard> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
child: BlocBuilder<CardBloc, BoardCardState>(
|
||||
buildWhen: (previous, current) {
|
||||
// Rebuild when:
|
||||
// 1.If the length of the cells is not the same
|
||||
@ -110,11 +116,12 @@ class _BoardCardState extends State<BoardCard> {
|
||||
},
|
||||
openAccessory: _handleOpenAccessory,
|
||||
openCard: (context) => widget.openCard(context),
|
||||
child: _CellColumn(
|
||||
groupId: widget.groupId,
|
||||
child: _CardContent<T>(
|
||||
rowNotifier: rowNotifier,
|
||||
cellBuilder: widget.cellBuilder,
|
||||
cells: state.cells,
|
||||
cardConfiguration: widget.configuration,
|
||||
cardData: widget.cardData,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -143,7 +150,7 @@ class _BoardCardState extends State<BoardCard> {
|
||||
throw UnimplementedError();
|
||||
case AccessoryType.more:
|
||||
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 {
|
||||
final String groupId;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
class _CardContent<CustomCardData> extends StatelessWidget {
|
||||
final CardCellBuilder<CustomCardData> cellBuilder;
|
||||
final EditableRowNotifier rowNotifier;
|
||||
final List<BoardCellEquatable> cells;
|
||||
const _CellColumn({
|
||||
required this.groupId,
|
||||
final CardConfiguration<CustomCardData>? cardConfiguration;
|
||||
final CustomCardData? cardData;
|
||||
const _CardContent({
|
||||
required this.rowNotifier,
|
||||
required this.cellBuilder,
|
||||
required this.cells,
|
||||
required this.cardData,
|
||||
this.cardConfiguration,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -188,7 +197,7 @@ class _CellColumn extends StatelessWidget {
|
||||
cells.asMap().forEach(
|
||||
(int index, BoardCellEquatable cell) {
|
||||
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
|
||||
final cellNotifier = EditableCellNotifier(isEditing: isEditing);
|
||||
final cellNotifier = EditableCardNotifier(isEditing: isEditing);
|
||||
|
||||
if (index == 0) {
|
||||
// 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(),
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
child: cellBuilder.buildCell(
|
||||
groupId,
|
||||
cell.identifier,
|
||||
cellNotifier,
|
||||
cellId: cell.identifier,
|
||||
cellNotifier: cellNotifier,
|
||||
cardConfiguration: cardConfiguration,
|
||||
cardData: cardData,
|
||||
),
|
||||
);
|
||||
|
@ -1,34 +1,36 @@
|
||||
import 'dart:collection';
|
||||
import 'package:equatable/equatable.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:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../../application/row/row_cache.dart';
|
||||
import '../../../application/row/row_service.dart';
|
||||
import 'card_data_controller.dart';
|
||||
|
||||
import '../../application/cell/cell_service.dart';
|
||||
import '../../application/row/row_cache.dart';
|
||||
import '../../application/row/row_service.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 RowBackendService _rowBackendSvc;
|
||||
final CardDataController _dataController;
|
||||
final RowCache _rowCache;
|
||||
VoidCallback? _rowCallback;
|
||||
|
||||
BoardCardBloc({
|
||||
CardBloc({
|
||||
required this.row,
|
||||
required this.groupFieldId,
|
||||
required String viewId,
|
||||
required CardDataController dataController,
|
||||
required RowCache rowCache,
|
||||
required bool isEditing,
|
||||
}) : _rowBackendSvc = RowBackendService(
|
||||
viewId: viewId,
|
||||
),
|
||||
_dataController = dataController,
|
||||
}) : _rowBackendSvc = RowBackendService(viewId: viewId),
|
||||
_rowCache = rowCache,
|
||||
super(
|
||||
BoardCardState.initial(
|
||||
dataController.rowPB,
|
||||
_makeCells(groupFieldId, dataController.loadData()),
|
||||
row,
|
||||
_makeCells(groupFieldId, rowCache.loadGridCells(row.id)),
|
||||
isEditing,
|
||||
),
|
||||
) {
|
||||
@ -54,7 +56,10 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_dataController.dispose();
|
||||
if (_rowCallback != null) {
|
||||
_rowCache.removeRowListener(_rowCallback!);
|
||||
_rowCallback = null;
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@ -69,8 +74,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_dataController.addListener(
|
||||
onRowChanged: (cellMap, reason) {
|
||||
_rowCallback = _rowCache.addListener(
|
||||
rowId: row.id,
|
||||
onCellUpdated: (cellMap, reason) {
|
||||
if (!isClosed) {
|
||||
final cells = _makeCells(groupFieldId, cellMap);
|
||||
add(BoardCardEvent.didReceiveCells(cells, reason));
|
@ -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:flutter/material.dart';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import 'board_cell.dart';
|
||||
import 'board_checkbox_cell.dart';
|
||||
import 'board_checklist_cell.dart';
|
||||
import 'board_date_cell.dart';
|
||||
import 'board_number_cell.dart';
|
||||
import 'board_select_option_cell.dart';
|
||||
import 'board_text_cell.dart';
|
||||
import 'board_url_cell.dart';
|
||||
import '../../application/cell/cell_service.dart';
|
||||
import 'cells/card_cell.dart';
|
||||
import 'cells/checkbox_card_cell.dart';
|
||||
import 'cells/checklist_card_cell.dart';
|
||||
import 'cells/date_card_cell.dart';
|
||||
import 'cells/number_card_cell.dart';
|
||||
import 'cells/select_option_card_cell.dart';
|
||||
import 'cells/text_card_cell.dart';
|
||||
import 'cells/url_card_cell.dart';
|
||||
|
||||
abstract class BoardCellBuilderDelegate {
|
||||
CellCache get cellCache;
|
||||
}
|
||||
// T represents as the Generic card data
|
||||
class CardCellBuilder<CustomCardData> {
|
||||
final CellCache cellCache;
|
||||
|
||||
class BoardCellBuilder {
|
||||
final BoardCellBuilderDelegate delegate;
|
||||
CardCellBuilder(this.cellCache);
|
||||
|
||||
BoardCellBuilder(this.delegate);
|
||||
|
||||
Widget buildCell(
|
||||
String groupId,
|
||||
CellIdentifier cellId,
|
||||
EditableCellNotifier cellNotifier,
|
||||
) {
|
||||
Widget buildCell({
|
||||
CustomCardData? cardData,
|
||||
required CellIdentifier cellId,
|
||||
EditableCardNotifier? cellNotifier,
|
||||
CardConfiguration<CustomCardData>? cardConfiguration,
|
||||
}) {
|
||||
final cellControllerBuilder = CellControllerBuilder(
|
||||
cellId: cellId,
|
||||
cellCache: delegate.cellCache,
|
||||
cellCache: cellCache,
|
||||
);
|
||||
|
||||
final key = cellId.key();
|
||||
switch (cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return BoardCheckboxCell(
|
||||
groupId: groupId,
|
||||
return CheckboxCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return BoardDateCell(
|
||||
groupId: groupId,
|
||||
return DateCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return BoardSelectOptionCell(
|
||||
groupId: groupId,
|
||||
return SelectOptionCardCell<CustomCardData>(
|
||||
renderHook: cardConfiguration?.renderHook[FieldType.SingleSelect],
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
cardData: cardData,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return BoardSelectOptionCell(
|
||||
groupId: groupId,
|
||||
return SelectOptionCardCell<CustomCardData>(
|
||||
renderHook: cardConfiguration?.renderHook[FieldType.MultiSelect],
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
cardData: cardData,
|
||||
editableNotifier: cellNotifier,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Checklist:
|
||||
return BoardChecklistCell(
|
||||
return ChecklistCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return BoardNumberCell(
|
||||
groupId: groupId,
|
||||
return NumberCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return BoardTextCell(
|
||||
groupId: groupId,
|
||||
return TextCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
editableNotifier: cellNotifier,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.URL:
|
||||
return BoardUrlCell(
|
||||
groupId: groupId,
|
||||
return URLCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
@ -1,14 +1,39 @@
|
||||
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';
|
||||
|
||||
abstract class FocusableBoardCell {
|
||||
set becomeFocus(bool isFocus);
|
||||
typedef CellRenderHook<C, T> = Widget? Function(C cellData, T cardData);
|
||||
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;
|
||||
|
||||
EditableCellNotifier({bool isEditing = false})
|
||||
EditableCardNotifier({bool isEditing = false})
|
||||
: isCellEditing = ValueNotifier(isEditing);
|
||||
|
||||
void dispose() {
|
||||
@ -17,7 +42,7 @@ class EditableCellNotifier {
|
||||
}
|
||||
|
||||
class EditableRowNotifier {
|
||||
final Map<EditableCellId, EditableCellNotifier> _cells = {};
|
||||
final Map<EditableCellId, EditableCardNotifier> _cells = {};
|
||||
final ValueNotifier<bool> isEditing;
|
||||
|
||||
EditableRowNotifier({required bool isEditing})
|
||||
@ -25,7 +50,7 @@ class EditableRowNotifier {
|
||||
|
||||
void bindCell(
|
||||
CellIdentifier cellIdentifier,
|
||||
EditableCellNotifier notifier,
|
||||
EditableCardNotifier notifier,
|
||||
) {
|
||||
assert(
|
||||
_cells.values.isEmpty,
|
||||
@ -80,7 +105,7 @@ abstract class EditableCell {
|
||||
// the row notifier receive its cells event. For example: begin editing the
|
||||
// cell or end editing the cell.
|
||||
//
|
||||
EditableCellNotifier? get editableNotifier;
|
||||
EditableCardNotifier? get editableNotifier;
|
||||
}
|
||||
|
||||
class EditableCellId {
|
@ -1,33 +1,33 @@
|
||||
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_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardCheckboxCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
import '../bloc/checkbox_card_cell_bloc.dart';
|
||||
import 'card_cell.dart';
|
||||
|
||||
class CheckboxCardCell extends CardCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardCheckboxCell({
|
||||
required this.groupId,
|
||||
const CheckboxCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardCheckboxCell> createState() => _BoardCheckboxCellState();
|
||||
State<CheckboxCardCell> createState() => _CheckboxCardCellState();
|
||||
}
|
||||
|
||||
class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
late BoardCheckboxCellBloc _cellBloc;
|
||||
class _CheckboxCardCellState extends State<CheckboxCardCell> {
|
||||
late CheckboxCardCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as CheckboxCellController;
|
||||
_cellBloc = BoardCheckboxCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardCheckboxCellEvent.initial());
|
||||
_cellBloc = CheckboxCardCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const CheckboxCardCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>(
|
||||
child: BlocBuilder<CheckboxCardCellBloc, CheckboxCardCellState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.isSelected != current.isSelected,
|
||||
builder: (context, state) {
|
||||
@ -49,8 +49,8 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
icon: icon,
|
||||
width: 20,
|
||||
onPressed: () => context
|
||||
.read<BoardCheckboxCellBloc>()
|
||||
.add(const BoardCheckboxCellEvent.select()),
|
||||
.read<CheckboxCardCellBloc>()
|
||||
.add(const CheckboxCardCellEvent.select()),
|
||||
),
|
||||
);
|
||||
},
|
@ -1,27 +1,28 @@
|
||||
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_bloc/flutter_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;
|
||||
const BoardChecklistCell({required this.cellControllerBuilder, Key? key})
|
||||
const ChecklistCardCell({required this.cellControllerBuilder, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardChecklistCell> createState() => _BoardChecklistCellState();
|
||||
State<ChecklistCardCell> createState() => _ChecklistCardCellState();
|
||||
}
|
||||
|
||||
class _BoardChecklistCellState extends State<BoardChecklistCell> {
|
||||
late ChecklistCellBloc _cellBloc;
|
||||
class _ChecklistCardCellState extends State<ChecklistCardCell> {
|
||||
late ChecklistCardCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as ChecklistCellController;
|
||||
_cellBloc = ChecklistCellBloc(cellController: cellController);
|
||||
_cellBloc = ChecklistCardCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const ChecklistCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
@ -30,7 +31,7 @@ class _BoardChecklistCellState extends State<BoardChecklistCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
child: BlocBuilder<ChecklistCardCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) =>
|
||||
ChecklistProgressBar(percent: state.percent),
|
||||
),
|
@ -1,35 +1,34 @@
|
||||
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:flutter/material.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 {
|
||||
final String groupId;
|
||||
class DateCardCell extends CardCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardDateCell({
|
||||
required this.groupId,
|
||||
const DateCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardDateCell> createState() => _BoardDateCellState();
|
||||
State<DateCardCell> createState() => _DateCardCellState();
|
||||
}
|
||||
|
||||
class _BoardDateCellState extends State<BoardDateCell> {
|
||||
late BoardDateCellBloc _cellBloc;
|
||||
class _DateCardCellState extends State<DateCardCell> {
|
||||
late DateCardCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as DateCellController;
|
||||
|
||||
_cellBloc = BoardDateCellBloc(cellController: cellController)
|
||||
..add(const BoardDateCellEvent.initial());
|
||||
_cellBloc = DateCardCellBloc(cellController: cellController)
|
||||
..add(const DateCardCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -37,7 +36,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>(
|
||||
child: BlocBuilder<DateCardCellBloc, DateCardCellState>(
|
||||
buildWhen: (previous, current) => previous.dateStr != current.dateStr,
|
||||
builder: (context, state) {
|
||||
if (state.dateStr.isEmpty) {
|
||||
@ -47,7 +46,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
vertical: CardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,32 +1,35 @@
|
||||
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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
||||
import '../../../grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
|
||||
import '../../application/card/board_select_option_cell_bloc.dart';
|
||||
import 'board_cell.dart';
|
||||
import '../bloc/select_option_card_cell_bloc.dart';
|
||||
import 'card_cell.dart';
|
||||
|
||||
class BoardSelectOptionCell extends StatefulWidget with EditableCell {
|
||||
final String groupId;
|
||||
class SelectOptionCardCell<T> extends CardCell<T> with EditableCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
@override
|
||||
final EditableCellNotifier? editableNotifier;
|
||||
final CellRenderHook<List<SelectOptionPB>, T>? renderHook;
|
||||
|
||||
const BoardSelectOptionCell({
|
||||
required this.groupId,
|
||||
@override
|
||||
final EditableCardNotifier? editableNotifier;
|
||||
|
||||
SelectOptionCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
required T? cardData,
|
||||
this.renderHook,
|
||||
this.editableNotifier,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
}) : super(key: key, cardData: cardData);
|
||||
|
||||
@override
|
||||
State<BoardSelectOptionCell> createState() => _BoardSelectOptionCellState();
|
||||
State<SelectOptionCardCell> createState() => _SelectOptionCardCellState();
|
||||
}
|
||||
|
||||
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
late BoardSelectOptionCellBloc _cellBloc;
|
||||
class _SelectOptionCardCellState extends State<SelectOptionCardCell> {
|
||||
late SelectOptionCardCellBloc _cellBloc;
|
||||
late PopoverController _popover;
|
||||
|
||||
@override
|
||||
@ -34,8 +37,8 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
_popover = PopoverController();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as SelectOptionCellController;
|
||||
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
|
||||
..add(const BoardSelectOptionCellEvent.initial());
|
||||
_cellBloc = SelectOptionCardCellBloc(cellController: cellController)
|
||||
..add(const SelectOptionCardCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -43,12 +46,17 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||
child: BlocBuilder<SelectOptionCardCellBloc, SelectOptionCardCellState>(
|
||||
buildWhen: (previous, current) {
|
||||
return previous.selectedOptions != current.selectedOptions;
|
||||
}, builder: (context, state) {
|
||||
// Returns SizedBox if the content of the cell is empty
|
||||
if (_isEmpty(state)) return const SizedBox();
|
||||
Widget? custom = widget.renderHook?.call(
|
||||
state.selectedOptions,
|
||||
widget.cardData,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
final children = state.selectedOptions.map(
|
||||
(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) {
|
||||
final constraints = BoxConstraints.loose(Size(
|
||||
SelectOptionCellEditor.editorPanelWidth,
|
@ -5,29 +5,27 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
import '../../application/card/board_text_cell_bloc.dart';
|
||||
import 'board_cell.dart';
|
||||
import 'define.dart';
|
||||
import '../bloc/text_card_cell_bloc.dart';
|
||||
import '../define.dart';
|
||||
import 'card_cell.dart';
|
||||
|
||||
class BoardTextCell extends StatefulWidget with EditableCell {
|
||||
final String groupId;
|
||||
class TextCardCell extends CardCell with EditableCell {
|
||||
@override
|
||||
final EditableCellNotifier? editableNotifier;
|
||||
final EditableCardNotifier? editableNotifier;
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardTextCell({
|
||||
required this.groupId,
|
||||
const TextCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
this.editableNotifier,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardTextCell> createState() => _BoardTextCellState();
|
||||
State<TextCardCell> createState() => _TextCardCellState();
|
||||
}
|
||||
|
||||
class _BoardTextCellState extends State<BoardTextCell> {
|
||||
late BoardTextCellBloc _cellBloc;
|
||||
class _TextCardCellState extends State<TextCardCell> {
|
||||
late TextCardCellBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
bool focusWhenInit = false;
|
||||
final focusNode = SingleListenerFocusNode();
|
||||
@ -36,8 +34,8 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as TextCellController;
|
||||
_cellBloc = BoardTextCellBloc(cellController: cellController)
|
||||
..add(const BoardTextCellEvent.initial());
|
||||
_cellBloc = TextCardCellBloc(cellController: cellController)
|
||||
..add(const TextCardCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false;
|
||||
if (focusWhenInit) {
|
||||
@ -51,7 +49,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
if (!focusNode.hasFocus) {
|
||||
focusWhenInit = false;
|
||||
widget.editableNotifier?.isCellEditing.value = false;
|
||||
_cellBloc.add(const BoardTextCellEvent.enableEdit(false));
|
||||
_cellBloc.add(const TextCardCellEvent.enableEdit(false));
|
||||
}
|
||||
});
|
||||
_bindEditableNotifier();
|
||||
@ -68,12 +66,12 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
focusNode.requestFocus();
|
||||
});
|
||||
}
|
||||
_cellBloc.add(BoardTextCellEvent.enableEdit(isEditing));
|
||||
_cellBloc.add(TextCardCellEvent.enableEdit(isEditing));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant BoardTextCell oldWidget) {
|
||||
void didUpdateWidget(covariant TextCardCell oldWidget) {
|
||||
_bindEditableNotifier();
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
@ -82,13 +80,13 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocListener<BoardTextCellBloc, BoardTextCellState>(
|
||||
child: BlocListener<TextCardCellBloc, TextCardCellState>(
|
||||
listener: (context, state) {
|
||||
if (_controller.text != state.content) {
|
||||
_controller.text = state.content;
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||
child: BlocBuilder<TextCardCellBloc, TextCardCellState>(
|
||||
buildWhen: (previous, current) {
|
||||
if (previous.content != current.content &&
|
||||
_controller.text == current.content &&
|
||||
@ -120,7 +118,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
}
|
||||
|
||||
Future<void> focusChanged() async {
|
||||
_cellBloc.add(BoardTextCellEvent.updateText(_controller.text));
|
||||
_cellBloc.add(TextCardCellEvent.updateText(_controller.text));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -131,10 +129,10 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildText(BoardTextCellState state) {
|
||||
Widget _buildText(TextCardCellState state) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
vertical: CardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
@ -156,7 +154,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
decoration: InputDecoration(
|
||||
// Magic number 4 makes the textField take up the same space as FlowyText
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding + 4,
|
||||
vertical: CardSizes.cardCellVPadding + 4,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
@ -4,32 +4,31 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
|
||||
import '../../application/card/board_url_cell_bloc.dart';
|
||||
import 'define.dart';
|
||||
import '../bloc/url_card_cell_bloc.dart';
|
||||
import '../define.dart';
|
||||
import 'card_cell.dart';
|
||||
|
||||
class BoardUrlCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
class URLCardCell extends CardCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardUrlCell({
|
||||
required this.groupId,
|
||||
const URLCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardUrlCell> createState() => _BoardUrlCellState();
|
||||
State<URLCardCell> createState() => _URLCardCellState();
|
||||
}
|
||||
|
||||
class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
late BoardURLCellBloc _cellBloc;
|
||||
class _URLCardCellState extends State<URLCardCell> {
|
||||
late URLCardCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as URLCellController;
|
||||
_cellBloc = BoardURLCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardURLCellEvent.initial());
|
||||
_cellBloc = URLCardCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const URLCardCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -37,7 +36,7 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
||||
child: BlocBuilder<URLCardCellBloc, URLCardCellState>(
|
||||
buildWhen: (previous, current) => previous.content != current.content,
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
@ -47,7 +46,7 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
vertical: CardSizes.cardCellVPadding,
|
||||
),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.left,
|
@ -1,3 +1,3 @@
|
||||
class BoardSizes {
|
||||
class CardSizes {
|
||||
static double get cardCellVPadding => 6;
|
||||
}
|
@ -36,7 +36,7 @@ class DocumentService {
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
||||
final request = ViewIdPB(value: docId);
|
||||
return FolderEventCloseView(request).send();
|
||||
final payload = ViewIdPB(value: docId);
|
||||
return FolderEventCloseView(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -416,6 +416,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ARCHS = arm64;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
@ -549,6 +550,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ARCHS = arm64;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
@ -573,6 +575,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ARCHS = arm64;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
|
@ -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/row/row_cache.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/application/database_controller.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_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
|
||||
@ -38,9 +39,14 @@ class AppFlowyBoardTest {
|
||||
.then((result) {
|
||||
return result.fold(
|
||||
(view) async {
|
||||
final context =
|
||||
BoardTestContext(view, BoardDataController(view: view));
|
||||
final result = await context._boardDataController.openGrid();
|
||||
final context = BoardTestContext(
|
||||
view,
|
||||
DatabaseController(
|
||||
view: view,
|
||||
layoutType: LayoutTypePB.Board,
|
||||
),
|
||||
);
|
||||
final result = await context._boardDataController.open();
|
||||
result.fold((l) => null, (r) => throw Exception(r));
|
||||
return context;
|
||||
},
|
||||
@ -62,7 +68,7 @@ Duration boardResponseDuration({int milliseconds = 200}) {
|
||||
|
||||
class BoardTestContext {
|
||||
final ViewPB gridView;
|
||||
final BoardDataController _boardDataController;
|
||||
final DatabaseController _boardDataController;
|
||||
|
||||
BoardTestContext(this.gridView, this._boardDataController);
|
||||
|
||||
@ -108,7 +114,8 @@ class BoardTestContext {
|
||||
final rowCache = _boardDataController.rowCache;
|
||||
|
||||
final rowDataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
viewId: rowInfo.viewId,
|
||||
rowId: rowInfo.rowPB.id,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
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_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/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/text_filter.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
@ -50,7 +51,10 @@ void main() {
|
||||
test('filter rows with condition: text is empty', () async {
|
||||
final context = await gridTest.createTestGrid();
|
||||
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(
|
||||
view: context.gridView,
|
||||
databaseController: gridController,
|
||||
@ -71,7 +75,10 @@ void main() {
|
||||
() async {
|
||||
final context = await gridTest.createTestGrid();
|
||||
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(
|
||||
view: context.gridView,
|
||||
databaseController: gridController,
|
||||
@ -112,7 +119,10 @@ void main() {
|
||||
final context = await gridTest.createTestGrid();
|
||||
final checkboxField = context.checkboxFieldContext();
|
||||
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(
|
||||
view: context.gridView,
|
||||
databaseController: gridController,
|
||||
@ -131,7 +141,10 @@ void main() {
|
||||
final context = await gridTest.createTestGrid();
|
||||
final checkboxField = context.checkboxFieldContext();
|
||||
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(
|
||||
view: context.gridView,
|
||||
databaseController: gridController,
|
||||
|
@ -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/workspace/application/app/app_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart';
|
||||
|
||||
import '../util.dart';
|
||||
|
||||
@ -16,8 +17,13 @@ Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
|
||||
.then((result) {
|
||||
return result.fold(
|
||||
(view) async {
|
||||
final context = GridTestContext(view, DatabaseController(view: view));
|
||||
final result = await context.gridController.openGrid();
|
||||
final context = GridTestContext(
|
||||
view,
|
||||
DatabaseController(
|
||||
view: view,
|
||||
layoutType: LayoutTypePB.Grid,
|
||||
));
|
||||
final result = await context.gridController.open();
|
||||
|
||||
await editCells(context);
|
||||
await gridResponseFuture(milliseconds: 500);
|
||||
|
@ -1,5 +1,6 @@
|
||||
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:bloc_test/bloc_test.dart';
|
||||
import 'util.dart';
|
||||
@ -20,7 +21,10 @@ void main() {
|
||||
"create a row",
|
||||
build: () => GridBloc(
|
||||
view: context.gridView,
|
||||
databaseController: DatabaseController(view: context.gridView))
|
||||
databaseController: DatabaseController(
|
||||
view: context.gridView,
|
||||
layoutType: LayoutTypePB.Grid,
|
||||
))
|
||||
..add(const GridEvent.initial()),
|
||||
act: (bloc) => bloc.add(const GridEvent.createRow()),
|
||||
wait: const Duration(milliseconds: 300),
|
||||
@ -33,7 +37,10 @@ void main() {
|
||||
"delete the last row",
|
||||
build: () => GridBloc(
|
||||
view: context.gridView,
|
||||
databaseController: DatabaseController(view: context.gridView))
|
||||
databaseController: DatabaseController(
|
||||
view: context.gridView,
|
||||
layoutType: LayoutTypePB.Grid,
|
||||
))
|
||||
..add(const GridEvent.initial()),
|
||||
act: (bloc) async {
|
||||
await gridResponseFuture();
|
||||
|
@ -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/row/row_cache.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/grid.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-database/field_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../../util.dart';
|
||||
|
||||
@ -31,7 +35,7 @@ class GridTestContext {
|
||||
return gridController.fieldController;
|
||||
}
|
||||
|
||||
Future<void> createRow() async {
|
||||
Future<Either<RowPB, FlowyError>> createRow() async {
|
||||
return gridController.createRow();
|
||||
}
|
||||
|
||||
@ -69,7 +73,8 @@ class GridTestContext {
|
||||
final rowCache = gridController.rowCache;
|
||||
|
||||
final rowDataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
rowId: rowInfo.rowPB.id,
|
||||
viewId: rowInfo.viewId,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
@ -169,8 +174,13 @@ class AppFlowyGridTest {
|
||||
.then((result) {
|
||||
return result.fold(
|
||||
(view) async {
|
||||
final context = GridTestContext(view, DatabaseController(view: view));
|
||||
final result = await context.gridController.openGrid();
|
||||
final context = GridTestContext(
|
||||
view,
|
||||
DatabaseController(
|
||||
view: view,
|
||||
layoutType: LayoutTypePB.Grid,
|
||||
));
|
||||
final result = await context.gridController.open();
|
||||
result.fold((l) => null, (r) => throw Exception(r));
|
||||
return context;
|
||||
},
|
||||
|
@ -1,6 +1,4 @@
|
||||
import {
|
||||
CreateBoardCardPayloadPB,
|
||||
DatabaseEventCreateBoardCard,
|
||||
DatabaseEventCreateRow,
|
||||
DatabaseEventGetDatabase,
|
||||
DatabaseEventGetFields,
|
||||
@ -41,17 +39,12 @@ export class DatabaseBackendService {
|
||||
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 });
|
||||
return DatabaseEventCreateRow(payload);
|
||||
};
|
||||
|
||||
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;
|
||||
if (groupId !== undefined) {
|
||||
payload.group_id = groupId;
|
||||
}
|
||||
return DatabaseEventCreateBoardCard(payload);
|
||||
return DatabaseEventCreateRow(payload);
|
||||
};
|
||||
|
||||
moveRow = (rowId: string, groupId?: string) => {
|
||||
|
@ -94,7 +94,7 @@ export class DatabaseGroupController {
|
||||
};
|
||||
|
||||
createRow = async () => {
|
||||
return this.databaseBackendSvc.createGroupRow(this.group.group_id);
|
||||
return this.databaseBackendSvc.createRow(this.group.group_id);
|
||||
};
|
||||
|
||||
subscribe = (callbacks: GroupDataCallbacks) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::errors::{SyncError, SyncResult};
|
||||
use database_model::{
|
||||
BuildDatabaseContext, DatabaseBlockMetaRevision, DatabaseBlockRevision, FieldRevision,
|
||||
RowRevision,
|
||||
LayoutSetting, RowRevision,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -30,6 +30,7 @@ impl DatabaseBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn add_field(&mut self, field: FieldRevision) {
|
||||
self.build_context.field_revs.push(Arc::new(field));
|
||||
}
|
||||
@ -54,6 +55,10 @@ impl DatabaseBuilder {
|
||||
&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 {
|
||||
self.build_context
|
||||
}
|
||||
|
@ -176,8 +176,8 @@ impl DatabaseRevisionPad {
|
||||
T: Into<FieldTypeRevision>,
|
||||
{
|
||||
let new_field_type = new_field_type.into();
|
||||
self.modify_database(|grid_meta| {
|
||||
match grid_meta
|
||||
self.modify_database(|database_rev| {
|
||||
match database_rev
|
||||
.fields
|
||||
.iter_mut()
|
||||
.find(|field_rev| field_rev.id == field_id)
|
||||
|
@ -312,7 +312,7 @@ impl DatabaseViewRevisionPad {
|
||||
}
|
||||
|
||||
/// updates the settings for the given layout type
|
||||
pub fn update_layout_setting<T>(
|
||||
pub fn set_layout_setting<T>(
|
||||
&mut self,
|
||||
layout: &LayoutRevision,
|
||||
settings: &T,
|
||||
@ -364,14 +364,18 @@ pub struct DatabaseViewRevisionChangeset {
|
||||
pub md5: String,
|
||||
}
|
||||
|
||||
pub fn make_database_view_rev_json_str(grid_revision: &DatabaseViewRevision) -> SyncResult<String> {
|
||||
let json = serde_json::to_string(grid_revision).map_err(|err| {
|
||||
pub fn make_database_view_rev_json_str(
|
||||
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))
|
||||
})?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
pub fn make_database_view_operations(grid_view: &DatabaseViewRevision) -> DatabaseViewOperations {
|
||||
let json = serde_json::to_string(grid_view).unwrap();
|
||||
pub fn make_database_view_operations(
|
||||
database_view_rev: &DatabaseViewRevision,
|
||||
) -> DatabaseViewOperations {
|
||||
let json = serde_json::to_string(database_view_rev).unwrap();
|
||||
DatabaseViewOperationsBuilder::new().insert(&json).build()
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use database_model::{CalendarLayout, CalendarLayoutSetting};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf)]
|
||||
pub struct CalendarSettingsPB {
|
||||
pub struct CalendarLayoutSettingsPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
pub layout_field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub layout_ty: CalendarLayout,
|
||||
pub layout_ty: CalendarLayoutPB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub first_day_of_week: i32,
|
||||
@ -23,51 +21,23 @@ pub struct CalendarSettingsPB {
|
||||
pub show_week_numbers: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CalendarSettingsParams {
|
||||
pub(crate) view_id: String,
|
||||
layout_ty: CalendarLayout,
|
||||
first_day_of_week: i32,
|
||||
show_weekends: bool,
|
||||
show_week_numbers: bool,
|
||||
}
|
||||
|
||||
const DEFAULT_FIRST_DAY_OF_WEEK: i32 = 0;
|
||||
const DEFAULT_SHOW_WEEKENDS: bool = true;
|
||||
const DEFAULT_SHOW_WEEK_NUMBERS: bool = true;
|
||||
|
||||
impl CalendarSettingsParams {
|
||||
pub fn default_with(view_id: String) -> Self {
|
||||
CalendarSettingsParams {
|
||||
view_id,
|
||||
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 std::convert::From<CalendarLayoutSettingsPB> for CalendarLayoutSetting {
|
||||
fn from(pb: CalendarLayoutSettingsPB) -> Self {
|
||||
CalendarLayoutSetting {
|
||||
layout_ty: pb.layout_ty.into(),
|
||||
first_day_of_week: pb.first_day_of_week,
|
||||
show_weekends: pb.show_weekends,
|
||||
show_week_numbers: pb.show_week_numbers,
|
||||
layout_field_id: pb.layout_field_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<CalendarSettingsParams> for CalendarSettingsPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CalendarSettingsParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
|
||||
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,
|
||||
impl std::convert::From<CalendarLayoutSetting> for CalendarLayoutSettingsPB {
|
||||
fn from(params: CalendarLayoutSetting) -> Self {
|
||||
CalendarLayoutSettingsPB {
|
||||
layout_field_id: params.layout_field_id,
|
||||
layout_ty: params.layout_ty.into(),
|
||||
first_day_of_week: params.first_day_of_week,
|
||||
show_weekends: params.show_weekends,
|
||||
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)]
|
||||
pub enum CalendarLayout {
|
||||
pub enum CalendarLayoutPB {
|
||||
#[default]
|
||||
MonthLayout = 0,
|
||||
WeekLayout = 1,
|
||||
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,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{FieldIdPB, RowPB};
|
||||
use crate::entities::{FieldIdPB, LayoutTypePB, RowPB};
|
||||
use flowy_derive::ProtoBuf;
|
||||
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,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{CreateRowParams, FieldType, LayoutTypePB, RowPB};
|
||||
use crate::entities::{FieldType, RowPB};
|
||||
use crate::services::group::Group;
|
||||
use database_model::{FieldTypeRevision, GroupConfigurationRevision};
|
||||
use flowy_derive::ProtoBuf;
|
||||
@ -7,41 +7,6 @@ use flowy_error::ErrorCode;
|
||||
use std::convert::TryInto;
|
||||
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)]
|
||||
pub struct GroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::LayoutTypePB;
|
||||
|
||||
use database_model::RowRevision;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// [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)]
|
||||
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)]
|
||||
@ -184,7 +197,7 @@ pub struct CreateRowParams {
|
||||
pub view_id: String,
|
||||
pub start_row_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 {
|
||||
@ -192,12 +205,20 @@ impl TryInto<CreateRowParams> for CreateRowPayloadPB {
|
||||
|
||||
fn try_into(self) -> Result<CreateRowParams, Self::Error> {
|
||||
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 {
|
||||
view_id: view_id.0,
|
||||
start_row_id: self.start_row_id,
|
||||
group_id: None,
|
||||
layout: LayoutTypePB::Grid,
|
||||
start_row_id,
|
||||
group_id: self.group_id,
|
||||
cell_data_by_field_id: self.data.map(|data| data.cell_data_by_field_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, CalendarSettingsPB,
|
||||
DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB,
|
||||
DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB,
|
||||
RepeatedGroupConfigurationPB, RepeatedSortPB,
|
||||
AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB,
|
||||
CalendarLayoutSettingsPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams,
|
||||
DeleteGroupPayloadPB, DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams,
|
||||
InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGroupConfigurationPB, RepeatedSortPB,
|
||||
};
|
||||
use database_model::LayoutRevision;
|
||||
use database_model::{CalendarLayoutSetting, LayoutRevision};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
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)]
|
||||
pub struct LayoutSettingPB {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub calendar: Option<CalendarSettingsPB>,
|
||||
pub calendar: Option<CalendarLayoutSettingsPB>,
|
||||
}
|
||||
|
||||
impl LayoutSettingPB {
|
||||
@ -170,3 +202,24 @@ impl LayoutSettingPB {
|
||||
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>,
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ pub(crate) async fn move_row_handler(
|
||||
}
|
||||
|
||||
#[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>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RowPB, FlowyError> {
|
||||
@ -549,17 +549,6 @@ pub(crate) async fn get_group_handler(
|
||||
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)]
|
||||
pub(crate) async fn move_group_handler(
|
||||
data: AFPluginData<MoveGroupPayloadPB>,
|
||||
@ -599,23 +588,56 @@ pub(crate) async fn get_databases_handler(
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn set_calendar_setting_handler(
|
||||
data: AFPluginData<CalendarSettingsPB>,
|
||||
pub(crate) async fn set_layout_setting_handler(
|
||||
data: AFPluginData<UpdateLayoutSettingPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> FlowyResult<()> {
|
||||
let params: CalendarSettingsParams = data.into_inner().try_into()?;
|
||||
let _ = manager.get_database_editor(params.view_id.as_ref()).await?;
|
||||
//TODO(nathan):
|
||||
todo!("nathan: depends on the main branch refactoring")
|
||||
let params: UpdateLayoutSettingParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_editor(params.view_id.as_ref()).await?;
|
||||
database_editor
|
||||
.set_layout_setting(¶ms.view_id, params.layout_setting)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn get_calendar_setting_handler(
|
||||
data: AFPluginData<DatabaseViewIdPB>,
|
||||
pub(crate) async fn get_layout_setting_handler(
|
||||
data: AFPluginData<DatabaseLayoutIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> FlowyResult<()> {
|
||||
let view_id = data.into_inner().value;
|
||||
let _ = manager.get_database_editor(view_id.as_ref()).await?;
|
||||
//TODO(nathan):
|
||||
todo!("nathan: depends on the main branch refactoring")
|
||||
) -> DataResult<LayoutSettingPB, FlowyError> {
|
||||
let params = data.into_inner();
|
||||
let database_editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let layout_setting = database_editor
|
||||
.get_layout_setting(¶ms.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(¶ms.view_id).await?;
|
||||
let events = database_editor
|
||||
.get_all_calendar_events(¶ms.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(¶ms.view_id).await?;
|
||||
let event = database_editor
|
||||
.get_calendar_event(¶ms.view_id, ¶ms.row_id)
|
||||
.await;
|
||||
match event {
|
||||
None => Err(FlowyError::record_not_found()),
|
||||
Some(event) => data_result_ok(event),
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
|
||||
.event(DatabaseEvent::CreateTypeOption, create_field_type_option_data_handler)
|
||||
// Row
|
||||
.event(DatabaseEvent::CreateRow, create_table_row_handler)
|
||||
.event(DatabaseEvent::CreateRow, create_row_handler)
|
||||
.event(DatabaseEvent::GetRow, get_row_handler)
|
||||
.event(DatabaseEvent::DeleteRow, delete_row_handler)
|
||||
.event(DatabaseEvent::DuplicateRow, duplicate_row_handler)
|
||||
@ -44,7 +44,6 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
|
||||
// Date
|
||||
.event(DatabaseEvent::UpdateDateCell, update_date_cell_handler)
|
||||
// Group
|
||||
.event(DatabaseEvent::CreateBoardCard, create_board_card_handler)
|
||||
.event(DatabaseEvent::MoveGroup, move_group_handler)
|
||||
.event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
|
||||
.event(DatabaseEvent::GetGroups, get_groups_handler)
|
||||
@ -52,8 +51,11 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
|
||||
// Database
|
||||
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
||||
// Calendar
|
||||
.event(DatabaseEvent::SetCalenderSetting, set_calendar_setting_handler)
|
||||
.event(DatabaseEvent::GetCalendarSetting, get_calendar_setting_handler);
|
||||
.event(DatabaseEvent::GetAllCalendarEvents, get_calendar_events_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
|
||||
}
|
||||
@ -227,9 +229,6 @@ pub enum DatabaseEvent {
|
||||
#[event(input = "DatabaseGroupIdPB", output = "GroupPB")]
|
||||
GetGroup = 101,
|
||||
|
||||
#[event(input = "CreateBoardCardPayloadPB", output = "RowPB")]
|
||||
CreateBoardCard = 110,
|
||||
|
||||
#[event(input = "MoveGroupPayloadPB")]
|
||||
MoveGroup = 111,
|
||||
|
||||
@ -242,9 +241,18 @@ pub enum DatabaseEvent {
|
||||
#[event(output = "RepeatedDatabaseDescPB")]
|
||||
GetDatabases = 114,
|
||||
|
||||
#[event(input = "CalendarSettingsPB")]
|
||||
SetCalenderSetting = 115,
|
||||
#[event(input = "UpdateLayoutSettingPB")]
|
||||
SetLayoutSetting = 115,
|
||||
|
||||
#[event()]
|
||||
GetCalendarSetting = 116,
|
||||
#[event(input = "DatabaseLayoutIdPB", output = "LayoutSettingPB")]
|
||||
GetLayoutSetting = 116,
|
||||
|
||||
#[event(input = "CalendarEventRequestPB", output = "RepeatedCalendarEventPB")]
|
||||
GetAllCalendarEvents = 117,
|
||||
|
||||
#[event(input = "RowIdPB", output = "CalendarEventPB")]
|
||||
GetCalendarEvent = 118,
|
||||
|
||||
#[event(input = "MoveCalendarEventPB")]
|
||||
MoveCalendarEvent = 119,
|
||||
}
|
||||
|
@ -371,6 +371,7 @@ pub async fn create_new_database(
|
||||
block_metas,
|
||||
blocks,
|
||||
database_view_data,
|
||||
layout_setting,
|
||||
} = build_context;
|
||||
|
||||
for block_meta_data in &blocks {
|
||||
@ -405,11 +406,14 @@ pub async fn create_new_database(
|
||||
|
||||
// Create database view
|
||||
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())
|
||||
} else {
|
||||
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_bytes = database_view_ops.json_bytes();
|
||||
let revision = Revision::initial_revision(view_id, database_view_bytes);
|
||||
|
@ -31,8 +31,12 @@ pub enum DatabaseNotification {
|
||||
DidReorderSingleRow = 66,
|
||||
/// Trigger when the settings of the database are changed
|
||||
DidUpdateSettings = 70,
|
||||
DidUpdateCalendarSettings = 80,
|
||||
DidArrangeCalendarWithNewField = 81,
|
||||
// Trigger when the layout setting of the database is updated
|
||||
DidUpdateLayoutSettings = 80,
|
||||
// Trigger when the layout field of the database is changed
|
||||
DidSetNewLayoutField = 81,
|
||||
|
||||
DidArrangeCalendarWithNewField = 82,
|
||||
}
|
||||
|
||||
impl std::default::Default for DatabaseNotification {
|
||||
|
@ -425,7 +425,9 @@ impl DatabaseEditor {
|
||||
}
|
||||
|
||||
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
|
||||
.database_views
|
||||
@ -915,6 +917,7 @@ impl DatabaseEditor {
|
||||
field_revs: duplicated_fields.into_iter().map(Arc::new).collect(),
|
||||
block_metas: duplicated_blocks,
|
||||
blocks: blocks_meta_data,
|
||||
layout_setting: Default::default(),
|
||||
database_view_data,
|
||||
})
|
||||
}
|
||||
@ -929,12 +932,64 @@ impl DatabaseEditor {
|
||||
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 block_id = self.block_id().await?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,16 @@ impl DatabaseViewData for DatabaseViewDataImpl {
|
||||
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>> {
|
||||
let block_manager = self.blocks.clone();
|
||||
let row_id = row_id.to_owned();
|
||||
|
@ -18,8 +18,9 @@ use crate::services::sort::{
|
||||
DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType,
|
||||
};
|
||||
use database_model::{
|
||||
gen_database_filter_id, gen_database_id, gen_database_sort_id, FieldRevision, FieldTypeRevision,
|
||||
FilterRevision, LayoutRevision, RowChangeset, RowRevision, SortRevision,
|
||||
gen_database_filter_id, gen_database_id, gen_database_sort_id, CalendarLayoutSetting,
|
||||
FieldRevision, FieldTypeRevision, FilterRevision, LayoutRevision, RowChangeset, RowRevision,
|
||||
SortRevision,
|
||||
};
|
||||
use flowy_client_sync::client_database::{
|
||||
make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad,
|
||||
@ -32,6 +33,7 @@ use lib_infra::future::Fut;
|
||||
use nanoid::nanoid;
|
||||
use revision_model::Revision;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
@ -43,6 +45,8 @@ pub trait DatabaseViewData: Send + Sync + 'static {
|
||||
/// Returns the field with the field_id
|
||||
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
|
||||
fn index_of_row(&self, row_id: &str) -> Fut<Option<usize>>;
|
||||
|
||||
@ -661,25 +665,84 @@ impl DatabaseViewEditor {
|
||||
}
|
||||
|
||||
/// Returns the current calendar settings
|
||||
pub async fn v_get_calendar_settings(&self) -> FlowyResult<CalendarSettingsParams> {
|
||||
let settings = self
|
||||
.pad
|
||||
.read()
|
||||
.await
|
||||
.get_layout_setting(&LayoutRevision::Calendar)
|
||||
.unwrap_or_else(|| CalendarSettingsParams::default_with(self.view_id.to_string()));
|
||||
Ok(settings)
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
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
|
||||
.read()
|
||||
.await
|
||||
.get_layout_setting::<CalendarLayoutSetting>(layout_ty)
|
||||
{
|
||||
// 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
|
||||
pub async fn v_update_calendar_settings(
|
||||
&self,
|
||||
params: CalendarSettingsParams,
|
||||
) -> FlowyResult<()> {
|
||||
pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> {
|
||||
// Maybe it needs no send notification to refresh the UI
|
||||
self
|
||||
.modify(|pad| Ok(pad.update_layout_setting(&LayoutRevision::Calendar, ¶ms)?))
|
||||
.await?;
|
||||
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
|
||||
.modify(|pad| Ok(pad.set_layout_setting(&layout_ty, &new_calendar_setting)?))
|
||||
.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(())
|
||||
}
|
||||
|
||||
@ -772,6 +835,100 @@ impl DatabaseViewEditor {
|
||||
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) {
|
||||
let setting = self.v_get_setting().await;
|
||||
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(
|
||||
delegate: Arc<dyn DatabaseViewData>,
|
||||
field_id: &str,
|
||||
) -> FlowyResult<Vec<RowSingleCellData>> {
|
||||
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_type: FieldType = field_rev.ty.into();
|
||||
let mut cells = vec![];
|
||||
|
@ -1,8 +1,8 @@
|
||||
#![allow(clippy::while_let_loop)]
|
||||
use crate::entities::{
|
||||
AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams,
|
||||
DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, MoveGroupParams,
|
||||
RepeatedGroupPB, RowPB,
|
||||
DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, LayoutSettingParams,
|
||||
MoveGroupParams, RepeatedGroupPB, RowPB,
|
||||
};
|
||||
use crate::manager::DatabaseUser;
|
||||
use crate::services::cell::AtomicCellDataCache;
|
||||
@ -16,7 +16,9 @@ use crate::services::filter::FilterType;
|
||||
use crate::services::persistence::rev_sqlite::{
|
||||
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_error::FlowyResult;
|
||||
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration};
|
||||
@ -207,6 +209,24 @@ impl DatabaseViews {
|
||||
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<()> {
|
||||
let view_editor = self.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.v_initialize_new_group(params).await
|
||||
|
@ -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::field::RowSingleCellData;
|
||||
use crate::services::filter::{FilterController, FilterDelegate, FilterType};
|
||||
@ -7,8 +7,8 @@ use crate::services::row::DatabaseBlockRowRevision;
|
||||
use crate::services::sort::{SortDelegate, SortType};
|
||||
use bytes::Bytes;
|
||||
use database_model::{
|
||||
FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, LayoutRevision,
|
||||
RowRevision, SortRevision,
|
||||
CalendarLayoutSetting, FieldRevision, FieldTypeRevision, FilterRevision,
|
||||
GroupConfigurationRevision, LayoutRevision, RowRevision, SortRevision,
|
||||
};
|
||||
use flowy_client_sync::client_database::{DatabaseViewRevisionChangeset, DatabaseViewRevisionPad};
|
||||
use flowy_client_sync::make_operations_from_revisions;
|
||||
@ -153,7 +153,7 @@ pub fn make_database_view_setting(
|
||||
LayoutRevision::Board => {},
|
||||
LayoutRevision::Calendar => {
|
||||
layout_settings.calendar = view_pad
|
||||
.get_layout_setting::<CalendarSettingsParams>(&layout_type)
|
||||
.get_layout_setting::<CalendarLayoutSetting>(&layout_type)
|
||||
.map(|params| params.into());
|
||||
},
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ pub const UNCHECK: &str = "No";
|
||||
pub struct CheckboxCellData(String);
|
||||
|
||||
impl CheckboxCellData {
|
||||
pub fn into_inner(self) -> bool {
|
||||
self.is_check()
|
||||
}
|
||||
|
||||
pub fn is_check(&self) -> bool {
|
||||
self.0 == CHECK
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
use crate::services::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 indexmap::IndexMap;
|
||||
use std::collections::HashMap;
|
||||
@ -16,7 +18,15 @@ pub struct RowRevisionBuilder {
|
||||
|
||||
impl RowRevisionBuilder {
|
||||
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()
|
||||
.map(|field| (field.id.clone(), field.clone()))
|
||||
.collect::<HashMap<String, Arc<FieldRevision>>>();
|
||||
@ -29,12 +39,49 @@ impl RowRevisionBuilder {
|
||||
};
|
||||
|
||||
let block_id = block_id.to_string();
|
||||
|
||||
Self {
|
||||
let mut builder = Self {
|
||||
block_id,
|
||||
field_rev_map,
|
||||
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) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::field::*;
|
||||
use crate::services::row::RowRevisionBuilder;
|
||||
use database_model::BuildDatabaseContext;
|
||||
use database_model::{BuildDatabaseContext, CalendarLayoutSetting, LayoutRevision, LayoutSetting};
|
||||
use flowy_client_sync::client_database::DatabaseBuilder;
|
||||
|
||||
pub fn make_default_grid() -> BuildDatabaseContext {
|
||||
@ -80,7 +80,7 @@ pub fn make_default_calendar() -> BuildDatabaseContext {
|
||||
let mut database_builder = DatabaseBuilder::new();
|
||||
// text
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Description")
|
||||
.name("Title")
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
@ -92,6 +92,7 @@ pub fn make_default_calendar() -> BuildDatabaseContext {
|
||||
.name("Date")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let date_field_id = date_field.id.clone();
|
||||
database_builder.add_field(date_field);
|
||||
|
||||
// single select
|
||||
@ -101,6 +102,12 @@ pub fn make_default_calendar() -> BuildDatabaseContext {
|
||||
.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);
|
||||
database_builder.build()
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ use crate::database::database_editor::DatabaseEditorTest;
|
||||
use database_model::{
|
||||
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::row::DatabaseBlockRow;
|
||||
use std::collections::HashMap;
|
||||
@ -81,7 +81,7 @@ impl DatabaseRowTest {
|
||||
view_id: self.editor.database_id.clone(),
|
||||
start_row_id: None,
|
||||
group_id: None,
|
||||
layout: LayoutTypePB::Grid,
|
||||
cell_data_by_field_id: None,
|
||||
};
|
||||
let row_order = self.editor.create_row(params).await.unwrap();
|
||||
self
|
||||
|
@ -7,8 +7,6 @@ use flowy_database::services::field::{
|
||||
use flowy_database::services::row::RowRevisionBuilder;
|
||||
use std::sync::Arc;
|
||||
|
||||
use strum::EnumCount;
|
||||
|
||||
pub struct DatabaseRowTestBuilder {
|
||||
field_revs: Vec<Arc<FieldRevision>>,
|
||||
inner_builder: RowRevisionBuilder,
|
||||
@ -16,7 +14,6 @@ pub struct DatabaseRowTestBuilder {
|
||||
|
||||
impl DatabaseRowTestBuilder {
|
||||
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());
|
||||
Self {
|
||||
field_revs,
|
||||
|
@ -33,6 +33,10 @@ impl DatabaseEditorTest {
|
||||
Self::new(LayoutTypePB::Board).await
|
||||
}
|
||||
|
||||
pub async fn new_calendar() -> Self {
|
||||
Self::new(LayoutTypePB::Calendar).await
|
||||
}
|
||||
|
||||
pub async fn new(layout: LayoutTypePB) -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
@ -64,10 +68,6 @@ impl DatabaseEditorTest {
|
||||
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
|
||||
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 app_id = test.app.id;
|
||||
Self {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::database::database_editor::DatabaseEditorTest;
|
||||
use database_model::{FieldRevision, RowChangeset};
|
||||
use flowy_database::entities::{
|
||||
CreateRowParams, FieldType, GroupPB, LayoutTypePB, MoveGroupParams, MoveGroupRowParams, RowPB,
|
||||
CreateRowParams, FieldType, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB,
|
||||
};
|
||||
use flowy_database::services::cell::{
|
||||
delete_select_option_cell, insert_select_option_cell, insert_url_cell,
|
||||
@ -130,7 +130,7 @@ impl DatabaseGroupTest {
|
||||
view_id: self.view_id.clone(),
|
||||
start_row_id: None,
|
||||
group_id: Some(group.group_id.clone()),
|
||||
layout: LayoutTypePB::Board,
|
||||
cell_data_by_field_id: None,
|
||||
};
|
||||
let _ = self.editor.create_row(params).await.unwrap();
|
||||
},
|
||||
|
@ -0,0 +1,2 @@
|
||||
mod script;
|
||||
mod test;
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
|
||||
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()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ mod database_ref_test;
|
||||
mod field_test;
|
||||
mod filter_test;
|
||||
mod group_test;
|
||||
mod layout_test;
|
||||
mod snapshot_test;
|
||||
mod sort_test;
|
||||
|
||||
|
@ -186,6 +186,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("Payload should not be empty")]
|
||||
UnexpectedEmptyPayload = 60,
|
||||
|
||||
#[error("Only the date type can be used in calendar")]
|
||||
UnexpectedCalendarFieldType = 61,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -82,6 +82,10 @@ impl FlowyError {
|
||||
static_flowy_error!(field_record_not_found, ErrorCode::FieldRecordNotFound);
|
||||
static_flowy_error!(payload_none, ErrorCode::UnexpectedEmptyPayload);
|
||||
static_flowy_error!(http, ErrorCode::HttpError);
|
||||
static_flowy_error!(
|
||||
unexpect_calendar_field_type,
|
||||
ErrorCode::UnexpectedCalendarFieldType
|
||||
);
|
||||
}
|
||||
|
||||
impl std::convert::From<ErrorCode> for FlowyError {
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::DatabaseBlockRevision;
|
||||
use crate::{DatabaseBlockRevision, LayoutSetting};
|
||||
use bytes::Bytes;
|
||||
use indexmap::IndexMap;
|
||||
use nanoid::nanoid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn gen_database_id() -> String {
|
||||
@ -195,6 +196,7 @@ pub struct BuildDatabaseContext {
|
||||
pub field_revs: Vec<Arc<FieldRevision>>,
|
||||
pub block_metas: Vec<DatabaseBlockMetaRevision>,
|
||||
pub blocks: Vec<DatabaseBlockRevision>,
|
||||
pub layout_setting: LayoutSetting,
|
||||
|
||||
// String in JSON format. It can be deserialized into [GridViewRevision]
|
||||
pub database_view_data: String,
|
||||
@ -223,3 +225,36 @@ impl std::convert::TryFrom<Bytes> for BuildDatabaseContext {
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -47,8 +47,8 @@ pub struct DatabaseViewRevision {
|
||||
pub layout: LayoutRevision,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "LayoutSettings::is_empty")]
|
||||
pub layout_settings: LayoutSettings,
|
||||
#[serde(skip_serializing_if = "LayoutSetting::is_empty")]
|
||||
pub layout_settings: LayoutSetting,
|
||||
|
||||
#[serde(default)]
|
||||
pub filters: FilterConfiguration,
|
||||
@ -90,18 +90,23 @@ impl DatabaseViewRevision {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct LayoutSettings {
|
||||
pub struct LayoutSetting {
|
||||
#[serde(with = "indexmap::serde_seq")]
|
||||
inner: IndexMap<LayoutRevision, String>,
|
||||
}
|
||||
|
||||
impl LayoutSettings {
|
||||
impl LayoutSetting {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Default::default(),
|
||||
}
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for LayoutSettings {
|
||||
impl std::ops::Deref for LayoutSetting {
|
||||
type Target = IndexMap<LayoutRevision, String>;
|
||||
|
||||
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 {
|
||||
&mut self.inner
|
||||
}
|
||||
|
@ -62,15 +62,15 @@ impl fmt::Display for SyncError {
|
||||
pub enum ErrorCode {
|
||||
DocumentIdInvalid = 0,
|
||||
DocumentNotfound = 1,
|
||||
UndoFail = 200,
|
||||
RedoFail = 201,
|
||||
OutOfBound = 202,
|
||||
RevisionConflict = 203,
|
||||
RecordNotFound = 300,
|
||||
CannotDeleteThePrimaryField = 301,
|
||||
UnexpectedEmptyRevision = 302,
|
||||
SerdeError = 999,
|
||||
InternalError = 1000,
|
||||
UndoFail = 2,
|
||||
RedoFail = 3,
|
||||
OutOfBound = 4,
|
||||
RevisionConflict = 5,
|
||||
RecordNotFound = 6,
|
||||
CannotDeleteThePrimaryField = 7,
|
||||
UnexpectedEmptyRevision = 8,
|
||||
SerdeError = 100,
|
||||
InternalError = 101,
|
||||
}
|
||||
|
||||
impl std::convert::From<lib_ot::errors::OTError> for SyncError {
|
||||
|
Loading…
Reference in New Issue
Block a user