database_controller.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
  2. import 'package:appflowy/plugins/database_view/application/layout/calendar_setting_listener.dart';
  3. import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
  4. import 'package:appflowy_backend/log.dart';
  5. import 'package:appflowy_backend/protobuf/flowy-database/calendar_entities.pb.dart';
  6. import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
  7. import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
  8. import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
  9. import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
  10. import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
  11. import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
  12. import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
  13. import 'package:collection/collection.dart';
  14. import 'dart:async';
  15. import 'package:dartz/dartz.dart';
  16. import 'database_view_service.dart';
  17. import 'defines.dart';
  18. import 'layout/layout_setting_listener.dart';
  19. import 'row/row_cache.dart';
  20. import 'group/group_listener.dart';
  21. typedef OnGroupByField = void Function(List<GroupPB>);
  22. typedef OnUpdateGroup = void Function(List<GroupPB>);
  23. typedef OnDeleteGroup = void Function(List<String>);
  24. typedef OnInsertGroup = void Function(InsertedGroupPB);
  25. class GroupCallbacks {
  26. final OnGroupByField? onGroupByField;
  27. final OnUpdateGroup? onUpdateGroup;
  28. final OnDeleteGroup? onDeleteGroup;
  29. final OnInsertGroup? onInsertGroup;
  30. GroupCallbacks({
  31. this.onGroupByField,
  32. this.onUpdateGroup,
  33. this.onDeleteGroup,
  34. this.onInsertGroup,
  35. });
  36. }
  37. class LayoutCallbacks {
  38. final void Function(LayoutSettingPB) onLayoutChanged;
  39. final void Function(LayoutSettingPB) onLoadLayout;
  40. LayoutCallbacks({
  41. required this.onLayoutChanged,
  42. required this.onLoadLayout,
  43. });
  44. }
  45. class CalendarLayoutCallbacks {
  46. final void Function(LayoutSettingPB) onCalendarLayoutChanged;
  47. CalendarLayoutCallbacks({required this.onCalendarLayoutChanged});
  48. }
  49. class DatabaseCallbacks {
  50. OnDatabaseChanged? onDatabaseChanged;
  51. OnFieldsChanged? onFieldsChanged;
  52. OnFiltersChanged? onFiltersChanged;
  53. OnRowsChanged? onRowsChanged;
  54. OnRowsDeleted? onRowsDeleted;
  55. OnRowsUpdated? onRowsUpdated;
  56. OnRowsCreated? onRowsCreated;
  57. DatabaseCallbacks({
  58. this.onDatabaseChanged,
  59. this.onRowsChanged,
  60. this.onFieldsChanged,
  61. this.onFiltersChanged,
  62. this.onRowsUpdated,
  63. this.onRowsDeleted,
  64. this.onRowsCreated,
  65. });
  66. }
  67. class DatabaseController {
  68. final String viewId;
  69. final DatabaseViewBackendService _databaseViewBackendSvc;
  70. final FieldController fieldController;
  71. late DatabaseViewCache _viewCache;
  72. final LayoutTypePB layoutType;
  73. // Callbacks
  74. DatabaseCallbacks? _databaseCallbacks;
  75. GroupCallbacks? _groupCallbacks;
  76. LayoutCallbacks? _layoutCallbacks;
  77. CalendarLayoutCallbacks? _calendarLayoutCallbacks;
  78. // Getters
  79. RowCache get rowCache => _viewCache.rowCache;
  80. // Listener
  81. final DatabaseGroupListener groupListener;
  82. final DatabaseLayoutListener layoutListener;
  83. final DatabaseCalendarLayoutListener calendarLayoutListener;
  84. DatabaseController({required ViewPB view, required this.layoutType})
  85. : viewId = view.id,
  86. _databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id),
  87. fieldController = FieldController(viewId: view.id),
  88. groupListener = DatabaseGroupListener(view.id),
  89. layoutListener = DatabaseLayoutListener(view.id),
  90. calendarLayoutListener = DatabaseCalendarLayoutListener(view.id) {
  91. _viewCache = DatabaseViewCache(
  92. viewId: viewId,
  93. fieldController: fieldController,
  94. );
  95. _listenOnRowsChanged();
  96. _listenOnFieldsChanged();
  97. _listenOnGroupChanged();
  98. _listenOnLayoutChanged();
  99. if (layoutType == LayoutTypePB.Calendar) {
  100. _listenOnCalendarLayoutChanged();
  101. }
  102. }
  103. void setListener({
  104. DatabaseCallbacks? onDatabaseChanged,
  105. LayoutCallbacks? onLayoutChanged,
  106. GroupCallbacks? onGroupChanged,
  107. CalendarLayoutCallbacks? onCalendarLayoutChanged,
  108. }) {
  109. _layoutCallbacks = onLayoutChanged;
  110. _databaseCallbacks = onDatabaseChanged;
  111. _groupCallbacks = onGroupChanged;
  112. _calendarLayoutCallbacks = onCalendarLayoutChanged;
  113. }
  114. Future<Either<Unit, FlowyError>> open() async {
  115. return _databaseViewBackendSvc.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. Future<Either<RowPB, FlowyError>> createRow({
  142. String? startRowId,
  143. String? groupId,
  144. void Function(RowDataBuilder builder)? withCells,
  145. }) {
  146. Map<String, String>? cellDataByFieldId;
  147. if (withCells != null) {
  148. final rowBuilder = RowDataBuilder();
  149. withCells(rowBuilder);
  150. cellDataByFieldId = rowBuilder.build();
  151. }
  152. return _databaseViewBackendSvc.createRow(
  153. startRowId: startRowId,
  154. groupId: groupId,
  155. cellDataByFieldId: cellDataByFieldId,
  156. );
  157. }
  158. Future<Either<Unit, FlowyError>> moveGroupRow({
  159. required RowPB fromRow,
  160. required String groupId,
  161. RowPB? toRow,
  162. }) {
  163. return _databaseViewBackendSvc.moveGroupRow(
  164. fromRowId: fromRow.id,
  165. toGroupId: groupId,
  166. toRowId: toRow?.id,
  167. );
  168. }
  169. Future<Either<Unit, FlowyError>> moveRow({
  170. required RowPB fromRow,
  171. required RowPB toRow,
  172. }) {
  173. return _databaseViewBackendSvc.moveRow(
  174. fromRowId: fromRow.id,
  175. toRowId: toRow.id,
  176. );
  177. }
  178. Future<Either<Unit, FlowyError>> moveGroup({
  179. required String fromGroupId,
  180. required String toGroupId,
  181. }) {
  182. return _databaseViewBackendSvc.moveGroup(
  183. fromGroupId: fromGroupId,
  184. toGroupId: toGroupId,
  185. );
  186. }
  187. Future<void> updateCalenderLayoutSetting(
  188. CalendarLayoutSettingsPB layoutSetting,
  189. ) async {
  190. await _databaseViewBackendSvc
  191. .updateLayoutSetting(calendarLayoutSetting: layoutSetting)
  192. .then((result) {
  193. result.fold((l) => null, (r) => Log.error(r));
  194. });
  195. }
  196. Future<void> dispose() async {
  197. await _databaseViewBackendSvc.closeView();
  198. await fieldController.dispose();
  199. await groupListener.stop();
  200. await _viewCache.dispose();
  201. _databaseCallbacks = null;
  202. _groupCallbacks = null;
  203. _layoutCallbacks = null;
  204. _calendarLayoutCallbacks = null;
  205. }
  206. Future<void> _loadGroups() async {
  207. final result = await _databaseViewBackendSvc.loadGroups();
  208. return Future(
  209. () => result.fold(
  210. (groups) {
  211. _groupCallbacks?.onGroupByField?.call(groups.items);
  212. },
  213. (err) => Log.error(err),
  214. ),
  215. );
  216. }
  217. Future<void> _loadLayoutSetting() async {
  218. _databaseViewBackendSvc.getLayoutSetting(layoutType).then((result) {
  219. result.fold(
  220. (l) {
  221. _layoutCallbacks?.onLoadLayout(l);
  222. },
  223. (r) => Log.error(r),
  224. );
  225. });
  226. }
  227. void _listenOnRowsChanged() {
  228. final callbacks = DatabaseViewCallbacks(
  229. onRowsChanged: (rows, rowByRowId, reason) {
  230. _databaseCallbacks?.onRowsChanged?.call(rows, rowByRowId, reason);
  231. },
  232. onRowsDeleted: (ids) {
  233. _databaseCallbacks?.onRowsDeleted?.call(ids);
  234. },
  235. onRowsUpdated: (ids) {
  236. _databaseCallbacks?.onRowsUpdated?.call(ids);
  237. },
  238. onRowsCreated: (ids) {
  239. _databaseCallbacks?.onRowsCreated?.call(ids);
  240. },
  241. );
  242. _viewCache.setListener(callbacks);
  243. }
  244. void _listenOnFieldsChanged() {
  245. fieldController.addListener(
  246. onReceiveFields: (fields) {
  247. _databaseCallbacks?.onFieldsChanged?.call(UnmodifiableListView(fields));
  248. },
  249. onFilters: (filters) {
  250. _databaseCallbacks?.onFiltersChanged?.call(filters);
  251. },
  252. );
  253. }
  254. void _listenOnGroupChanged() {
  255. groupListener.start(
  256. onNumOfGroupsChanged: (result) {
  257. result.fold(
  258. (changeset) {
  259. if (changeset.updateGroups.isNotEmpty) {
  260. _groupCallbacks?.onUpdateGroup?.call(changeset.updateGroups);
  261. }
  262. if (changeset.deletedGroups.isNotEmpty) {
  263. _groupCallbacks?.onDeleteGroup?.call(changeset.deletedGroups);
  264. }
  265. for (final insertedGroup in changeset.insertedGroups) {
  266. _groupCallbacks?.onInsertGroup?.call(insertedGroup);
  267. }
  268. },
  269. (r) => Log.error(r),
  270. );
  271. },
  272. onGroupByNewField: (result) {
  273. result.fold(
  274. (groups) {
  275. _groupCallbacks?.onGroupByField?.call(groups);
  276. },
  277. (r) => Log.error(r),
  278. );
  279. },
  280. );
  281. }
  282. void _listenOnLayoutChanged() {
  283. layoutListener.start(
  284. onLayoutChanged: (result) {
  285. result.fold(
  286. (l) {
  287. _layoutCallbacks?.onLayoutChanged(l);
  288. },
  289. (r) => Log.error(r),
  290. );
  291. },
  292. );
  293. }
  294. void _listenOnCalendarLayoutChanged() {
  295. calendarLayoutListener.start(
  296. onCalendarLayoutChanged: (result) {
  297. result.fold(
  298. (l) {
  299. _calendarLayoutCallbacks?.onCalendarLayoutChanged(l);
  300. },
  301. (r) => Log.error(r),
  302. );
  303. },
  304. );
  305. }
  306. }
  307. class RowDataBuilder {
  308. final _cellDataByFieldId = <String, String>{};
  309. void insertText(FieldInfo fieldInfo, String text) {
  310. assert(fieldInfo.fieldType == FieldType.RichText);
  311. _cellDataByFieldId[fieldInfo.field.id] = text;
  312. }
  313. void insertNumber(FieldInfo fieldInfo, int num) {
  314. assert(fieldInfo.fieldType == FieldType.Number);
  315. _cellDataByFieldId[fieldInfo.field.id] = num.toString();
  316. }
  317. /// The date should use the UTC timezone. Becuase the backend uses UTC timezone to format the time string.
  318. void insertDate(FieldInfo fieldInfo, DateTime date) {
  319. assert(fieldInfo.fieldType == FieldType.DateTime);
  320. final timestamp = (date.toUtc().millisecondsSinceEpoch ~/ 1000);
  321. _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
  322. }
  323. Map<String, String> build() {
  324. return _cellDataByFieldId;
  325. }
  326. }