Browse Source

[flutter]: add delete and restore notification

appflowy 3 years ago
parent
commit
ded8eb5d1c

+ 18 - 5
app_flowy/lib/workspace/application/app/app_bloc.dart

@@ -20,7 +20,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
   ) async* {
     yield* event.map(initial: (e) async* {
       listener.start(
-        viewsChangeCallback: _handleViewsOrFail,
+        viewsChangeCallback: _handleViewsChanged,
         updatedCallback: (app) => add(AppEvent.appDidUpdate(app)),
       );
       yield* _fetchViews();
@@ -37,11 +37,11 @@ class AppBloc extends Bloc<AppEvent, AppState> {
         },
       );
     }, didReceiveViews: (e) async* {
-      yield state.copyWith(views: e.views);
+      yield* handleDidReceiveViews(e.views);
     }, delete: (e) async* {
       final result = await appManager.delete();
       yield result.fold(
-        (l) => state.copyWith(successOrFailure: left(unit)),
+        (unit) => state.copyWith(successOrFailure: left(unit)),
         (error) => state.copyWith(successOrFailure: right(error)),
       );
     }, rename: (e) async* {
@@ -61,8 +61,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
     return super.close();
   }
 
-  void _handleViewsOrFail(Either<List<View>, WorkspaceError> viewsOrFail) {
-    viewsOrFail.fold(
+  void _handleViewsChanged(Either<List<View>, WorkspaceError> result) {
+    result.fold(
       (views) => add(AppEvent.didReceiveViews(views)),
       (error) {
         Log.error(error);
@@ -70,6 +70,19 @@ class AppBloc extends Bloc<AppEvent, AppState> {
     );
   }
 
+  Stream<AppState> handleDidReceiveViews(List<View> views) async* {
+    final selectedView = state.selectedView;
+    AppState newState = state.copyWith(views: views);
+    if (selectedView != null) {
+      final index = views.indexWhere((element) => element.id == selectedView.id);
+      if (index != -1) {
+        newState = newState.copyWith(selectedView: null);
+      }
+    }
+
+    yield newState;
+  }
+
   Stream<AppState> _fetchViews() async* {
     final viewsOrFailed = await appManager.getViews();
     yield viewsOrFailed.fold(

+ 36 - 3
app_flowy/lib/workspace/application/doc/doc_bloc.dart

@@ -1,5 +1,6 @@
 import 'dart:convert';
 
+import 'package:app_flowy/workspace/domain/i_view.dart';
 import 'package:flutter_quill/flutter_quill.dart';
 import 'package:flowy_log/flowy_log.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
@@ -12,24 +13,50 @@ part 'doc_bloc.freezed.dart';
 
 class DocBloc extends Bloc<DocEvent, DocState> {
   final IDoc docManager;
+  final IViewListener listener;
   late Document document;
   late StreamSubscription _subscription;
 
-  DocBloc({required this.docManager}) : super(DocState.initial());
+  DocBloc({required this.docManager, required this.listener}) : super(DocState.initial());
 
   @override
   Stream<DocState> mapEventToState(DocEvent event) async* {
-    yield* event.map(initial: _initial);
+    yield* event.map(
+      initial: _initial,
+      deleted: (Deleted value) async* {
+        yield state.copyWith(isDeleted: true);
+      },
+      restore: (Restore value) async* {
+        yield state.copyWith(isDeleted: false);
+      },
+    );
   }
 
   @override
   Future<void> close() async {
+    await listener.stop();
     await _subscription.cancel();
     docManager.closeDoc();
     return super.close();
   }
 
   Stream<DocState> _initial(Initial value) async* {
+    listener.deletedNotifier.addPublishListener((result) {
+      result.fold(
+        (view) => add(const DocEvent.deleted()),
+        (error) {},
+      );
+    });
+
+    listener.restoredNotifier.addPublishListener((result) {
+      result.fold(
+        (view) => add(const DocEvent.restore()),
+        (error) {},
+      );
+    });
+
+    listener.start();
+
     final result = await docManager.readDoc();
     yield result.fold(
       (doc) {
@@ -78,15 +105,21 @@ class DocBloc extends Bloc<DocEvent, DocState> {
 @freezed
 class DocEvent with _$DocEvent {
   const factory DocEvent.initial() = Initial;
+  const factory DocEvent.deleted() = Deleted;
+  const factory DocEvent.restore() = Restore;
 }
 
 @freezed
 class DocState with _$DocState {
   const factory DocState({
     required DocLoadState loadState,
+    required bool isDeleted,
   }) = _DocState;
 
-  factory DocState.initial() => const DocState(loadState: _Loading());
+  factory DocState.initial() => const DocState(
+        loadState: _Loading(),
+        isDeleted: false,
+      );
 }
 
 @freezed

+ 228 - 8
app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart

@@ -19,6 +19,14 @@ class _$DocEventTearOff {
   Initial initial() {
     return const Initial();
   }
+
+  Deleted deleted() {
+    return const Deleted();
+  }
+
+  Restore restore() {
+    return const Restore();
+  }
 }
 
 /// @nodoc
@@ -29,22 +37,30 @@ mixin _$DocEvent {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
     required TResult Function() initial,
+    required TResult Function() deleted,
+    required TResult Function() restore,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? initial,
+    TResult Function()? deleted,
+    TResult Function()? restore,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult map<TResult extends Object?>({
     required TResult Function(Initial value) initial,
+    required TResult Function(Deleted value) deleted,
+    required TResult Function(Restore value) restore,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Initial value)? initial,
+    TResult Function(Deleted value)? deleted,
+    TResult Function(Restore value)? restore,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
@@ -103,6 +119,8 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
     required TResult Function() initial,
+    required TResult Function() deleted,
+    required TResult Function() restore,
   }) {
     return initial();
   }
@@ -111,6 +129,8 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? initial,
+    TResult Function()? deleted,
+    TResult Function()? restore,
     required TResult orElse(),
   }) {
     if (initial != null) {
@@ -123,6 +143,8 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   TResult map<TResult extends Object?>({
     required TResult Function(Initial value) initial,
+    required TResult Function(Deleted value) deleted,
+    required TResult Function(Restore value) restore,
   }) {
     return initial(this);
   }
@@ -131,6 +153,8 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Initial value)? initial,
+    TResult Function(Deleted value)? deleted,
+    TResult Function(Restore value)? restore,
     required TResult orElse(),
   }) {
     if (initial != null) {
@@ -144,13 +168,188 @@ abstract class Initial implements DocEvent {
   const factory Initial() = _$Initial;
 }
 
+/// @nodoc
+abstract class $DeletedCopyWith<$Res> {
+  factory $DeletedCopyWith(Deleted value, $Res Function(Deleted) then) =
+      _$DeletedCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$DeletedCopyWithImpl<$Res> extends _$DocEventCopyWithImpl<$Res>
+    implements $DeletedCopyWith<$Res> {
+  _$DeletedCopyWithImpl(Deleted _value, $Res Function(Deleted) _then)
+      : super(_value, (v) => _then(v as Deleted));
+
+  @override
+  Deleted get _value => super._value as Deleted;
+}
+
+/// @nodoc
+
+class _$Deleted implements Deleted {
+  const _$Deleted();
+
+  @override
+  String toString() {
+    return 'DocEvent.deleted()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is Deleted);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() deleted,
+    required TResult Function() restore,
+  }) {
+    return deleted();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? deleted,
+    TResult Function()? restore,
+    required TResult orElse(),
+  }) {
+    if (deleted != null) {
+      return deleted();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Initial value) initial,
+    required TResult Function(Deleted value) deleted,
+    required TResult Function(Restore value) restore,
+  }) {
+    return deleted(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Initial value)? initial,
+    TResult Function(Deleted value)? deleted,
+    TResult Function(Restore value)? restore,
+    required TResult orElse(),
+  }) {
+    if (deleted != null) {
+      return deleted(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class Deleted implements DocEvent {
+  const factory Deleted() = _$Deleted;
+}
+
+/// @nodoc
+abstract class $RestoreCopyWith<$Res> {
+  factory $RestoreCopyWith(Restore value, $Res Function(Restore) then) =
+      _$RestoreCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$RestoreCopyWithImpl<$Res> extends _$DocEventCopyWithImpl<$Res>
+    implements $RestoreCopyWith<$Res> {
+  _$RestoreCopyWithImpl(Restore _value, $Res Function(Restore) _then)
+      : super(_value, (v) => _then(v as Restore));
+
+  @override
+  Restore get _value => super._value as Restore;
+}
+
+/// @nodoc
+
+class _$Restore implements Restore {
+  const _$Restore();
+
+  @override
+  String toString() {
+    return 'DocEvent.restore()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is Restore);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() deleted,
+    required TResult Function() restore,
+  }) {
+    return restore();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? deleted,
+    TResult Function()? restore,
+    required TResult orElse(),
+  }) {
+    if (restore != null) {
+      return restore();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Initial value) initial,
+    required TResult Function(Deleted value) deleted,
+    required TResult Function(Restore value) restore,
+  }) {
+    return restore(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Initial value)? initial,
+    TResult Function(Deleted value)? deleted,
+    TResult Function(Restore value)? restore,
+    required TResult orElse(),
+  }) {
+    if (restore != null) {
+      return restore(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class Restore implements DocEvent {
+  const factory Restore() = _$Restore;
+}
+
 /// @nodoc
 class _$DocStateTearOff {
   const _$DocStateTearOff();
 
-  _DocState call({required DocLoadState loadState}) {
+  _DocState call({required DocLoadState loadState, required bool isDeleted}) {
     return _DocState(
       loadState: loadState,
+      isDeleted: isDeleted,
     );
   }
 }
@@ -161,6 +360,7 @@ const $DocState = _$DocStateTearOff();
 /// @nodoc
 mixin _$DocState {
   DocLoadState get loadState => throw _privateConstructorUsedError;
+  bool get isDeleted => throw _privateConstructorUsedError;
 
   @JsonKey(ignore: true)
   $DocStateCopyWith<DocState> get copyWith =>
@@ -171,7 +371,7 @@ mixin _$DocState {
 abstract class $DocStateCopyWith<$Res> {
   factory $DocStateCopyWith(DocState value, $Res Function(DocState) then) =
       _$DocStateCopyWithImpl<$Res>;
-  $Res call({DocLoadState loadState});
+  $Res call({DocLoadState loadState, bool isDeleted});
 
   $DocLoadStateCopyWith<$Res> get loadState;
 }
@@ -187,12 +387,17 @@ class _$DocStateCopyWithImpl<$Res> implements $DocStateCopyWith<$Res> {
   @override
   $Res call({
     Object? loadState = freezed,
+    Object? isDeleted = freezed,
   }) {
     return _then(_value.copyWith(
       loadState: loadState == freezed
           ? _value.loadState
           : loadState // ignore: cast_nullable_to_non_nullable
               as DocLoadState,
+      isDeleted: isDeleted == freezed
+          ? _value.isDeleted
+          : isDeleted // ignore: cast_nullable_to_non_nullable
+              as bool,
     ));
   }
 
@@ -209,7 +414,7 @@ abstract class _$DocStateCopyWith<$Res> implements $DocStateCopyWith<$Res> {
   factory _$DocStateCopyWith(_DocState value, $Res Function(_DocState) then) =
       __$DocStateCopyWithImpl<$Res>;
   @override
-  $Res call({DocLoadState loadState});
+  $Res call({DocLoadState loadState, bool isDeleted});
 
   @override
   $DocLoadStateCopyWith<$Res> get loadState;
@@ -227,12 +432,17 @@ class __$DocStateCopyWithImpl<$Res> extends _$DocStateCopyWithImpl<$Res>
   @override
   $Res call({
     Object? loadState = freezed,
+    Object? isDeleted = freezed,
   }) {
     return _then(_DocState(
       loadState: loadState == freezed
           ? _value.loadState
           : loadState // ignore: cast_nullable_to_non_nullable
               as DocLoadState,
+      isDeleted: isDeleted == freezed
+          ? _value.isDeleted
+          : isDeleted // ignore: cast_nullable_to_non_nullable
+              as bool,
     ));
   }
 }
@@ -240,14 +450,16 @@ class __$DocStateCopyWithImpl<$Res> extends _$DocStateCopyWithImpl<$Res>
 /// @nodoc
 
 class _$_DocState implements _DocState {
-  const _$_DocState({required this.loadState});
+  const _$_DocState({required this.loadState, required this.isDeleted});
 
   @override
   final DocLoadState loadState;
+  @override
+  final bool isDeleted;
 
   @override
   String toString() {
-    return 'DocState(loadState: $loadState)';
+    return 'DocState(loadState: $loadState, isDeleted: $isDeleted)';
   }
 
   @override
@@ -256,12 +468,17 @@ class _$_DocState implements _DocState {
         (other is _DocState &&
             (identical(other.loadState, loadState) ||
                 const DeepCollectionEquality()
-                    .equals(other.loadState, loadState)));
+                    .equals(other.loadState, loadState)) &&
+            (identical(other.isDeleted, isDeleted) ||
+                const DeepCollectionEquality()
+                    .equals(other.isDeleted, isDeleted)));
   }
 
   @override
   int get hashCode =>
-      runtimeType.hashCode ^ const DeepCollectionEquality().hash(loadState);
+      runtimeType.hashCode ^
+      const DeepCollectionEquality().hash(loadState) ^
+      const DeepCollectionEquality().hash(isDeleted);
 
   @JsonKey(ignore: true)
   @override
@@ -270,11 +487,14 @@ class _$_DocState implements _DocState {
 }
 
 abstract class _DocState implements DocState {
-  const factory _DocState({required DocLoadState loadState}) = _$_DocState;
+  const factory _DocState(
+      {required DocLoadState loadState, required bool isDeleted}) = _$_DocState;
 
   @override
   DocLoadState get loadState => throw _privateConstructorUsedError;
   @override
+  bool get isDeleted => throw _privateConstructorUsedError;
+  @override
   @JsonKey(ignore: true)
   _$DocStateCopyWith<_DocState> get copyWith =>
       throw _privateConstructorUsedError;

+ 4 - 1
app_flowy/lib/workspace/application/view/view_bloc.dart

@@ -20,7 +20,10 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
   Stream<ViewState> mapEventToState(ViewEvent event) async* {
     yield* event.map(
       initial: (e) async* {
-        listener.start(updatedCallback: (result) => add(ViewEvent.viewDidUpdate(result)));
+        listener.updatedNotifier.addPublishListener((result) {
+          add(ViewEvent.viewDidUpdate(result));
+        });
+        listener.start();
         yield state;
       },
       setIsEditing: (e) async* {

+ 12 - 1
app_flowy/lib/workspace/domain/i_view.dart

@@ -1,9 +1,14 @@
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:dartz/dartz.dart';
+import 'package:flowy_infra/notifier.dart';
 
 typedef ViewUpdatedCallback = void Function(Either<View, WorkspaceError>);
 
+typedef DeleteNotifierValue = Either<View, WorkspaceError>;
+typedef UpdateNotifierValue = Either<View, WorkspaceError>;
+typedef RestoreNotifierValue = Either<View, WorkspaceError>;
+
 abstract class IView {
   View get view;
 
@@ -15,7 +20,13 @@ abstract class IView {
 }
 
 abstract class IViewListener {
-  void start({ViewUpdatedCallback? updatedCallback});
+  void start();
+
+  PublishNotifier<UpdateNotifierValue> get updatedNotifier;
+
+  PublishNotifier<DeleteNotifierValue> get deletedNotifier;
+
+  PublishNotifier<RestoreNotifierValue> get restoredNotifier;
 
   Future<void> stop();
 }

+ 6 - 1
app_flowy/lib/workspace/infrastructure/deps_resolver.dart

@@ -87,7 +87,12 @@ class HomeDepsResolver {
     );
 
     // Doc
-    getIt.registerFactoryParam<DocBloc, String, void>((docId, _) => DocBloc(docManager: getIt<IDoc>(param1: docId)));
+    getIt.registerFactoryParam<DocBloc, View, void>(
+      (view, _) => DocBloc(
+        docManager: getIt<IDoc>(param1: view.id),
+        listener: getIt<IViewListener>(param1: view),
+      ),
+    );
 
     // trash
     getIt.registerLazySingleton<TrashRepo>(() => TrashRepo());

+ 12 - 2
app_flowy/lib/workspace/infrastructure/i_view_impl.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/workspace/domain/i_view.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/view_repo.dart';
+import 'package:flowy_infra/notifier.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
@@ -40,12 +41,21 @@ class IViewListenerImpl extends IViewListener {
   });
 
   @override
-  void start({ViewUpdatedCallback? updatedCallback}) {
-    repo.startWatching(update: updatedCallback);
+  void start() {
+    repo.start();
   }
 
   @override
   Future<void> stop() async {
     await repo.close();
   }
+
+  @override
+  PublishNotifier<DeleteNotifierValue> get deletedNotifier => repo.deletedNotifier;
+
+  @override
+  PublishNotifier<UpdateNotifierValue> get updatedNotifier => repo.updatedNotifier;
+
+  @override
+  PublishNotifier<RestoreNotifierValue> get restoredNotifier => repo.updatedNotifier;
 }

+ 21 - 14
app_flowy/lib/workspace/infrastructure/repos/view_repo.dart

@@ -11,6 +11,7 @@ import 'package:flowy_sdk/protobuf/flowy-workspace/view_update.pb.dart';
 import 'package:flowy_sdk/rust_stream.dart';
 
 import 'package:app_flowy/workspace/domain/i_view.dart';
+import 'package:flowy_infra/notifier.dart';
 
 import 'helper.dart';
 
@@ -52,7 +53,9 @@ class ViewRepository {
 
 class ViewListenerRepository {
   StreamSubscription<SubscribeObject>? _subscription;
-  ViewUpdatedCallback? _update;
+  PublishNotifier<UpdateNotifierValue> updatedNotifier = PublishNotifier<UpdateNotifierValue>();
+  PublishNotifier<DeleteNotifierValue> deletedNotifier = PublishNotifier<DeleteNotifierValue>();
+  PublishNotifier<RestoreNotifierValue> restoredNotifier = PublishNotifier<RestoreNotifierValue>();
   late WorkspaceNotificationParser _parser;
   View view;
 
@@ -60,10 +63,7 @@ class ViewListenerRepository {
     required this.view,
   });
 
-  void startWatching({
-    ViewUpdatedCallback? update,
-  }) {
-    _update = update;
+  void start() {
     _parser = WorkspaceNotificationParser(
       id: view.id,
       callback: (ty, result) {
@@ -77,15 +77,22 @@ class ViewListenerRepository {
   void _handleObservableType(WorkspaceNotification ty, Either<Uint8List, WorkspaceError> result) {
     switch (ty) {
       case WorkspaceNotification.ViewUpdated:
-        if (_update != null) {
-          result.fold(
-            (payload) {
-              final view = View.fromBuffer(payload);
-              _update!(left(view));
-            },
-            (error) => _update!(right(error)),
-          );
-        }
+        result.fold(
+          (payload) => updatedNotifier.value = left(View.fromBuffer(payload)),
+          (error) => updatedNotifier.value = right(error),
+        );
+        break;
+      case WorkspaceNotification.ViewDeleted:
+        result.fold(
+          (payload) => deletedNotifier.value = left(View.fromBuffer(payload)),
+          (error) => deletedNotifier.value = right(error),
+        );
+        break;
+      case WorkspaceNotification.ViewRestored:
+        result.fold(
+          (payload) => restoredNotifier.value = left(View.fromBuffer(payload)),
+          (error) => restoredNotifier.value = right(error),
+        );
         break;
       default:
         break;

+ 1 - 1
app_flowy/lib/workspace/presentation/stack_page/doc/doc_page.dart

@@ -28,7 +28,7 @@ class _DocPageState extends State<DocPage> {
 
   @override
   void initState() {
-    docBloc = getIt<DocBloc>(param1: super.widget.view.id)..add(const DocEvent.initial());
+    docBloc = getIt<DocBloc>(param1: super.widget.view)..add(const DocEvent.initial());
     super.initState();
   }
 

+ 2 - 1
app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart

@@ -15,7 +15,7 @@ class DocStackContext extends HomeStackContext {
 
   DocStackContext({required View view, Key? key}) : _view = view {
     _listener = getIt<IViewListener>(param1: view);
-    _listener.start(updatedCallback: (result) {
+    _listener.updatedNotifier.addPublishListener((result) {
       result.fold(
         (newView) {
           _view = newView;
@@ -24,6 +24,7 @@ class DocStackContext extends HomeStackContext {
         (error) {},
       );
     });
+    _listener.start();
   }
 
   @override

+ 1 - 1
app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/section.dart

@@ -68,7 +68,7 @@ class ViewSection extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    // The ViewListNotifier will be updated after ViewListData changed passed by parent widget
+    // The ViewSectionNotifier will be updated after AppDataNotifier changed passed by parent widget
     return ChangeNotifierProxyProvider<AppDataNotifier, ViewSectionNotifier>(
       create: (_) {
         final views = Provider.of<AppDataNotifier>(context, listen: false).views;

+ 20 - 0
app_flowy/packages/flowy_infra/lib/notifier.dart

@@ -0,0 +1,20 @@
+import 'package:flutter/material.dart';
+
+class PublishNotifier<T> extends ChangeNotifier {
+  T? _value;
+
+  set value(T newValue) {
+    _value = newValue;
+    notifyListeners();
+  }
+
+  T? get currentValue => _value;
+
+  void addPublishListener(void Function(T) callback) {
+    super.addListener(() {
+      if (_value != null) {
+        callback(_value!);
+      }
+    });
+  }
+}

+ 4 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbenum.dart

@@ -19,6 +19,8 @@ class WorkspaceNotification extends $pb.ProtobufEnum {
   static const WorkspaceNotification AppUpdated = WorkspaceNotification._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppUpdated');
   static const WorkspaceNotification AppViewsChanged = WorkspaceNotification._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppViewsChanged');
   static const WorkspaceNotification ViewUpdated = WorkspaceNotification._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewUpdated');
+  static const WorkspaceNotification ViewDeleted = WorkspaceNotification._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewDeleted');
+  static const WorkspaceNotification ViewRestored = WorkspaceNotification._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewRestored');
   static const WorkspaceNotification UserUnauthorized = WorkspaceNotification._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
   static const WorkspaceNotification TrashUpdated = WorkspaceNotification._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TrashUpdated');
 
@@ -32,6 +34,8 @@ class WorkspaceNotification extends $pb.ProtobufEnum {
     AppUpdated,
     AppViewsChanged,
     ViewUpdated,
+    ViewDeleted,
+    ViewRestored,
     UserUnauthorized,
     TrashUpdated,
   ];

+ 3 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbjson.dart

@@ -21,10 +21,12 @@ const WorkspaceNotification$json = const {
     const {'1': 'AppUpdated', '2': 21},
     const {'1': 'AppViewsChanged', '2': 24},
     const {'1': 'ViewUpdated', '2': 31},
+    const {'1': 'ViewDeleted', '2': 32},
+    const {'1': 'ViewRestored', '2': 33},
     const {'1': 'UserUnauthorized', '2': 100},
     const {'1': 'TrashUpdated', '2': 1000},
   ],
 };
 
 /// Descriptor for `WorkspaceNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceNotificationDescriptor = $convert.base64Decode('ChVXb3Jrc3BhY2VOb3RpZmljYXRpb24SCwoHVW5rbm93bhAAEhcKE1VzZXJDcmVhdGVXb3Jrc3BhY2UQChIXChNVc2VyRGVsZXRlV29ya3NwYWNlEAsSFAoQV29ya3NwYWNlVXBkYXRlZBAMEhgKFFdvcmtzcGFjZUxpc3RVcGRhdGVkEA0SGAoUV29ya3NwYWNlQXBwc0NoYW5nZWQQDhIOCgpBcHBVcGRhdGVkEBUSEwoPQXBwVmlld3NDaGFuZ2VkEBgSDwoLVmlld1VwZGF0ZWQQHxIUChBVc2VyVW5hdXRob3JpemVkEGQSEQoMVHJhc2hVcGRhdGVkEOgH');
+final $typed_data.Uint8List workspaceNotificationDescriptor = $convert.base64Decode('ChVXb3Jrc3BhY2VOb3RpZmljYXRpb24SCwoHVW5rbm93bhAAEhcKE1VzZXJDcmVhdGVXb3Jrc3BhY2UQChIXChNVc2VyRGVsZXRlV29ya3NwYWNlEAsSFAoQV29ya3NwYWNlVXBkYXRlZBAMEhgKFFdvcmtzcGFjZUxpc3RVcGRhdGVkEA0SGAoUV29ya3NwYWNlQXBwc0NoYW5nZWQQDhIOCgpBcHBVcGRhdGVkEBUSEwoPQXBwVmlld3NDaGFuZ2VkEBgSDwoLVmlld1VwZGF0ZWQQHxIPCgtWaWV3RGVsZXRlZBAgEhAKDFZpZXdSZXN0b3JlZBAhEhQKEFVzZXJVbmF1dGhvcml6ZWQQZBIRCgxUcmFzaFVwZGF0ZWQQ6Ac=');

+ 1 - 1
app_flowy/pubspec.lock

@@ -1025,5 +1025,5 @@ packages:
     source: hosted
     version: "8.0.0"
 sdks:
-  dart: ">=2.14.0 <3.0.0"
+  dart: ">=2.15.0-116.0.dev <3.0.0"
   flutter: ">=2.5.0"

+ 1 - 1
app_flowy/pubspec.yaml

@@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 version: 1.0.0+1
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: ">=2.15.0-116.0.dev <3.0.0"
 
 # Dependencies specify other packages that your package needs in order to work.
 # To automatically upgrade your package dependencies to the latest versions

+ 4 - 0
rust-lib/flowy-workspace/src/notify/observable.rs

@@ -2,6 +2,8 @@ use flowy_dart_notify::DartNotifyBuilder;
 use flowy_derive::ProtoBuf_Enum;
 const OBSERVABLE_CATEGORY: &'static str = "Workspace";
 
+// Opti: Using the Rust macro to generate the serde code automatically that can
+// be use directly in flutter
 #[derive(ProtoBuf_Enum, Debug)]
 pub(crate) enum WorkspaceNotification {
     Unknown              = 0,
@@ -13,6 +15,8 @@ pub(crate) enum WorkspaceNotification {
     AppUpdated           = 21,
     AppViewsChanged      = 24,
     ViewUpdated          = 31,
+    ViewDeleted          = 32,
+    ViewRestored         = 33,
     UserUnauthorized     = 100,
     TrashUpdated         = 1000,
 }

+ 38 - 27
rust-lib/flowy-workspace/src/protobuf/model/observable.rs

@@ -34,6 +34,8 @@ pub enum WorkspaceNotification {
     AppUpdated = 21,
     AppViewsChanged = 24,
     ViewUpdated = 31,
+    ViewDeleted = 32,
+    ViewRestored = 33,
     UserUnauthorized = 100,
     TrashUpdated = 1000,
 }
@@ -54,6 +56,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceNotification {
             21 => ::std::option::Option::Some(WorkspaceNotification::AppUpdated),
             24 => ::std::option::Option::Some(WorkspaceNotification::AppViewsChanged),
             31 => ::std::option::Option::Some(WorkspaceNotification::ViewUpdated),
+            32 => ::std::option::Option::Some(WorkspaceNotification::ViewDeleted),
+            33 => ::std::option::Option::Some(WorkspaceNotification::ViewRestored),
             100 => ::std::option::Option::Some(WorkspaceNotification::UserUnauthorized),
             1000 => ::std::option::Option::Some(WorkspaceNotification::TrashUpdated),
             _ => ::std::option::Option::None
@@ -71,6 +75,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceNotification {
             WorkspaceNotification::AppUpdated,
             WorkspaceNotification::AppViewsChanged,
             WorkspaceNotification::ViewUpdated,
+            WorkspaceNotification::ViewDeleted,
+            WorkspaceNotification::ViewRestored,
             WorkspaceNotification::UserUnauthorized,
             WorkspaceNotification::TrashUpdated,
         ];
@@ -101,37 +107,42 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceNotification {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x10observable.proto*\xff\x01\n\x15WorkspaceNotification\x12\x0b\n\x07\
+    \n\x10observable.proto*\xa2\x02\n\x15WorkspaceNotification\x12\x0b\n\x07\
     Unknown\x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13UserDe\
     leteWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x18\n\
     \x14WorkspaceListUpdated\x10\r\x12\x18\n\x14WorkspaceAppsChanged\x10\x0e\
     \x12\x0e\n\nAppUpdated\x10\x15\x12\x13\n\x0fAppViewsChanged\x10\x18\x12\
-    \x0f\n\x0bViewUpdated\x10\x1f\x12\x14\n\x10UserUnauthorized\x10d\x12\x11\
-    \n\x0cTrashUpdated\x10\xe8\x07J\xed\x03\n\x06\x12\x04\0\0\x0e\x01\n\x08\
-    \n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x0e\x01\n\n\n\
-    \x03\x05\0\x01\x12\x03\x02\x05\x1a\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\
-    \x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0b\n\x0c\n\x05\x05\
-    \0\x02\0\x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\
-    \x04\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x17\n\x0c\n\x05\
-    \x05\0\x02\x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\
-    \x05\x04\x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x17\n\x0c\n\
-    \x05\x05\0\x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\x04\x05\0\x02\x03\x12\
-    \x03\x06\x04\x1a\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x14\n\x0c\
-    \n\x05\x05\0\x02\x03\x02\x12\x03\x06\x17\x19\n\x0b\n\x04\x05\0\x02\x04\
-    \x12\x03\x07\x04\x1e\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x18\n\
-    \x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x1b\x1d\n\x0b\n\x04\x05\0\x02\
-    \x05\x12\x03\x08\x04\x1e\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\
-    \x18\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\x1b\x1d\n\x0b\n\x04\x05\0\
-    \x02\x06\x12\x03\t\x04\x14\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\
-    \x0e\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x11\x13\n\x0b\n\x04\x05\0\
-    \x02\x07\x12\x03\n\x04\x19\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\
-    \x13\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\n\x16\x18\n\x0b\n\x04\x05\0\
-    \x02\x08\x12\x03\x0b\x04\x15\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\
-    \x04\x0f\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x12\x14\n\x0b\n\x04\
-    \x05\0\x02\t\x12\x03\x0c\x04\x1b\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x0c\
-    \x04\x14\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x0c\x17\x1a\n\x0b\n\x04\x05\
-    \0\x02\n\x12\x03\r\x04\x18\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\r\x04\x10\
-    \n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\r\x13\x17b\x06proto3\
+    \x0f\n\x0bViewUpdated\x10\x1f\x12\x0f\n\x0bViewDeleted\x10\x20\x12\x10\n\
+    \x0cViewRestored\x10!\x12\x14\n\x10UserUnauthorized\x10d\x12\x11\n\x0cTr\
+    ashUpdated\x10\xe8\x07J\xbf\x04\n\x06\x12\x04\0\0\x10\x01\n\x08\n\x01\
+    \x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x10\x01\n\n\n\x03\x05\
+    \0\x01\x12\x03\x02\x05\x1a\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
+    \x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x17\n\x0c\n\x05\x05\0\x02\
+    \x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\
+    \x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x17\n\x0c\n\x05\x05\0\
+    \x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\
+    \x04\x1a\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x14\n\x0c\n\x05\
+    \x05\0\x02\x03\x02\x12\x03\x06\x17\x19\n\x0b\n\x04\x05\0\x02\x04\x12\x03\
+    \x07\x04\x1e\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x18\n\x0c\n\
+    \x05\x05\0\x02\x04\x02\x12\x03\x07\x1b\x1d\n\x0b\n\x04\x05\0\x02\x05\x12\
+    \x03\x08\x04\x1e\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x18\n\x0c\
+    \n\x05\x05\0\x02\x05\x02\x12\x03\x08\x1b\x1d\n\x0b\n\x04\x05\0\x02\x06\
+    \x12\x03\t\x04\x14\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\x0e\n\x0c\
+    \n\x05\x05\0\x02\x06\x02\x12\x03\t\x11\x13\n\x0b\n\x04\x05\0\x02\x07\x12\
+    \x03\n\x04\x19\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\x13\n\x0c\n\
+    \x05\x05\0\x02\x07\x02\x12\x03\n\x16\x18\n\x0b\n\x04\x05\0\x02\x08\x12\
+    \x03\x0b\x04\x15\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\x04\x0f\n\x0c\
+    \n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x12\x14\n\x0b\n\x04\x05\0\x02\t\x12\
+    \x03\x0c\x04\x15\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x0c\x04\x0f\n\x0c\n\
+    \x05\x05\0\x02\t\x02\x12\x03\x0c\x12\x14\n\x0b\n\x04\x05\0\x02\n\x12\x03\
+    \r\x04\x16\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\r\x04\x10\n\x0c\n\x05\x05\
+    \0\x02\n\x02\x12\x03\r\x13\x15\n\x0b\n\x04\x05\0\x02\x0b\x12\x03\x0e\x04\
+    \x1b\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x0e\x04\x14\n\x0c\n\x05\x05\0\
+    \x02\x0b\x02\x12\x03\x0e\x17\x1a\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x0f\
+    \x04\x18\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x0f\x04\x10\n\x0c\n\x05\
+    \x05\0\x02\x0c\x02\x12\x03\x0f\x13\x17b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 0
rust-lib/flowy-workspace/src/protobuf/proto/observable.proto

@@ -10,6 +10,8 @@ enum WorkspaceNotification {
     AppUpdated = 21;
     AppViewsChanged = 24;
     ViewUpdated = 31;
+    ViewDeleted = 32;
+    ViewRestored = 33;
     UserUnauthorized = 100;
     TrashUpdated = 1000;
 }

+ 39 - 8
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -22,6 +22,7 @@ use flowy_document::{
 
 use crate::{entities::trash::TrashType, errors::WorkspaceResult};
 
+use crate::entities::trash::TrashIdentifiers;
 use futures::{FutureExt, StreamExt};
 use std::{collections::HashSet, sync::Arc};
 
@@ -263,16 +264,26 @@ async fn handle_trash_event(
     let db_result = database.db_connection();
 
     match event {
-        TrashEvent::NewTrash(identifiers, ret) | TrashEvent::Putback(identifiers, ret) => {
+        TrashEvent::NewTrash(identifiers, ret) => {
             let result = || {
                 let conn = &*db_result?;
-                let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-                    for identifier in identifiers.items {
-                        let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
-                        let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?;
-                    }
-                    Ok(())
-                })?;
+                let view_tables = get_view_table_from(identifiers, conn)?;
+                for view_table in view_tables {
+                    let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?;
+                    notify_dart(view_table, WorkspaceNotification::ViewDeleted);
+                }
+                Ok::<(), WorkspaceError>(())
+            };
+            let _ = ret.send(result()).await;
+        },
+        TrashEvent::Putback(identifiers, ret) => {
+            let result = || {
+                let conn = &*db_result?;
+                let view_tables = get_view_table_from(identifiers, conn)?;
+                for view_table in view_tables {
+                    let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?;
+                    notify_dart(view_table, WorkspaceNotification::ViewRestored);
+                }
                 Ok::<(), WorkspaceError>(())
             };
             let _ = ret.send(result()).await;
@@ -302,6 +313,26 @@ async fn handle_trash_event(
     }
 }
 
+fn get_view_table_from(
+    identifiers: TrashIdentifiers,
+    conn: &SqliteConnection,
+) -> Result<Vec<ViewTable>, WorkspaceError> {
+    let mut view_tables = vec![];
+    let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+        for identifier in identifiers.items {
+            let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
+            view_tables.push(view_table);
+        }
+        Ok(())
+    })?;
+    Ok(view_tables)
+}
+
+fn notify_dart(view_table: ViewTable, notification: WorkspaceNotification) {
+    let view: View = view_table.into();
+    send_dart_notification(&view.id, notification).payload(view).send();
+}
+
 #[tracing::instrument(skip(belong_to_id, trash_can, conn), fields(view_count), err)]
 fn notify_views_changed(belong_to_id: &str, trash_can: Arc<TrashCan>, conn: &SqliteConnection) -> WorkspaceResult<()> {
     let repeated_view = read_local_belonging_view(belong_to_id, trash_can.clone(), conn)?;