| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 | 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-database2/calendar_entities.pb.dart';import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart';import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';import 'package:collection/collection.dart';import 'dart:async';import 'package:dartz/dartz.dart';import 'database_view_service.dart';import 'defines.dart';import 'layout/layout_service.dart';import 'layout/layout_setting_listener.dart';import 'row/row_cache.dart';import 'group/group_listener.dart';import 'row/row_service.dart';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 DatabaseLayoutSettingCallbacks {  final void Function(DatabaseLayoutSettingPB) onLayoutChanged;  final void Function(DatabaseLayoutSettingPB) onLoadLayout;  DatabaseLayoutSettingCallbacks({    required this.onLayoutChanged,    required this.onLoadLayout,  });}class DatabaseCallbacks {  OnDatabaseChanged? onDatabaseChanged;  OnFieldsChanged? onFieldsChanged;  OnFiltersChanged? onFiltersChanged;  OnSortsChanged? onSortsChanged;  OnNumOfRowsChanged? onNumOfRowsChanged;  OnRowsDeleted? onRowsDeleted;  OnRowsUpdated? onRowsUpdated;  OnRowsCreated? onRowsCreated;  DatabaseCallbacks({    this.onDatabaseChanged,    this.onNumOfRowsChanged,    this.onFieldsChanged,    this.onFiltersChanged,    this.onSortsChanged,    this.onRowsUpdated,    this.onRowsDeleted,    this.onRowsCreated,  });}class DatabaseController {  final String viewId;  final DatabaseViewBackendService _databaseViewBackendSvc;  final FieldController fieldController;  DatabaseLayoutPB databaseLayout;  DatabaseLayoutSettingPB? databaseLayoutSetting;  late DatabaseViewCache _viewCache;  // Callbacks  final List<DatabaseCallbacks> _databaseCallbacks = [];  final List<GroupCallbacks> _groupCallbacks = [];  final List<DatabaseLayoutSettingCallbacks> _layoutCallbacks = [];  // Getters  RowCache get rowCache => _viewCache.rowCache;  // Listener  final DatabaseGroupListener _groupListener;  final DatabaseLayoutSettingListener _layoutListener;  DatabaseController({required ViewPB view})      : viewId = view.id,        _databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id),        fieldController = FieldController(viewId: view.id),        _groupListener = DatabaseGroupListener(view.id),        databaseLayout = databaseLayoutFromViewLayout(view.layout),        _layoutListener = DatabaseLayoutSettingListener(view.id) {    _viewCache = DatabaseViewCache(      viewId: viewId,      fieldController: fieldController,    );    _listenOnRowsChanged();    _listenOnFieldsChanged();    _listenOnGroupChanged();    _listenOnLayoutChanged();  }  void addListener({    DatabaseCallbacks? onDatabaseChanged,    DatabaseLayoutSettingCallbacks? onLayoutChanged,    GroupCallbacks? onGroupChanged,  }) {    if (onLayoutChanged != null) {      _layoutCallbacks.add(onLayoutChanged);    }    if (onDatabaseChanged != null) {      _databaseCallbacks.add(onDatabaseChanged);    }    if (onGroupChanged != null) {      _groupCallbacks.add(onGroupChanged);    }  }  Future<Either<Unit, FlowyError>> open() async {    return _databaseViewBackendSvc.openDatabase().then((result) {      return result.fold(        (DatabasePB database) async {          databaseLayout = database.layoutType;          // Load the actual database field data.          final fieldsOrFail = await fieldController.loadFields(            fieldIds: database.fields,          );          return fieldsOrFail.fold(            (fields) {              // Notify the database is changed after the fields are loaded.              // The database won't can't be used until the fields are loaded.              for (final callback in _databaseCallbacks) {                callback.onDatabaseChanged?.call(database);              }              _viewCache.rowCache.setInitialRows(database.rows);              return Future(() async {                await _loadGroups();                await _loadLayoutSetting();                return left(fields);              });            },            (err) {              Log.error(err);              return right(err);            },          );        },        (err) => right(err),      );    });  }  Future<Either<RowMetaPB, FlowyError>> createRow({    RowId? startRowId,    String? groupId,    void Function(RowDataBuilder builder)? withCells,  }) {    Map<String, String>? cellDataByFieldId;    if (withCells != null) {      final rowBuilder = RowDataBuilder();      withCells(rowBuilder);      cellDataByFieldId = rowBuilder.build();    }    return _databaseViewBackendSvc.createRow(      startRowId: startRowId,      groupId: groupId,      cellDataByFieldId: cellDataByFieldId,    );  }  Future<Either<Unit, FlowyError>> moveGroupRow({    required RowMetaPB fromRow,    required String groupId,    RowMetaPB? toRow,  }) {    return _databaseViewBackendSvc.moveGroupRow(      fromRowId: fromRow.id,      toGroupId: groupId,      toRowId: toRow?.id,    );  }  Future<Either<Unit, FlowyError>> moveRow({    required String fromRowId,    required String toRowId,  }) {    return _databaseViewBackendSvc.moveRow(      fromRowId: fromRowId,      toRowId: toRowId,    );  }  Future<Either<Unit, FlowyError>> moveGroup({    required String fromGroupId,    required String toGroupId,  }) {    return _databaseViewBackendSvc.moveGroup(      fromGroupId: fromGroupId,      toGroupId: toGroupId,    );  }  Future<void> updateLayoutSetting(    CalendarLayoutSettingPB calendarlLayoutSetting,  ) async {    await _databaseViewBackendSvc        .updateLayoutSetting(      calendarLayoutSetting: calendarlLayoutSetting,      layoutType: databaseLayout,    )        .then((result) {      result.fold((l) => null, (r) => Log.error(r));    });  }  Future<void> dispose() async {    await _databaseViewBackendSvc.closeView();    await fieldController.dispose();    await _groupListener.stop();    await _viewCache.dispose();    _databaseCallbacks.clear();    _groupCallbacks.clear();    _layoutCallbacks.clear();  }  Future<void> _loadGroups() async {    final result = await _databaseViewBackendSvc.loadGroups();    return Future(      () => result.fold(        (groups) {          for (final callback in _groupCallbacks) {            callback.onGroupByField?.call(groups.items);          }        },        (err) => Log.error(err),      ),    );  }  Future<void> _loadLayoutSetting() async {    _databaseViewBackendSvc.getLayoutSetting(databaseLayout).then((result) {      result.fold(        (newDatabaseLayoutSetting) {          databaseLayoutSetting = newDatabaseLayoutSetting;          databaseLayoutSetting?.freeze();          for (final callback in _layoutCallbacks) {            callback.onLoadLayout(newDatabaseLayoutSetting);          }        },        (r) => Log.error(r),      );    });  }  void _listenOnRowsChanged() {    final callbacks = DatabaseViewCallbacks(      onNumOfRowsChanged: (rows, rowByRowId, reason) {        for (final callback in _databaseCallbacks) {          callback.onNumOfRowsChanged?.call(rows, rowByRowId, reason);        }      },      onRowsDeleted: (ids) {        for (final callback in _databaseCallbacks) {          callback.onRowsDeleted?.call(ids);        }      },      onRowsUpdated: (ids, reason) {        for (final callback in _databaseCallbacks) {          callback.onRowsUpdated?.call(ids, reason);        }      },      onRowsCreated: (ids) {        for (final callback in _databaseCallbacks) {          callback.onRowsCreated?.call(ids);        }      },    );    _viewCache.addListener(callbacks);  }  void _listenOnFieldsChanged() {    fieldController.addListener(      onReceiveFields: (fields) {        for (final callback in _databaseCallbacks) {          callback.onFieldsChanged?.call(UnmodifiableListView(fields));        }      },      onSorts: (sorts) {        for (final callback in _databaseCallbacks) {          callback.onSortsChanged?.call(sorts);        }      },      onFilters: (filters) {        for (final callback in _databaseCallbacks) {          callback.onFiltersChanged?.call(filters);        }      },    );  }  void _listenOnGroupChanged() {    _groupListener.start(      onNumOfGroupsChanged: (result) {        result.fold(          (changeset) {            if (changeset.updateGroups.isNotEmpty) {              for (final callback in _groupCallbacks) {                callback.onUpdateGroup?.call(changeset.updateGroups);              }            }            if (changeset.deletedGroups.isNotEmpty) {              for (final callback in _groupCallbacks) {                callback.onDeleteGroup?.call(changeset.deletedGroups);              }            }            for (final insertedGroup in changeset.insertedGroups) {              for (final callback in _groupCallbacks) {                callback.onInsertGroup?.call(insertedGroup);              }            }          },          (r) => Log.error(r),        );      },      onGroupByNewField: (result) {        result.fold(          (groups) {            for (final callback in _groupCallbacks) {              callback.onGroupByField?.call(groups);            }          },          (r) => Log.error(r),        );      },    );  }  void _listenOnLayoutChanged() {    _layoutListener.start(      onLayoutChanged: (result) {        result.fold(          (newLayout) {            databaseLayoutSetting = newLayout;            databaseLayoutSetting?.freeze();            for (final callback in _layoutCallbacks) {              callback.onLayoutChanged(newLayout);            }          },          (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(      [        FieldType.DateTime,        FieldType.LastEditedTime,        FieldType.CreatedTime,      ].contains(fieldInfo.fieldType),    );    final timestamp = date.millisecondsSinceEpoch ~/ 1000;    _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();  }  Map<String, String> build() {    return _cellDataByFieldId;  }}
 |