database_controller.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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>> moveRow({
  159. required RowPB fromRow,
  160. required String groupId,
  161. RowPB? toRow,
  162. }) {
  163. return _databaseViewBackendSvc.moveRow(
  164. fromRowId: fromRow.id,
  165. toGroupId: groupId,
  166. toRowId: toRow?.id,
  167. );
  168. }
  169. Future<Either<Unit, FlowyError>> moveGroup({
  170. required String fromGroupId,
  171. required String toGroupId,
  172. }) {
  173. return _databaseViewBackendSvc.moveGroup(
  174. fromGroupId: fromGroupId,
  175. toGroupId: toGroupId,
  176. );
  177. }
  178. Future<void> updateCalenderLayoutSetting(
  179. CalendarLayoutSettingsPB layoutSetting,
  180. ) async {
  181. await _databaseViewBackendSvc
  182. .updateLayoutSetting(calendarLayoutSetting: layoutSetting)
  183. .then((result) {
  184. result.fold((l) => null, (r) => Log.error(r));
  185. });
  186. }
  187. Future<void> dispose() async {
  188. await _databaseViewBackendSvc.closeView();
  189. await fieldController.dispose();
  190. await groupListener.stop();
  191. await _viewCache.dispose();
  192. _databaseCallbacks = null;
  193. _groupCallbacks = null;
  194. _layoutCallbacks = null;
  195. _calendarLayoutCallbacks = null;
  196. }
  197. Future<void> _loadGroups() async {
  198. final result = await _databaseViewBackendSvc.loadGroups();
  199. return Future(
  200. () => result.fold(
  201. (groups) {
  202. _groupCallbacks?.onGroupByField?.call(groups.items);
  203. },
  204. (err) => Log.error(err),
  205. ),
  206. );
  207. }
  208. Future<void> _loadLayoutSetting() async {
  209. _databaseViewBackendSvc.getLayoutSetting(layoutType).then((result) {
  210. result.fold(
  211. (l) {
  212. _layoutCallbacks?.onLoadLayout(l);
  213. },
  214. (r) => Log.error(r),
  215. );
  216. });
  217. }
  218. void _listenOnRowsChanged() {
  219. final callbacks = DatabaseViewCallbacks(
  220. onRowsChanged: (rows, rowByRowId, reason) {
  221. _databaseCallbacks?.onRowsChanged?.call(rows, rowByRowId, reason);
  222. },
  223. onRowsDeleted: (ids) {
  224. _databaseCallbacks?.onRowsDeleted?.call(ids);
  225. },
  226. onRowsUpdated: (ids) {
  227. _databaseCallbacks?.onRowsUpdated?.call(ids);
  228. },
  229. onRowsCreated: (ids) {
  230. _databaseCallbacks?.onRowsCreated?.call(ids);
  231. },
  232. );
  233. _viewCache.setListener(callbacks);
  234. }
  235. void _listenOnFieldsChanged() {
  236. fieldController.addListener(
  237. onReceiveFields: (fields) {
  238. _databaseCallbacks?.onFieldsChanged?.call(UnmodifiableListView(fields));
  239. },
  240. onFilters: (filters) {
  241. _databaseCallbacks?.onFiltersChanged?.call(filters);
  242. },
  243. );
  244. }
  245. void _listenOnGroupChanged() {
  246. groupListener.start(
  247. onNumOfGroupsChanged: (result) {
  248. result.fold(
  249. (changeset) {
  250. if (changeset.updateGroups.isNotEmpty) {
  251. _groupCallbacks?.onUpdateGroup?.call(changeset.updateGroups);
  252. }
  253. if (changeset.deletedGroups.isNotEmpty) {
  254. _groupCallbacks?.onDeleteGroup?.call(changeset.deletedGroups);
  255. }
  256. for (final insertedGroup in changeset.insertedGroups) {
  257. _groupCallbacks?.onInsertGroup?.call(insertedGroup);
  258. }
  259. },
  260. (r) => Log.error(r),
  261. );
  262. },
  263. onGroupByNewField: (result) {
  264. result.fold(
  265. (groups) {
  266. _groupCallbacks?.onGroupByField?.call(groups);
  267. },
  268. (r) => Log.error(r),
  269. );
  270. },
  271. );
  272. }
  273. void _listenOnLayoutChanged() {
  274. layoutListener.start(
  275. onLayoutChanged: (result) {
  276. result.fold(
  277. (l) {
  278. _layoutCallbacks?.onLayoutChanged(l);
  279. },
  280. (r) => Log.error(r),
  281. );
  282. },
  283. );
  284. }
  285. void _listenOnCalendarLayoutChanged() {
  286. calendarLayoutListener.start(
  287. onCalendarLayoutChanged: (result) {
  288. result.fold(
  289. (l) {
  290. _calendarLayoutCallbacks?.onCalendarLayoutChanged(l);
  291. },
  292. (r) => Log.error(r),
  293. );
  294. },
  295. );
  296. }
  297. }
  298. class RowDataBuilder {
  299. final _cellDataByFieldId = <String, String>{};
  300. void insertText(FieldInfo fieldInfo, String text) {
  301. assert(fieldInfo.fieldType == FieldType.RichText);
  302. _cellDataByFieldId[fieldInfo.field.id] = text;
  303. }
  304. void insertNumber(FieldInfo fieldInfo, int num) {
  305. assert(fieldInfo.fieldType == FieldType.Number);
  306. _cellDataByFieldId[fieldInfo.field.id] = num.toString();
  307. }
  308. /// The date should use the UTC timezone. Becuase the backend uses UTC timezone to format the time string.
  309. void insertDate(FieldInfo fieldInfo, DateTime date) {
  310. assert(fieldInfo.fieldType == FieldType.DateTime);
  311. final timestamp = (date.toUtc().millisecondsSinceEpoch ~/ 1000);
  312. _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
  313. }
  314. Map<String, String> build() {
  315. return _cellDataByFieldId;
  316. }
  317. }