database_controller.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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-database2/calendar_entities.pb.dart';
  5. import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
  6. import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
  7. import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
  8. import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart';
  9. import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
  10. import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
  11. import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
  12. import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
  13. import 'package:collection/collection.dart';
  14. import 'dart:async';
  15. import 'package:dartz/dartz.dart';
  16. import 'package:flutter/material.dart';
  17. import 'database_view_service.dart';
  18. import 'defines.dart';
  19. import 'layout/layout_service.dart';
  20. import 'layout/layout_setting_listener.dart';
  21. import 'row/row_cache.dart';
  22. import 'group/group_listener.dart';
  23. import 'row/row_service.dart';
  24. typedef OnGroupByField = void Function(List<GroupPB>);
  25. typedef OnUpdateGroup = void Function(List<GroupPB>);
  26. typedef OnDeleteGroup = void Function(List<String>);
  27. typedef OnInsertGroup = void Function(InsertedGroupPB);
  28. class GroupCallbacks {
  29. final OnGroupByField? onGroupByField;
  30. final OnUpdateGroup? onUpdateGroup;
  31. final OnDeleteGroup? onDeleteGroup;
  32. final OnInsertGroup? onInsertGroup;
  33. GroupCallbacks({
  34. this.onGroupByField,
  35. this.onUpdateGroup,
  36. this.onDeleteGroup,
  37. this.onInsertGroup,
  38. });
  39. }
  40. class DatabaseLayoutSettingCallbacks {
  41. final void Function(DatabaseLayoutSettingPB) onLayoutChanged;
  42. final void Function(DatabaseLayoutSettingPB) onLoadLayout;
  43. DatabaseLayoutSettingCallbacks({
  44. required this.onLayoutChanged,
  45. required this.onLoadLayout,
  46. });
  47. }
  48. class DatabaseCallbacks {
  49. OnDatabaseChanged? onDatabaseChanged;
  50. OnFieldsChanged? onFieldsChanged;
  51. OnFiltersChanged? onFiltersChanged;
  52. OnSortsChanged? onSortsChanged;
  53. OnNumOfRowsChanged? onNumOfRowsChanged;
  54. OnRowsDeleted? onRowsDeleted;
  55. OnRowsUpdated? onRowsUpdated;
  56. OnRowsCreated? onRowsCreated;
  57. DatabaseCallbacks({
  58. this.onDatabaseChanged,
  59. this.onNumOfRowsChanged,
  60. this.onFieldsChanged,
  61. this.onFiltersChanged,
  62. this.onSortsChanged,
  63. this.onRowsUpdated,
  64. this.onRowsDeleted,
  65. this.onRowsCreated,
  66. });
  67. }
  68. class DatabaseController {
  69. final String viewId;
  70. final DatabaseViewBackendService _databaseViewBackendSvc;
  71. final FieldController fieldController;
  72. DatabaseLayoutPB databaseLayout;
  73. DatabaseLayoutSettingPB? databaseLayoutSetting;
  74. late DatabaseViewCache _viewCache;
  75. // Callbacks
  76. final List<DatabaseCallbacks> _databaseCallbacks = [];
  77. final List<GroupCallbacks> _groupCallbacks = [];
  78. final List<DatabaseLayoutSettingCallbacks> _layoutCallbacks = [];
  79. // Getters
  80. RowCache get rowCache => _viewCache.rowCache;
  81. // Listener
  82. final DatabaseGroupListener _groupListener;
  83. final DatabaseLayoutSettingListener _layoutListener;
  84. final ValueNotifier<bool> _isLoading = ValueNotifier(true);
  85. DatabaseController({required ViewPB view})
  86. : viewId = view.id,
  87. _databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id),
  88. fieldController = FieldController(viewId: view.id),
  89. _groupListener = DatabaseGroupListener(view.id),
  90. databaseLayout = databaseLayoutFromViewLayout(view.layout),
  91. _layoutListener = DatabaseLayoutSettingListener(view.id) {
  92. _viewCache = DatabaseViewCache(
  93. viewId: viewId,
  94. fieldController: fieldController,
  95. );
  96. _listenOnRowsChanged();
  97. _listenOnFieldsChanged();
  98. _listenOnGroupChanged();
  99. _listenOnLayoutChanged();
  100. }
  101. void setIsLoading(bool isLoading) {
  102. _isLoading.value = isLoading;
  103. }
  104. ValueNotifier<bool> get isLoading => _isLoading;
  105. void addListener({
  106. DatabaseCallbacks? onDatabaseChanged,
  107. DatabaseLayoutSettingCallbacks? onLayoutChanged,
  108. GroupCallbacks? onGroupChanged,
  109. }) {
  110. if (onLayoutChanged != null) {
  111. _layoutCallbacks.add(onLayoutChanged);
  112. }
  113. if (onDatabaseChanged != null) {
  114. _databaseCallbacks.add(onDatabaseChanged);
  115. }
  116. if (onGroupChanged != null) {
  117. _groupCallbacks.add(onGroupChanged);
  118. }
  119. }
  120. Future<Either<Unit, FlowyError>> open() async {
  121. return _databaseViewBackendSvc.openDatabase().then((result) {
  122. return result.fold(
  123. (DatabasePB database) async {
  124. databaseLayout = database.layoutType;
  125. // Load the actual database field data.
  126. final fieldsOrFail = await fieldController.loadFields(
  127. fieldIds: database.fields,
  128. );
  129. return fieldsOrFail.fold(
  130. (fields) {
  131. // Notify the database is changed after the fields are loaded.
  132. // The database won't can't be used until the fields are loaded.
  133. for (final callback in _databaseCallbacks) {
  134. callback.onDatabaseChanged?.call(database);
  135. }
  136. _viewCache.rowCache.setInitialRows(database.rows);
  137. return Future(() async {
  138. await _loadGroups();
  139. await _loadLayoutSetting();
  140. return left(fields);
  141. });
  142. },
  143. (err) {
  144. Log.error(err);
  145. return right(err);
  146. },
  147. );
  148. },
  149. (err) => right(err),
  150. );
  151. });
  152. }
  153. Future<Either<RowMetaPB, FlowyError>> createRow({
  154. RowId? startRowId,
  155. String? groupId,
  156. void Function(RowDataBuilder builder)? withCells,
  157. }) {
  158. Map<String, String>? cellDataByFieldId;
  159. if (withCells != null) {
  160. final rowBuilder = RowDataBuilder();
  161. withCells(rowBuilder);
  162. cellDataByFieldId = rowBuilder.build();
  163. }
  164. return _databaseViewBackendSvc.createRow(
  165. startRowId: startRowId,
  166. groupId: groupId,
  167. cellDataByFieldId: cellDataByFieldId,
  168. );
  169. }
  170. Future<Either<Unit, FlowyError>> moveGroupRow({
  171. required RowMetaPB fromRow,
  172. required String groupId,
  173. RowMetaPB? toRow,
  174. }) {
  175. return _databaseViewBackendSvc.moveGroupRow(
  176. fromRowId: fromRow.id,
  177. toGroupId: groupId,
  178. toRowId: toRow?.id,
  179. );
  180. }
  181. Future<Either<Unit, FlowyError>> moveRow({
  182. required String fromRowId,
  183. required String toRowId,
  184. }) {
  185. return _databaseViewBackendSvc.moveRow(
  186. fromRowId: fromRowId,
  187. toRowId: toRowId,
  188. );
  189. }
  190. Future<Either<Unit, FlowyError>> moveGroup({
  191. required String fromGroupId,
  192. required String toGroupId,
  193. }) {
  194. return _databaseViewBackendSvc.moveGroup(
  195. fromGroupId: fromGroupId,
  196. toGroupId: toGroupId,
  197. );
  198. }
  199. Future<void> updateLayoutSetting(
  200. CalendarLayoutSettingPB calendarlLayoutSetting,
  201. ) async {
  202. await _databaseViewBackendSvc
  203. .updateLayoutSetting(
  204. calendarLayoutSetting: calendarlLayoutSetting,
  205. layoutType: databaseLayout,
  206. )
  207. .then((result) {
  208. result.fold((l) => null, (r) => Log.error(r));
  209. });
  210. }
  211. Future<void> dispose() async {
  212. await _databaseViewBackendSvc.closeView();
  213. await fieldController.dispose();
  214. await _groupListener.stop();
  215. await _viewCache.dispose();
  216. _databaseCallbacks.clear();
  217. _groupCallbacks.clear();
  218. _layoutCallbacks.clear();
  219. }
  220. Future<void> _loadGroups() async {
  221. final result = await _databaseViewBackendSvc.loadGroups();
  222. return Future(
  223. () => result.fold(
  224. (groups) {
  225. for (final callback in _groupCallbacks) {
  226. callback.onGroupByField?.call(groups.items);
  227. }
  228. },
  229. (err) => Log.error(err),
  230. ),
  231. );
  232. }
  233. Future<void> _loadLayoutSetting() async {
  234. _databaseViewBackendSvc.getLayoutSetting(databaseLayout).then((result) {
  235. result.fold(
  236. (newDatabaseLayoutSetting) {
  237. databaseLayoutSetting = newDatabaseLayoutSetting;
  238. databaseLayoutSetting?.freeze();
  239. for (final callback in _layoutCallbacks) {
  240. callback.onLoadLayout(newDatabaseLayoutSetting);
  241. }
  242. },
  243. (r) => Log.error(r),
  244. );
  245. });
  246. }
  247. void _listenOnRowsChanged() {
  248. final callbacks = DatabaseViewCallbacks(
  249. onNumOfRowsChanged: (rows, rowByRowId, reason) {
  250. for (final callback in _databaseCallbacks) {
  251. callback.onNumOfRowsChanged?.call(rows, rowByRowId, reason);
  252. }
  253. },
  254. onRowsDeleted: (ids) {
  255. for (final callback in _databaseCallbacks) {
  256. callback.onRowsDeleted?.call(ids);
  257. }
  258. },
  259. onRowsUpdated: (ids, reason) {
  260. for (final callback in _databaseCallbacks) {
  261. callback.onRowsUpdated?.call(ids, reason);
  262. }
  263. },
  264. onRowsCreated: (ids) {
  265. for (final callback in _databaseCallbacks) {
  266. callback.onRowsCreated?.call(ids);
  267. }
  268. },
  269. );
  270. _viewCache.addListener(callbacks);
  271. }
  272. void _listenOnFieldsChanged() {
  273. fieldController.addListener(
  274. onReceiveFields: (fields) {
  275. for (final callback in _databaseCallbacks) {
  276. callback.onFieldsChanged?.call(UnmodifiableListView(fields));
  277. }
  278. },
  279. onSorts: (sorts) {
  280. for (final callback in _databaseCallbacks) {
  281. callback.onSortsChanged?.call(sorts);
  282. }
  283. },
  284. onFilters: (filters) {
  285. for (final callback in _databaseCallbacks) {
  286. callback.onFiltersChanged?.call(filters);
  287. }
  288. },
  289. );
  290. }
  291. void _listenOnGroupChanged() {
  292. _groupListener.start(
  293. onNumOfGroupsChanged: (result) {
  294. result.fold(
  295. (changeset) {
  296. if (changeset.updateGroups.isNotEmpty) {
  297. for (final callback in _groupCallbacks) {
  298. callback.onUpdateGroup?.call(changeset.updateGroups);
  299. }
  300. }
  301. if (changeset.deletedGroups.isNotEmpty) {
  302. for (final callback in _groupCallbacks) {
  303. callback.onDeleteGroup?.call(changeset.deletedGroups);
  304. }
  305. }
  306. for (final insertedGroup in changeset.insertedGroups) {
  307. for (final callback in _groupCallbacks) {
  308. callback.onInsertGroup?.call(insertedGroup);
  309. }
  310. }
  311. },
  312. (r) => Log.error(r),
  313. );
  314. },
  315. onGroupByNewField: (result) {
  316. result.fold(
  317. (groups) {
  318. for (final callback in _groupCallbacks) {
  319. callback.onGroupByField?.call(groups);
  320. }
  321. },
  322. (r) => Log.error(r),
  323. );
  324. },
  325. );
  326. }
  327. void _listenOnLayoutChanged() {
  328. _layoutListener.start(
  329. onLayoutChanged: (result) {
  330. result.fold(
  331. (newLayout) {
  332. databaseLayoutSetting = newLayout;
  333. databaseLayoutSetting?.freeze();
  334. for (final callback in _layoutCallbacks) {
  335. callback.onLayoutChanged(newLayout);
  336. }
  337. },
  338. (r) => Log.error(r),
  339. );
  340. },
  341. );
  342. }
  343. }
  344. class RowDataBuilder {
  345. final _cellDataByFieldId = <String, String>{};
  346. void insertText(FieldInfo fieldInfo, String text) {
  347. assert(fieldInfo.fieldType == FieldType.RichText);
  348. _cellDataByFieldId[fieldInfo.field.id] = text;
  349. }
  350. void insertNumber(FieldInfo fieldInfo, int num) {
  351. assert(fieldInfo.fieldType == FieldType.Number);
  352. _cellDataByFieldId[fieldInfo.field.id] = num.toString();
  353. }
  354. void insertDate(FieldInfo fieldInfo, DateTime date) {
  355. assert(
  356. [
  357. FieldType.DateTime,
  358. FieldType.LastEditedTime,
  359. FieldType.CreatedTime,
  360. ].contains(fieldInfo.fieldType),
  361. );
  362. final timestamp = date.millisecondsSinceEpoch ~/ 1000;
  363. _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
  364. }
  365. Map<String, String> build() {
  366. return _cellDataByFieldId;
  367. }
  368. }