feat: sync the icon & title (#4150)

* feat: sync the icon & title

* feat: diff the view data when refreshing

* fix: unable to update folder

* test: refactor bloc tests
This commit is contained in:
Lucas.Xu 2023-12-20 10:08:35 +08:00 committed by GitHub
parent b466b425a5
commit 9a1ea138fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 378 additions and 789 deletions

View File

@ -202,7 +202,7 @@ extension Expectation on WidgetTester {
return find.descendant(
of: find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget is InnerViewItem &&
widget.view.name == parentName &&
widget.view.layout == parentLayout,
skipOffstage: false,

View File

@ -45,7 +45,7 @@ class MobilePersonalFolder extends StatelessWidget {
...views.map(
(view) => MobileViewItem(
key: ValueKey(
'${FolderCategoryType.personal.name} ${view.hashCode} ',
'${FolderCategoryType.personal.name} ${view.id}',
),
isDraggable: true,
categoryType: FolderCategoryType.personal,

View File

@ -76,14 +76,10 @@ class MobileViewItem extends StatelessWidget {
p.lastCreatedView?.id != c.lastCreatedView!.id,
listener: (context, state) => context.pushView(state.lastCreatedView!),
builder: (context, state) {
// don't remove this code. it's related to the backend service.
view.childViews
..clear()
..addAll(state.childViews);
return InnerMobileViewItem(
view: state.view,
parentView: parentView,
childViews: state.childViews,
childViews: state.view.childViews,
categoryType: categoryType,
level: level,
leftPadding: leftPadding,
@ -163,7 +159,7 @@ class InnerMobileViewItem extends StatelessWidget {
if (childViews.isNotEmpty) {
final children = childViews.map((childView) {
return MobileViewItem(
key: ValueKey('${categoryType.name} ${childView.hashCode}'),
key: ValueKey('${categoryType.name} ${childView.id}'),
parentView: view,
categoryType: categoryType,
isFirstChild: childView.id == childViews.first.id,

View File

@ -1,286 +0,0 @@
import 'dart:collection';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'app_bloc.freezed.dart';
class AppBloc extends Bloc<AppEvent, AppState> {
final ViewBackendService appService;
final ViewListener viewListener;
AppBloc({required ViewPB view})
: appService = ViewBackendService(),
viewListener = ViewListener(viewId: view.id),
super(AppState.initial(view)) {
on<AppEvent>((event, emit) async {
await event.map(
initial: (e) async {
_startListening();
await _loadViews(emit);
},
createView: (CreateView value) async {
await _createView(value, emit);
},
loadViews: (_) async {
await _loadViews(emit);
},
delete: (e) async {
await _deleteApp(emit);
},
deleteView: (deletedView) async {
await _deleteView(emit, deletedView.viewId);
},
rename: (e) async {
await _renameView(e, emit);
},
appDidUpdate: (e) async {
final latestCreatedView = state.latestCreatedView;
final views = e.view.childViews;
AppState newState = state.copyWith(
views: views,
view: e.view,
);
if (latestCreatedView != null) {
final index = views
.indexWhere((element) => element.id == latestCreatedView.id);
if (index == -1) {
newState = newState.copyWith(latestCreatedView: null);
}
emit(newState);
}
emit(newState);
},
);
});
}
void _startListening() {
viewListener.start(
onViewUpdated: (app) {
if (!isClosed) {
add(AppEvent.appDidUpdate(app));
}
},
);
}
Future<void> _renameView(Rename e, Emitter<AppState> emit) async {
final result = await ViewBackendService.updateView(
viewId: state.view.id,
name: e.newName,
);
result.fold(
(l) => emit(state.copyWith(successOrFailure: left(unit))),
(error) => emit(state.copyWith(successOrFailure: right(error))),
);
}
// Delete the current app
Future<void> _deleteApp(Emitter<AppState> emit) async {
final result = await ViewBackendService.delete(viewId: state.view.id);
result.fold(
(unit) => emit(state.copyWith(successOrFailure: left(unit))),
(error) => emit(state.copyWith(successOrFailure: right(error))),
);
}
Future<void> _deleteView(Emitter<AppState> emit, String viewId) async {
final result = await ViewBackendService.deleteView(viewId: viewId);
result.fold(
(unit) => emit(state.copyWith(successOrFailure: left(unit))),
(error) => emit(state.copyWith(successOrFailure: right(error))),
);
}
Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
// create a child view for the current view
final result = await ViewBackendService.createView(
parentViewId: state.view.id,
name: value.name,
desc: value.desc ?? "",
layoutType: value.layoutType,
initialDataBytes: value.initialDataBytes,
ext: value.ext ?? {},
openAfterCreate: true,
);
result.fold(
(view) => emit(
state.copyWith(
latestCreatedView: value.openAfterCreated ? view : null,
successOrFailure: left(unit),
),
),
(error) {
Log.error(error);
emit(state.copyWith(successOrFailure: right(error)));
},
);
}
@override
Future<void> close() async {
await viewListener.stop();
return super.close();
}
Future<void> _loadViews(Emitter<AppState> emit) async {
final viewsOrFailed =
await ViewBackendService.getChildViews(viewId: state.view.id);
viewsOrFailed.fold(
(views) => emit(state.copyWith(views: views)),
(error) {
Log.error(error);
emit(state.copyWith(successOrFailure: right(error)));
},
);
}
}
@freezed
class AppEvent with _$AppEvent {
const factory AppEvent.initial() = Initial;
const factory AppEvent.createView(
String name,
ViewLayoutPB layoutType, {
String? desc,
/// ~~The initial data should be the JSON of the document~~
/// ~~For example: {"document":{"type":"editor","children":[]}}~~
///
/// - Document:
/// the initial data should be the string that can be converted into [DocumentDataPB]
///
List<int>? initialDataBytes,
Map<String, String>? ext,
/// open the view after created
@Default(true) bool openAfterCreated,
}) = CreateView;
const factory AppEvent.loadViews() = LoadApp;
const factory AppEvent.delete() = DeleteApp;
const factory AppEvent.deleteView(String viewId) = DeleteView;
const factory AppEvent.rename(String newName) = Rename;
const factory AppEvent.appDidUpdate(ViewPB view) = AppDidUpdate;
}
@freezed
class AppState with _$AppState {
const factory AppState({
required ViewPB view,
required List<ViewPB> views,
ViewPB? latestCreatedView,
required Either<Unit, FlowyError> successOrFailure,
}) = _AppState;
factory AppState.initial(ViewPB view) => AppState(
view: view,
views: view.childViews,
successOrFailure: left(unit),
);
}
class ViewDataContext extends ChangeNotifier {
final String viewId;
final ValueNotifier<List<ViewPB>> _viewsNotifier = ValueNotifier([]);
final ValueNotifier<ViewPB?> _selectedViewNotifier = ValueNotifier(null);
VoidCallback? _menuSharedStateListener;
ExpandableController expandController =
ExpandableController(initialExpanded: false);
ViewDataContext({required this.viewId}) {
_setLatestView(getIt<MenuSharedState>().latestOpenView);
_menuSharedStateListener =
getIt<MenuSharedState>().addLatestViewListener((view) {
_setLatestView(view);
});
}
VoidCallback onViewSelected(void Function(ViewPB?) callback) {
void listener() {
callback(_selectedViewNotifier.value);
}
_selectedViewNotifier.addListener(listener);
return listener;
}
void removeOnViewSelectedListener(VoidCallback listener) {
_selectedViewNotifier.removeListener(listener);
}
void _setLatestView(ViewPB? view) {
view?.freeze();
if (_selectedViewNotifier.value != view) {
_selectedViewNotifier.value = view;
_expandIfNeed();
notifyListeners();
}
}
ViewPB? get selectedView => _selectedViewNotifier.value;
set views(List<ViewPB> views) {
if (_viewsNotifier.value != views) {
_viewsNotifier.value = views;
notifyListeners();
}
}
UnmodifiableListView<ViewPB> get views =>
UnmodifiableListView(_viewsNotifier.value);
VoidCallback onViewsChanged(
void Function(UnmodifiableListView<ViewPB>) callback,
) {
void listener() {
callback(views);
}
_viewsNotifier.addListener(listener);
return listener;
}
void removeOnViewChangedListener(VoidCallback listener) {
_viewsNotifier.removeListener(listener);
}
void _expandIfNeed() {
if (_selectedViewNotifier.value == null) {
return;
}
if (!_viewsNotifier.value
.map((e) => e.id)
.toList()
.contains(_selectedViewNotifier.value?.id)) {
return;
}
if (expandController.expanded == false) {
// Workaround: Delay 150 milliseconds to make the smooth animation while expanding
Future.delayed(const Duration(milliseconds: 150), () {
expandController.expanded = true;
});
}
}
@override
void dispose() {
if (_menuSharedStateListener != null) {
getIt<MenuSharedState>()
.removeLatestViewListener(_menuSharedStateListener!);
}
super.dispose();
}
}

View File

@ -1 +0,0 @@
export 'app_bloc.dart';

View File

@ -1,109 +0,0 @@
import 'dart:async';
import 'package:appflowy/workspace/application/app/app_bloc.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
part 'menu_view_section_bloc.freezed.dart';
class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
void Function()? _viewsListener;
void Function()? _selectedViewlistener;
final ViewDataContext _appViewData;
ViewSectionBloc({
required ViewDataContext appViewData,
}) : _appViewData = appViewData,
super(ViewSectionState.initial(appViewData)) {
on<ViewSectionEvent>((event, emit) async {
await event.when(
initial: () async {
_startListening();
},
setSelectedView: (view) {
emit(state.copyWith(selectedView: view));
},
didReceiveViewUpdated: (views) {
emit(state.copyWith(views: views));
},
moveView: (fromIndex, toIndex) async {
_moveView(fromIndex, toIndex, emit);
},
);
});
}
void _startListening() {
_viewsListener = _appViewData.onViewsChanged((views) {
if (!isClosed) {
add(ViewSectionEvent.didReceiveViewUpdated(views));
}
});
_selectedViewlistener = _appViewData.onViewSelected((view) {
if (!isClosed) {
add(ViewSectionEvent.setSelectedView(view));
}
});
}
Future<void> _moveView(
int fromIndex,
int toIndex,
Emitter<ViewSectionState> emit,
) async {
if (fromIndex < state.views.length) {
final viewId = state.views[fromIndex].id;
final views = List<ViewPB>.from(state.views);
views.insert(toIndex, views.removeAt(fromIndex));
emit(state.copyWith(views: views));
final result = await ViewBackendService.moveView(
viewId: viewId,
fromIndex: fromIndex,
toIndex: toIndex,
);
result.fold((l) => null, (err) => Log.error(err));
}
}
@override
Future<void> close() async {
if (_selectedViewlistener != null) {
_appViewData.removeOnViewSelectedListener(_selectedViewlistener!);
}
if (_viewsListener != null) {
_appViewData.removeOnViewChangedListener(_viewsListener!);
}
return super.close();
}
}
@freezed
class ViewSectionEvent with _$ViewSectionEvent {
const factory ViewSectionEvent.initial() = _Initial;
const factory ViewSectionEvent.setSelectedView(ViewPB? view) =
_SetSelectedView;
const factory ViewSectionEvent.moveView(int fromIndex, int toIndex) =
_MoveView;
const factory ViewSectionEvent.didReceiveViewUpdated(List<ViewPB> views) =
_DidReceiveViewUpdated;
}
@freezed
class ViewSectionState with _$ViewSectionState {
const factory ViewSectionState({
required List<ViewPB> views,
ViewPB? selectedView,
}) = _ViewSectionState;
factory ViewSectionState.initial(ViewDataContext appViewData) =>
ViewSectionState(
views: appViewData.views,
selectedView: appViewData.selectedView,
);
}

View File

@ -7,12 +7,14 @@ import 'package:appflowy/workspace/application/favorite/favorite_listener.dart';
import 'package:appflowy/workspace/application/recent/recent_service.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/log.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 'package:dartz/dartz.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:protobuf/protobuf.dart';
part 'view_bloc.freezed.dart';
@ -36,20 +38,17 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
add(ViewEvent.viewDidUpdate(left(result)));
},
onViewChildViewsUpdated: (result) async {
final view = await ViewBackendService.getView(
result.parentViewId,
);
view.fold(
(view) => add(ViewEvent.viewDidUpdate(left(view))),
(error) => add(ViewEvent.viewDidUpdate(right(error))),
);
final view = await _updateChildViews(result);
if (!isClosed && view != null) {
add(ViewEvent.viewUpdateChildView(view));
}
},
);
favoriteListener.start(
favoritesUpdated: (result, isFavorite) {
result.fold((error) {}, (result) {
final current =
result.items.firstWhereOrNull((v) => v.id == view.id);
result.items.firstWhereOrNull((v) => v.id == state.view.id);
if (current != null) {
add(ViewEvent.viewDidUpdate(left(current)));
}
@ -57,28 +56,40 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
},
);
final isExpanded = await _getViewIsExpanded(view);
emit(state.copyWith(isExpanded: isExpanded));
await _loadViewsWhenExpanded(emit, isExpanded);
},
setIsEditing: (e) {
emit(state.copyWith(isEditing: e.isEditing));
},
setIsExpanded: (e) async {
if (e.isExpanded) {
if (e.isExpanded && !state.isExpanded) {
await _loadViewsWhenExpanded(emit, true);
} else {
emit(state.copyWith(isExpanded: e.isExpanded));
}
await _setViewIsExpanded(view, e.isExpanded);
},
viewDidUpdate: (e) {
viewDidUpdate: (e) async {
final result = await ViewBackendService.getView(
view.id,
);
final view_ = result.fold((l) => l, (r) => null);
e.result.fold(
(view) => emit(
state.copyWith(
view: view,
childViews: view.childViews,
successOrFailure: left(unit),
),
),
(view) async {
Log.debug('viewDidUpdate: $view');
// ignore child view changes because it only contains one level
// children data.
if (_isSameViewIgnoreChildren(view, state.view)) {
// do nothing.
}
emit(
state.copyWith(
view: view_ ?? view,
successOrFailure: left(unit),
),
);
},
(error) => emit(
state.copyWith(successOrFailure: right(error)),
),
@ -91,7 +102,17 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
);
emit(
result.fold(
(l) => state.copyWith(successOrFailure: left(unit)),
(l) {
final view = state.view;
view.freeze();
final newView = view.rebuild(
(b) => b.name = e.newName,
);
return state.copyWith(
successOrFailure: left(unit),
view: newView,
);
},
(error) => state.copyWith(successOrFailure: right(error)),
),
);
@ -149,6 +170,13 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
),
);
},
viewUpdateChildView: (e) async {
emit(
state.copyWith(
view: e.result,
),
);
},
);
});
}
@ -165,30 +193,39 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
bool isExpanded,
) async {
if (!isExpanded) {
return;
}
if (state.childViews.isNotEmpty) {
// notify the old child views
emit(
state.copyWith(
childViews: state.childViews,
isExpanded: true,
view: view,
isExpanded: false,
isLoading: false,
),
);
return;
}
final viewsOrFailed =
await ViewBackendService.getChildViews(viewId: state.view.id);
viewsOrFailed.fold(
(childViews) => emit(
state.copyWith(
childViews: childViews,
isExpanded: true,
),
),
(childViews) {
state.view.freeze();
final viewWithChildViews = state.view.rebuild((b) {
b.childViews.clear();
b.childViews.addAll(childViews);
});
emit(
state.copyWith(
view: viewWithChildViews,
isExpanded: true,
isLoading: false,
),
);
},
(error) => emit(
state.copyWith(
successOrFailure: right(error),
isExpanded: true,
isLoading: false,
),
),
);
@ -216,6 +253,70 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
});
});
}
Future<ViewPB?> _updateChildViews(
ChildViewUpdatePB update,
) async {
Log.debug(
'received child views of ${this.view.name}(${this.view.id}) update, $update',
);
if (update.createChildViews.isNotEmpty) {
// refresh the child views if the update isn't empty
// because there's no info to get the inserted index.
assert(update.parentViewId == this.view.id);
final view = await ViewBackendService.getView(
update.parentViewId,
);
return view.fold((l) => l, (r) => null);
}
final view = state.view;
view.freeze();
final childViews = [...view.childViews];
if (update.deleteChildViews.isNotEmpty) {
childViews.removeWhere((v) => update.deleteChildViews.contains(v.id));
return view.rebuild((p0) {
p0.childViews.clear();
p0.childViews.addAll(childViews);
});
}
if (update.updateChildViews.isNotEmpty) {
final view = await ViewBackendService.getView(
update.parentViewId,
);
final childViews = view.fold((l) => l.childViews, (r) => []);
bool isSameOrder = true;
if (childViews.length == update.updateChildViews.length) {
for (var i = 0; i < childViews.length; i++) {
if (childViews[i].id != update.updateChildViews[i].id) {
isSameOrder = false;
break;
}
}
} else {
isSameOrder = false;
}
if (!isSameOrder) {
return view.fold((l) => l, (r) => null);
}
}
return null;
}
bool _isSameViewIgnoreChildren(ViewPB from, ViewPB to) {
return _hash(from) == _hash(to);
}
int _hash(ViewPB view) => Object.hash(
view.id,
view.name,
view.createTime,
view.icon,
view.parentViewId,
view.layout,
);
}
@freezed
@ -239,25 +340,27 @@ class ViewEvent with _$ViewEvent {
}) = CreateView;
const factory ViewEvent.viewDidUpdate(Either<ViewPB, FlowyError> result) =
ViewDidUpdate;
const factory ViewEvent.viewUpdateChildView(ViewPB result) =
ViewUpdateChildView;
}
@freezed
class ViewState with _$ViewState {
const factory ViewState({
required ViewPB view,
required List<ViewPB> childViews,
required bool isEditing,
required bool isExpanded,
required Either<Unit, FlowyError> successOrFailure,
@Default(true) bool isLoading,
@Default(null) ViewPB? lastCreatedView,
}) = _ViewState;
factory ViewState.init(ViewPB view) => ViewState(
view: view,
childViews: view.childViews,
isExpanded: false,
isEditing: false,
successOrFailure: left(unit),
lastCreatedView: null,
isLoading: true,
);
}

View File

@ -1,7 +1,6 @@
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/draggable_item/draggable_item.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:flutter/material.dart';
@ -66,9 +65,6 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
if (!_shouldAccept(data.data, position)) {
return;
}
Log.debug(
'offset: $offset, position: $position, size: ${renderBox.size}',
);
_updatePosition(position);
},
onLeave: (_) => _updatePosition(

View File

@ -84,14 +84,10 @@ class ViewItem extends StatelessWidget {
listener: (context, state) =>
context.read<TabsBloc>().openPlugin(state.lastCreatedView!),
builder: (context, state) {
// don't remove this code. it's related to the backend service.
view.childViews
..clear()
..addAll(state.childViews);
return InnerViewItem(
view: state.view,
parentView: parentView,
childViews: state.childViews,
childViews: state.view.childViews,
categoryType: categoryType,
level: level,
leftPadding: leftPadding,

View File

@ -164,12 +164,14 @@ class _ViewTitleState extends State<_ViewTitle> {
_resetTextEditingController();
viewListener.start(
onViewUpdated: (view) {
if (name != view.name || icon != view.icon.value) {
widget.onUpdated();
}
setState(() {
name = view.name;
icon = view.icon.value;
_resetTextEditingController();
});
widget.onUpdated();
},
);
}

View File

@ -1,9 +1,10 @@
// ignore: import_of_legacy_library_into_null_safe
import 'dart:ffi';
import 'package:ffi/ffi.dart' as ffi;
import 'package:flutter/foundation.dart';
import 'package:logger/logger.dart';
import 'package:ffi/ffi.dart' as ffi;
import 'ffi.dart';
class Log {
@ -13,15 +14,14 @@ class Log {
Log() {
_logger = Logger(
printer: PrettyPrinter(
methodCount: 2, // number of method calls to be displayed
errorMethodCount:
8, // number of method calls if stacktrace is provided
lineLength: 120, // width of the output
colors: true, // Colorful log messages
printEmojis: true, // Print an emoji for each log message
printTime: false // Should each log print contain a timestamp
),
level: kDebugMode ? Level.verbose : Level.info,
methodCount: 2, // number of method calls to be displayed
errorMethodCount: 8, // number of method calls if stacktrace is provided
lineLength: 120, // width of the output
colors: true, // Colorful log messages
printEmojis: true, // Print an emoji for each log message
printTime: false, // Should each log print contain a timestamp
),
level: kDebugMode ? Level.trace : Level.info,
);
}
@ -29,7 +29,11 @@ class Log {
if (isReleaseVersion()) {
log(0, toNativeUtf8(msg));
} else {
Log.shared._logger.i(msg, error, stackTrace);
Log.shared._logger.i(
msg,
error: error,
stackTrace: stackTrace,
);
}
}
@ -37,7 +41,11 @@ class Log {
if (isReleaseVersion()) {
log(1, toNativeUtf8(msg));
} else {
Log.shared._logger.d(msg, error, stackTrace);
Log.shared._logger.d(
msg,
error: error,
stackTrace: stackTrace,
);
}
}
@ -45,7 +53,11 @@ class Log {
if (isReleaseVersion()) {
log(3, toNativeUtf8(msg));
} else {
Log.shared._logger.w(msg, error, stackTrace);
Log.shared._logger.w(
msg,
error: error,
stackTrace: stackTrace,
);
}
}
@ -53,7 +65,11 @@ class Log {
if (isReleaseVersion()) {
log(2, toNativeUtf8(msg));
} else {
Log.shared._logger.v(msg, error, stackTrace);
Log.shared._logger.t(
msg,
error: error,
stackTrace: stackTrace,
);
}
}
@ -61,7 +77,11 @@ class Log {
if (isReleaseVersion()) {
log(4, toNativeUtf8(msg));
} else {
Log.shared._logger.e(msg, error, stackTrace);
Log.shared._logger.e(
msg,
error: error,
stackTrace: stackTrace,
);
}
}
}

View File

@ -16,7 +16,7 @@ dependencies:
protobuf: ^3.1.0
dartz: ^0.10.1
freezed_annotation:
logger: ^1.0.0
logger: ^2.0.0
plugin_platform_interface: ^2.1.3
json_annotation: ^4.7.0

View File

@ -1067,13 +1067,13 @@ packages:
source: hosted
version: "0.1.5"
logger:
dependency: transitive
dependency: "direct overridden"
description:
name: logger
sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f"
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "2.0.2+1"
logging:
dependency: transitive
description:

View File

@ -163,6 +163,8 @@ dependency_overrides:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "82f3baf"
logger: ^2.0.0
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
@ -7,11 +8,10 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
import 'package:appflowy/plugins/database_view/board/board.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import '../../util.dart';
import '../grid_test/util.dart';
@ -27,7 +27,7 @@ class AppFlowyBoardTest {
}
Future<BoardTestContext> createTestBoard() async {
final app = await unitTest.createTestApp();
final app = await unitTest.createWorkspace();
final builder = BoardPluginBuilder();
return ViewBackendService.createView(
parentViewId: app.id,

View File

@ -5,7 +5,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
import '../util.dart';
Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
final app = await gridTest.unitTest.createTestApp();
final app = await gridTest.unitTest.createWorkspace();
final context = await ViewBackendService.createView(
parentViewId: app.id,
name: "Filter Grid",

View File

@ -162,7 +162,7 @@ class AppFlowyGridTest {
}
Future<GridTestContext> createTestGrid() async {
final app = await unitTest.createTestApp();
final app = await unitTest.createWorkspace();
final context = await ViewBackendService.createView(
parentViewId: app.id,
name: "Test Grid",

View File

@ -1,166 +0,0 @@
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/workspace/application/app/app_bloc.dart';
import 'package:appflowy/workspace/application/menu/menu_view_section_bloc.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../util.dart';
void main() {
late AppFlowyUnitTest testContext;
setUpAll(() async {
testContext = await AppFlowyUnitTest.ensureInitialized();
});
test('rename app test', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.rename('Hello world'));
await blocResponseFuture();
expect(bloc.state.view.name, 'Hello world');
});
test('delete app test', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.delete());
await blocResponseFuture();
final apps = await testContext.loadApps();
expect(apps.where((element) => element.id == app.id).isEmpty, true);
});
test('create documents in order', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("1", ViewLayoutPB.Document));
await blocResponseFuture();
bloc.add(const AppEvent.createView("2", ViewLayoutPB.Document));
await blocResponseFuture();
bloc.add(const AppEvent.createView("3", ViewLayoutPB.Document));
await blocResponseFuture();
assert(bloc.state.views[0].name == '1');
assert(bloc.state.views[1].name == '2');
assert(bloc.state.views[2].name == '3');
});
test('reorder documents test', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("1", ViewLayoutPB.Document));
await blocResponseFuture();
bloc.add(const AppEvent.createView("2", ViewLayoutPB.Document));
await blocResponseFuture();
bloc.add(const AppEvent.createView("3", ViewLayoutPB.Document));
await blocResponseFuture();
assert(bloc.state.views.length == 3);
final appViewData = ViewDataContext(viewId: app.id);
appViewData.views = bloc.state.views;
final viewSectionBloc = ViewSectionBloc(
appViewData: appViewData,
)..add(const ViewSectionEvent.initial());
await blocResponseFuture();
viewSectionBloc.add(const ViewSectionEvent.moveView(0, 2));
await blocResponseFuture();
assert(bloc.state.views[0].name == '2');
assert(bloc.state.views[1].name == '3');
assert(bloc.state.views[2].name == '1');
});
test('open latest view test', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
assert(
bloc.state.latestCreatedView == null,
"assert initial latest create view is null after initialize",
);
bloc.add(const AppEvent.createView("1", ViewLayoutPB.Document));
await blocResponseFuture();
assert(
bloc.state.latestCreatedView!.id == bloc.state.views.last.id,
"create a view and assert the latest create view is this view",
);
bloc.add(const AppEvent.createView("2", ViewLayoutPB.Document));
await blocResponseFuture();
assert(
bloc.state.latestCreatedView!.id == bloc.state.views.last.id,
"create a view and assert the latest create view is this view",
);
});
test('open latest documents test', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("document 1", ViewLayoutPB.Document));
await blocResponseFuture();
final document1 = bloc.state.latestCreatedView;
assert(document1!.name == "document 1");
bloc.add(const AppEvent.createView("document 2", ViewLayoutPB.Document));
await blocResponseFuture();
final document2 = bloc.state.latestCreatedView;
assert(document2!.name == "document 2");
// Open document 1
// ignore: unused_local_variable
final documentBloc = DocumentBloc(view: document1!)
..add(const DocumentEvent.initial());
await blocResponseFuture();
final workspaceSetting = await FolderEventGetCurrentWorkspaceSetting()
.send()
.then((result) => result.fold((l) => l, (r) => throw Exception()));
workspaceSetting.latestView.id == document1.id;
});
test('open latest document test', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("document 1", ViewLayoutPB.Document));
await blocResponseFuture();
final document = bloc.state.latestCreatedView;
assert(document!.name == "document 1");
bloc.add(const AppEvent.createView("grid 2", ViewLayoutPB.Grid));
await blocResponseFuture();
final grid = bloc.state.latestCreatedView;
assert(grid!.name == "grid 2");
var workspaceSetting = await FolderEventGetCurrentWorkspaceSetting()
.send()
.then((result) => result.fold((l) => l, (r) => throw Exception()));
workspaceSetting.latestView.id == grid!.id;
// Open grid 1
// ignore: unused_local_variable
final documentBloc = DocumentBloc(view: document!)
..add(const DocumentEvent.initial());
await blocResponseFuture();
workspaceSetting = await FolderEventGetCurrentWorkspaceSetting()
.send()
.then((result) => result.fold((l) => l, (r) => throw Exception()));
workspaceSetting.latestView.id == document.id;
});
}

View File

@ -1,63 +0,0 @@
import 'package:appflowy/workspace/application/app/app_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../util.dart';
void main() {
late AppFlowyUnitTest testContext;
setUpAll(() async {
testContext = await AppFlowyUnitTest.ensureInitialized();
});
test('create a document', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("Test document", ViewLayoutPB.Document));
await blocResponseFuture();
assert(bloc.state.views.length == 1);
assert(bloc.state.views.last.name == "Test document");
assert(bloc.state.views.last.layout == ViewLayoutPB.Document);
});
test('create a grid', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("Test grid", ViewLayoutPB.Grid));
await blocResponseFuture();
assert(bloc.state.views.length == 1);
assert(bloc.state.views.last.name == "Test grid");
assert(bloc.state.views.last.layout == ViewLayoutPB.Grid);
});
test('create a kanban', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("Test board", ViewLayoutPB.Board));
await blocResponseFuture();
assert(bloc.state.views.length == 1);
assert(bloc.state.views.last.name == "Test board");
assert(bloc.state.views.last.layout == ViewLayoutPB.Board);
});
test('create a calendar', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(const AppEvent.createView("Test calendar", ViewLayoutPB.Calendar));
await blocResponseFuture();
assert(bloc.state.views.length == 1);
assert(bloc.state.views.last.name == "Test calendar");
assert(bloc.state.views.last.layout == ViewLayoutPB.Calendar);
});
}

View File

@ -1,6 +1,6 @@
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/workspace/application/app/app_bloc.dart';
import 'package:appflowy/workspace/application/home/home_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
@ -13,7 +13,7 @@ void main() {
testContext = await AppFlowyUnitTest.ensureInitialized();
});
test('initi home screen', () async {
test('init home screen', () async {
final workspaceSetting = await FolderEventGetCurrentWorkspaceSetting()
.send()
.then((result) => result.fold((l) => l, (r) => throw Exception()));
@ -36,16 +36,16 @@ void main() {
..add(const HomeEvent.initial());
await blocResponseFuture();
final app = await testContext.createTestApp();
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
assert(appBloc.state.latestCreatedView == null);
final app = await testContext.createWorkspace();
final appBloc = ViewBloc(view: app)..add(const ViewEvent.initial());
assert(appBloc.state.lastCreatedView == null);
appBloc
.add(const AppEvent.createView("New document", ViewLayoutPB.Document));
.add(const ViewEvent.createView("New document", ViewLayoutPB.Document));
await blocResponseFuture();
assert(appBloc.state.latestCreatedView != null);
final latestView = appBloc.state.latestCreatedView!;
assert(appBloc.state.lastCreatedView != null);
final latestView = appBloc.state.lastCreatedView!;
final _ = DocumentBloc(view: latestView)
..add(const DocumentEvent.initial());

View File

@ -1,5 +1,5 @@
import 'package:appflowy/plugins/trash/application/trash_bloc.dart';
import 'package:appflowy/workspace/application/app/app_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
@ -7,42 +7,42 @@ import '../../util.dart';
class TrashTestContext {
late ViewPB view;
late AppBloc appBloc;
late ViewBloc viewBloc;
late List<ViewPB> allViews;
final AppFlowyUnitTest unitTest;
TrashTestContext(this.unitTest);
Future<void> initialize() async {
view = await unitTest.createTestApp();
appBloc = AppBloc(view: view)..add(const AppEvent.initial());
view = await unitTest.createWorkspace();
viewBloc = ViewBloc(view: view)..add(const ViewEvent.initial());
await blocResponseFuture();
appBloc.add(
const AppEvent.createView(
viewBloc.add(
const ViewEvent.createView(
"Document 1",
ViewLayoutPB.Document,
),
);
await blocResponseFuture();
appBloc.add(
const AppEvent.createView(
viewBloc.add(
const ViewEvent.createView(
"Document 2",
ViewLayoutPB.Document,
),
);
await blocResponseFuture();
appBloc.add(
const AppEvent.createView(
viewBloc.add(
const ViewEvent.createView(
"Document 3",
ViewLayoutPB.Document,
),
);
await blocResponseFuture();
allViews = [...appBloc.state.view.childViews];
allViews = [...viewBloc.state.view.childViews];
assert(allViews.length == 3, 'but receive ${allViews.length}');
}
}
@ -67,22 +67,29 @@ void main() {
await blocResponseFuture(millisecond: 200);
// delete a view
final deletedView = context.appBloc.state.view.childViews[0];
context.appBloc.add(AppEvent.deleteView(deletedView.id));
final deletedView = context.viewBloc.state.view.childViews[0];
final deleteViewBloc = ViewBloc(view: deletedView)
..add(const ViewEvent.initial());
await blocResponseFuture();
assert(context.appBloc.state.view.childViews.length == 2);
deleteViewBloc.add(const ViewEvent.delete());
await blocResponseFuture();
assert(context.viewBloc.state.view.childViews.length == 2);
assert(trashBloc.state.objects.length == 1);
assert(trashBloc.state.objects.first.id == deletedView.id);
// put back
trashBloc.add(TrashEvent.putback(deletedView.id));
await blocResponseFuture();
assert(context.appBloc.state.view.childViews.length == 3);
assert(context.viewBloc.state.view.childViews.length == 3);
assert(trashBloc.state.objects.isEmpty);
// delete all views
for (final view in context.allViews) {
context.appBloc.add(AppEvent.deleteView(view.id));
final deleteViewBloc = ViewBloc(view: view)
..add(const ViewEvent.initial());
await blocResponseFuture();
deleteViewBloc.add(const ViewEvent.delete());
await blocResponseFuture();
await blocResponseFuture();
}
expect(trashBloc.state.objects[0].id, context.allViews[0].id);

View File

@ -1,100 +1,181 @@
import 'package:appflowy/workspace/application/app/app_bloc.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../util.dart';
void main() {
const name = 'Hello world';
late AppFlowyUnitTest testContext;
setUpAll(() async {
testContext = await AppFlowyUnitTest.ensureInitialized();
});
Future<ViewBloc> createTestViewBloc() async {
final view = await testContext.createWorkspace();
final viewBloc = ViewBloc(view: view)
..add(
const ViewEvent.initial(),
);
await blocResponseFuture();
return viewBloc;
}
test('rename view test', () async {
final app = await testContext.createTestApp();
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
appBloc.add(
const AppEvent.createView("Test document", ViewLayoutPB.Document),
);
final viewBloc = await createTestViewBloc();
viewBloc.add(const ViewEvent.rename(name));
await blocResponseFuture();
final viewBloc = ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial());
viewBloc.add(const ViewEvent.rename('Hello world'));
await blocResponseFuture();
assert(viewBloc.state.view.name == "Hello world");
expect(viewBloc.state.view.name, name);
});
test('duplicate view test', () async {
final app = await testContext.createTestApp();
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
appBloc.add(
const AppEvent.createView("Test document", ViewLayoutPB.Document),
final viewBloc = await createTestViewBloc();
// create a nested view
viewBloc.add(
const ViewEvent.createView(name, ViewLayoutPB.Document),
);
await blocResponseFuture();
final viewBloc = ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial());
expect(viewBloc.state.view.childViews.length, 1);
final childViewBloc = ViewBloc(view: viewBloc.state.view.childViews.first)
..add(
const ViewEvent.initial(),
);
childViewBloc.add(const ViewEvent.duplicate());
await blocResponseFuture();
viewBloc.add(const ViewEvent.duplicate());
await blocResponseFuture();
expect(appBloc.state.views.length, 2);
expect(viewBloc.state.view.childViews.length, 2);
});
test('delete view test', () async {
final app = await testContext.createTestApp();
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
await blocResponseFuture();
appBloc.add(
const AppEvent.createView("Test document", ViewLayoutPB.Document),
final viewBloc = await createTestViewBloc();
viewBloc.add(
const ViewEvent.createView(name, ViewLayoutPB.Document),
);
await blocResponseFuture();
expect(appBloc.state.views.length, 1);
final viewBloc = ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial());
expect(viewBloc.state.view.childViews.length, 1);
final childViewBloc = ViewBloc(view: viewBloc.state.view.childViews.first)
..add(
const ViewEvent.initial(),
);
await blocResponseFuture();
viewBloc.add(const ViewEvent.delete());
childViewBloc.add(const ViewEvent.delete());
await blocResponseFuture();
assert(appBloc.state.views.isEmpty);
assert(viewBloc.state.view.childViews.isEmpty);
});
test('create nested view test', () async {
final app = await testContext.createTestApp();
final appBloc = AppBloc(view: app);
appBloc
..add(
const AppEvent.initial(),
)
..add(
const AppEvent.createView('Document 1', ViewLayoutPB.Document),
);
final viewBloc = await createTestViewBloc();
viewBloc.add(
const ViewEvent.createView('Document 1', ViewLayoutPB.Document),
);
await blocResponseFuture();
// create a nested view
const name = 'Document 1 - 1';
final viewBloc = ViewBloc(view: appBloc.state.views.first);
viewBloc
final document1Bloc = ViewBloc(view: viewBloc.state.view.childViews.first)
..add(
const ViewEvent.initial(),
)
);
await blocResponseFuture();
const name = 'Document 1 - 1';
document1Bloc.add(
const ViewEvent.createView('Document 1 - 1', ViewLayoutPB.Document),
);
await blocResponseFuture();
expect(document1Bloc.state.view.childViews.length, 1);
expect(document1Bloc.state.view.childViews.first.name, name);
});
test('create documents in order', () async {
final viewBloc = await createTestViewBloc();
final names = ['1', '2', '3'];
for (final name in names) {
viewBloc.add(
ViewEvent.createView(name, ViewLayoutPB.Document),
);
await blocResponseFuture();
}
expect(viewBloc.state.view.childViews.length, 3);
for (var i = 0; i < names.length; i++) {
expect(viewBloc.state.view.childViews[i].name, names[i]);
}
});
test('open latest view test', () async {
final viewBloc = await createTestViewBloc();
expect(viewBloc.state.lastCreatedView, isNull);
viewBloc.add(const ViewEvent.createView('1', ViewLayoutPB.Document));
await blocResponseFuture();
expect(
viewBloc.state.lastCreatedView!.id,
viewBloc.state.view.childViews.last.id,
);
expect(
viewBloc.state.lastCreatedView!.name,
'1',
);
viewBloc.add(const ViewEvent.createView('2', ViewLayoutPB.Document));
await blocResponseFuture();
expect(
viewBloc.state.lastCreatedView!.name,
'2',
);
});
test('open latest document test', () async {
const name1 = 'document';
final viewBloc = await createTestViewBloc();
viewBloc.add(const ViewEvent.createView(name1, ViewLayoutPB.Document));
await blocResponseFuture();
final document = viewBloc.state.lastCreatedView!;
assert(document.name == name1);
const gird = 'grid';
viewBloc.add(const ViewEvent.createView(gird, ViewLayoutPB.Document));
await blocResponseFuture();
assert(viewBloc.state.lastCreatedView!.name == gird);
var workspaceSetting =
await FolderEventGetCurrentWorkspaceSetting().send().then(
(result) => result.fold(
(l) => l,
(r) => throw Exception(),
),
);
workspaceSetting.latestView.id == viewBloc.state.lastCreatedView!.id;
// ignore: unused_local_variable
final documentBloc = DocumentBloc(view: document)
..add(
const ViewEvent.createView(name, ViewLayoutPB.Document),
const DocumentEvent.initial(),
);
await blocResponseFuture();
assert(viewBloc.state.childViews.first.name == name);
workspaceSetting =
await FolderEventGetCurrentWorkspaceSetting().send().then(
(result) => result.fold(
(l) => l,
(r) => throw Exception(),
),
);
workspaceSetting.latestView.id == document.id;
});
test('create views', () async {
final viewBloc = await createTestViewBloc();
const layouts = ViewLayoutPB.values;
for (var i = 0; i < layouts.length; i++) {
final layout = layouts[i];
viewBloc.add(
ViewEvent.createView('Test $layout', layout),
);
await blocResponseFuture();
expect(viewBloc.state.view.childViews.length, i + 1);
expect(viewBloc.state.view.childViews.last.name, 'Test $layout');
expect(viewBloc.state.view.childViews.last.layout, layout);
}
});
}

View File

@ -1,17 +1,17 @@
import 'package:appflowy/main.dart';
import 'package:appflowy/startup/launch_configuration.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:appflowy/main.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AppFlowyIntegrateTest {
@ -84,7 +84,7 @@ class AppFlowyUnitTest {
workspaceService = WorkspaceService(workspaceId: currentWorkspace.id);
}
Future<ViewPB> createTestApp() async {
Future<ViewPB> createWorkspace() async {
final result = await workspaceService.createApp(name: "Test App");
return result.fold(
(app) => app,

View File

@ -2,7 +2,9 @@ use std::collections::HashSet;
use std::sync::{Arc, Weak};
use collab::core::collab_state::SyncState;
use collab_folder::{Folder, TrashChange, TrashChangeReceiver, ViewChange, ViewChangeReceiver};
use collab_folder::{
Folder, TrashChange, TrashChangeReceiver, View, ViewChange, ViewChangeReceiver,
};
use tokio_stream::wrappers::WatchStream;
use tokio_stream::StreamExt;
use tracing::{event, Level};
@ -43,11 +45,12 @@ pub(crate) fn subscribe_folder_view_changed(
}
},
ViewChange::DidUpdate { view } => {
notify_view_did_change(view.clone());
notify_child_views_changed(
view_pb_without_child_views(Arc::new(view.clone())),
ChildViewChangeReason::DidUpdateView,
);
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id.clone()]);
},
};
}
@ -181,6 +184,14 @@ pub(crate) fn notify_did_update_workspace(workspace_id: &str, folder: &Folder) {
.send();
}
fn notify_view_did_change(view: View) -> Option<()> {
let view_pb = view_pb_without_child_views(Arc::new(view.clone()));
send_notification(&view.id, FolderNotification::DidUpdateView)
.payload(view_pb)
.send();
None
}
pub enum ChildViewChangeReason {
DidCreateView,
DidDeleteView,