mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-07-19 15:06:53 +00:00
365 lines
11 KiB
Dart
365 lines
11 KiB
Dart
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
|
import 'package:appflowy/plugins/database/application/view/view_cache.dart';
|
|
import 'package:appflowy_backend/log.dart';
|
|
import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.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/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-folder/view.pb.dart';
|
|
import 'package:collection/collection.dart';
|
|
import 'dart:async';
|
|
import 'package:dartz/dartz.dart';
|
|
import 'package:flutter/material.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';
|
|
|
|
typedef OnGroupConfigurationChanged = void Function(List<GroupSettingPB>);
|
|
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 {
|
|
GroupCallbacks({
|
|
this.onGroupConfigurationChanged,
|
|
this.onGroupByField,
|
|
this.onUpdateGroup,
|
|
this.onDeleteGroup,
|
|
this.onInsertGroup,
|
|
});
|
|
|
|
final OnGroupConfigurationChanged? onGroupConfigurationChanged;
|
|
final OnGroupByField? onGroupByField;
|
|
final OnUpdateGroup? onUpdateGroup;
|
|
final OnDeleteGroup? onDeleteGroup;
|
|
final OnInsertGroup? onInsertGroup;
|
|
}
|
|
|
|
class DatabaseLayoutSettingCallbacks {
|
|
DatabaseLayoutSettingCallbacks({required this.onLayoutSettingsChanged});
|
|
|
|
final void Function(DatabaseLayoutSettingPB) onLayoutSettingsChanged;
|
|
}
|
|
|
|
class DatabaseCallbacks {
|
|
DatabaseCallbacks({
|
|
this.onDatabaseChanged,
|
|
this.onNumOfRowsChanged,
|
|
this.onFieldsChanged,
|
|
this.onFiltersChanged,
|
|
this.onSortsChanged,
|
|
this.onRowsUpdated,
|
|
this.onRowsDeleted,
|
|
this.onRowsCreated,
|
|
});
|
|
|
|
OnDatabaseChanged? onDatabaseChanged;
|
|
OnFieldsChanged? onFieldsChanged;
|
|
OnFiltersChanged? onFiltersChanged;
|
|
OnSortsChanged? onSortsChanged;
|
|
OnNumOfRowsChanged? onNumOfRowsChanged;
|
|
OnRowsDeleted? onRowsDeleted;
|
|
OnRowsUpdated? onRowsUpdated;
|
|
OnRowsCreated? onRowsCreated;
|
|
}
|
|
|
|
class DatabaseController {
|
|
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();
|
|
}
|
|
|
|
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;
|
|
|
|
final ValueNotifier<bool> _isLoading = ValueNotifier(true);
|
|
|
|
void setIsLoading(bool isLoading) {
|
|
_isLoading.value = isLoading;
|
|
}
|
|
|
|
ValueNotifier<bool> get isLoading => _isLoading;
|
|
|
|
void addListener({
|
|
DatabaseCallbacks? onDatabaseChanged,
|
|
DatabaseLayoutSettingCallbacks? onLayoutSettingsChanged,
|
|
GroupCallbacks? onGroupChanged,
|
|
}) {
|
|
if (onLayoutSettingsChanged != null) {
|
|
_layoutCallbacks.add(onLayoutSettingsChanged);
|
|
}
|
|
|
|
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<Unit, FlowyError>> moveGroupRow({
|
|
required RowMetaPB fromRow,
|
|
required String fromGroupId,
|
|
required String toGroupId,
|
|
RowMetaPB? toRow,
|
|
}) {
|
|
return _databaseViewBackendSvc.moveGroupRow(
|
|
fromRowId: fromRow.id,
|
|
fromGroupId: fromGroupId,
|
|
toGroupId: toGroupId,
|
|
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({
|
|
BoardLayoutSettingPB? boardLayoutSetting,
|
|
CalendarLayoutSettingPB? calendarLayoutSetting,
|
|
}) async {
|
|
await _databaseViewBackendSvc
|
|
.updateLayoutSetting(
|
|
boardLayoutSetting: boardLayoutSetting,
|
|
calendarLayoutSetting: calendarLayoutSetting,
|
|
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 groupsResult = await _databaseViewBackendSvc.loadGroups();
|
|
groupsResult.fold(
|
|
(groups) {
|
|
for (final callback in _groupCallbacks) {
|
|
callback.onGroupByField?.call(groups.items);
|
|
}
|
|
},
|
|
(err) => Log.error(err),
|
|
);
|
|
}
|
|
|
|
Future<void> _loadLayoutSetting() {
|
|
return _databaseViewBackendSvc
|
|
.getLayoutSetting(databaseLayout)
|
|
.then((result) {
|
|
result.fold(
|
|
(newDatabaseLayoutSetting) {
|
|
databaseLayoutSetting = newDatabaseLayoutSetting;
|
|
|
|
for (final callback in _layoutCallbacks) {
|
|
callback.onLayoutSettingsChanged(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.onLayoutSettingsChanged(newLayout);
|
|
}
|
|
},
|
|
(r) => Log.error(r),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|