Skip to content

Commit 7106195

Browse files
authored
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
1 parent 90da54d commit 7106195

File tree

92 files changed

+2400
-1298
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+2400
-1298
lines changed

frontend/appflowy_flutter/assets/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,12 @@
372372
},
373373
"calendar": {
374374
"menuName": "Calendar",
375+
"defaultNewCalendarTitle": "Untitled",
375376
"navigation": {
376377
"today": "Today",
377378
"jumpToday": "Jump to Today",
378379
"previousMonth": "Previous Month",
379380
"nextMonth": "Next Month"
380381
}
381382
}
382-
}
383+
}

frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_cache.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ part of 'cell_service.dart';
22

33
typedef CellByFieldId = LinkedHashMap<String, CellIdentifier>;
44

5-
class GridBaseCell {
5+
class DatabaseCell {
66
dynamic object;
7-
GridBaseCell({
7+
DatabaseCell({
88
required this.object,
99
});
1010
}
@@ -44,7 +44,7 @@ class CellCache {
4444
}
4545
}
4646

47-
void insert<T extends GridBaseCell>(CellCacheKey key, T value) {
47+
void insert<T extends DatabaseCell>(CellCacheKey key, T value) {
4848
var map = _cellDataByFieldId[key.fieldId];
4949
if (map == null) {
5050
_cellDataByFieldId[key.fieldId] = {};

frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ class CellController<T, D> extends Equatable {
170170
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
171171
_cellDataLoader.loadData().then((data) {
172172
if (data != null) {
173-
_cellCache.insert(_cacheKey, GridBaseCell(object: data));
173+
_cellCache.insert(_cacheKey, DatabaseCell(object: data));
174174
} else {
175175
_cellCache.remove(_cacheKey);
176176
}
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
2+
import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
3+
import 'package:appflowy_backend/log.dart';
4+
import 'package:appflowy_backend/protobuf/flowy-database/calendar_entities.pb.dart';
5+
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
6+
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
7+
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
8+
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
9+
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
10+
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
11+
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
12+
import 'package:collection/collection.dart';
13+
import 'dart:async';
14+
import 'package:dartz/dartz.dart';
15+
import 'database_service.dart';
16+
import 'defines.dart';
17+
import 'layout/layout_setting_listener.dart';
18+
import 'row/row_cache.dart';
19+
import 'group/group_listener.dart';
20+
21+
typedef OnRowsChanged = void Function(
22+
List<RowInfo> rowInfos,
23+
RowsChangedReason,
24+
);
25+
26+
typedef OnGroupByField = void Function(List<GroupPB>);
27+
typedef OnUpdateGroup = void Function(List<GroupPB>);
28+
typedef OnDeleteGroup = void Function(List<String>);
29+
typedef OnInsertGroup = void Function(InsertedGroupPB);
30+
31+
class GroupCallbacks {
32+
final OnGroupByField? onGroupByField;
33+
final OnUpdateGroup? onUpdateGroup;
34+
final OnDeleteGroup? onDeleteGroup;
35+
final OnInsertGroup? onInsertGroup;
36+
37+
GroupCallbacks({
38+
this.onGroupByField,
39+
this.onUpdateGroup,
40+
this.onDeleteGroup,
41+
this.onInsertGroup,
42+
});
43+
}
44+
45+
class LayoutCallbacks {
46+
final void Function(LayoutSettingPB) onLayoutChanged;
47+
final void Function(LayoutSettingPB) onLoadLayout;
48+
49+
LayoutCallbacks({
50+
required this.onLayoutChanged,
51+
required this.onLoadLayout,
52+
});
53+
}
54+
55+
class DatabaseCallbacks {
56+
OnDatabaseChanged? onDatabaseChanged;
57+
OnRowsChanged? onRowsChanged;
58+
OnFieldsChanged? onFieldsChanged;
59+
OnFiltersChanged? onFiltersChanged;
60+
DatabaseCallbacks({
61+
this.onDatabaseChanged,
62+
this.onRowsChanged,
63+
this.onFieldsChanged,
64+
this.onFiltersChanged,
65+
});
66+
}
67+
68+
class DatabaseController {
69+
final String viewId;
70+
final DatabaseBackendService _databaseBackendSvc;
71+
final FieldController fieldController;
72+
late DatabaseViewCache _viewCache;
73+
final LayoutTypePB layoutType;
74+
75+
// Callbacks
76+
DatabaseCallbacks? _databaseCallbacks;
77+
GroupCallbacks? _groupCallbacks;
78+
LayoutCallbacks? _layoutCallbacks;
79+
80+
// Getters
81+
List<RowInfo> get rowInfos => _viewCache.rowInfos;
82+
RowCache get rowCache => _viewCache.rowCache;
83+
84+
// Listener
85+
final DatabaseGroupListener groupListener;
86+
final DatabaseLayoutListener layoutListener;
87+
88+
DatabaseController({required ViewPB view, required this.layoutType})
89+
: viewId = view.id,
90+
_databaseBackendSvc = DatabaseBackendService(viewId: view.id),
91+
fieldController = FieldController(viewId: view.id),
92+
groupListener = DatabaseGroupListener(view.id),
93+
layoutListener = DatabaseLayoutListener(view.id) {
94+
_viewCache = DatabaseViewCache(
95+
viewId: viewId,
96+
fieldController: fieldController,
97+
);
98+
_listenOnRowsChanged();
99+
_listenOnFieldsChanged();
100+
_listenOnGroupChanged();
101+
_listenOnLayoutChanged();
102+
}
103+
104+
void addListener({
105+
DatabaseCallbacks? onDatabaseChanged,
106+
LayoutCallbacks? onLayoutChanged,
107+
GroupCallbacks? onGroupChanged,
108+
}) {
109+
_layoutCallbacks = onLayoutChanged;
110+
_databaseCallbacks = onDatabaseChanged;
111+
_groupCallbacks = onGroupChanged;
112+
}
113+
114+
Future<Either<Unit, FlowyError>> open() async {
115+
return _databaseBackendSvc.openGrid().then((result) {
116+
return result.fold(
117+
(database) async {
118+
_databaseCallbacks?.onDatabaseChanged?.call(database);
119+
_viewCache.rowCache.setInitialRows(database.rows);
120+
return await fieldController
121+
.loadFields(
122+
fieldIds: database.fields,
123+
)
124+
.then(
125+
(result) {
126+
return result.fold(
127+
(l) => Future(() async {
128+
await _loadGroups();
129+
await _loadLayoutSetting();
130+
return left(l);
131+
}),
132+
(err) => right(err),
133+
);
134+
},
135+
);
136+
},
137+
(err) => right(err),
138+
);
139+
});
140+
}
141+
142+
Future<Either<RowPB, FlowyError>> createRow({
143+
String? startRowId,
144+
String? groupId,
145+
void Function(RowDataBuilder builder)? withCells,
146+
}) {
147+
Map<String, String>? cellDataByFieldId;
148+
149+
if (withCells != null) {
150+
final rowBuilder = RowDataBuilder();
151+
withCells(rowBuilder);
152+
cellDataByFieldId = rowBuilder.build();
153+
}
154+
155+
return _databaseBackendSvc.createRow(
156+
startRowId: startRowId,
157+
groupId: groupId,
158+
cellDataByFieldId: cellDataByFieldId,
159+
);
160+
}
161+
162+
Future<Either<Unit, FlowyError>> moveRow(RowPB fromRow,
163+
{RowPB? toRow, String? groupId}) {
164+
return _databaseBackendSvc.moveRow(
165+
fromRowId: fromRow.id,
166+
toGroupId: groupId,
167+
toRowId: toRow?.id,
168+
);
169+
}
170+
171+
Future<Either<Unit, FlowyError>> moveGroup(
172+
{required String fromGroupId, required String toGroupId}) {
173+
return _databaseBackendSvc.moveGroup(
174+
fromGroupId: fromGroupId,
175+
toGroupId: toGroupId,
176+
);
177+
}
178+
179+
Future<void> updateCalenderLayoutSetting(
180+
CalendarLayoutSettingsPB layoutSetting) async {
181+
await _databaseBackendSvc
182+
.updateLayoutSetting(calendarLayoutSetting: layoutSetting)
183+
.then((result) {
184+
result.fold((l) => null, (r) => Log.error(r));
185+
});
186+
}
187+
188+
Future<void> dispose() async {
189+
await _databaseBackendSvc.closeView();
190+
await fieldController.dispose();
191+
await groupListener.stop();
192+
}
193+
194+
Future<void> _loadGroups() async {
195+
final result = await _databaseBackendSvc.loadGroups();
196+
return Future(
197+
() => result.fold(
198+
(groups) {
199+
_groupCallbacks?.onGroupByField?.call(groups.items);
200+
},
201+
(err) => Log.error(err),
202+
),
203+
);
204+
}
205+
206+
Future<void> _loadLayoutSetting() async {
207+
_databaseBackendSvc.getLayoutSetting(layoutType).then((result) {
208+
result.fold(
209+
(l) {
210+
_layoutCallbacks?.onLoadLayout(l);
211+
},
212+
(r) => Log.error(r),
213+
);
214+
});
215+
}
216+
217+
void _listenOnRowsChanged() {
218+
_viewCache.addListener(onRowsChanged: (reason) {
219+
_databaseCallbacks?.onRowsChanged?.call(rowInfos, reason);
220+
});
221+
}
222+
223+
void _listenOnFieldsChanged() {
224+
fieldController.addListener(
225+
onReceiveFields: (fields) {
226+
_databaseCallbacks?.onFieldsChanged?.call(UnmodifiableListView(fields));
227+
},
228+
onFilters: (filters) {
229+
_databaseCallbacks?.onFiltersChanged?.call(filters);
230+
},
231+
);
232+
}
233+
234+
void _listenOnGroupChanged() {
235+
groupListener.start(
236+
onNumOfGroupsChanged: (result) {
237+
result.fold((changeset) {
238+
if (changeset.updateGroups.isNotEmpty) {
239+
_groupCallbacks?.onUpdateGroup?.call(changeset.updateGroups);
240+
}
241+
242+
if (changeset.deletedGroups.isNotEmpty) {
243+
_groupCallbacks?.onDeleteGroup?.call(changeset.deletedGroups);
244+
}
245+
246+
for (final insertedGroup in changeset.insertedGroups) {
247+
_groupCallbacks?.onInsertGroup?.call(insertedGroup);
248+
}
249+
}, (r) => Log.error(r));
250+
},
251+
onGroupByNewField: (result) {
252+
result.fold((groups) {
253+
_groupCallbacks?.onGroupByField?.call(groups);
254+
}, (r) => Log.error(r));
255+
},
256+
);
257+
}
258+
259+
void _listenOnLayoutChanged() {
260+
layoutListener.start(onLayoutChanged: (result) {
261+
result.fold((l) {
262+
_layoutCallbacks?.onLayoutChanged(l);
263+
}, (r) => Log.error(r));
264+
});
265+
}
266+
}
267+
268+
class RowDataBuilder {
269+
final _cellDataByFieldId = <String, String>{};
270+
271+
void insertText(FieldInfo fieldInfo, String text) {
272+
assert(fieldInfo.fieldType == FieldType.RichText);
273+
_cellDataByFieldId[fieldInfo.field.id] = text;
274+
}
275+
276+
void insertNumber(FieldInfo fieldInfo, int num) {
277+
assert(fieldInfo.fieldType == FieldType.Number);
278+
_cellDataByFieldId[fieldInfo.field.id] = num.toString();
279+
}
280+
281+
void insertDate(FieldInfo fieldInfo, DateTime date) {
282+
assert(fieldInfo.fieldType == FieldType.DateTime);
283+
final timestamp = (date.millisecondsSinceEpoch ~/ 1000);
284+
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
285+
}
286+
287+
Map<String, String> build() {
288+
return _cellDataByFieldId;
289+
}
290+
}

0 commit comments

Comments
 (0)