Forráskód Böngészése

construct appflowy home screen

appflowy 4 éve
szülő
commit
02f0eef08b
86 módosított fájl, 4793 hozzáadás és 353 törlés
  1. 12 0
      .idea/appflowy_client.iml
  2. 42 4
      .idea/libraries/Dart_Packages.xml
  3. 28 0
      app_flowy/lib/home/application/edit_pannel/edit_pannel_bloc.dart
  4. 492 0
      app_flowy/lib/home/application/edit_pannel/edit_pannel_bloc.freezed.dart
  5. 9 0
      app_flowy/lib/home/application/edit_pannel/edit_pannel_event.dart
  6. 14 0
      app_flowy/lib/home/application/edit_pannel/edit_pannel_state.dart
  7. 44 0
      app_flowy/lib/home/application/menu/menu_bloc.dart
  8. 525 0
      app_flowy/lib/home/application/menu/menu_bloc.freezed.dart
  9. 8 0
      app_flowy/lib/home/application/menu/menu_event.dart
  10. 14 0
      app_flowy/lib/home/application/menu/menu_state.dart
  11. 19 0
      app_flowy/lib/home/application/watcher/home_watcher_bloc.dart
  12. 562 0
      app_flowy/lib/home/application/watcher/home_watcher_bloc.freezed.dart
  13. 7 0
      app_flowy/lib/home/application/watcher/home_watcher_event.dart
  14. 7 0
      app_flowy/lib/home/application/watcher/home_watcher_state.dart
  15. 0 5
      app_flowy/lib/home/presentation/auth_widget.dart
  16. 43 0
      app_flowy/lib/home/presentation/home_layout.dart
  17. 180 3
      app_flowy/lib/home/presentation/home_screen.dart
  18. 11 0
      app_flowy/lib/home/presentation/home_sizes.dart
  19. 67 0
      app_flowy/lib/home/presentation/widgets/edit_pannel/edit_pannel.dart
  20. 87 0
      app_flowy/lib/home/presentation/widgets/edit_pannel/pannel_animation.dart
  21. 44 0
      app_flowy/lib/home/presentation/widgets/fading_index_stack.dart
  22. 7 0
      app_flowy/lib/home/presentation/widgets/home_stack_page.dart
  23. 51 0
      app_flowy/lib/home/presentation/widgets/home_top_bar.dart
  24. 4 0
      app_flowy/lib/home/presentation/widgets/menu/hom_menu_size.dart
  25. 121 0
      app_flowy/lib/home/presentation/widgets/menu/home_menu.dart
  26. 2 0
      app_flowy/lib/home/presentation/widgets/menu/prelude.dart
  27. 1 1
      app_flowy/lib/startup/deps_inject/prelude.dart
  28. 3 3
      app_flowy/lib/startup/launch.dart
  29. 3 1
      app_flowy/lib/startup/tasks/app_widget_task.dart
  30. 3 1
      app_flowy/lib/startup/tasks/rust_sdk_init_task.dart
  31. 5 9
      app_flowy/lib/welcome/application/welcome_bloc.dart
  32. 30 126
      app_flowy/lib/welcome/application/welcome_bloc.freezed.dart
  33. 1 2
      app_flowy/lib/welcome/application/welcome_event.dart
  34. 1 1
      app_flowy/lib/welcome/application/welcome_state.dart
  35. 5 2
      app_flowy/lib/welcome/domain/auth_state.dart
  36. 198 30
      app_flowy/lib/welcome/domain/auth_state.freezed.dart
  37. 0 12
      app_flowy/lib/welcome/domain/deps.dart
  38. 13 0
      app_flowy/lib/welcome/domain/interface.dart
  39. 0 38
      app_flowy/lib/welcome/infrastructure/deps_impl.dart
  40. 61 0
      app_flowy/lib/welcome/infrastructure/interface_impl.dart
  41. 48 13
      app_flowy/lib/welcome/presentation/welcome_screen.dart
  42. 0 27
      app_flowy/lib/welcome/presentation/widgets/body.dart
  43. 4 4
      app_flowy/packages/flowy_logger/pubspec.lock
  44. 6 6
      app_flowy/packages/flowy_sdk/example/pubspec.lock
  45. 1 19
      app_flowy/packages/flowy_sdk/lib/flowy_sdk.dart
  46. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf.dart
  47. 3 3
      app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pb.dart
  48. 1 1
      app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pbenum.dart
  49. 1 1
      app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pbjson.dart
  50. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pbserver.dart
  51. 4 4
      app_flowy/packages/flowy_sdk/pubspec.lock
  52. 143 0
      app_flowy/packages/flowy_style/lib/buttons/base_styled_button.dart
  53. 40 0
      app_flowy/packages/flowy_style/lib/buttons/ok_cancel_button.dart
  54. 54 0
      app_flowy/packages/flowy_style/lib/buttons/primary_button.dart
  55. 98 0
      app_flowy/packages/flowy_style/lib/buttons/secondary_button.dart
  56. 16 0
      app_flowy/packages/flowy_style/lib/clickable_extension.dart
  57. 38 0
      app_flowy/packages/flowy_style/lib/constraint_flex_view.dart
  58. 10 0
      app_flowy/packages/flowy_style/lib/dialog/dialog_context.dart
  59. 3 0
      app_flowy/packages/flowy_style/lib/dialog/dialog_size.dart
  60. 218 0
      app_flowy/packages/flowy_style/lib/dialog/styled_dialogs.dart
  61. 35 0
      app_flowy/packages/flowy_style/lib/mouse_hover_builder.dart
  62. 33 0
      app_flowy/packages/flowy_style/lib/rounded_button.dart
  63. 35 0
      app_flowy/packages/flowy_style/lib/rounded_input_field.dart
  64. 83 0
      app_flowy/packages/flowy_style/lib/scrolling/styled_list.dart
  65. 243 0
      app_flowy/packages/flowy_style/lib/scrolling/styled_scroll_bar.dart
  66. 135 0
      app_flowy/packages/flowy_style/lib/scrolling/styled_scrollview.dart
  67. 43 0
      app_flowy/packages/flowy_style/lib/seperated_column.dart
  68. 94 0
      app_flowy/packages/flowy_style/lib/size.dart
  69. 29 0
      app_flowy/packages/flowy_style/lib/spacing.dart
  70. 60 0
      app_flowy/packages/flowy_style/lib/strings.dart
  71. 18 0
      app_flowy/packages/flowy_style/lib/styled_bar_title.dart
  72. 15 0
      app_flowy/packages/flowy_style/lib/styled_close_button.dart
  73. 44 0
      app_flowy/packages/flowy_style/lib/styled_container.dart
  74. 17 0
      app_flowy/packages/flowy_style/lib/styled_image_icon.dart
  75. 354 0
      app_flowy/packages/flowy_style/lib/styled_text_input.dart
  76. 29 0
      app_flowy/packages/flowy_style/lib/text_field_container.dart
  77. 39 4
      app_flowy/packages/flowy_style/pubspec.lock
  78. 3 0
      app_flowy/packages/flowy_style/pubspec.yaml
  79. 36 1
      app_flowy/pubspec.lock
  80. 5 0
      app_flowy/pubspec.yaml
  81. 2 2
      rust-lib/flowy-user/src/entities/mod.rs
  82. 0 0
      rust-lib/flowy-user/src/entities/user_detail.rs
  83. 3 3
      rust-lib/flowy-user/src/protobuf/model/mod.rs
  84. 20 20
      rust-lib/flowy-user/src/protobuf/model/user_detail.rs
  85. 0 1
      rust-lib/flowy-user/src/protobuf/proto/user_detail.proto
  86. 2 2
      rust-lib/flowy-user/src/sql_tables/user_table.rs

+ 12 - 0
.idea/appflowy_client.iml

@@ -66,6 +66,18 @@
       <excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_editor/example/.dart_tool" />
       <excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_editor/example/.pub" />
       <excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_editor/example/build" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_editor/example/.pub" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_editor/example/build" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_editor/example/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_editor/.pub" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_editor/build" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_editor/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/.pub" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/build" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/example/.pub" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/example/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/example/build" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />

+ 42 - 4
.idea/libraries/Dart_Packages.xml

@@ -219,6 +219,13 @@
             </list>
           </value>
         </entry>
+        <entry key="expandable">
+          <value>
+            <list>
+              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/expandable-4.1.4/lib" />
+            </list>
+          </value>
+        </entry>
         <entry key="fake_async">
           <value>
             <list>
@@ -238,7 +245,6 @@
           <value>
             <list>
               <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.2/lib" />
-              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.1/lib" />
               <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.0/lib" />
             </list>
           </value>
@@ -424,6 +430,13 @@
             </list>
           </value>
         </entry>
+        <entry key="lint">
+          <value>
+            <list>
+              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/lint-1.5.3/lib" />
+            </list>
+          </value>
+        </entry>
         <entry key="lints">
           <value>
             <list>
@@ -625,6 +638,13 @@
             </list>
           </value>
         </entry>
+        <entry key="sized_context">
+          <value>
+            <list>
+              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/sized_context-0.2.1+1/lib" />
+            </list>
+          </value>
+        </entry>
         <entry key="sky_engine">
           <value>
             <list>
@@ -681,6 +701,13 @@
             </list>
           </value>
         </entry>
+        <entry key="styled_widget">
+          <value>
+            <list>
+              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/styled_widget-0.3.1+2/lib" />
+            </list>
+          </value>
+        </entry>
         <entry key="sync_http">
           <value>
             <list>
@@ -739,6 +766,13 @@
             </list>
           </value>
         </entry>
+        <entry key="universal_platform">
+          <value>
+            <list>
+              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/universal_platform-0.1.3/lib" />
+            </list>
+          </value>
+        </entry>
         <entry key="url_launcher">
           <value>
             <list>
@@ -801,7 +835,7 @@
         <entry key="vm_service">
           <value>
             <list>
-              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-6.2.0/lib" />
+              <option value="$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-7.1.0/lib" />
             </list>
           </value>
         </entry>
@@ -903,11 +937,11 @@
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/dart_style-2.0.1/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/dartz-0.10.0-nullsafety.2/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/equatable-2.0.3/lib" />
+      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/expandable-4.1.4/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/ffi-1.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/ffi-1.1.2/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.0/lib" />
-      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.1/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.2/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/fixnum-1.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/flutter_bloc-7.0.1/lib" />
@@ -934,6 +968,7 @@
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/isolates-3.0.3+8/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/js-0.6.3/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/json_annotation-4.0.1/lib" />
+      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/lint-1.5.3/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/lints-1.0.1/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/logger-1.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/logging-1.0.1/lib" />
@@ -966,6 +1001,7 @@
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/shelf-1.1.4/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.3/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-1.0.1/lib" />
+      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/sized_context-0.2.1+1/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/source_gen-1.0.2/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib" />
@@ -973,6 +1009,7 @@
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/stream_transform-2.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/string_validator-0.3.0/lib" />
+      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/styled_widget-0.3.1+2/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/sync_http-0.3.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.3.0/lib" />
@@ -983,6 +1020,7 @@
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/timing-1.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/tuple-2.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib" />
+      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/universal_platform-0.1.3/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.3/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.9/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-2.0.0/lib" />
@@ -994,7 +1032,7 @@
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-2.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/uuid-3.0.4/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/lib" />
-      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-6.2.0/lib" />
+      <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/vm_service-7.1.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/watcher-1.0.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.2.0/lib" />
       <root url="file://$PROJECT_DIR$/../../flutter/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-2.1.0/lib" />

+ 28 - 0
app_flowy/lib/home/application/edit_pannel/edit_pannel_bloc.dart

@@ -0,0 +1,28 @@
+import 'package:app_flowy/home/domain/edit_context.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+part 'edit_pannel_event.dart';
+part 'edit_pannel_state.dart';
+part 'edit_pannel_bloc.freezed.dart';
+
+class EditPannelBloc extends Bloc<EditPannelEvent, EditPannelState> {
+  EditPannelBloc() : super(EditPannelState.initial());
+
+  @override
+  Stream<EditPannelState> mapEventToState(
+    EditPannelEvent event,
+  ) async* {
+    yield* event.map(
+      startEdit: (e) async* {
+        yield state.copyWith(isEditing: true, editContext: some(e.context));
+      },
+      endEdit: (value) async* {
+        yield state.copyWith(isEditing: false, editContext: none());
+      },
+    );
+  }
+}

+ 492 - 0
app_flowy/lib/home/application/edit_pannel/edit_pannel_bloc.freezed.dart

@@ -0,0 +1,492 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'edit_pannel_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$EditPannelEventTearOff {
+  const _$EditPannelEventTearOff();
+
+  _StartEdit startEdit(EditPannelContext context) {
+    return _StartEdit(
+      context,
+    );
+  }
+
+  _EndEdit endEdit(EditPannelContext context) {
+    return _EndEdit(
+      context,
+    );
+  }
+}
+
+/// @nodoc
+const $EditPannelEvent = _$EditPannelEventTearOff();
+
+/// @nodoc
+mixin _$EditPannelEvent {
+  EditPannelContext get context => throw _privateConstructorUsedError;
+
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function(EditPannelContext context) startEdit,
+    required TResult Function(EditPannelContext context) endEdit,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function(EditPannelContext context)? startEdit,
+    TResult Function(EditPannelContext context)? endEdit,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_StartEdit value) startEdit,
+    required TResult Function(_EndEdit value) endEdit,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_StartEdit value)? startEdit,
+    TResult Function(_EndEdit value)? endEdit,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $EditPannelEventCopyWith<EditPannelEvent> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $EditPannelEventCopyWith<$Res> {
+  factory $EditPannelEventCopyWith(
+          EditPannelEvent value, $Res Function(EditPannelEvent) then) =
+      _$EditPannelEventCopyWithImpl<$Res>;
+  $Res call({EditPannelContext context});
+}
+
+/// @nodoc
+class _$EditPannelEventCopyWithImpl<$Res>
+    implements $EditPannelEventCopyWith<$Res> {
+  _$EditPannelEventCopyWithImpl(this._value, this._then);
+
+  final EditPannelEvent _value;
+  // ignore: unused_field
+  final $Res Function(EditPannelEvent) _then;
+
+  @override
+  $Res call({
+    Object? context = freezed,
+  }) {
+    return _then(_value.copyWith(
+      context: context == freezed
+          ? _value.context
+          : context // ignore: cast_nullable_to_non_nullable
+              as EditPannelContext,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$StartEditCopyWith<$Res>
+    implements $EditPannelEventCopyWith<$Res> {
+  factory _$StartEditCopyWith(
+          _StartEdit value, $Res Function(_StartEdit) then) =
+      __$StartEditCopyWithImpl<$Res>;
+  @override
+  $Res call({EditPannelContext context});
+}
+
+/// @nodoc
+class __$StartEditCopyWithImpl<$Res> extends _$EditPannelEventCopyWithImpl<$Res>
+    implements _$StartEditCopyWith<$Res> {
+  __$StartEditCopyWithImpl(_StartEdit _value, $Res Function(_StartEdit) _then)
+      : super(_value, (v) => _then(v as _StartEdit));
+
+  @override
+  _StartEdit get _value => super._value as _StartEdit;
+
+  @override
+  $Res call({
+    Object? context = freezed,
+  }) {
+    return _then(_StartEdit(
+      context == freezed
+          ? _value.context
+          : context // ignore: cast_nullable_to_non_nullable
+              as EditPannelContext,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_StartEdit implements _StartEdit {
+  const _$_StartEdit(this.context);
+
+  @override
+  final EditPannelContext context;
+
+  @override
+  String toString() {
+    return 'EditPannelEvent.startEdit(context: $context)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _StartEdit &&
+            (identical(other.context, context) ||
+                const DeepCollectionEquality().equals(other.context, context)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(context);
+
+  @JsonKey(ignore: true)
+  @override
+  _$StartEditCopyWith<_StartEdit> get copyWith =>
+      __$StartEditCopyWithImpl<_StartEdit>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function(EditPannelContext context) startEdit,
+    required TResult Function(EditPannelContext context) endEdit,
+  }) {
+    return startEdit(context);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function(EditPannelContext context)? startEdit,
+    TResult Function(EditPannelContext context)? endEdit,
+    required TResult orElse(),
+  }) {
+    if (startEdit != null) {
+      return startEdit(context);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_StartEdit value) startEdit,
+    required TResult Function(_EndEdit value) endEdit,
+  }) {
+    return startEdit(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_StartEdit value)? startEdit,
+    TResult Function(_EndEdit value)? endEdit,
+    required TResult orElse(),
+  }) {
+    if (startEdit != null) {
+      return startEdit(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _StartEdit implements EditPannelEvent {
+  const factory _StartEdit(EditPannelContext context) = _$_StartEdit;
+
+  @override
+  EditPannelContext get context => throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$StartEditCopyWith<_StartEdit> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class _$EndEditCopyWith<$Res>
+    implements $EditPannelEventCopyWith<$Res> {
+  factory _$EndEditCopyWith(_EndEdit value, $Res Function(_EndEdit) then) =
+      __$EndEditCopyWithImpl<$Res>;
+  @override
+  $Res call({EditPannelContext context});
+}
+
+/// @nodoc
+class __$EndEditCopyWithImpl<$Res> extends _$EditPannelEventCopyWithImpl<$Res>
+    implements _$EndEditCopyWith<$Res> {
+  __$EndEditCopyWithImpl(_EndEdit _value, $Res Function(_EndEdit) _then)
+      : super(_value, (v) => _then(v as _EndEdit));
+
+  @override
+  _EndEdit get _value => super._value as _EndEdit;
+
+  @override
+  $Res call({
+    Object? context = freezed,
+  }) {
+    return _then(_EndEdit(
+      context == freezed
+          ? _value.context
+          : context // ignore: cast_nullable_to_non_nullable
+              as EditPannelContext,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_EndEdit implements _EndEdit {
+  const _$_EndEdit(this.context);
+
+  @override
+  final EditPannelContext context;
+
+  @override
+  String toString() {
+    return 'EditPannelEvent.endEdit(context: $context)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _EndEdit &&
+            (identical(other.context, context) ||
+                const DeepCollectionEquality().equals(other.context, context)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(context);
+
+  @JsonKey(ignore: true)
+  @override
+  _$EndEditCopyWith<_EndEdit> get copyWith =>
+      __$EndEditCopyWithImpl<_EndEdit>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function(EditPannelContext context) startEdit,
+    required TResult Function(EditPannelContext context) endEdit,
+  }) {
+    return endEdit(context);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function(EditPannelContext context)? startEdit,
+    TResult Function(EditPannelContext context)? endEdit,
+    required TResult orElse(),
+  }) {
+    if (endEdit != null) {
+      return endEdit(context);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_StartEdit value) startEdit,
+    required TResult Function(_EndEdit value) endEdit,
+  }) {
+    return endEdit(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_StartEdit value)? startEdit,
+    TResult Function(_EndEdit value)? endEdit,
+    required TResult orElse(),
+  }) {
+    if (endEdit != null) {
+      return endEdit(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _EndEdit implements EditPannelEvent {
+  const factory _EndEdit(EditPannelContext context) = _$_EndEdit;
+
+  @override
+  EditPannelContext get context => throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$EndEditCopyWith<_EndEdit> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+class _$EditPannelStateTearOff {
+  const _$EditPannelStateTearOff();
+
+  _EditPannelState call(
+      {required bool isEditing,
+      required Option<EditPannelContext> editContext}) {
+    return _EditPannelState(
+      isEditing: isEditing,
+      editContext: editContext,
+    );
+  }
+}
+
+/// @nodoc
+const $EditPannelState = _$EditPannelStateTearOff();
+
+/// @nodoc
+mixin _$EditPannelState {
+  bool get isEditing => throw _privateConstructorUsedError;
+  Option<EditPannelContext> get editContext =>
+      throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $EditPannelStateCopyWith<EditPannelState> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $EditPannelStateCopyWith<$Res> {
+  factory $EditPannelStateCopyWith(
+          EditPannelState value, $Res Function(EditPannelState) then) =
+      _$EditPannelStateCopyWithImpl<$Res>;
+  $Res call({bool isEditing, Option<EditPannelContext> editContext});
+}
+
+/// @nodoc
+class _$EditPannelStateCopyWithImpl<$Res>
+    implements $EditPannelStateCopyWith<$Res> {
+  _$EditPannelStateCopyWithImpl(this._value, this._then);
+
+  final EditPannelState _value;
+  // ignore: unused_field
+  final $Res Function(EditPannelState) _then;
+
+  @override
+  $Res call({
+    Object? isEditing = freezed,
+    Object? editContext = freezed,
+  }) {
+    return _then(_value.copyWith(
+      isEditing: isEditing == freezed
+          ? _value.isEditing
+          : isEditing // ignore: cast_nullable_to_non_nullable
+              as bool,
+      editContext: editContext == freezed
+          ? _value.editContext
+          : editContext // ignore: cast_nullable_to_non_nullable
+              as Option<EditPannelContext>,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$EditPannelStateCopyWith<$Res>
+    implements $EditPannelStateCopyWith<$Res> {
+  factory _$EditPannelStateCopyWith(
+          _EditPannelState value, $Res Function(_EditPannelState) then) =
+      __$EditPannelStateCopyWithImpl<$Res>;
+  @override
+  $Res call({bool isEditing, Option<EditPannelContext> editContext});
+}
+
+/// @nodoc
+class __$EditPannelStateCopyWithImpl<$Res>
+    extends _$EditPannelStateCopyWithImpl<$Res>
+    implements _$EditPannelStateCopyWith<$Res> {
+  __$EditPannelStateCopyWithImpl(
+      _EditPannelState _value, $Res Function(_EditPannelState) _then)
+      : super(_value, (v) => _then(v as _EditPannelState));
+
+  @override
+  _EditPannelState get _value => super._value as _EditPannelState;
+
+  @override
+  $Res call({
+    Object? isEditing = freezed,
+    Object? editContext = freezed,
+  }) {
+    return _then(_EditPannelState(
+      isEditing: isEditing == freezed
+          ? _value.isEditing
+          : isEditing // ignore: cast_nullable_to_non_nullable
+              as bool,
+      editContext: editContext == freezed
+          ? _value.editContext
+          : editContext // ignore: cast_nullable_to_non_nullable
+              as Option<EditPannelContext>,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_EditPannelState implements _EditPannelState {
+  const _$_EditPannelState(
+      {required this.isEditing, required this.editContext});
+
+  @override
+  final bool isEditing;
+  @override
+  final Option<EditPannelContext> editContext;
+
+  @override
+  String toString() {
+    return 'EditPannelState(isEditing: $isEditing, editContext: $editContext)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _EditPannelState &&
+            (identical(other.isEditing, isEditing) ||
+                const DeepCollectionEquality()
+                    .equals(other.isEditing, isEditing)) &&
+            (identical(other.editContext, editContext) ||
+                const DeepCollectionEquality()
+                    .equals(other.editContext, editContext)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^
+      const DeepCollectionEquality().hash(isEditing) ^
+      const DeepCollectionEquality().hash(editContext);
+
+  @JsonKey(ignore: true)
+  @override
+  _$EditPannelStateCopyWith<_EditPannelState> get copyWith =>
+      __$EditPannelStateCopyWithImpl<_EditPannelState>(this, _$identity);
+}
+
+abstract class _EditPannelState implements EditPannelState {
+  const factory _EditPannelState(
+      {required bool isEditing,
+      required Option<EditPannelContext> editContext}) = _$_EditPannelState;
+
+  @override
+  bool get isEditing => throw _privateConstructorUsedError;
+  @override
+  Option<EditPannelContext> get editContext =>
+      throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$EditPannelStateCopyWith<_EditPannelState> get copyWith =>
+      throw _privateConstructorUsedError;
+}

+ 9 - 0
app_flowy/lib/home/application/edit_pannel/edit_pannel_event.dart

@@ -0,0 +1,9 @@
+part of 'edit_pannel_bloc.dart';
+
+@freezed
+abstract class EditPannelEvent with _$EditPannelEvent {
+  const factory EditPannelEvent.startEdit(EditPannelContext context) =
+      _StartEdit;
+
+  const factory EditPannelEvent.endEdit(EditPannelContext context) = _EndEdit;
+}

+ 14 - 0
app_flowy/lib/home/application/edit_pannel/edit_pannel_state.dart

@@ -0,0 +1,14 @@
+part of 'edit_pannel_bloc.dart';
+
+@freezed
+abstract class EditPannelState implements _$EditPannelState {
+  const factory EditPannelState({
+    required bool isEditing,
+    required Option<EditPannelContext> editContext,
+  }) = _EditPannelState;
+
+  factory EditPannelState.initial() => EditPannelState(
+        isEditing: false,
+        editContext: none(),
+      );
+}

+ 44 - 0
app_flowy/lib/home/application/menu/menu_bloc.dart

@@ -0,0 +1,44 @@
+import 'dart:async';
+import 'package:app_flowy/home/domain/page_context.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+part 'menu_event.dart';
+part 'menu_state.dart';
+part 'menu_bloc.freezed.dart';
+
+class MenuBloc extends Bloc<MenuEvent, MenuState> {
+  MenuBloc() : super(MenuState.initial());
+
+  @override
+  Stream<MenuState> mapEventToState(
+    MenuEvent event,
+  ) async* {
+    yield* event.map(
+      collapse: (e) async* {
+        final isCollapse = state.isCollapse;
+        yield state.copyWith(isCollapse: !isCollapse);
+      },
+      openPage: (e) async* {
+        yield* _performActionOnOpenPage(e);
+      },
+      createApp: (e) async* {
+        yield* _performActionOnCreateApp(e);
+      },
+    );
+  }
+
+  Stream<MenuState> _performActionOnOpenPage(_OpenPage e) async* {
+    yield state.copyWith(pageContext: some(e.context));
+  }
+
+  Stream<MenuState> _performActionOnCreateApp(_CreateApp e) async* {
+    yield state;
+  }
+
+  @override
+  Future<void> close() {
+    return super.close();
+  }
+}

+ 525 - 0
app_flowy/lib/home/application/menu/menu_bloc.freezed.dart

@@ -0,0 +1,525 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'menu_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$MenuEventTearOff {
+  const _$MenuEventTearOff();
+
+  Collapse collapse() {
+    return const Collapse();
+  }
+
+  _OpenPage openPage(PageContext context) {
+    return _OpenPage(
+      context,
+    );
+  }
+
+  _CreateApp createApp() {
+    return const _CreateApp();
+  }
+}
+
+/// @nodoc
+const $MenuEvent = _$MenuEventTearOff();
+
+/// @nodoc
+mixin _$MenuEvent {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() collapse,
+    required TResult Function(PageContext context) openPage,
+    required TResult Function() createApp,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? collapse,
+    TResult Function(PageContext context)? openPage,
+    TResult Function()? createApp,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Collapse value) collapse,
+    required TResult Function(_OpenPage value) openPage,
+    required TResult Function(_CreateApp value) createApp,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Collapse value)? collapse,
+    TResult Function(_OpenPage value)? openPage,
+    TResult Function(_CreateApp value)? createApp,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $MenuEventCopyWith<$Res> {
+  factory $MenuEventCopyWith(MenuEvent value, $Res Function(MenuEvent) then) =
+      _$MenuEventCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$MenuEventCopyWithImpl<$Res> implements $MenuEventCopyWith<$Res> {
+  _$MenuEventCopyWithImpl(this._value, this._then);
+
+  final MenuEvent _value;
+  // ignore: unused_field
+  final $Res Function(MenuEvent) _then;
+}
+
+/// @nodoc
+abstract class $CollapseCopyWith<$Res> {
+  factory $CollapseCopyWith(Collapse value, $Res Function(Collapse) then) =
+      _$CollapseCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$CollapseCopyWithImpl<$Res> extends _$MenuEventCopyWithImpl<$Res>
+    implements $CollapseCopyWith<$Res> {
+  _$CollapseCopyWithImpl(Collapse _value, $Res Function(Collapse) _then)
+      : super(_value, (v) => _then(v as Collapse));
+
+  @override
+  Collapse get _value => super._value as Collapse;
+}
+
+/// @nodoc
+
+class _$Collapse implements Collapse {
+  const _$Collapse();
+
+  @override
+  String toString() {
+    return 'MenuEvent.collapse()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is Collapse);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() collapse,
+    required TResult Function(PageContext context) openPage,
+    required TResult Function() createApp,
+  }) {
+    return collapse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? collapse,
+    TResult Function(PageContext context)? openPage,
+    TResult Function()? createApp,
+    required TResult orElse(),
+  }) {
+    if (collapse != null) {
+      return collapse();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Collapse value) collapse,
+    required TResult Function(_OpenPage value) openPage,
+    required TResult Function(_CreateApp value) createApp,
+  }) {
+    return collapse(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Collapse value)? collapse,
+    TResult Function(_OpenPage value)? openPage,
+    TResult Function(_CreateApp value)? createApp,
+    required TResult orElse(),
+  }) {
+    if (collapse != null) {
+      return collapse(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class Collapse implements MenuEvent {
+  const factory Collapse() = _$Collapse;
+}
+
+/// @nodoc
+abstract class _$OpenPageCopyWith<$Res> {
+  factory _$OpenPageCopyWith(_OpenPage value, $Res Function(_OpenPage) then) =
+      __$OpenPageCopyWithImpl<$Res>;
+  $Res call({PageContext context});
+}
+
+/// @nodoc
+class __$OpenPageCopyWithImpl<$Res> extends _$MenuEventCopyWithImpl<$Res>
+    implements _$OpenPageCopyWith<$Res> {
+  __$OpenPageCopyWithImpl(_OpenPage _value, $Res Function(_OpenPage) _then)
+      : super(_value, (v) => _then(v as _OpenPage));
+
+  @override
+  _OpenPage get _value => super._value as _OpenPage;
+
+  @override
+  $Res call({
+    Object? context = freezed,
+  }) {
+    return _then(_OpenPage(
+      context == freezed
+          ? _value.context
+          : context // ignore: cast_nullable_to_non_nullable
+              as PageContext,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_OpenPage implements _OpenPage {
+  const _$_OpenPage(this.context);
+
+  @override
+  final PageContext context;
+
+  @override
+  String toString() {
+    return 'MenuEvent.openPage(context: $context)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _OpenPage &&
+            (identical(other.context, context) ||
+                const DeepCollectionEquality().equals(other.context, context)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(context);
+
+  @JsonKey(ignore: true)
+  @override
+  _$OpenPageCopyWith<_OpenPage> get copyWith =>
+      __$OpenPageCopyWithImpl<_OpenPage>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() collapse,
+    required TResult Function(PageContext context) openPage,
+    required TResult Function() createApp,
+  }) {
+    return openPage(context);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? collapse,
+    TResult Function(PageContext context)? openPage,
+    TResult Function()? createApp,
+    required TResult orElse(),
+  }) {
+    if (openPage != null) {
+      return openPage(context);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Collapse value) collapse,
+    required TResult Function(_OpenPage value) openPage,
+    required TResult Function(_CreateApp value) createApp,
+  }) {
+    return openPage(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Collapse value)? collapse,
+    TResult Function(_OpenPage value)? openPage,
+    TResult Function(_CreateApp value)? createApp,
+    required TResult orElse(),
+  }) {
+    if (openPage != null) {
+      return openPage(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _OpenPage implements MenuEvent {
+  const factory _OpenPage(PageContext context) = _$_OpenPage;
+
+  PageContext get context => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  _$OpenPageCopyWith<_OpenPage> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class _$CreateAppCopyWith<$Res> {
+  factory _$CreateAppCopyWith(
+          _CreateApp value, $Res Function(_CreateApp) then) =
+      __$CreateAppCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$CreateAppCopyWithImpl<$Res> extends _$MenuEventCopyWithImpl<$Res>
+    implements _$CreateAppCopyWith<$Res> {
+  __$CreateAppCopyWithImpl(_CreateApp _value, $Res Function(_CreateApp) _then)
+      : super(_value, (v) => _then(v as _CreateApp));
+
+  @override
+  _CreateApp get _value => super._value as _CreateApp;
+}
+
+/// @nodoc
+
+class _$_CreateApp implements _CreateApp {
+  const _$_CreateApp();
+
+  @override
+  String toString() {
+    return 'MenuEvent.createApp()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _CreateApp);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() collapse,
+    required TResult Function(PageContext context) openPage,
+    required TResult Function() createApp,
+  }) {
+    return createApp();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? collapse,
+    TResult Function(PageContext context)? openPage,
+    TResult Function()? createApp,
+    required TResult orElse(),
+  }) {
+    if (createApp != null) {
+      return createApp();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Collapse value) collapse,
+    required TResult Function(_OpenPage value) openPage,
+    required TResult Function(_CreateApp value) createApp,
+  }) {
+    return createApp(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Collapse value)? collapse,
+    TResult Function(_OpenPage value)? openPage,
+    TResult Function(_CreateApp value)? createApp,
+    required TResult orElse(),
+  }) {
+    if (createApp != null) {
+      return createApp(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _CreateApp implements MenuEvent {
+  const factory _CreateApp() = _$_CreateApp;
+}
+
+/// @nodoc
+class _$MenuStateTearOff {
+  const _$MenuStateTearOff();
+
+  _MenuState call(
+      {required bool isCollapse, required Option<PageContext> pageContext}) {
+    return _MenuState(
+      isCollapse: isCollapse,
+      pageContext: pageContext,
+    );
+  }
+}
+
+/// @nodoc
+const $MenuState = _$MenuStateTearOff();
+
+/// @nodoc
+mixin _$MenuState {
+  bool get isCollapse => throw _privateConstructorUsedError;
+  Option<PageContext> get pageContext => throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $MenuStateCopyWith<MenuState> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $MenuStateCopyWith<$Res> {
+  factory $MenuStateCopyWith(MenuState value, $Res Function(MenuState) then) =
+      _$MenuStateCopyWithImpl<$Res>;
+  $Res call({bool isCollapse, Option<PageContext> pageContext});
+}
+
+/// @nodoc
+class _$MenuStateCopyWithImpl<$Res> implements $MenuStateCopyWith<$Res> {
+  _$MenuStateCopyWithImpl(this._value, this._then);
+
+  final MenuState _value;
+  // ignore: unused_field
+  final $Res Function(MenuState) _then;
+
+  @override
+  $Res call({
+    Object? isCollapse = freezed,
+    Object? pageContext = freezed,
+  }) {
+    return _then(_value.copyWith(
+      isCollapse: isCollapse == freezed
+          ? _value.isCollapse
+          : isCollapse // ignore: cast_nullable_to_non_nullable
+              as bool,
+      pageContext: pageContext == freezed
+          ? _value.pageContext
+          : pageContext // ignore: cast_nullable_to_non_nullable
+              as Option<PageContext>,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$MenuStateCopyWith<$Res> implements $MenuStateCopyWith<$Res> {
+  factory _$MenuStateCopyWith(
+          _MenuState value, $Res Function(_MenuState) then) =
+      __$MenuStateCopyWithImpl<$Res>;
+  @override
+  $Res call({bool isCollapse, Option<PageContext> pageContext});
+}
+
+/// @nodoc
+class __$MenuStateCopyWithImpl<$Res> extends _$MenuStateCopyWithImpl<$Res>
+    implements _$MenuStateCopyWith<$Res> {
+  __$MenuStateCopyWithImpl(_MenuState _value, $Res Function(_MenuState) _then)
+      : super(_value, (v) => _then(v as _MenuState));
+
+  @override
+  _MenuState get _value => super._value as _MenuState;
+
+  @override
+  $Res call({
+    Object? isCollapse = freezed,
+    Object? pageContext = freezed,
+  }) {
+    return _then(_MenuState(
+      isCollapse: isCollapse == freezed
+          ? _value.isCollapse
+          : isCollapse // ignore: cast_nullable_to_non_nullable
+              as bool,
+      pageContext: pageContext == freezed
+          ? _value.pageContext
+          : pageContext // ignore: cast_nullable_to_non_nullable
+              as Option<PageContext>,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_MenuState implements _MenuState {
+  const _$_MenuState({required this.isCollapse, required this.pageContext});
+
+  @override
+  final bool isCollapse;
+  @override
+  final Option<PageContext> pageContext;
+
+  @override
+  String toString() {
+    return 'MenuState(isCollapse: $isCollapse, pageContext: $pageContext)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _MenuState &&
+            (identical(other.isCollapse, isCollapse) ||
+                const DeepCollectionEquality()
+                    .equals(other.isCollapse, isCollapse)) &&
+            (identical(other.pageContext, pageContext) ||
+                const DeepCollectionEquality()
+                    .equals(other.pageContext, pageContext)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^
+      const DeepCollectionEquality().hash(isCollapse) ^
+      const DeepCollectionEquality().hash(pageContext);
+
+  @JsonKey(ignore: true)
+  @override
+  _$MenuStateCopyWith<_MenuState> get copyWith =>
+      __$MenuStateCopyWithImpl<_MenuState>(this, _$identity);
+}
+
+abstract class _MenuState implements MenuState {
+  const factory _MenuState(
+      {required bool isCollapse,
+      required Option<PageContext> pageContext}) = _$_MenuState;
+
+  @override
+  bool get isCollapse => throw _privateConstructorUsedError;
+  @override
+  Option<PageContext> get pageContext => throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$MenuStateCopyWith<_MenuState> get copyWith =>
+      throw _privateConstructorUsedError;
+}

+ 8 - 0
app_flowy/lib/home/application/menu/menu_event.dart

@@ -0,0 +1,8 @@
+part of 'menu_bloc.dart';
+
+@freezed
+abstract class MenuEvent with _$MenuEvent {
+  const factory MenuEvent.collapse() = Collapse;
+  const factory MenuEvent.openPage(PageContext context) = _OpenPage;
+  const factory MenuEvent.createApp() = _CreateApp;
+}

+ 14 - 0
app_flowy/lib/home/application/menu/menu_state.dart

@@ -0,0 +1,14 @@
+part of 'menu_bloc.dart';
+
+@freezed
+abstract class MenuState implements _$MenuState {
+  const factory MenuState({
+    required bool isCollapse,
+    required Option<PageContext> pageContext,
+  }) = _MenuState;
+
+  factory MenuState.initial() => MenuState(
+        isCollapse: false,
+        pageContext: none(),
+      );
+}

+ 19 - 0
app_flowy/lib/home/application/watcher/home_watcher_bloc.dart

@@ -0,0 +1,19 @@
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+part 'home_watcher_event.dart';
+part 'home_watcher_state.dart';
+part 'home_watcher_bloc.freezed.dart';
+
+class HomeWatcherBloc extends Bloc<HomeWatcherEvent, HomeWatcherState> {
+  HomeWatcherBloc() : super(const HomeWatcherState.initial());
+
+  @override
+  Stream<HomeWatcherState> mapEventToState(
+    HomeWatcherEvent event,
+  ) async* {
+    yield state;
+  }
+}

+ 562 - 0
app_flowy/lib/home/application/watcher/home_watcher_bloc.freezed.dart

@@ -0,0 +1,562 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'home_watcher_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$HomeWatcherEventTearOff {
+  const _$HomeWatcherEventTearOff();
+
+  _Started started(String workspaceId) {
+    return _Started(
+      workspaceId,
+    );
+  }
+
+  _Stop stop(String workspaceId) {
+    return _Stop(
+      workspaceId,
+    );
+  }
+}
+
+/// @nodoc
+const $HomeWatcherEvent = _$HomeWatcherEventTearOff();
+
+/// @nodoc
+mixin _$HomeWatcherEvent {
+  String get workspaceId => throw _privateConstructorUsedError;
+
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function(String workspaceId) started,
+    required TResult Function(String workspaceId) stop,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function(String workspaceId)? started,
+    TResult Function(String workspaceId)? stop,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Started value) started,
+    required TResult Function(_Stop value) stop,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Started value)? started,
+    TResult Function(_Stop value)? stop,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $HomeWatcherEventCopyWith<HomeWatcherEvent> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $HomeWatcherEventCopyWith<$Res> {
+  factory $HomeWatcherEventCopyWith(
+          HomeWatcherEvent value, $Res Function(HomeWatcherEvent) then) =
+      _$HomeWatcherEventCopyWithImpl<$Res>;
+  $Res call({String workspaceId});
+}
+
+/// @nodoc
+class _$HomeWatcherEventCopyWithImpl<$Res>
+    implements $HomeWatcherEventCopyWith<$Res> {
+  _$HomeWatcherEventCopyWithImpl(this._value, this._then);
+
+  final HomeWatcherEvent _value;
+  // ignore: unused_field
+  final $Res Function(HomeWatcherEvent) _then;
+
+  @override
+  $Res call({
+    Object? workspaceId = freezed,
+  }) {
+    return _then(_value.copyWith(
+      workspaceId: workspaceId == freezed
+          ? _value.workspaceId
+          : workspaceId // ignore: cast_nullable_to_non_nullable
+              as String,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$StartedCopyWith<$Res>
+    implements $HomeWatcherEventCopyWith<$Res> {
+  factory _$StartedCopyWith(_Started value, $Res Function(_Started) then) =
+      __$StartedCopyWithImpl<$Res>;
+  @override
+  $Res call({String workspaceId});
+}
+
+/// @nodoc
+class __$StartedCopyWithImpl<$Res> extends _$HomeWatcherEventCopyWithImpl<$Res>
+    implements _$StartedCopyWith<$Res> {
+  __$StartedCopyWithImpl(_Started _value, $Res Function(_Started) _then)
+      : super(_value, (v) => _then(v as _Started));
+
+  @override
+  _Started get _value => super._value as _Started;
+
+  @override
+  $Res call({
+    Object? workspaceId = freezed,
+  }) {
+    return _then(_Started(
+      workspaceId == freezed
+          ? _value.workspaceId
+          : workspaceId // ignore: cast_nullable_to_non_nullable
+              as String,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_Started implements _Started {
+  const _$_Started(this.workspaceId);
+
+  @override
+  final String workspaceId;
+
+  @override
+  String toString() {
+    return 'HomeWatcherEvent.started(workspaceId: $workspaceId)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _Started &&
+            (identical(other.workspaceId, workspaceId) ||
+                const DeepCollectionEquality()
+                    .equals(other.workspaceId, workspaceId)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(workspaceId);
+
+  @JsonKey(ignore: true)
+  @override
+  _$StartedCopyWith<_Started> get copyWith =>
+      __$StartedCopyWithImpl<_Started>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function(String workspaceId) started,
+    required TResult Function(String workspaceId) stop,
+  }) {
+    return started(workspaceId);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function(String workspaceId)? started,
+    TResult Function(String workspaceId)? stop,
+    required TResult orElse(),
+  }) {
+    if (started != null) {
+      return started(workspaceId);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Started value) started,
+    required TResult Function(_Stop value) stop,
+  }) {
+    return started(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Started value)? started,
+    TResult Function(_Stop value)? stop,
+    required TResult orElse(),
+  }) {
+    if (started != null) {
+      return started(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _Started implements HomeWatcherEvent {
+  const factory _Started(String workspaceId) = _$_Started;
+
+  @override
+  String get workspaceId => throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$StartedCopyWith<_Started> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class _$StopCopyWith<$Res> implements $HomeWatcherEventCopyWith<$Res> {
+  factory _$StopCopyWith(_Stop value, $Res Function(_Stop) then) =
+      __$StopCopyWithImpl<$Res>;
+  @override
+  $Res call({String workspaceId});
+}
+
+/// @nodoc
+class __$StopCopyWithImpl<$Res> extends _$HomeWatcherEventCopyWithImpl<$Res>
+    implements _$StopCopyWith<$Res> {
+  __$StopCopyWithImpl(_Stop _value, $Res Function(_Stop) _then)
+      : super(_value, (v) => _then(v as _Stop));
+
+  @override
+  _Stop get _value => super._value as _Stop;
+
+  @override
+  $Res call({
+    Object? workspaceId = freezed,
+  }) {
+    return _then(_Stop(
+      workspaceId == freezed
+          ? _value.workspaceId
+          : workspaceId // ignore: cast_nullable_to_non_nullable
+              as String,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_Stop implements _Stop {
+  const _$_Stop(this.workspaceId);
+
+  @override
+  final String workspaceId;
+
+  @override
+  String toString() {
+    return 'HomeWatcherEvent.stop(workspaceId: $workspaceId)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _Stop &&
+            (identical(other.workspaceId, workspaceId) ||
+                const DeepCollectionEquality()
+                    .equals(other.workspaceId, workspaceId)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(workspaceId);
+
+  @JsonKey(ignore: true)
+  @override
+  _$StopCopyWith<_Stop> get copyWith =>
+      __$StopCopyWithImpl<_Stop>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function(String workspaceId) started,
+    required TResult Function(String workspaceId) stop,
+  }) {
+    return stop(workspaceId);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function(String workspaceId)? started,
+    TResult Function(String workspaceId)? stop,
+    required TResult orElse(),
+  }) {
+    if (stop != null) {
+      return stop(workspaceId);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Started value) started,
+    required TResult Function(_Stop value) stop,
+  }) {
+    return stop(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Started value)? started,
+    TResult Function(_Stop value)? stop,
+    required TResult orElse(),
+  }) {
+    if (stop != null) {
+      return stop(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _Stop implements HomeWatcherEvent {
+  const factory _Stop(String workspaceId) = _$_Stop;
+
+  @override
+  String get workspaceId => throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$StopCopyWith<_Stop> get copyWith => throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+class _$HomeWatcherStateTearOff {
+  const _$HomeWatcherStateTearOff();
+
+  _Initial initial() {
+    return const _Initial();
+  }
+
+  _Loading loading() {
+    return const _Loading();
+  }
+}
+
+/// @nodoc
+const $HomeWatcherState = _$HomeWatcherStateTearOff();
+
+/// @nodoc
+mixin _$HomeWatcherState {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() loading,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? loading,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_Loading value) loading,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_Loading value)? loading,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $HomeWatcherStateCopyWith<$Res> {
+  factory $HomeWatcherStateCopyWith(
+          HomeWatcherState value, $Res Function(HomeWatcherState) then) =
+      _$HomeWatcherStateCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$HomeWatcherStateCopyWithImpl<$Res>
+    implements $HomeWatcherStateCopyWith<$Res> {
+  _$HomeWatcherStateCopyWithImpl(this._value, this._then);
+
+  final HomeWatcherState _value;
+  // ignore: unused_field
+  final $Res Function(HomeWatcherState) _then;
+}
+
+/// @nodoc
+abstract class _$InitialCopyWith<$Res> {
+  factory _$InitialCopyWith(_Initial value, $Res Function(_Initial) then) =
+      __$InitialCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$InitialCopyWithImpl<$Res> extends _$HomeWatcherStateCopyWithImpl<$Res>
+    implements _$InitialCopyWith<$Res> {
+  __$InitialCopyWithImpl(_Initial _value, $Res Function(_Initial) _then)
+      : super(_value, (v) => _then(v as _Initial));
+
+  @override
+  _Initial get _value => super._value as _Initial;
+}
+
+/// @nodoc
+
+class _$_Initial implements _Initial {
+  const _$_Initial();
+
+  @override
+  String toString() {
+    return 'HomeWatcherState.initial()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _Initial);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() loading,
+  }) {
+    return initial();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? loading,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_Loading value) loading,
+  }) {
+    return initial(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_Loading value)? loading,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _Initial implements HomeWatcherState {
+  const factory _Initial() = _$_Initial;
+}
+
+/// @nodoc
+abstract class _$LoadingCopyWith<$Res> {
+  factory _$LoadingCopyWith(_Loading value, $Res Function(_Loading) then) =
+      __$LoadingCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$LoadingCopyWithImpl<$Res> extends _$HomeWatcherStateCopyWithImpl<$Res>
+    implements _$LoadingCopyWith<$Res> {
+  __$LoadingCopyWithImpl(_Loading _value, $Res Function(_Loading) _then)
+      : super(_value, (v) => _then(v as _Loading));
+
+  @override
+  _Loading get _value => super._value as _Loading;
+}
+
+/// @nodoc
+
+class _$_Loading implements _Loading {
+  const _$_Loading();
+
+  @override
+  String toString() {
+    return 'HomeWatcherState.loading()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _Loading);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() loading,
+  }) {
+    return loading();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? loading,
+    required TResult orElse(),
+  }) {
+    if (loading != null) {
+      return loading();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_Loading value) loading,
+  }) {
+    return loading(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_Loading value)? loading,
+    required TResult orElse(),
+  }) {
+    if (loading != null) {
+      return loading(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _Loading implements HomeWatcherState {
+  const factory _Loading() = _$_Loading;
+}

+ 7 - 0
app_flowy/lib/home/application/watcher/home_watcher_event.dart

@@ -0,0 +1,7 @@
+part of 'home_watcher_bloc.dart';
+
+@freezed
+abstract class HomeWatcherEvent with _$HomeWatcherEvent {
+  const factory HomeWatcherEvent.started(String workspaceId) = _Started;
+  const factory HomeWatcherEvent.stop(String workspaceId) = _Stop;
+}

+ 7 - 0
app_flowy/lib/home/application/watcher/home_watcher_state.dart

@@ -0,0 +1,7 @@
+part of 'home_watcher_bloc.dart';
+
+@freezed
+abstract class HomeWatcherState with _$HomeWatcherState {
+  const factory HomeWatcherState.initial() = _Initial;
+  const factory HomeWatcherState.loading() = _Loading;
+}

+ 0 - 5
app_flowy/lib/home/presentation/auth_widget.dart

@@ -1,5 +0,0 @@
-import 'package:flutter/widgets.dart';
-
-abstract class IAuth {
-  Widget authScreen();
-}

+ 43 - 0
app_flowy/lib/home/presentation/home_layout.dart

@@ -0,0 +1,43 @@
+import 'package:app_flowy/home/application/home_bloc.dart';
+import 'package:flutter/material.dart';
+import 'package:flowy_style/time/duration.dart';
+import 'package:flowy_style/size.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:sized_context/sized_context.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'home_sizes.dart';
+
+class HomeLayout {
+  late double menuWidth;
+  late bool showMenu;
+  late bool showEditPannel;
+  late double editPannelWidth;
+  late double homePageLOffset;
+  late double homePageROffset;
+  late Duration animDuration;
+
+  HomeLayout(BuildContext context, BoxConstraints homeScreenConstraint) {
+    final homeBlocState = context.read<HomeBloc>().state;
+
+    showEditPannel = homeBlocState.editContext.isSome();
+
+    menuWidth = Sizes.sideBarSm;
+    if (context.widthPx >= PageBreaks.desktop) {
+      menuWidth = Sizes.sideBarLg;
+    }
+
+    // if (menuBlocState.isCollapse) {
+    //   showMenu = false;
+    // } else {
+    //   showMenu = context.widthPx > PageBreaks.TabletPortrait;
+    // }
+    showMenu = context.widthPx > PageBreaks.tabletPortrait;
+
+    homePageLOffset = showMenu ? menuWidth : 0.0;
+    animDuration = .35.seconds;
+
+    editPannelWidth = HomeSizes.editPannelWidth;
+    homePageROffset = showEditPannel ? editPannelWidth : 0;
+  }
+}

+ 180 - 3
app_flowy/lib/home/presentation/home_screen.dart

@@ -1,15 +1,144 @@
+import 'package:app_flowy/home/application/home_bloc.dart';
+import 'package:app_flowy/home/application/watcher/home_watcher_bloc.dart';
 import 'package:app_flowy/home/domain/page_context.dart';
 import 'package:app_flowy/home/presentation/widgets/blank_page.dart';
+import 'package:app_flowy/home/presentation/widgets/edit_pannel/edit_pannel.dart';
+import 'package:app_flowy/home/presentation/widgets/edit_pannel/pannel_animation.dart';
+import 'package:app_flowy/home/presentation/widgets/home_top_bar.dart';
+import 'package:app_flowy/home/presentation/widgets/menu/home_menu.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:flowy_logger/flowy_logger.dart';
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
+import 'package:flowy_style/styled_container.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+import 'home_layout.dart';
+import 'widgets/fading_index_stack.dart';
 
 class HomeScreen extends StatelessWidget {
   static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
-  const HomeScreen({Key? key}) : super(key: key);
+  final UserDetail userDetail;
+  const HomeScreen(this.userDetail, {Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return Container(
-      child: null,
+    return MultiBlocProvider(
+      providers: [
+        BlocProvider<HomeWatcherBloc>(
+            create: (context) => getIt<HomeWatcherBloc>()),
+        BlocProvider<HomeBloc>(create: (context) => getIt<HomeBloc>()),
+      ],
+      child: Scaffold(
+        key: HomeScreen.scaffoldKey,
+        body: BlocBuilder<HomeBloc, HomeState>(
+          builder: (context, state) {
+            return StyledContainer(
+              Theme.of(context).colorScheme.background,
+              child: _buildBody(state),
+            );
+          },
+        ),
+      ),
+    );
+  }
+
+  Widget _buildBody(HomeState state) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        final layout = HomeLayout(context, constraints);
+        const homePage = HomePage();
+        final menu = _buildHomeMenu(
+          layout: layout,
+          context: context,
+        );
+        final editPannel = _buildEditPannel(
+          homeState: state,
+          layout: layout,
+          context: context,
+        );
+        return _layoutWidgets(
+            layout: layout,
+            homePage: homePage,
+            homeMenu: menu,
+            editPannel: editPannel);
+      },
+    );
+  }
+
+  Widget _buildHomeMenu(
+      {required HomeLayout layout, required BuildContext context}) {
+    final homeBloc = context.read<HomeBloc>();
+    Widget homeMenu = HomeMenu(
+      pageContextChanged: (pageContext) {
+        pageContext.fold(
+          () => homeBloc.add(const HomeEvent.setPage(BlankPageContext())),
+          (pageContext) {
+            homeBloc.add(HomeEvent.setPage(pageContext));
+          },
+        );
+      },
+      isCollapseChanged: (isCollapse) {
+        homeBloc.add(HomeEvent.showMenu(!isCollapse));
+      },
+    );
+    homeMenu = RepaintBoundary(child: homeMenu);
+    homeMenu = FocusTraversalGroup(child: homeMenu);
+    return homeMenu;
+  }
+
+  Widget _buildEditPannel(
+      {required HomeState homeState,
+      required BuildContext context,
+      required HomeLayout layout}) {
+    final homeBloc = context.read<HomeBloc>();
+    Widget editPannel = EditPannel(
+      context: homeState.editContext,
+      onEndEdit: () => homeBloc.add(const HomeEvent.dismissEditPannel()),
+    );
+    // editPannel = RepaintBoundary(child: editPannel);
+    // editPannel = FocusTraversalGroup(child: editPannel);
+    return editPannel;
+  }
+
+  Widget _layoutWidgets(
+      {required HomeLayout layout,
+      required Widget homeMenu,
+      required Widget homePage,
+      required Widget editPannel}) {
+    return Stack(
+      children: [
+        homeMenu
+            .animatedPanelX(
+              closeX: -layout.menuWidth,
+              isClosed: !layout.showMenu,
+            )
+            .positioned(
+                left: 0,
+                top: 0,
+                width: layout.menuWidth,
+                bottom: 0,
+                animate: true)
+            .animate(layout.animDuration, Curves.easeOut),
+        homePage
+            .constrained(minWidth: 500)
+            .positioned(
+                left: layout.homePageLOffset,
+                right: layout.homePageROffset,
+                bottom: 0,
+                top: 0,
+                animate: true)
+            .animate(layout.animDuration, Curves.easeOut),
+        editPannel
+            .animatedPanelX(
+              duration: layout.animDuration.inMilliseconds * 0.001,
+              closeX: layout.editPannelWidth,
+              isClosed: !layout.showEditPannel,
+            )
+            .positioned(
+                right: 0, top: 0, bottom: 0, width: layout.editPannelWidth),
+      ],
     );
   }
 }
@@ -32,3 +161,51 @@ List<Widget> buildPagesWidget(PageContext pageContext) {
     }
   }).toList();
 }
+
+class HomePage extends StatelessWidget {
+  static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
+  // final Size size;
+  const HomePage({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    Log.info('HomePage build');
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      children: const [
+        HomeTopBar(),
+        HomeIndexStack(),
+      ],
+    );
+  }
+}
+
+class HomeIndexStack extends StatelessWidget {
+  const HomeIndexStack({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<HomeBloc, HomeState>(
+      buildWhen: (p, c) {
+        if (p.pageContext != c.pageContext) {
+          Log.info(
+              'PageContext switch from ${p.pageContext.pageType} to ${c.pageContext.pageType}');
+        }
+        return p.pageContext != c.pageContext;
+      },
+      builder: (context, state) {
+        final pageContext = context.read<HomeBloc>().state.pageContext;
+        return Expanded(
+          child: Container(
+            color: Colors.white,
+            child: FocusTraversalGroup(
+              child: FadingIndexedStack(
+                index: pages.indexOf(pageContext.pageType),
+                children: buildPagesWidget(pageContext),
+              ),
+            ),
+          ),
+        );
+      },
+    );
+  }
+}

+ 11 - 0
app_flowy/lib/home/presentation/home_sizes.dart

@@ -0,0 +1,11 @@
+class HomeSizes {
+  static double get menuTopBarHeight => 60;
+  static double get menuAddButtonHeight => 60;
+  static double get topBarHeight => 60;
+  static double get editPannelTopBarHeight => 60;
+  static double get editPannelWidth => 400;
+}
+
+class HomeInsets {
+  static double get topBarTitlePadding => 12;
+}

+ 67 - 0
app_flowy/lib/home/presentation/widgets/edit_pannel/edit_pannel.dart

@@ -0,0 +1,67 @@
+import 'package:app_flowy/home/application/edit_pannel/edit_pannel_bloc.dart';
+import 'package:app_flowy/home/domain/edit_context.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_style/styled_bar_title.dart';
+import 'package:flowy_style/styled_close_button.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import '../../home_sizes.dart';
+
+class EditPannel extends StatelessWidget {
+  late final EditPannelContext editContext;
+  final VoidCallback onEndEdit;
+  EditPannel(
+      {Key? key,
+      required Option<EditPannelContext> context,
+      required this.onEndEdit})
+      : super(key: key) {
+    editContext = context.fold(() => const BlankEditPannelContext(), (c) => c);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      color: Theme.of(context).colorScheme.primaryVariant,
+      child: BlocProvider(
+        create: (context) => getIt<EditPannelBloc>(),
+        child: BlocBuilder<EditPannelBloc, EditPannelState>(
+          builder: (context, state) {
+            return Column(
+              crossAxisAlignment: CrossAxisAlignment.stretch,
+              children: [
+                EditPannelTopBar(onClose: () => onEndEdit()),
+                Expanded(
+                  child: editContext.child,
+                ),
+              ],
+            );
+          },
+        ),
+      ),
+    );
+  }
+}
+
+class EditPannelTopBar extends StatelessWidget {
+  final VoidCallback onClose;
+  const EditPannelTopBar({Key? key, required this.onClose}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: HomeSizes.editPannelTopBarHeight,
+      child: Padding(
+        padding: const EdgeInsets.all(8.0),
+        child: Row(
+          children: [
+            const StyleBarTitle(
+              title: 'Title',
+            ),
+            const Spacer(),
+            StyleCloseButton(onPressed: onClose),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 87 - 0
app_flowy/lib/home/presentation/widgets/edit_pannel/pannel_animation.dart

@@ -0,0 +1,87 @@
+import 'package:flutter/material.dart';
+
+class AnimatedPanel extends StatefulWidget {
+  final bool isClosed;
+  final double closedX;
+  final double closedY;
+  final double duration;
+  final Curve? curve;
+  final Widget? child;
+
+  const AnimatedPanel(
+      {Key? key,
+      this.isClosed = false,
+      this.closedX = 0.0,
+      this.closedY = 0.0,
+      this.duration = 0.0,
+      this.curve,
+      this.child})
+      : super(key: key);
+
+  @override
+  _AnimatedPanelState createState() => _AnimatedPanelState();
+}
+
+class _AnimatedPanelState extends State<AnimatedPanel> {
+  bool _isHidden = true;
+
+  @override
+  Widget build(BuildContext context) {
+    Offset closePos = Offset(widget.closedX, widget.closedY);
+    double duration = _isHidden && widget.isClosed ? 0 : widget.duration;
+    return TweenAnimationBuilder(
+      curve: widget.curve ?? Curves.easeOut,
+      tween: Tween<Offset>(
+        begin: !widget.isClosed ? Offset.zero : closePos,
+        end: !widget.isClosed ? Offset.zero : closePos,
+      ),
+      duration: Duration(milliseconds: (duration * 1000).round()),
+      builder: (_, Offset value, Widget? c) {
+        _isHidden =
+            widget.isClosed && value == Offset(widget.closedX, widget.closedY);
+        return _isHidden
+            ? Container()
+            : Transform.translate(offset: value, child: c);
+      },
+      child: widget.child,
+    );
+  }
+}
+
+extension AnimatedPanelExtensions on Widget {
+  Widget animatedPanelX(
+          {double closeX = 0.0,
+          bool? isClosed,
+          double? duration,
+          Curve? curve}) =>
+      animatedPanel(
+          closePos: Offset(closeX, 0),
+          isClosed: isClosed,
+          curve: curve,
+          duration: duration);
+
+  Widget animatedPanelY(
+          {double closeY = 0.0,
+          bool? isClosed,
+          double? duration,
+          Curve? curve}) =>
+      animatedPanel(
+          closePos: Offset(0, closeY),
+          isClosed: isClosed,
+          curve: curve,
+          duration: duration);
+
+  Widget animatedPanel(
+      {required Offset closePos,
+      bool? isClosed,
+      double? duration,
+      Curve? curve}) {
+    return AnimatedPanel(
+        closedX: closePos.dx,
+        closedY: closePos.dy,
+        child: this,
+        isClosed: isClosed ?? false,
+        duration: duration ?? .35,
+        curve: curve);
+  }
+}

+ 44 - 0
app_flowy/lib/home/presentation/widgets/fading_index_stack.dart

@@ -0,0 +1,44 @@
+import 'package:flutter/material.dart';
+import 'package:time/time.dart';
+
+class FadingIndexedStack extends StatefulWidget {
+  final int index;
+  final List<Widget> children;
+  final Duration duration;
+
+  const FadingIndexedStack({
+    Key? key,
+    required this.index,
+    required this.children,
+    this.duration = const Duration(
+      milliseconds: 250,
+    ),
+  }) : super(key: key);
+
+  @override
+  _FadingIndexedStackState createState() => _FadingIndexedStackState();
+}
+
+class _FadingIndexedStackState extends State<FadingIndexedStack> {
+  double _targetOpacity = 1;
+
+  @override
+  void didUpdateWidget(FadingIndexedStack oldWidget) {
+    if (oldWidget.index == widget.index) return;
+    setState(() => _targetOpacity = 0);
+    Future.delayed(1.milliseconds, () => setState(() => _targetOpacity = 1));
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return TweenAnimationBuilder<double>(
+      duration: _targetOpacity > 0 ? widget.duration : 0.milliseconds,
+      tween: Tween(begin: 0, end: _targetOpacity),
+      builder: (_, value, child) {
+        return Opacity(opacity: value, child: child);
+      },
+      child: IndexedStack(index: widget.index, children: widget.children),
+    );
+  }
+}

+ 7 - 0
app_flowy/lib/home/presentation/widgets/home_stack_page.dart

@@ -0,0 +1,7 @@
+import 'package:app_flowy/home/domain/page_context.dart';
+import 'package:flutter/material.dart';
+
+abstract class HomeStackPage extends StatefulWidget {
+  final PageContext pageContext;
+  const HomeStackPage({Key? key, required this.pageContext}) : super(key: key);
+}

+ 51 - 0
app_flowy/lib/home/presentation/widgets/home_top_bar.dart

@@ -0,0 +1,51 @@
+import 'package:app_flowy/home/application/home_bloc.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import '../home_sizes.dart';
+
+class HomeTopBar extends StatelessWidget {
+  const HomeTopBar({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      padding: EdgeInsets.symmetric(horizontal: HomeInsets.topBarTitlePadding),
+      height: HomeSizes.topBarHeight,
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          HomeTitle(),
+        ],
+      ),
+    );
+  }
+}
+
+class HomeTitle extends StatelessWidget {
+  final _editingController = TextEditingController(
+    text: '',
+  );
+
+  HomeTitle({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    _editingController.text =
+        context.read<HomeBloc>().state.pageContext.pageTitle;
+
+    return Expanded(
+      child: TextField(
+        controller: _editingController,
+        textAlign: TextAlign.left,
+        style: const TextStyle(fontSize: 28.0),
+        decoration: const InputDecoration(
+          hintText: 'Name the view',
+          border: UnderlineInputBorder(borderSide: BorderSide.none),
+        ),
+      ),
+    );
+  }
+}

+ 4 - 0
app_flowy/lib/home/presentation/widgets/menu/hom_menu_size.dart

@@ -0,0 +1,4 @@
+class HomeMenuSize {
+  static double get createViewButtonSize => 30;
+  static double get collapseIconSize => 24;
+}

+ 121 - 0
app_flowy/lib/home/presentation/widgets/menu/home_menu.dart

@@ -0,0 +1,121 @@
+import 'package:app_flowy/home/application/menu/menu_bloc.dart';
+import 'package:app_flowy/home/domain/page_context.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_style/size.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import '../../home_sizes.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+class HomeMenu extends StatelessWidget {
+  final Function(Option<PageContext>) pageContextChanged;
+  final Function(bool) isCollapseChanged;
+
+  const HomeMenu(
+      {Key? key,
+      required this.pageContextChanged,
+      required this.isCollapseChanged})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return MultiBlocProvider(
+        providers: [
+          BlocProvider<MenuBloc>(create: (context) => getIt<MenuBloc>()),
+        ],
+        child: MultiBlocListener(
+          listeners: bind(),
+          child: Container(
+            color: Theme.of(context).colorScheme.primaryVariant,
+            child: Padding(
+              padding: EdgeInsets.symmetric(horizontal: Insets.sm),
+              child: Column(
+                mainAxisAlignment: MainAxisAlignment.start,
+                children: [
+                  const MenuTopBar(),
+                  Container(),
+                  const NewAppButton(),
+                ],
+              ),
+            ),
+          ),
+        ));
+  }
+
+  // bind the function passed by ooutter with the bloc listener
+  List<BlocListener<MenuBloc, MenuState>> bind() {
+    return [
+      BlocListener<MenuBloc, MenuState>(
+        listenWhen: (p, c) => p.pageContext != c.pageContext,
+        listener: (context, state) => pageContextChanged(state.pageContext),
+      ),
+      BlocListener<MenuBloc, MenuState>(
+        listenWhen: (p, c) => p.isCollapse != c.isCollapse,
+        listener: (context, state) => isCollapseChanged(state.isCollapse),
+      )
+    ];
+  }
+}
+
+class MenuTopBar extends StatelessWidget {
+  const MenuTopBar({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<MenuBloc, MenuState>(
+      builder: (context, state) {
+        return SizedBox(
+          height: HomeSizes.menuTopBarHeight,
+          child: Row(
+            children: [
+              const Text(
+                'AppFlowy',
+                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
+              ).constrained(minWidth: 100),
+              const Spacer(),
+              IconButton(
+                icon: const Icon(Icons.arrow_left),
+                onPressed: () =>
+                    context.read<MenuBloc>().add(const MenuEvent.collapse()),
+              ),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}
+
+class NewAppButton extends StatelessWidget {
+  const NewAppButton({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: HomeSizes.menuAddButtonHeight,
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.start,
+        children: [
+          const Icon(Icons.add),
+          const SizedBox(
+            width: 10,
+          ),
+          TextButton(
+            onPressed: () async {
+              // Dialogs.show(OkCancelDialog(
+              //   title: "No Connection",
+              //   message:
+              //       "It appears your device is offline. Please check your connection and try again.",
+              //   onOkPressed: () => AppGlobals.nav.pop(),
+              // ));
+            },
+            child: const Text('New App',
+                style: TextStyle(
+                    color: Colors.black,
+                    fontWeight: FontWeight.bold,
+                    fontSize: 20)),
+          )
+        ],
+      ),
+    );
+  }
+}

+ 2 - 0
app_flowy/lib/home/presentation/widgets/menu/prelude.dart

@@ -0,0 +1,2 @@
+export 'home_menu.dart';
+export 'hom_menu_size.dart';

+ 1 - 1
app_flowy/lib/startup/deps_inject/prelude.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/startup/launch.dart';
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/welcome/infrastructure/deps_impl.dart';
+import 'package:app_flowy/welcome/infrastructure/interface_impl.dart';
 import 'package:flowy_sdk/flowy_sdk.dart';
 import 'package:get_it/get_it.dart';
 

+ 3 - 3
app_flowy/lib/startup/launch.dart

@@ -16,7 +16,7 @@ enum LaunchTaskType {
 /// some nonresident indispensable task in app launching task.
 abstract class LaunchTask {
   LaunchTaskType get type => LaunchTaskType.dataProcessing;
-  void initialize(LaunchContext context);
+  Future<void> initialize(LaunchContext context);
 }
 
 class AppLauncher {
@@ -30,10 +30,10 @@ class AppLauncher {
     tasks.add(task);
   }
 
-  void launch() {
+  void launch() async {
     final context = LaunchContext(getIt, env);
     for (var task in tasks) {
-      task.initialize(context);
+      await task.initialize(context);
     }
   }
 }

+ 3 - 1
app_flowy/lib/startup/tasks/app_widget_task.dart

@@ -10,10 +10,12 @@ class AppWidgetTask extends LaunchTask {
   LaunchTaskType get type => LaunchTaskType.appLauncher;
 
   @override
-  void initialize(LaunchContext context) {
+  Future<void> initialize(LaunchContext context) {
     final widget = context.getIt<AppFactory>().create();
     final app = AppWidget(child: widget);
     runApp(app);
+
+    return Future(() => {});
   }
 }
 

+ 3 - 1
app_flowy/lib/startup/tasks/rust_sdk_init_task.dart

@@ -12,7 +12,7 @@ class RustSDKInitTask extends LaunchTask {
   LaunchTaskType get type => LaunchTaskType.dataProcessing;
 
   @override
-  void initialize(LaunchContext context) async {
+  Future<void> initialize(LaunchContext context) async {
     WidgetsFlutterBinding.ensureInitialized();
 
     Bloc.observer = ApplicationBlocObserver();
@@ -31,6 +31,8 @@ class RustSDKInitTask extends LaunchTask {
       default:
         assert(false, 'Unsupported env');
     }
+
+    return Future(() => {});
   }
 }
 

+ 5 - 9
app_flowy/lib/welcome/application/welcome_bloc.dart

@@ -1,5 +1,5 @@
 import 'package:app_flowy/welcome/domain/auth_state.dart';
-import 'package:app_flowy/welcome/domain/deps.dart';
+import 'package:app_flowy/welcome/domain/interface.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
@@ -8,18 +8,14 @@ part 'welcome_state.dart';
 part 'welcome_bloc.freezed.dart';
 
 class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
-  final IWelcomeAuth authCheck;
-  WelcomeBloc(this.authCheck) : super(WelcomeState.initial());
+  final IWelcomeAuth authImpl;
+  WelcomeBloc(this.authImpl) : super(WelcomeState.initial());
 
   @override
   Stream<WelcomeState> mapEventToState(WelcomeEvent event) async* {
     yield* event.map(
-      check: (val) async* {
-        add(const WelcomeEvent.authCheck());
-        yield state;
-      },
-      authCheck: (val) async* {
-        final authState = await authCheck.getAuthState();
+      getUser: (val) async* {
+        final authState = await authImpl.currentUserState();
         yield state.copyWith(auth: authState);
       },
     );

+ 30 - 126
app_flowy/lib/welcome/application/welcome_bloc.freezed.dart

@@ -16,12 +16,8 @@ final _privateConstructorUsedError = UnsupportedError(
 class _$WelcomeEventTearOff {
   const _$WelcomeEventTearOff();
 
-  _Check check() {
-    return const _Check();
-  }
-
-  _AuthCheck authCheck() {
-    return const _AuthCheck();
+  _GetUser getUser() {
+    return const _GetUser();
   }
 }
 
@@ -32,27 +28,23 @@ const $WelcomeEvent = _$WelcomeEventTearOff();
 mixin _$WelcomeEvent {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
-    required TResult Function() check,
-    required TResult Function() authCheck,
+    required TResult Function() getUser,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
-    TResult Function()? check,
-    TResult Function()? authCheck,
+    TResult Function()? getUser,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult map<TResult extends Object?>({
-    required TResult Function(_Check value) check,
-    required TResult Function(_AuthCheck value) authCheck,
+    required TResult Function(_GetUser value) getUser,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
-    TResult Function(_Check value)? check,
-    TResult Function(_AuthCheck value)? authCheck,
+    TResult Function(_GetUser value)? getUser,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
@@ -75,118 +67,34 @@ class _$WelcomeEventCopyWithImpl<$Res> implements $WelcomeEventCopyWith<$Res> {
 }
 
 /// @nodoc
-abstract class _$CheckCopyWith<$Res> {
-  factory _$CheckCopyWith(_Check value, $Res Function(_Check) then) =
-      __$CheckCopyWithImpl<$Res>;
-}
-
-/// @nodoc
-class __$CheckCopyWithImpl<$Res> extends _$WelcomeEventCopyWithImpl<$Res>
-    implements _$CheckCopyWith<$Res> {
-  __$CheckCopyWithImpl(_Check _value, $Res Function(_Check) _then)
-      : super(_value, (v) => _then(v as _Check));
-
-  @override
-  _Check get _value => super._value as _Check;
-}
-
-/// @nodoc
-
-class _$_Check implements _Check {
-  const _$_Check();
-
-  @override
-  String toString() {
-    return 'WelcomeEvent.check()';
-  }
-
-  @override
-  bool operator ==(dynamic other) {
-    return identical(this, other) || (other is _Check);
-  }
-
-  @override
-  int get hashCode => runtimeType.hashCode;
-
-  @override
-  @optionalTypeArgs
-  TResult when<TResult extends Object?>({
-    required TResult Function() check,
-    required TResult Function() authCheck,
-  }) {
-    return check();
-  }
-
-  @override
-  @optionalTypeArgs
-  TResult maybeWhen<TResult extends Object?>({
-    TResult Function()? check,
-    TResult Function()? authCheck,
-    required TResult orElse(),
-  }) {
-    if (check != null) {
-      return check();
-    }
-    return orElse();
-  }
-
-  @override
-  @optionalTypeArgs
-  TResult map<TResult extends Object?>({
-    required TResult Function(_Check value) check,
-    required TResult Function(_AuthCheck value) authCheck,
-  }) {
-    return check(this);
-  }
-
-  @override
-  @optionalTypeArgs
-  TResult maybeMap<TResult extends Object?>({
-    TResult Function(_Check value)? check,
-    TResult Function(_AuthCheck value)? authCheck,
-    required TResult orElse(),
-  }) {
-    if (check != null) {
-      return check(this);
-    }
-    return orElse();
-  }
-}
-
-abstract class _Check implements WelcomeEvent {
-  const factory _Check() = _$_Check;
-}
-
-/// @nodoc
-abstract class _$AuthCheckCopyWith<$Res> {
-  factory _$AuthCheckCopyWith(
-          _AuthCheck value, $Res Function(_AuthCheck) then) =
-      __$AuthCheckCopyWithImpl<$Res>;
+abstract class _$GetUserCopyWith<$Res> {
+  factory _$GetUserCopyWith(_GetUser value, $Res Function(_GetUser) then) =
+      __$GetUserCopyWithImpl<$Res>;
 }
 
 /// @nodoc
-class __$AuthCheckCopyWithImpl<$Res> extends _$WelcomeEventCopyWithImpl<$Res>
-    implements _$AuthCheckCopyWith<$Res> {
-  __$AuthCheckCopyWithImpl(_AuthCheck _value, $Res Function(_AuthCheck) _then)
-      : super(_value, (v) => _then(v as _AuthCheck));
+class __$GetUserCopyWithImpl<$Res> extends _$WelcomeEventCopyWithImpl<$Res>
+    implements _$GetUserCopyWith<$Res> {
+  __$GetUserCopyWithImpl(_GetUser _value, $Res Function(_GetUser) _then)
+      : super(_value, (v) => _then(v as _GetUser));
 
   @override
-  _AuthCheck get _value => super._value as _AuthCheck;
+  _GetUser get _value => super._value as _GetUser;
 }
 
 /// @nodoc
 
-class _$_AuthCheck implements _AuthCheck {
-  const _$_AuthCheck();
+class _$_GetUser implements _GetUser {
+  const _$_GetUser();
 
   @override
   String toString() {
-    return 'WelcomeEvent.authCheck()';
+    return 'WelcomeEvent.getUser()';
   }
 
   @override
   bool operator ==(dynamic other) {
-    return identical(this, other) || (other is _AuthCheck);
+    return identical(this, other) || (other is _GetUser);
   }
 
   @override
@@ -195,21 +103,19 @@ class _$_AuthCheck implements _AuthCheck {
   @override
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
-    required TResult Function() check,
-    required TResult Function() authCheck,
+    required TResult Function() getUser,
   }) {
-    return authCheck();
+    return getUser();
   }
 
   @override
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
-    TResult Function()? check,
-    TResult Function()? authCheck,
+    TResult Function()? getUser,
     required TResult orElse(),
   }) {
-    if (authCheck != null) {
-      return authCheck();
+    if (getUser != null) {
+      return getUser();
     }
     return orElse();
   }
@@ -217,28 +123,26 @@ class _$_AuthCheck implements _AuthCheck {
   @override
   @optionalTypeArgs
   TResult map<TResult extends Object?>({
-    required TResult Function(_Check value) check,
-    required TResult Function(_AuthCheck value) authCheck,
+    required TResult Function(_GetUser value) getUser,
   }) {
-    return authCheck(this);
+    return getUser(this);
   }
 
   @override
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
-    TResult Function(_Check value)? check,
-    TResult Function(_AuthCheck value)? authCheck,
+    TResult Function(_GetUser value)? getUser,
     required TResult orElse(),
   }) {
-    if (authCheck != null) {
-      return authCheck(this);
+    if (getUser != null) {
+      return getUser(this);
     }
     return orElse();
   }
 }
 
-abstract class _AuthCheck implements WelcomeEvent {
-  const factory _AuthCheck() = _$_AuthCheck;
+abstract class _GetUser implements WelcomeEvent {
+  const factory _GetUser() = _$_GetUser;
 }
 
 /// @nodoc

+ 1 - 2
app_flowy/lib/welcome/application/welcome_event.dart

@@ -2,6 +2,5 @@ part of 'welcome_bloc.dart';
 
 @freezed
 abstract class WelcomeEvent with _$WelcomeEvent {
-  const factory WelcomeEvent.check() = _Check;
-  const factory WelcomeEvent.authCheck() = _AuthCheck;
+  const factory WelcomeEvent.getUser() = _GetUser;
 }

+ 1 - 1
app_flowy/lib/welcome/application/welcome_state.dart

@@ -7,6 +7,6 @@ abstract class WelcomeState implements _$WelcomeState {
   }) = _WelcomeState;
 
   factory WelcomeState.initial() => const WelcomeState(
-        auth: AuthState.unauthenticated(),
+        auth: AuthState.initial(),
       );
 }

+ 5 - 2
app_flowy/lib/welcome/domain/auth_state.dart

@@ -1,8 +1,11 @@
+import 'package:flowy_sdk/protobuf/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 part 'auth_state.freezed.dart';
 
 @freezed
 abstract class AuthState with _$AuthState {
-  const factory AuthState.authenticated() = Authenticated;
-  const factory AuthState.unauthenticated() = Unauthenticated;
+  const factory AuthState.authenticated(UserDetail userDetail) = Authenticated;
+  const factory AuthState.unauthenticated(UserError error) = Unauthenticated;
+  const factory AuthState.initial() = _Initial;
 }

+ 198 - 30
app_flowy/lib/welcome/domain/auth_state.freezed.dart

@@ -16,12 +16,20 @@ final _privateConstructorUsedError = UnsupportedError(
 class _$AuthStateTearOff {
   const _$AuthStateTearOff();
 
-  Authenticated authenticated() {
-    return const Authenticated();
+  Authenticated authenticated(UserDetail userDetail) {
+    return Authenticated(
+      userDetail,
+    );
   }
 
-  Unauthenticated unauthenticated() {
-    return const Unauthenticated();
+  Unauthenticated unauthenticated(UserError error) {
+    return Unauthenticated(
+      error,
+    );
+  }
+
+  _Initial initial() {
+    return const _Initial();
   }
 }
 
@@ -32,14 +40,16 @@ const $AuthState = _$AuthStateTearOff();
 mixin _$AuthState {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
-    required TResult Function() authenticated,
-    required TResult Function() unauthenticated,
+    required TResult Function(UserDetail userDetail) authenticated,
+    required TResult Function(UserError error) unauthenticated,
+    required TResult Function() initial,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
-    TResult Function()? authenticated,
-    TResult Function()? unauthenticated,
+    TResult Function(UserDetail userDetail)? authenticated,
+    TResult Function(UserError error)? unauthenticated,
+    TResult Function()? initial,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
@@ -47,12 +57,14 @@ mixin _$AuthState {
   TResult map<TResult extends Object?>({
     required TResult Function(Authenticated value) authenticated,
     required TResult Function(Unauthenticated value) unauthenticated,
+    required TResult Function(_Initial value) initial,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Authenticated value)? authenticated,
     TResult Function(Unauthenticated value)? unauthenticated,
+    TResult Function(_Initial value)? initial,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
@@ -78,6 +90,7 @@ abstract class $AuthenticatedCopyWith<$Res> {
   factory $AuthenticatedCopyWith(
           Authenticated value, $Res Function(Authenticated) then) =
       _$AuthenticatedCopyWithImpl<$Res>;
+  $Res call({UserDetail userDetail});
 }
 
 /// @nodoc
@@ -89,44 +102,71 @@ class _$AuthenticatedCopyWithImpl<$Res> extends _$AuthStateCopyWithImpl<$Res>
 
   @override
   Authenticated get _value => super._value as Authenticated;
+
+  @override
+  $Res call({
+    Object? userDetail = freezed,
+  }) {
+    return _then(Authenticated(
+      userDetail == freezed
+          ? _value.userDetail
+          : userDetail // ignore: cast_nullable_to_non_nullable
+              as UserDetail,
+    ));
+  }
 }
 
 /// @nodoc
 
 class _$Authenticated implements Authenticated {
-  const _$Authenticated();
+  const _$Authenticated(this.userDetail);
+
+  @override
+  final UserDetail userDetail;
 
   @override
   String toString() {
-    return 'AuthState.authenticated()';
+    return 'AuthState.authenticated(userDetail: $userDetail)';
   }
 
   @override
   bool operator ==(dynamic other) {
-    return identical(this, other) || (other is Authenticated);
+    return identical(this, other) ||
+        (other is Authenticated &&
+            (identical(other.userDetail, userDetail) ||
+                const DeepCollectionEquality()
+                    .equals(other.userDetail, userDetail)));
   }
 
   @override
-  int get hashCode => runtimeType.hashCode;
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(userDetail);
+
+  @JsonKey(ignore: true)
+  @override
+  $AuthenticatedCopyWith<Authenticated> get copyWith =>
+      _$AuthenticatedCopyWithImpl<Authenticated>(this, _$identity);
 
   @override
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
-    required TResult Function() authenticated,
-    required TResult Function() unauthenticated,
+    required TResult Function(UserDetail userDetail) authenticated,
+    required TResult Function(UserError error) unauthenticated,
+    required TResult Function() initial,
   }) {
-    return authenticated();
+    return authenticated(userDetail);
   }
 
   @override
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
-    TResult Function()? authenticated,
-    TResult Function()? unauthenticated,
+    TResult Function(UserDetail userDetail)? authenticated,
+    TResult Function(UserError error)? unauthenticated,
+    TResult Function()? initial,
     required TResult orElse(),
   }) {
     if (authenticated != null) {
-      return authenticated();
+      return authenticated(userDetail);
     }
     return orElse();
   }
@@ -136,6 +176,7 @@ class _$Authenticated implements Authenticated {
   TResult map<TResult extends Object?>({
     required TResult Function(Authenticated value) authenticated,
     required TResult Function(Unauthenticated value) unauthenticated,
+    required TResult Function(_Initial value) initial,
   }) {
     return authenticated(this);
   }
@@ -145,6 +186,7 @@ class _$Authenticated implements Authenticated {
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Authenticated value)? authenticated,
     TResult Function(Unauthenticated value)? unauthenticated,
+    TResult Function(_Initial value)? initial,
     required TResult orElse(),
   }) {
     if (authenticated != null) {
@@ -155,7 +197,12 @@ class _$Authenticated implements Authenticated {
 }
 
 abstract class Authenticated implements AuthState {
-  const factory Authenticated() = _$Authenticated;
+  const factory Authenticated(UserDetail userDetail) = _$Authenticated;
+
+  UserDetail get userDetail => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  $AuthenticatedCopyWith<Authenticated> get copyWith =>
+      throw _privateConstructorUsedError;
 }
 
 /// @nodoc
@@ -163,6 +210,7 @@ abstract class $UnauthenticatedCopyWith<$Res> {
   factory $UnauthenticatedCopyWith(
           Unauthenticated value, $Res Function(Unauthenticated) then) =
       _$UnauthenticatedCopyWithImpl<$Res>;
+  $Res call({UserError error});
 }
 
 /// @nodoc
@@ -174,44 +222,70 @@ class _$UnauthenticatedCopyWithImpl<$Res> extends _$AuthStateCopyWithImpl<$Res>
 
   @override
   Unauthenticated get _value => super._value as Unauthenticated;
+
+  @override
+  $Res call({
+    Object? error = freezed,
+  }) {
+    return _then(Unauthenticated(
+      error == freezed
+          ? _value.error
+          : error // ignore: cast_nullable_to_non_nullable
+              as UserError,
+    ));
+  }
 }
 
 /// @nodoc
 
 class _$Unauthenticated implements Unauthenticated {
-  const _$Unauthenticated();
+  const _$Unauthenticated(this.error);
+
+  @override
+  final UserError error;
 
   @override
   String toString() {
-    return 'AuthState.unauthenticated()';
+    return 'AuthState.unauthenticated(error: $error)';
   }
 
   @override
   bool operator ==(dynamic other) {
-    return identical(this, other) || (other is Unauthenticated);
+    return identical(this, other) ||
+        (other is Unauthenticated &&
+            (identical(other.error, error) ||
+                const DeepCollectionEquality().equals(other.error, error)));
   }
 
   @override
-  int get hashCode => runtimeType.hashCode;
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(error);
+
+  @JsonKey(ignore: true)
+  @override
+  $UnauthenticatedCopyWith<Unauthenticated> get copyWith =>
+      _$UnauthenticatedCopyWithImpl<Unauthenticated>(this, _$identity);
 
   @override
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
-    required TResult Function() authenticated,
-    required TResult Function() unauthenticated,
+    required TResult Function(UserDetail userDetail) authenticated,
+    required TResult Function(UserError error) unauthenticated,
+    required TResult Function() initial,
   }) {
-    return unauthenticated();
+    return unauthenticated(error);
   }
 
   @override
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
-    TResult Function()? authenticated,
-    TResult Function()? unauthenticated,
+    TResult Function(UserDetail userDetail)? authenticated,
+    TResult Function(UserError error)? unauthenticated,
+    TResult Function()? initial,
     required TResult orElse(),
   }) {
     if (unauthenticated != null) {
-      return unauthenticated();
+      return unauthenticated(error);
     }
     return orElse();
   }
@@ -221,6 +295,7 @@ class _$Unauthenticated implements Unauthenticated {
   TResult map<TResult extends Object?>({
     required TResult Function(Authenticated value) authenticated,
     required TResult Function(Unauthenticated value) unauthenticated,
+    required TResult Function(_Initial value) initial,
   }) {
     return unauthenticated(this);
   }
@@ -230,6 +305,7 @@ class _$Unauthenticated implements Unauthenticated {
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Authenticated value)? authenticated,
     TResult Function(Unauthenticated value)? unauthenticated,
+    TResult Function(_Initial value)? initial,
     required TResult orElse(),
   }) {
     if (unauthenticated != null) {
@@ -240,5 +316,97 @@ class _$Unauthenticated implements Unauthenticated {
 }
 
 abstract class Unauthenticated implements AuthState {
-  const factory Unauthenticated() = _$Unauthenticated;
+  const factory Unauthenticated(UserError error) = _$Unauthenticated;
+
+  UserError get error => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  $UnauthenticatedCopyWith<Unauthenticated> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class _$InitialCopyWith<$Res> {
+  factory _$InitialCopyWith(_Initial value, $Res Function(_Initial) then) =
+      __$InitialCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$InitialCopyWithImpl<$Res> extends _$AuthStateCopyWithImpl<$Res>
+    implements _$InitialCopyWith<$Res> {
+  __$InitialCopyWithImpl(_Initial _value, $Res Function(_Initial) _then)
+      : super(_value, (v) => _then(v as _Initial));
+
+  @override
+  _Initial get _value => super._value as _Initial;
+}
+
+/// @nodoc
+
+class _$_Initial implements _Initial {
+  const _$_Initial();
+
+  @override
+  String toString() {
+    return 'AuthState.initial()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _Initial);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function(UserDetail userDetail) authenticated,
+    required TResult Function(UserError error) unauthenticated,
+    required TResult Function() initial,
+  }) {
+    return initial();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function(UserDetail userDetail)? authenticated,
+    TResult Function(UserError error)? unauthenticated,
+    TResult Function()? initial,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Authenticated value) authenticated,
+    required TResult Function(Unauthenticated value) unauthenticated,
+    required TResult Function(_Initial value) initial,
+  }) {
+    return initial(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Authenticated value)? authenticated,
+    TResult Function(Unauthenticated value)? unauthenticated,
+    TResult Function(_Initial value)? initial,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _Initial implements AuthState {
+  const factory _Initial() = _$_Initial;
 }

+ 0 - 12
app_flowy/lib/welcome/domain/deps.dart

@@ -1,12 +0,0 @@
-import 'package:flutter/widgets.dart';
-
-import 'auth_state.dart';
-
-abstract class IWelcomeAuth {
-  Future<AuthState> getAuthState();
-}
-
-abstract class IWelcomeRoute {
-  Widget signIn();
-  Widget home();
-}

+ 13 - 0
app_flowy/lib/welcome/domain/interface.dart

@@ -0,0 +1,13 @@
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
+import 'package:flutter/widgets.dart';
+
+import 'auth_state.dart';
+
+abstract class IWelcomeAuth {
+  Future<AuthState> currentUserState();
+}
+
+abstract class IWelcomeRoute {
+  Widget pushSignInScreen();
+  Widget pushHomeScreen(UserDetail userDetail);
+}

+ 0 - 38
app_flowy/lib/welcome/infrastructure/deps_impl.dart

@@ -1,38 +0,0 @@
-import 'package:app_flowy/home/presentation/home_screen.dart';
-import 'package:app_flowy/welcome/application/welcome_bloc.dart';
-import 'package:app_flowy/welcome/domain/auth_state.dart';
-import 'package:app_flowy/welcome/domain/deps.dart';
-import 'package:flutter/widgets.dart';
-import 'package:get_it/get_it.dart';
-import 'package:time/time.dart';
-
-class Welcome {
-  static Future<void> dependencyResolved(GetIt getIt) async {
-    getIt.registerFactory<IWelcomeAuth>(() => AuthCheck());
-    getIt.registerFactory<IWelcomeRoute>(() => WelcomeRoute());
-
-    getIt
-        .registerFactory<WelcomeBloc>(() => WelcomeBloc(getIt<IWelcomeAuth>()));
-  }
-}
-
-class AuthCheck implements IWelcomeAuth {
-  @override
-  Future<AuthState> getAuthState() async {
-    return Future<AuthState>.delayed(3.0.seconds, () {
-      return const AuthState.authenticated();
-    });
-  }
-}
-
-class WelcomeRoute implements IWelcomeRoute {
-  @override
-  Widget home() {
-    return const HomeScreen();
-  }
-
-  @override
-  Widget signIn() {
-    return Container();
-  }
-}

+ 61 - 0
app_flowy/lib/welcome/infrastructure/interface_impl.dart

@@ -0,0 +1,61 @@
+import 'package:app_flowy/home/application/edit_pannel/edit_pannel_bloc.dart';
+import 'package:app_flowy/home/application/home_bloc.dart';
+import 'package:app_flowy/home/application/menu/menu_bloc.dart';
+import 'package:app_flowy/home/application/watcher/home_watcher_bloc.dart';
+import 'package:app_flowy/home/presentation/home_screen.dart';
+import 'package:app_flowy/welcome/application/welcome_bloc.dart';
+import 'package:app_flowy/welcome/domain/auth_state.dart';
+import 'package:app_flowy/welcome/domain/interface.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:get_it/get_it.dart';
+
+class Welcome {
+  static Future<void> dependencyResolved(GetIt getIt) async {
+    getIt.registerFactory<IWelcomeAuth>(() => WelcomeAuthImpl());
+    getIt.registerFactory<IWelcomeRoute>(() => WelcomeRoute());
+    getIt.registerFactory<HomeBloc>(() => HomeBloc());
+    getIt.registerFactory<HomeWatcherBloc>(() => HomeWatcherBloc());
+    getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
+
+    getIt.registerFactory<MenuBloc>(() => MenuBloc());
+
+    getIt
+        .registerFactory<WelcomeBloc>(() => WelcomeBloc(getIt<IWelcomeAuth>()));
+  }
+}
+
+class WelcomeAuthImpl implements IWelcomeAuth {
+  @override
+  Future<AuthState> currentUserState() {
+    final result = UserEventGetStatus().send();
+    return result.then((result) {
+      return result.fold(
+        (userDetail) {
+          return AuthState.authenticated(userDetail);
+        },
+        (userError) {
+          return AuthState.unauthenticated(userError);
+        },
+      );
+    });
+  }
+}
+
+class WelcomeRoute implements IWelcomeRoute {
+  @override
+  Widget pushHomeScreen(UserDetail user) {
+    return HomeScreen(user);
+  }
+
+  @override
+  Widget pushSignInScreen() {
+    return Container(
+      width: 100,
+      height: 100,
+      color: Colors.red,
+    );
+  }
+}

+ 48 - 13
app_flowy/lib/welcome/presentation/welcome_screen.dart

@@ -1,7 +1,8 @@
-import 'package:app_flowy/welcome/domain/deps.dart';
-import 'package:app_flowy/welcome/presentation/widgets/body.dart';
+import 'package:app_flowy/welcome/domain/interface.dart';
+import 'package:app_flowy/welcome/domain/auth_state.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/welcome/application/welcome_bloc.dart';
+import 'package:flowy_logger/flowy_logger.dart';
 import 'package:flowy_style/route/animation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -14,16 +15,15 @@ class WelcomeScreen extends StatelessWidget {
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) {
-        return getIt<WelcomeBloc>()..add(const WelcomeEvent.check());
+        return getIt<WelcomeBloc>()..add(const WelcomeEvent.getUser());
       },
       child: Scaffold(
         body: BlocListener<WelcomeBloc, WelcomeState>(
           listener: (context, state) {
             state.auth.map(
-              authenticated: (_) =>
-                  _pushToScreen(context, getIt<IWelcomeRoute>().home()),
-              unauthenticated: (_) =>
-                  _pushToScreen(context, getIt<IWelcomeRoute>().signIn()),
+              authenticated: (r) => _handleAuthenticated(context, r),
+              unauthenticated: (r) => _handleUnauthenticated(context, r),
+              initial: (r) => {},
             );
           },
           child: const Body(),
@@ -34,11 +34,46 @@ class WelcomeScreen extends StatelessWidget {
 
   void _pushToScreen(BuildContext context, Widget screen) {
     /// Let the splash view sit for a bit. Mainly for aesthetics and to ensure a smooth intro animation.
-    Future<void>.delayed(1.0.seconds, () {
-      Navigator.push(
-          context,
-          PageRoutes.fade(
-              () => screen, RouteDurations.slow.inMilliseconds * .001));
-    });
+    Navigator.push(
+        context,
+        PageRoutes.fade(
+            () => screen, RouteDurations.slow.inMilliseconds * .001));
+  }
+
+  void _handleAuthenticated(BuildContext context, Authenticated result) {
+    _pushToScreen(
+        context, getIt<IWelcomeRoute>().pushHomeScreen(result.userDetail));
+  }
+
+  void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
+    Log.error(result.error);
+
+    _pushToScreen(context, getIt<IWelcomeRoute>().pushSignInScreen());
+  }
+}
+
+class Body extends StatelessWidget {
+  const Body({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    var size = MediaQuery.of(context).size;
+
+    return Container(
+      alignment: Alignment.center,
+      child: SingleChildScrollView(
+        child: Stack(
+          alignment: Alignment.center,
+          children: [
+            Image(
+                fit: BoxFit.cover,
+                width: size.width,
+                height: size.height,
+                image: const AssetImage(
+                    'assets/images/appflowy_launch_splash.jpg')),
+            const CircularProgressIndicator.adaptive(),
+          ],
+        ),
+      ),
+    );
   }
 }

+ 0 - 27
app_flowy/lib/welcome/presentation/widgets/body.dart

@@ -1,27 +0,0 @@
-import 'package:flutter/material.dart';
-
-class Body extends StatelessWidget {
-  const Body({Key? key}) : super(key: key);
-  @override
-  Widget build(BuildContext context) {
-    var size = MediaQuery.of(context).size;
-
-    return Container(
-      alignment: Alignment.center,
-      child: SingleChildScrollView(
-        child: Stack(
-          alignment: Alignment.center,
-          children: [
-            Image(
-                fit: BoxFit.cover,
-                width: size.width,
-                height: size.height,
-                image: const AssetImage(
-                    'assets/images/appflowy_launch_splash.jpg')),
-            const CircularProgressIndicator.adaptive(),
-          ],
-        ),
-      ),
-    );
-  }
-}

+ 4 - 4
app_flowy/packages/flowy_logger/pubspec.lock

@@ -7,7 +7,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -28,7 +28,7 @@ packages:
       name: charcode
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   clock:
     dependency: transitive
     description:
@@ -94,7 +94,7 @@ packages:
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.4.0"
   path:
     dependency: transitive
     description:
@@ -148,7 +148,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   typed_data:
     dependency: transitive
     description:

+ 6 - 6
app_flowy/packages/flowy_sdk/example/pubspec.lock

@@ -14,7 +14,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -35,7 +35,7 @@ packages:
       name: charcode
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   clock:
     dependency: transitive
     description:
@@ -91,7 +91,7 @@ packages:
       name: file
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "6.1.1"
+    version: "6.1.2"
   fixnum:
     dependency: transitive
     description:
@@ -200,7 +200,7 @@ packages:
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.4.0"
   path:
     dependency: transitive
     description:
@@ -282,7 +282,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   time:
     dependency: transitive
     description:
@@ -317,7 +317,7 @@ packages:
       name: vm_service
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "6.2.0"
+    version: "7.1.0"
   webdriver:
     dependency: transitive
     description:

+ 1 - 19
app_flowy/packages/flowy_sdk/lib/flowy_sdk.dart

@@ -2,15 +2,12 @@ export 'package:async/async.dart';
 
 import 'dart:io';
 import 'dart:async';
-import 'package:dartz/dartz.dart';
+// import 'package:dartz/dartz.dart';
 import 'package:flutter/services.dart';
 import 'dart:ffi';
 import 'ffi/ffi.dart' as ffi;
 import 'package:ffi/ffi.dart';
 
-import 'package:flowy_sdk/protobuf.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-
 class FlowySDK {
   static const MethodChannel _channel = MethodChannel('flowy_sdk');
   static Future<String> get platformVersion async {
@@ -24,21 +21,6 @@ class FlowySDK {
 
   Future<void> init(Directory sdkDir) async {
     ffi.store_dart_post_cobject(NativeApi.postCObject);
-
     ffi.init_sdk(sdkDir.path.toNativeUtf8());
-
-    final params = SignInRequest.create();
-    params.email = "[email protected]";
-    params.password = "Helloworld!2";
-    Either<UserDetail, UserError> resp = await UserEventSignIn(params).send();
-
-    resp.fold(
-      (result) {
-        print(result);
-      },
-      (error) {
-        print(error);
-      },
-    );
   }
 }

+ 2 - 2
app_flowy/packages/flowy_sdk/lib/protobuf.dart

@@ -1,10 +1,10 @@
-// Auto-generated, do not edit 
+// Auto-generated, do not edit
 export 'protobuf/kv.pb.dart';
 export 'protobuf/ffi_response.pb.dart';
 export 'protobuf/ffi_request.pb.dart';
-export 'protobuf/user_status.pb.dart';
 export 'protobuf/sign_up.pb.dart';
 export 'protobuf/sign_in.pb.dart';
 export 'protobuf/user_table.pb.dart';
 export 'protobuf/errors.pb.dart';
+export 'protobuf/user_detail.pb.dart';
 export 'protobuf/event.pb.dart';

+ 3 - 3
app_flowy/packages/flowy_sdk/lib/protobuf/user_status.pb.dart → app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pb.dart

@@ -1,6 +1,6 @@
 ///
 //  Generated code. Do not modify.
-//  source: user_status.proto
+//  source: user_detail.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
@@ -9,9 +9,9 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
-import 'user_status.pbenum.dart';
+import 'user_detail.pbenum.dart';
 
-export 'user_status.pbenum.dart';
+export 'user_detail.pbenum.dart';
 
 class UserDetail extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserDetail', createEmptyInstance: create)

+ 1 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/user_status.pbenum.dart → app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pbenum.dart

@@ -1,6 +1,6 @@
 ///
 //  Generated code. Do not modify.
-//  source: user_status.proto
+//  source: user_detail.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields

+ 1 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/user_status.pbjson.dart → app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pbjson.dart

@@ -1,6 +1,6 @@
 ///
 //  Generated code. Do not modify.
-//  source: user_status.proto
+//  source: user_detail.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields

+ 2 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/user_status.pbserver.dart → app_flowy/packages/flowy_sdk/lib/protobuf/user_detail.pbserver.dart

@@ -1,9 +1,9 @@
 ///
 //  Generated code. Do not modify.
-//  source: user_status.proto
+//  source: user_detail.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
 
-export 'user_status.pb.dart';
+export 'user_detail.pb.dart';
 

+ 4 - 4
app_flowy/packages/flowy_sdk/pubspec.lock

@@ -28,7 +28,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -105,7 +105,7 @@ packages:
       name: charcode
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   checked_yaml:
     dependency: transitive
     description:
@@ -325,7 +325,7 @@ packages:
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.4.0"
   mime:
     dependency: transitive
     description:
@@ -449,7 +449,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   time:
     dependency: transitive
     description:

+ 143 - 0
app_flowy/packages/flowy_style/lib/buttons/base_styled_button.dart

@@ -0,0 +1,143 @@
+import 'package:flowy_style/size.dart';
+import 'package:flowy_style/text_style.dart';
+import 'package:flowy_style/theme.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:provider/provider.dart';
+
+class BaseStyledButton extends StatefulWidget {
+  final Widget child;
+  final VoidCallback? onPressed;
+  final Function(bool)? onFocusChanged;
+  final Function(bool)? onHighlightChanged;
+  final Color? bgColor;
+  final Color? focusColor;
+  final Color? hoverColor;
+  final Color? downColor;
+  final EdgeInsets? contentPadding;
+  final double? minWidth;
+  final double? minHeight;
+  final double? borderRadius;
+  final bool useBtnText;
+  final bool autoFocus;
+
+  final ShapeBorder? shape;
+
+  final Color outlineColor;
+
+  const BaseStyledButton({
+    Key? key,
+    required this.child,
+    this.onPressed,
+    this.onFocusChanged,
+    this.onHighlightChanged,
+    this.bgColor,
+    this.focusColor,
+    this.contentPadding,
+    this.minWidth,
+    this.minHeight,
+    this.borderRadius,
+    this.hoverColor,
+    this.downColor,
+    this.shape,
+    this.useBtnText = true,
+    this.autoFocus = false,
+    this.outlineColor = Colors.transparent,
+  }) : super(key: key);
+
+  @override
+  _BaseStyledBtnState createState() => _BaseStyledBtnState();
+}
+
+class _BaseStyledBtnState extends State<BaseStyledButton> {
+  late FocusNode _focusNode;
+  bool _isFocused = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _focusNode = FocusNode(debugLabel: '', canRequestFocus: true);
+    _focusNode.addListener(() {
+      if (_focusNode.hasFocus != _isFocused) {
+        setState(() => _isFocused = _focusNode.hasFocus);
+        widget.onFocusChanged?.call(_isFocused);
+      }
+    });
+  }
+
+  @override
+  void dispose() {
+    _focusNode.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Container(
+      decoration: BoxDecoration(
+        color: widget.bgColor ?? theme.surface,
+        borderRadius: BorderRadius.circular(widget.borderRadius ?? Corners.s5),
+        boxShadow: _isFocused
+            ? [
+                BoxShadow(
+                    color: theme.focus.withOpacity(0.25),
+                    offset: Offset.zero,
+                    blurRadius: 8.0,
+                    spreadRadius: 0.0),
+                BoxShadow(
+                    color: widget.bgColor ?? theme.surface,
+                    offset: Offset.zero,
+                    blurRadius: 8.0,
+                    spreadRadius: -4.0),
+              ]
+            : [],
+      ),
+      foregroundDecoration: _isFocused
+          ? ShapeDecoration(
+              shape: RoundedRectangleBorder(
+                side: BorderSide(
+                  width: 1.8,
+                  color: theme.focus,
+                ),
+                borderRadius:
+                    BorderRadius.circular(widget.borderRadius ?? Corners.s5),
+              ),
+            )
+          : null,
+      child: RawMaterialButton(
+        focusNode: _focusNode,
+        autofocus: widget.autoFocus,
+        textStyle: widget.useBtnText ? TextStyles.Btn : null,
+        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+        visualDensity: VisualDensity.compact,
+        splashColor: Colors.transparent,
+        mouseCursor: SystemMouseCursors.click,
+        elevation: 0,
+        hoverElevation: 0,
+        highlightElevation: 0,
+        focusElevation: 0,
+        fillColor: Colors.transparent,
+        hoverColor: widget.hoverColor ?? theme.surface,
+        highlightColor: widget.downColor ?? theme.accent1.withOpacity(.1),
+        focusColor: widget.focusColor ?? Colors.grey.withOpacity(0.35),
+        child: Opacity(
+          child: Padding(
+            padding: widget.contentPadding ?? EdgeInsets.all(Insets.m),
+            child: widget.child,
+          ),
+          opacity: widget.onPressed != null ? 1 : .7,
+        ),
+        constraints: BoxConstraints(
+            minHeight: widget.minHeight ?? 0, minWidth: widget.minWidth ?? 0),
+        onPressed: widget.onPressed,
+        shape: widget.shape ??
+            RoundedRectangleBorder(
+                side: BorderSide(color: widget.outlineColor, width: 1.5),
+                borderRadius:
+                    BorderRadius.circular(widget.borderRadius ?? Corners.s5)),
+      ),
+    );
+  }
+}

+ 40 - 0
app_flowy/packages/flowy_style/lib/buttons/ok_cancel_button.dart

@@ -0,0 +1,40 @@
+import 'package:flowy_style/size.dart';
+import 'package:flowy_style/spacing.dart';
+import 'package:flowy_style/strings.dart';
+import 'package:flutter/material.dart';
+
+import 'primary_button.dart';
+import 'secondary_button.dart';
+
+class OkCancelButton extends StatelessWidget {
+  final VoidCallback? onOkPressed;
+  final VoidCallback? onCancelPressed;
+  final String? okTitle;
+  final String? cancelTitle;
+  final double? minHeight;
+
+  const OkCancelButton(
+      {Key? key,
+      this.onOkPressed,
+      this.onCancelPressed,
+      this.okTitle,
+      this.cancelTitle,
+      this.minHeight})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.start,
+      children: <Widget>[
+        if (onOkPressed != null)
+          PrimaryTextButton(okTitle ?? S.BTN_OK.toUpperCase(),
+              onPressed: onOkPressed),
+        HSpace(Insets.m),
+        if (onCancelPressed != null)
+          SecondaryTextButton(cancelTitle ?? S.BTN_CANCEL.toUpperCase(),
+              onPressed: onCancelPressed),
+      ],
+    );
+  }
+}

+ 54 - 0
app_flowy/packages/flowy_style/lib/buttons/primary_button.dart

@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import '../size.dart';
+import '../text_style.dart';
+import '../theme.dart';
+import 'base_styled_button.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:textstyle_extensions/textstyle_extensions.dart';
+
+class PrimaryButton extends StatelessWidget {
+  final Widget child;
+  final VoidCallback? onPressed;
+  final bool bigMode;
+
+  const PrimaryButton(
+      {Key? key, required this.child, this.onPressed, this.bigMode = false})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BaseStyledButton(
+      minWidth: bigMode ? 160 : 78,
+      minHeight: bigMode ? 60 : 42,
+      contentPadding: EdgeInsets.all(bigMode ? Insets.l : Insets.m),
+      bgColor: theme.accent1Darker,
+      hoverColor: theme.isDark ? theme.accent1 : theme.accent1Dark,
+      downColor: theme.accent1Darker,
+      borderRadius: bigMode ? Corners.s8 : Corners.s5,
+      child: child,
+      onPressed: onPressed,
+    );
+  }
+}
+
+class PrimaryTextButton extends StatelessWidget {
+  final String label;
+  final VoidCallback? onPressed;
+  final bool bigMode;
+
+  const PrimaryTextButton(this.label,
+      {Key? key, this.onPressed, this.bigMode = false})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    TextStyle txtStyle = (bigMode ? TextStyles.Callout : TextStyles.Footnote)
+        .textColor(Colors.white);
+    return PrimaryButton(
+        bigMode: bigMode,
+        onPressed: onPressed,
+        child: Text(label, style: txtStyle));
+  }
+}

+ 98 - 0
app_flowy/packages/flowy_style/lib/buttons/secondary_button.dart

@@ -0,0 +1,98 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:textstyle_extensions/textstyle_extensions.dart';
+
+import '../size.dart';
+import '../styled_image_icon.dart';
+import '../text_style.dart';
+import '../theme.dart';
+import 'base_styled_button.dart';
+
+class SecondaryTextButton extends StatelessWidget {
+  final String label;
+  final VoidCallback? onPressed;
+
+  const SecondaryTextButton(this.label, {Key? key, this.onPressed})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    TextStyle txtStyle = TextStyles.Footnote.textColor(theme.accent1Darker);
+    return SecondaryButton(
+        onPressed: onPressed, child: Text(label, style: txtStyle));
+  }
+}
+
+class SecondaryIconButton extends StatelessWidget {
+  /// Must be either an `AssetImage` for an `ImageIcon` or an `IconData` for a regular `Icon`
+  final AssetImage icon;
+  final Function()? onPressed;
+  final Color? color;
+
+  const SecondaryIconButton(this.icon, {Key? key, this.onPressed, this.color})
+      : assert((icon is AssetImage) || (icon is IconData)),
+        super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return SecondaryButton(
+      onPressed: onPressed,
+      minHeight: 36,
+      minWidth: 36,
+      contentPadding: Insets.sm,
+      child: StyledImageIcon(icon, size: 20, color: color ?? theme.grey),
+    );
+  }
+}
+
+class SecondaryButton extends StatefulWidget {
+  final Widget child;
+  final VoidCallback? onPressed;
+  final double? minWidth;
+  final double? minHeight;
+  final double? contentPadding;
+  final Function(bool)? onFocusChanged;
+
+  const SecondaryButton(
+      {Key? key,
+      required this.child,
+      this.onPressed,
+      this.minWidth,
+      this.minHeight,
+      this.contentPadding,
+      this.onFocusChanged})
+      : super(key: key);
+
+  @override
+  _SecondaryButtonState createState() => _SecondaryButtonState();
+}
+
+class _SecondaryButtonState extends State<SecondaryButton> {
+  bool _isMouseOver = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return MouseRegion(
+      onEnter: (_) => setState(() => _isMouseOver = true),
+      onExit: (_) => setState(() => _isMouseOver = false),
+      child: BaseStyledButton(
+        minWidth: widget.minWidth ?? 78,
+        minHeight: widget.minHeight ?? 42,
+        contentPadding: EdgeInsets.all(widget.contentPadding ?? Insets.m),
+        bgColor: theme.surface,
+        outlineColor:
+            (_isMouseOver ? theme.accent1 : theme.grey).withOpacity(.35),
+        hoverColor: theme.surface,
+        onFocusChanged: widget.onFocusChanged,
+        downColor: theme.greyWeak.withOpacity(.35),
+        borderRadius: Corners.s5,
+        child: IgnorePointer(child: widget.child),
+        onPressed: widget.onPressed,
+      ),
+    );
+  }
+}

+ 16 - 0
app_flowy/packages/flowy_style/lib/clickable_extension.dart

@@ -0,0 +1,16 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+
+extension ClickableExtensions on Widget {
+  Widget clickable(void Function() action, {bool opaque = true}) {
+    return GestureDetector(
+      behavior: opaque ? HitTestBehavior.opaque : HitTestBehavior.deferToChild,
+      onTap: action,
+      child: MouseRegion(
+        cursor: SystemMouseCursors.click,
+        opaque: opaque,
+        child: this,
+      ),
+    );
+  }
+}

+ 38 - 0
app_flowy/packages/flowy_style/lib/constraint_flex_view.dart

@@ -0,0 +1,38 @@
+import 'package:flutter/material.dart';
+
+class ConstrainedFlexView extends StatelessWidget {
+  final Widget child;
+  final double minSize;
+  final Axis axis;
+  final EdgeInsets scrollPadding;
+
+  const ConstrainedFlexView(this.minSize,
+      {Key? key,
+      required this.child,
+      this.axis = Axis.horizontal,
+      this.scrollPadding = EdgeInsets.zero})
+      : super(key: key);
+
+  bool get isHz => axis == Axis.horizontal;
+
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(
+      builder: (_, constraints) {
+        final viewSize = isHz ? constraints.maxWidth : constraints.maxHeight;
+        if (viewSize > minSize) return child;
+        return Padding(
+          padding: scrollPadding,
+          child: SingleChildScrollView(
+            child: ConstrainedBox(
+              constraints: BoxConstraints(
+                  maxHeight: isHz ? double.infinity : minSize,
+                  maxWidth: isHz ? minSize : double.infinity),
+              child: child,
+            ),
+          ),
+        );
+      },
+    );
+  }
+}

+ 10 - 0
app_flowy/packages/flowy_style/lib/dialog/dialog_context.dart

@@ -0,0 +1,10 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+
+abstract class DialogContext extends Equatable {
+  bool get barrierDismissable => true;
+  final String identifier;
+
+  const DialogContext({required this.identifier});
+  Widget buildWiget(BuildContext context);
+}

+ 3 - 0
app_flowy/packages/flowy_style/lib/dialog/dialog_size.dart

@@ -0,0 +1,3 @@
+class DialogSize {
+  static double get minDialogWidth => 380;
+}

+ 218 - 0
app_flowy/packages/flowy_style/lib/dialog/styled_dialogs.dart

@@ -0,0 +1,218 @@
+import 'package:flowy_style/buttons/ok_cancel_button.dart';
+import 'package:flowy_style/dialog/dialog_size.dart';
+import 'package:flowy_style/scrolling/styled_list.dart';
+import 'package:flowy_style/size.dart';
+import 'package:flowy_style/spacing.dart';
+import 'package:flowy_style/text_style.dart';
+import 'package:flowy_style/theme.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:textstyle_extensions/textstyle_extensions.dart';
+
+import 'dialog_context.dart';
+export 'dialog_context.dart';
+
+class Dialogs {
+  static Future<dynamic> show(Widget child, BuildContext context) async {
+    return await Navigator.of(context).push(
+      StyledDialogRoute(
+        pageBuilder: (BuildContext buildContext, Animation<double> animation,
+            Animation<double> secondaryAnimation) {
+          return SafeArea(child: child);
+        },
+      ),
+    );
+    /*return await showDialog(
+      context: context ?? MainViewContext.value,
+      builder: (context) => child,
+    );*/
+  }
+
+  static Future<dynamic> showWithContext(
+      DialogContext dialogContext, BuildContext context) async {
+    return await Navigator.of(context).push(
+      StyledDialogRoute(
+        barrierDismissible: dialogContext.barrierDismissable,
+        pageBuilder: (BuildContext buildContext, Animation<double> animation,
+            Animation<double> secondaryAnimation) {
+          return SafeArea(child: dialogContext.buildWiget(buildContext));
+        },
+      ),
+    );
+  }
+}
+
+class StyledDialogRoute<T> extends PopupRoute<T> {
+  StyledDialogRoute({
+    required RoutePageBuilder pageBuilder,
+    bool barrierDismissible = false,
+    String? barrierLabel,
+    Color barrierColor = const Color(0x80000000),
+    Duration transitionDuration = const Duration(milliseconds: 200),
+    RouteTransitionsBuilder? transitionBuilder,
+    RouteSettings? settings,
+  })  : _pageBuilder = pageBuilder,
+        _barrierDismissible = barrierDismissible,
+        _barrierLabel = barrierLabel ?? '',
+        _barrierColor = barrierColor,
+        _transitionDuration = transitionDuration,
+        _transitionBuilder = transitionBuilder,
+        super(settings: settings);
+
+  final RoutePageBuilder _pageBuilder;
+
+  @override
+  bool get barrierDismissible => _barrierDismissible;
+  final bool _barrierDismissible;
+
+  @override
+  String get barrierLabel => _barrierLabel;
+  final String _barrierLabel;
+
+  @override
+  Color get barrierColor => _barrierColor;
+  final Color _barrierColor;
+
+  @override
+  Duration get transitionDuration => _transitionDuration;
+  final Duration _transitionDuration;
+
+  final RouteTransitionsBuilder? _transitionBuilder;
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation,
+      Animation<double> secondaryAnimation) {
+    return Semantics(
+      child: _pageBuilder(context, animation, secondaryAnimation),
+      scopesRoute: true,
+      explicitChildNodes: true,
+    );
+  }
+
+  @override
+  Widget buildTransitions(BuildContext context, Animation<double> animation,
+      Animation<double> secondaryAnimation, Widget child) {
+    if (_transitionBuilder == null) {
+      return FadeTransition(
+          opacity: CurvedAnimation(parent: animation, curve: Curves.linear),
+          child: child);
+    } else {
+      return _transitionBuilder!(context, animation, secondaryAnimation, child);
+    } // Some default transition
+  }
+}
+
+class StyledDialog extends StatelessWidget {
+  final Widget child;
+  final double? maxWidth;
+  final double? maxHeight;
+  final EdgeInsets? padding;
+  final EdgeInsets? margin;
+  final BorderRadius? borderRadius;
+  final Color? bgColor;
+  final bool shrinkWrap;
+
+  const StyledDialog({
+    Key? key,
+    required this.child,
+    this.maxWidth,
+    this.maxHeight,
+    this.padding,
+    this.margin,
+    this.bgColor,
+    this.borderRadius,
+    this.shrinkWrap = true,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final radius = borderRadius ?? Corners.s8Border;
+    final theme = context.watch<AppTheme>();
+
+    Widget innerContent = Container(
+      padding: padding ?? EdgeInsets.all(Insets.lGutter),
+      color: bgColor ?? theme.surface,
+      child: child,
+    );
+
+    if (shrinkWrap) {
+      innerContent =
+          IntrinsicWidth(child: IntrinsicHeight(child: innerContent));
+    }
+
+    return FocusTraversalGroup(
+      child: Container(
+        margin: margin ?? EdgeInsets.all(Insets.lGutter * 2),
+        alignment: Alignment.center,
+        child: ConstrainedBox(
+          constraints: BoxConstraints(
+            minWidth: DialogSize.minDialogWidth,
+            maxHeight: maxHeight ?? double.infinity,
+            maxWidth: maxWidth ?? double.infinity,
+          ),
+          child: ClipRRect(
+            borderRadius: radius,
+            child: SingleChildScrollView(
+              physics: StyledScrollPhysics(),
+              //https://medium.com/saugo360/https-medium-com-saugo360-flutter-using-overlay-to-display-floating-widgets-2e6d0e8decb9
+              child: Material(
+                type: MaterialType.transparency,
+                child: innerContent,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class OkCancelDialog extends StatelessWidget {
+  final VoidCallback? onOkPressed;
+  final VoidCallback? onCancelPressed;
+  final String? okTitle;
+  final String? cancelTitle;
+  final String? title;
+  final String message;
+  final double? maxWidth;
+
+  const OkCancelDialog(
+      {Key? key,
+      this.onOkPressed,
+      this.onCancelPressed,
+      this.okTitle,
+      this.cancelTitle,
+      this.title,
+      required this.message,
+      this.maxWidth})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return StyledDialog(
+      maxWidth: maxWidth ?? 500,
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: <Widget>[
+          if (title != null) ...[
+            Text(title!.toUpperCase(),
+                style: TextStyles.T1.textColor(theme.accent1Darker)),
+            VSpace(Insets.sm * 1.5),
+            Container(color: theme.greyWeak.withOpacity(.35), height: 1),
+            VSpace(Insets.m * 1.5),
+          ],
+          Text(message, style: TextStyles.Body1.textHeight(1.5)),
+          SizedBox(height: Insets.l),
+          OkCancelButton(
+            onOkPressed: onOkPressed,
+            onCancelPressed: onCancelPressed,
+            okTitle: okTitle?.toUpperCase(),
+            cancelTitle: cancelTitle?.toUpperCase(),
+          )
+        ],
+      ),
+    );
+  }
+}

+ 35 - 0
app_flowy/packages/flowy_style/lib/mouse_hover_builder.dart

@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+
+typedef HoverBuilder = Widget Function(BuildContext context, bool isHovering);
+
+class MouseHoverBuilder extends StatefulWidget {
+  final bool isClickable;
+
+  const MouseHoverBuilder(
+      {Key? key, required this.builder, this.isClickable = false})
+      : super(key: key);
+
+  final HoverBuilder builder;
+
+  @override
+  _MouseHoverBuilderState createState() => _MouseHoverBuilderState();
+}
+
+class _MouseHoverBuilderState extends State<MouseHoverBuilder> {
+  bool isOver = false;
+
+  @override
+  Widget build(BuildContext context) {
+    return MouseRegion(
+      cursor: widget.isClickable
+          ? SystemMouseCursors.click
+          : SystemMouseCursors.basic,
+      onEnter: (p) => setOver(true),
+      onExit: (p) => setOver(false),
+      child: widget.builder(context, isOver),
+    );
+  }
+
+  void setOver(bool value) => setState(() => isOver = value);
+}

+ 33 - 0
app_flowy/packages/flowy_style/lib/rounded_button.dart

@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+
+class RoundedButton extends StatelessWidget {
+  final VoidCallback? press;
+  final String? title;
+  final Size? size;
+
+  const RoundedButton({
+    Key? key,
+    this.press,
+    this.title,
+    this.size,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ConstrainedBox(
+      constraints: BoxConstraints(
+        minWidth: 100,
+        maxWidth: size?.width ?? double.infinity,
+        minHeight: 50,
+        maxHeight: size?.height ?? double.infinity,
+      ),
+      child: Container(
+        margin: const EdgeInsets.symmetric(vertical: 10),
+        child: TextButton(
+          child: Text(title ?? ''),
+          onPressed: press,
+        ),
+      ),
+    );
+  }
+}

+ 35 - 0
app_flowy/packages/flowy_style/lib/rounded_input_field.dart

@@ -0,0 +1,35 @@
+import 'package:flowy_style/text_field_container.dart';
+import 'package:flutter/material.dart';
+
+class RoundedInputField extends StatelessWidget {
+  final String? hintText;
+  final IconData? icon;
+  final bool obscureText;
+  final ValueChanged<String>? onChanged;
+
+  const RoundedInputField({
+    Key? key,
+    this.hintText,
+    this.icon = Icons.person,
+    this.obscureText = false,
+    this.onChanged,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return TextFieldContainer(
+        child: TextFormField(
+      onChanged: onChanged,
+      cursorColor: const Color(0xFF6F35A5),
+      obscureText: obscureText,
+      decoration: InputDecoration(
+        icon: Icon(
+          icon,
+          color: const Color(0xFF6F35A5),
+        ),
+        hintText: hintText,
+        border: InputBorder.none,
+      ),
+    ));
+  }
+}

+ 83 - 0
app_flowy/packages/flowy_style/lib/scrolling/styled_list.dart

@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+
+import 'styled_scroll_bar.dart';
+
+class StyledScrollPhysics extends AlwaysScrollableScrollPhysics {}
+
+/// Core ListView for the app.
+/// Wraps a [ScrollbarListStack] + [ListView.builder] and assigns the 'Styled' scroll physics for the app
+/// Exposes a controller so other widgets can manipulate the list
+class StyledListView extends StatefulWidget {
+  final double? itemExtent;
+  final int? itemCount;
+  final Axis axis;
+  final EdgeInsets? padding;
+  final EdgeInsets? scrollbarPadding;
+  final double? barSize;
+
+  final IndexedWidgetBuilder itemBuilder;
+
+  StyledListView({
+    Key? key,
+    required this.itemBuilder,
+    required this.itemCount,
+    this.itemExtent,
+    this.axis = Axis.vertical,
+    this.padding,
+    this.barSize,
+    this.scrollbarPadding,
+  }) : super(key: key) {
+    assert(itemExtent != 0, 'Item extent should never be 0, null is ok.');
+  }
+
+  @override
+  StyledListViewState createState() => StyledListViewState();
+}
+
+/// State is public so this can easily be controlled externally
+class StyledListViewState extends State<StyledListView> {
+  late ScrollController scrollController;
+
+  @override
+  void initState() {
+    scrollController = ScrollController();
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    scrollController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(StyledListView oldWidget) {
+    if (oldWidget.itemCount != widget.itemCount ||
+        oldWidget.itemExtent != widget.itemExtent) {
+      setState(() {});
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final contentSize = (widget.itemCount ?? 0.0) * (widget.itemExtent ?? 00.0);
+    Widget listContent = ScrollbarListStack(
+      contentSize: contentSize,
+      axis: widget.axis,
+      controller: scrollController,
+      barSize: widget.barSize ?? 12,
+      scrollbarPadding: widget.scrollbarPadding,
+      child: ListView.builder(
+        padding: widget.padding,
+        scrollDirection: widget.axis,
+        physics: StyledScrollPhysics(),
+        controller: scrollController,
+        itemExtent: widget.itemExtent,
+        itemCount: widget.itemCount,
+        itemBuilder: (c, i) => widget.itemBuilder(c, i),
+      ),
+    );
+    return listContent;
+  }
+}

+ 243 - 0
app_flowy/packages/flowy_style/lib/scrolling/styled_scroll_bar.dart

@@ -0,0 +1,243 @@
+import 'dart:math';
+
+import 'package:flowy_style/mouse_hover_builder.dart';
+import 'package:flowy_style/size.dart';
+import 'package:flowy_style/theme.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+class StyledScrollbar extends StatefulWidget {
+  final double? size;
+  final Axis axis;
+  final ScrollController controller;
+  final Function(double)? onDrag;
+  final bool showTrack;
+  final Color? handleColor;
+  final Color? trackColor;
+
+  // ignore: todo
+  // TODO: Remove contentHeight if we can fix this issue
+  // https://stackoverflow.com/questions/60855712/flutter-how-to-force-scrollcontroller-to-recalculate-position-maxextents
+  final double? contentSize;
+
+  const StyledScrollbar(
+      {Key? key,
+      this.size,
+      required this.axis,
+      required this.controller,
+      this.onDrag,
+      this.contentSize,
+      this.showTrack = false,
+      this.handleColor,
+      this.trackColor})
+      : super(key: key);
+
+  @override
+  ScrollbarState createState() => ScrollbarState();
+}
+
+class ScrollbarState extends State<StyledScrollbar> {
+  double _viewExtent = 100;
+
+  @override
+  void initState() {
+    widget.controller.addListener(() => setState(() {}));
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(StyledScrollbar oldWidget) {
+    if (oldWidget.contentSize != widget.contentSize) setState(() {});
+    super.didUpdateWidget(oldWidget);
+  }
+
+//  void calculateSize() {
+//    //[SB] Only hack I can find  to make the ScrollController update it's maxExtents.
+//    //Call this whenever the content changes, so the scrollbar can recalculate it's size
+//    widget.controller.jumpTo(widget.controller.position.pixels + 1);
+//    Future.microtask(() => widget.controller
+//        .animateTo(widget.controller.position.pixels - 1, duration: 100.milliseconds, curve: Curves.linear));
+//  }
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return LayoutBuilder(
+      builder: (_, BoxConstraints constraints) {
+        double maxExtent;
+        final contentSize = widget.contentSize;
+        switch (widget.axis) {
+          case Axis.vertical:
+            // Use supplied contentSize if we have it, otherwise just fallback to maxScrollExtents
+            if (contentSize != null && contentSize > 0) {
+              maxExtent = contentSize - constraints.maxHeight;
+            } else {
+              maxExtent = widget.controller.position.maxScrollExtent;
+            }
+
+            _viewExtent = constraints.maxHeight;
+
+            break;
+          case Axis.horizontal:
+            // Use supplied contentSize if we have it, otherwise just fallback to maxScrollExtents
+
+            if (contentSize != null && contentSize > 0) {
+              maxExtent = contentSize - constraints.maxWidth;
+            } else {
+              maxExtent = widget.controller.position.maxScrollExtent;
+            }
+            _viewExtent = constraints.maxWidth;
+
+            break;
+        }
+
+        final contentExtent = maxExtent + _viewExtent;
+        // Calculate the alignment for the handle, this is a value between 0 and 1,
+        // it automatically takes the handle size into acct
+        // ignore: omit_local_variable_types
+        double handleAlignment =
+            maxExtent == 0 ? 0 : widget.controller.offset / maxExtent;
+
+        // Convert handle alignment from [0, 1] to [-1, 1]
+        handleAlignment *= 2.0;
+        handleAlignment -= 1.0;
+
+        // Calculate handleSize by comparing the total content size to our viewport
+        var handleExtent = _viewExtent;
+        if (contentExtent > _viewExtent) {
+          //Make sure handle is never small than the minSize
+          handleExtent = max(60, _viewExtent * _viewExtent / contentExtent);
+        }
+        // Hide the handle if content is < the viewExtent
+        var showHandle = contentExtent > _viewExtent && contentExtent > 0;
+        // Handle color
+        var handleColor = widget.handleColor ??
+            (theme.isDark ? theme.greyWeak.withOpacity(.2) : theme.greyWeak);
+        // Track color
+        var trackColor = widget.trackColor ??
+            (theme.isDark
+                ? theme.greyWeak.withOpacity(.1)
+                : theme.greyWeak.withOpacity(.3));
+
+        //Layout the stack, it just contains a child, and
+        return Stack(children: <Widget>[
+          /// TRACK, thin strip, aligned along the end of the parent
+          if (widget.showTrack)
+            Align(
+              alignment: const Alignment(1, 1),
+              child: Container(
+                color: trackColor,
+                width: widget.axis == Axis.vertical
+                    ? widget.size
+                    : double.infinity,
+                height: widget.axis == Axis.horizontal
+                    ? widget.size
+                    : double.infinity,
+              ),
+            ),
+
+          /// HANDLE - Clickable shape that changes scrollController when dragged
+          Align(
+            // Use calculated alignment to position handle from -1 to 1, let Alignment do the rest of the work
+            alignment: Alignment(
+              widget.axis == Axis.vertical ? 1 : handleAlignment,
+              widget.axis == Axis.horizontal ? 1 : handleAlignment,
+            ),
+            child: GestureDetector(
+              onVerticalDragUpdate: _handleVerticalDrag,
+              onHorizontalDragUpdate: _handleHorizontalDrag,
+              // HANDLE SHAPE
+              child: MouseHoverBuilder(
+                builder: (_, isHovered) => Container(
+                  width:
+                      widget.axis == Axis.vertical ? widget.size : handleExtent,
+                  height: widget.axis == Axis.horizontal
+                      ? widget.size
+                      : handleExtent,
+                  decoration: BoxDecoration(
+                      color: handleColor.withOpacity(isHovered ? 1 : .85),
+                      borderRadius: Corners.s3Border),
+                ),
+              ),
+            ),
+          )
+        ]).opacity(showHandle ? 1.0 : 0.0, animate: false);
+      },
+    );
+  }
+
+  void _handleHorizontalDrag(DragUpdateDetails details) {
+    var pos = widget.controller.offset;
+    var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) /
+        _viewExtent;
+    widget.controller.jumpTo((pos + details.delta.dx * pxRatio)
+        .clamp(0.0, widget.controller.position.maxScrollExtent));
+    widget.onDrag?.call(details.delta.dx);
+  }
+
+  void _handleVerticalDrag(DragUpdateDetails details) {
+    var pos = widget.controller.offset;
+    var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) /
+        _viewExtent;
+    widget.controller.jumpTo((pos + details.delta.dy * pxRatio)
+        .clamp(0.0, widget.controller.position.maxScrollExtent));
+    widget.onDrag?.call(details.delta.dy);
+  }
+}
+
+class ScrollbarListStack extends StatelessWidget {
+  final double barSize;
+  final Axis axis;
+  final Widget child;
+  final ScrollController controller;
+  final double? contentSize;
+  final EdgeInsets? scrollbarPadding;
+  final Color? handleColor;
+  final Color? trackColor;
+
+  const ScrollbarListStack(
+      {Key? key,
+      required this.barSize,
+      required this.axis,
+      required this.child,
+      required this.controller,
+      this.contentSize,
+      this.scrollbarPadding,
+      this.handleColor,
+      this.trackColor})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      children: <Widget>[
+        /// LIST
+        /// Wrap with a bit of padding on the right
+        child.padding(
+          right: axis == Axis.vertical ? barSize + Insets.sm : 0,
+          bottom: axis == Axis.horizontal ? barSize + Insets.sm : 0,
+        ),
+
+        /// SCROLLBAR
+        Padding(
+          padding: scrollbarPadding ?? EdgeInsets.zero,
+          child: StyledScrollbar(
+            size: barSize,
+            axis: axis,
+            controller: controller,
+            contentSize: contentSize,
+            trackColor: trackColor,
+            handleColor: handleColor,
+            showTrack: true,
+          ),
+        ),
+      ],
+    );
+  }
+}

+ 135 - 0
app_flowy/packages/flowy_style/lib/scrolling/styled_scrollview.dart

@@ -0,0 +1,135 @@
+import 'package:flutter/material.dart';
+
+import 'styled_list.dart';
+import 'styled_scroll_bar.dart';
+
+class StyledSingleChildScrollView extends StatefulWidget {
+  final double? contentSize;
+  final Axis axis;
+  final Color? trackColor;
+  final Color? handleColor;
+  final ScrollController? controller;
+
+  final Widget? child;
+
+  const StyledSingleChildScrollView({
+    Key? key,
+    @required this.child,
+    this.contentSize,
+    this.axis = Axis.vertical,
+    this.trackColor,
+    this.handleColor,
+    this.controller,
+  }) : super(key: key);
+
+  @override
+  _StyledSingleChildScrollViewState createState() =>
+      _StyledSingleChildScrollViewState();
+}
+
+class _StyledSingleChildScrollViewState
+    extends State<StyledSingleChildScrollView> {
+  late ScrollController scrollController;
+
+  @override
+  void initState() {
+    scrollController = widget.controller ?? ScrollController();
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    // scrollController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(StyledSingleChildScrollView oldWidget) {
+    if (oldWidget.child != widget.child) {
+      setState(() {});
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return ScrollbarListStack(
+      contentSize: widget.contentSize,
+      axis: widget.axis,
+      controller: scrollController,
+      barSize: 12,
+      trackColor: widget.trackColor,
+      handleColor: widget.handleColor,
+      child: SingleChildScrollView(
+        scrollDirection: widget.axis,
+        physics: StyledScrollPhysics(),
+        controller: scrollController,
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+class StyledCustomScrollView extends StatefulWidget {
+  final double? contentSize;
+  final Axis axis;
+  final Color? trackColor;
+  final Color? handleColor;
+  final ScrollController? controller;
+  final List<Widget> slivers;
+
+  const StyledCustomScrollView({
+    Key? key,
+    this.contentSize,
+    this.axis = Axis.vertical,
+    this.trackColor,
+    this.handleColor,
+    this.controller,
+    this.slivers = const <Widget>[],
+  }) : super(key: key);
+
+  @override
+  _StyledCustomScrollViewState createState() => _StyledCustomScrollViewState();
+}
+
+class _StyledCustomScrollViewState extends State<StyledCustomScrollView> {
+  late ScrollController scrollController;
+
+  @override
+  void initState() {
+    scrollController = widget.controller ?? ScrollController();
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    scrollController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(StyledCustomScrollView oldWidget) {
+    if (oldWidget.slivers != widget.slivers) {
+      setState(() {});
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return ScrollbarListStack(
+      contentSize: widget.contentSize,
+      axis: widget.axis,
+      controller: scrollController,
+      barSize: 12,
+      trackColor: widget.trackColor,
+      handleColor: widget.handleColor,
+      child: CustomScrollView(
+        scrollDirection: widget.axis,
+        physics: StyledScrollPhysics(),
+        controller: scrollController,
+        slivers: widget.slivers,
+      ),
+    );
+  }
+}

+ 43 - 0
app_flowy/packages/flowy_style/lib/seperated_column.dart

@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+
+typedef SeparatorBuilder = Widget Function();
+
+class SeparatedColumn extends StatelessWidget {
+  final List<Widget> children;
+  final SeparatorBuilder? separatorBuilder;
+  final MainAxisAlignment mainAxisAlignment;
+  final CrossAxisAlignment crossAxisAlignment;
+  final MainAxisSize mainAxisSize;
+  final TextBaseline? textBaseline;
+  final TextDirection? textDirection;
+  final VerticalDirection verticalDirection;
+
+  const SeparatedColumn({
+    Key? key,
+    required this.children,
+    this.separatorBuilder,
+    this.mainAxisAlignment = MainAxisAlignment.start,
+    this.crossAxisAlignment = CrossAxisAlignment.center,
+    this.mainAxisSize = MainAxisSize.max,
+    this.verticalDirection = VerticalDirection.down,
+    this.textBaseline,
+    this.textDirection,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    var c = children.toList();
+    for (var i = c.length; i-- > 0;) {
+      if (i > 0 && separatorBuilder != null) c.insert(i, separatorBuilder!());
+    }
+    return Column(
+      children: c,
+      mainAxisAlignment: mainAxisAlignment,
+      crossAxisAlignment: crossAxisAlignment,
+      mainAxisSize: mainAxisSize,
+      textBaseline: textBaseline,
+      textDirection: textDirection,
+      verticalDirection: verticalDirection,
+    );
+  }
+}

+ 94 - 0
app_flowy/packages/flowy_style/lib/size.dart

@@ -0,0 +1,94 @@
+import 'package:flutter/material.dart';
+
+class PageBreaks {
+  static double get largePhone => 550;
+
+  static double get tabletPortrait => 768;
+
+  static double get tabletLandscape => 1024;
+
+  static double get desktop => 1440;
+}
+
+class Insets {
+  static double gutterScale = 1;
+
+  static double scale = 1;
+
+  /// Dynamic insets, may get scaled with the device size
+  static double get mGutter => m * gutterScale;
+
+  static double get lGutter => l * gutterScale;
+
+  static double get xs => 2 * scale;
+
+  static double get sm => 6 * scale;
+
+  static double get m => 12 * scale;
+
+  static double get l => 24 * scale;
+
+  static double get xl => 36 * scale;
+}
+
+class FontSizes {
+  static double get scale => 1;
+
+  static double get s11 => 11 * scale;
+
+  static double get s12 => 12 * scale;
+
+  static double get s14 => 14 * scale;
+
+  static double get s16 => 16 * scale;
+
+  static double get s18 => 18 * scale;
+}
+
+class Sizes {
+  static double hitScale = 1;
+
+  static double get hit => 40 * hitScale;
+
+  static double get iconMed => 20;
+
+  static double get sideBarSm => 200 * hitScale;
+
+  static double get sideBarMed => 220 * hitScale;
+
+  static double get sideBarLg => 290 * hitScale;
+}
+
+class Corners {
+  static double get btn => s5;
+
+  static double get dialog => 12;
+
+  /// Xs
+  static double get s3 => 3;
+
+  static BorderRadius get s3Border => BorderRadius.all(s3Radius);
+
+  static Radius get s3Radius => Radius.circular(s3);
+
+  /// Small
+  static double get s5 => 5;
+
+  static BorderRadius get s5Border => BorderRadius.all(s5Radius);
+
+  static Radius get s5Radius => Radius.circular(s5);
+
+  /// Medium
+  static double get s8 => 8;
+
+  static BorderRadius get s8Border => BorderRadius.all(s8Radius);
+
+  static Radius get s8Radius => Radius.circular(s8);
+
+  /// Large
+  static double get s10 => 10;
+
+  static BorderRadius get s10Border => BorderRadius.all(s10Radius);
+
+  static Radius get s10Radius => Radius.circular(s10);
+}

+ 29 - 0
app_flowy/packages/flowy_style/lib/spacing.dart

@@ -0,0 +1,29 @@
+import 'package:flutter/cupertino.dart';
+
+class Space extends StatelessWidget {
+  final double width;
+  final double height;
+
+  const Space(this.width, this.height, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) => SizedBox(width: width, height: height);
+}
+
+class VSpace extends StatelessWidget {
+  final double size;
+
+  const VSpace(this.size, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) => Space(0, size);
+}
+
+class HSpace extends StatelessWidget {
+  final double size;
+
+  const HSpace(this.size, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) => Space(size, 0);
+}

+ 60 - 0
app_flowy/packages/flowy_style/lib/strings.dart

@@ -0,0 +1,60 @@
+// ignore_for_file: non_constant_identifier_names
+
+class _Strings {
+  static _Strings instance = _Strings();
+
+  String TITLE_CONTACTS_PAGE = "Contacts";
+  String TITLE_WHATS_HAPPENING = "What's happening this week?";
+  String TITLE_ADD_CONTACT = "Add Contact";
+  String TITLE_EDIT_CONTACT = "Edit Contact";
+
+  String BTN_OK = "Ok";
+  String BTN_CANCEL = "Cancel";
+  String BTN_SIGN_IN = "Sign In";
+  String BTN_SIGN_OUT = "Sign Out";
+  String BTN_COMPLETE = "Complete";
+  String BTN_SAVE = "Save";
+
+  String LBL_WELCOME = "Welcome!";
+  String LBL_NAME_FIRST = "First Name";
+  String LBL_NAME_MIDDLE = "Middle Name";
+  String LBL_NAME_LAST = "Last Name";
+  String LBL_STEP_X = "Step {0}";
+
+  String ERR_DEVICE_OAUTH_FAILED_TITLE = "Unable to connect to your account.";
+  String ERR_DEVICE_OAUTH_FAILED_MSG =
+      "Please make sure you've completed the sign-in process in your browser.";
+
+  String GOOGLE_OAUTH_TITLE = "GOOGLE SIGN-IN";
+  String GOOGLE_OAUTH_INSTRUCTIONS_1 =
+      "In order to import your Google Contacts, you'll need to authorize this application using your web browser.";
+  String GOOGLE_OAUTH_INSTRUCTIONS_2 =
+      "Copy this code to your clipboard by clicking the icon or selecting the text:";
+  String GOOGLE_OAUTH_INSTRUCTIONS_3 =
+      "Navigate to the following link in your web browser, and enter the above code:";
+  String GOOGLE_OAUTH_INSTRUCTIONS_4 =
+      "Press the button below when you've completed signup:";
+}
+
+_Strings get S => _Strings.instance;
+
+extension AddSupplant on String {
+  String sup(
+      [dynamic v0,
+      dynamic v1,
+      dynamic v2,
+      dynamic v3,
+      dynamic v4,
+      dynamic v5,
+      dynamic v6]) {
+    var _s = this;
+    if (v0 != null) _s = _s.replaceAll("{0}", "$v0");
+    if (v1 != null) _s = _s.replaceAll("{1}", "$v1");
+    if (v2 != null) _s = _s.replaceAll("{2}", "$v2");
+    if (v3 != null) _s = _s.replaceAll("{3}", "$v3");
+    if (v4 != null) _s = _s.replaceAll("{4}", "$v4");
+    if (v5 != null) _s = _s.replaceAll("{5}", "$v5");
+    if (v6 != null) _s = _s.replaceAll("{6}", "$v6");
+    return _s;
+  }
+}

+ 18 - 0
app_flowy/packages/flowy_style/lib/styled_bar_title.dart

@@ -0,0 +1,18 @@
+import 'package:flutter/material.dart';
+
+class StyleBarTitle extends StatelessWidget {
+  final String title;
+
+  const StyleBarTitle({
+    Key? key,
+    required this.title,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(
+      title,
+      style: const TextStyle(fontSize: 24),
+    );
+  }
+}

+ 15 - 0
app_flowy/packages/flowy_style/lib/styled_close_button.dart

@@ -0,0 +1,15 @@
+import 'package:flutter/material.dart';
+
+class StyleCloseButton extends StatelessWidget {
+  final VoidCallback? onPressed;
+
+  const StyleCloseButton({
+    Key? key,
+    this.onPressed,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return TextButton(onPressed: onPressed, child: const Icon(Icons.close));
+  }
+}

+ 44 - 0
app_flowy/packages/flowy_style/lib/styled_container.dart

@@ -0,0 +1,44 @@
+import 'package:flowy_style/time/duration.dart';
+import 'package:flutter/material.dart';
+
+class StyledContainer extends StatelessWidget {
+  final Color color;
+  final BorderRadiusGeometry? borderRadius;
+  final List<BoxShadow>? shadows;
+  final Widget? child;
+  final double? width;
+  final double? height;
+  final Alignment? align;
+  final EdgeInsets? margin;
+  final Duration? duration;
+  final BoxBorder? border;
+
+  const StyledContainer(this.color,
+      {Key? key,
+      this.borderRadius,
+      this.shadows,
+      this.child,
+      this.width,
+      this.height,
+      this.align,
+      this.margin,
+      this.duration,
+      this.border})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return AnimatedContainer(
+        width: width,
+        height: height,
+        child: child,
+        margin: margin,
+        alignment: align,
+        duration: duration ?? Durations.medium,
+        decoration: BoxDecoration(
+            color: color,
+            borderRadius: borderRadius,
+            boxShadow: shadows,
+            border: border));
+  }
+}

+ 17 - 0
app_flowy/packages/flowy_style/lib/styled_image_icon.dart

@@ -0,0 +1,17 @@
+import 'package:flowy_style/size.dart';
+import 'package:flutter/material.dart';
+
+class StyledImageIcon extends StatelessWidget {
+  final AssetImage image;
+  final Color? color;
+  final double? size;
+
+  const StyledImageIcon(this.image, {Key? key, this.color, this.size})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ImageIcon(image,
+        size: size ?? Sizes.iconMed, color: color ?? Colors.white);
+  }
+}

+ 354 - 0
app_flowy/packages/flowy_style/lib/styled_text_input.dart

@@ -0,0 +1,354 @@
+import 'dart:async';
+import 'dart:math' as math;
+import 'package:flowy_style/size.dart';
+import 'package:flowy_style/text_style.dart';
+import 'package:flowy_style/theme.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:provider/provider.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:textstyle_extensions/textstyle_extensions.dart';
+
+class StyledFormTextInput extends StatelessWidget {
+  static EdgeInsets kDefaultTextInputPadding =
+      EdgeInsets.only(bottom: Insets.sm, top: 4);
+
+  final String? label;
+  final bool? autoFocus;
+  final String? initialValue;
+  final String? hintText;
+  final EdgeInsets? contentPadding;
+  final TextStyle? textStyle;
+  final int? maxLines;
+  final TextEditingController? controller;
+  final TextCapitalization? capitalization;
+  final Function(String)? onChanged;
+  final Function()? onEditingComplete;
+  final Function(bool)? onFocusChanged;
+  final Function(FocusNode)? onFocusCreated;
+
+  const StyledFormTextInput(
+      {Key? key,
+      this.label,
+      this.autoFocus,
+      this.initialValue,
+      this.onChanged,
+      this.onEditingComplete,
+      this.hintText,
+      this.onFocusChanged,
+      this.onFocusCreated,
+      this.controller,
+      this.contentPadding,
+      this.capitalization,
+      this.textStyle,
+      this.maxLines})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return StyledSearchTextInput(
+      capitalization: capitalization,
+      label: label,
+      autoFocus: autoFocus,
+      initialValue: initialValue,
+      onChanged: onChanged,
+      onFocusCreated: onFocusCreated,
+      style: textStyle ?? TextStyles.Body1,
+      onEditingComplete: onEditingComplete,
+      onFocusChanged: onFocusChanged,
+      controller: controller,
+      maxLines: maxLines,
+      inputDecoration: InputDecoration(
+        isDense: true,
+        contentPadding: contentPadding ?? kDefaultTextInputPadding,
+        border: const ThinUnderlineBorder(
+            borderSide: BorderSide(width: 5, color: Colors.red)),
+        //focusedBorder: UnderlineInputBorder(borderSide: BorderSide(width: .5, color: Colors.red)),
+        hintText: hintText,
+      ),
+    );
+  }
+}
+
+class StyledSearchTextInput extends StatefulWidget {
+  final String? label;
+  final TextStyle? style;
+  final EdgeInsets? contentPadding;
+  final bool? autoFocus;
+  final bool? obscureText;
+  final IconData? icon;
+  final String? initialValue;
+  final int? maxLines;
+  final TextEditingController? controller;
+  final TextCapitalization? capitalization;
+  final TextInputType? type;
+  final bool? enabled;
+  final bool? autoValidate;
+  final bool? enableSuggestions;
+  final bool? autoCorrect;
+  final String? errorText;
+  final String? hintText;
+  final Widget? prefixIcon;
+  final Widget? suffixIcon;
+  final InputDecoration? inputDecoration;
+
+  final Function(String)? onChanged;
+  final Function()? onEditingComplete;
+  final Function()? onEditingCancel;
+  final Function(bool)? onFocusChanged;
+  final Function(FocusNode)? onFocusCreated;
+  final Function(String)? onFieldSubmitted;
+  final Function(String?)? onSaved;
+  final VoidCallback? onTap;
+
+  const StyledSearchTextInput({
+    Key? key,
+    this.label,
+    this.autoFocus = false,
+    this.obscureText = false,
+    this.type = TextInputType.text,
+    this.icon,
+    this.initialValue = '',
+    this.controller,
+    this.enabled,
+    this.autoValidate = false,
+    this.enableSuggestions = true,
+    this.autoCorrect = true,
+    this.errorText,
+    this.style,
+    this.contentPadding,
+    this.prefixIcon,
+    this.suffixIcon,
+    this.inputDecoration,
+    this.onChanged,
+    this.onEditingComplete,
+    this.onEditingCancel,
+    this.onFocusChanged,
+    this.onFocusCreated,
+    this.onFieldSubmitted,
+    this.onSaved,
+    this.onTap,
+    this.hintText,
+    this.capitalization,
+    this.maxLines,
+  }) : super(key: key);
+
+  @override
+  StyledSearchTextInputState createState() => StyledSearchTextInputState();
+}
+
+class StyledSearchTextInputState extends State<StyledSearchTextInput> {
+  late TextEditingController _controller;
+  late FocusNode _focusNode;
+
+  @override
+  void initState() {
+    _controller =
+        widget.controller ?? TextEditingController(text: widget.initialValue);
+    _focusNode = FocusNode(
+      debugLabel: widget.label ?? '',
+      onKey: (FocusNode node, RawKeyEvent evt) {
+        if (evt is RawKeyDownEvent) {
+          if (evt.logicalKey == LogicalKeyboardKey.escape) {
+            widget.onEditingCancel?.call();
+            return KeyEventResult.handled;
+          }
+        }
+
+        return KeyEventResult.ignored;
+      },
+      canRequestFocus: true,
+    );
+    // Listen for focus out events
+    _focusNode
+        .addListener(() => widget.onFocusChanged?.call(_focusNode.hasFocus));
+    widget.onFocusCreated?.call(_focusNode);
+    if (widget.autoFocus ?? false) {
+      scheduleMicrotask(() => _focusNode.requestFocus());
+    }
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    _focusNode.dispose();
+    super.dispose();
+  }
+
+  void clear() => _controller.clear();
+
+  String get text => _controller.text;
+
+  set text(String value) => _controller.text = value;
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Container(
+      padding: EdgeInsets.symmetric(vertical: Insets.sm),
+      child: TextFormField(
+        onChanged: widget.onChanged,
+        onEditingComplete: widget.onEditingComplete,
+        onFieldSubmitted: widget.onFieldSubmitted,
+        onSaved: widget.onSaved,
+        onTap: widget.onTap,
+        autofocus: widget.autoFocus ?? false,
+        focusNode: _focusNode,
+        keyboardType: widget.type,
+        obscureText: widget.obscureText ?? false,
+        autocorrect: widget.autoCorrect ?? false,
+        enableSuggestions: widget.enableSuggestions ?? false,
+        style: widget.style ?? TextStyles.Body1,
+        cursorColor: theme.accent1,
+        controller: _controller,
+        showCursor: true,
+        enabled: widget.enabled,
+        maxLines: widget.maxLines,
+        textCapitalization: widget.capitalization ?? TextCapitalization.none,
+        decoration: widget.inputDecoration ??
+            InputDecoration(
+                prefixIcon: widget.prefixIcon,
+                suffixIcon: widget.suffixIcon,
+                contentPadding:
+                    widget.contentPadding ?? EdgeInsets.all(Insets.m),
+                border: const OutlineInputBorder(borderSide: BorderSide.none),
+                isDense: true,
+                icon: widget.icon == null ? null : Icon(widget.icon),
+                errorText: widget.errorText,
+                errorMaxLines: 2,
+                hintText: widget.hintText,
+                hintStyle: TextStyles.Body1.textColor(theme.grey),
+                labelText: widget.label),
+      ),
+    );
+  }
+}
+
+class ThinUnderlineBorder extends InputBorder {
+  /// Creates an underline border for an [InputDecorator].
+  ///
+  /// The [borderSide] parameter defaults to [BorderSide.none] (it must not be
+  /// null). Applications typically do not specify a [borderSide] parameter
+  /// because the input decorator substitutes its own, using [copyWith], based
+  /// on the current theme and [InputDecorator.isFocused].
+  ///
+  /// The [borderRadius] parameter defaults to a value where the top left
+  /// and right corners have a circular radius of 4.0. The [borderRadius]
+  /// parameter must not be null.
+  const ThinUnderlineBorder({
+    BorderSide borderSide = const BorderSide(),
+    this.borderRadius = const BorderRadius.only(
+      topLeft: Radius.circular(4.0),
+      topRight: Radius.circular(4.0),
+    ),
+  }) : super(borderSide: borderSide);
+
+  /// The radii of the border's rounded rectangle corners.
+  ///
+  /// When this border is used with a filled input decorator, see
+  /// [InputDecoration.filled], the border radius defines the shape
+  /// of the background fill as well as the bottom left and right
+  /// edges of the underline itself.
+  ///
+  /// By default the top right and top left corners have a circular radius
+  /// of 4.0.
+  final BorderRadius borderRadius;
+
+  @override
+  bool get isOutline => false;
+
+  @override
+  UnderlineInputBorder copyWith(
+      {BorderSide? borderSide, BorderRadius? borderRadius}) {
+    return UnderlineInputBorder(
+      borderSide: borderSide ?? this.borderSide,
+      borderRadius: borderRadius ?? this.borderRadius,
+    );
+  }
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.only(bottom: borderSide.width);
+  }
+
+  @override
+  UnderlineInputBorder scale(double t) {
+    return UnderlineInputBorder(borderSide: borderSide.scale(t));
+  }
+
+  @override
+  Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
+    return Path()
+      ..addRect(Rect.fromLTWH(rect.left, rect.top, rect.width,
+          math.max(0.0, rect.height - borderSide.width)));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
+    return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a is UnderlineInputBorder) {
+      final newBorderRadius =
+          BorderRadius.lerp(a.borderRadius, borderRadius, t);
+
+      if (newBorderRadius != null) {
+        return UnderlineInputBorder(
+          borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
+          borderRadius: newBorderRadius,
+        );
+      }
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is UnderlineInputBorder) {
+      final newBorderRadius =
+          BorderRadius.lerp(b.borderRadius, borderRadius, t);
+      if (newBorderRadius != null) {
+        return UnderlineInputBorder(
+          borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
+          borderRadius: newBorderRadius,
+        );
+      }
+    }
+    return super.lerpTo(b, t);
+  }
+
+  /// Draw a horizontal line at the bottom of [rect].
+  ///
+  /// The [borderSide] defines the line's color and weight. The `textDirection`
+  /// `gap` and `textDirection` parameters are ignored.
+  /// @override
+
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    double? gapStart,
+    double gapExtent = 0.0,
+    double gapPercentage = 0.0,
+    TextDirection? textDirection,
+  }) {
+    if (borderRadius.bottomLeft != Radius.zero ||
+        borderRadius.bottomRight != Radius.zero) {
+      canvas.clipPath(getOuterPath(rect, textDirection: textDirection));
+    }
+    canvas.drawLine(rect.bottomLeft, rect.bottomRight, borderSide.toPaint());
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    if (other.runtimeType != runtimeType) return false;
+    return other is InputBorder && other.borderSide == borderSide;
+  }
+
+  @override
+  int get hashCode => borderSide.hashCode;
+}

+ 29 - 0
app_flowy/packages/flowy_style/lib/text_field_container.dart

@@ -0,0 +1,29 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+
+class TextFieldContainer extends StatelessWidget {
+  final Widget child;
+  const TextFieldContainer({
+    Key? key,
+    required this.child,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      margin: const EdgeInsets.symmetric(vertical: 10),
+      padding: const EdgeInsets.symmetric(horizontal: 20),
+      decoration: BoxDecoration(
+        color: Colors.white,
+        borderRadius: BorderRadius.circular(30),
+      ),
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Widget>('child', child));
+  }
+}

+ 39 - 4
app_flowy/packages/flowy_style/pubspec.lock

@@ -14,7 +14,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -35,7 +35,7 @@ packages:
       name: charcode
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   clock:
     dependency: transitive
     description:
@@ -64,6 +64,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.10.0-nullsafety.2"
+  equatable:
+    dependency: "direct main"
+    description:
+      name: equatable
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.3"
   fake_async:
     dependency: transitive
     description:
@@ -95,6 +102,13 @@ packages:
       relative: true
     source: path
     version: "0.0.1"
+  lint:
+    dependency: transitive
+    description:
+      name: lint
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.5.3"
   lints:
     dependency: transitive
     description:
@@ -115,7 +129,14 @@ packages:
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.4.0"
+  nested:
+    dependency: transitive
+    description:
+      name: nested
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
   path:
     dependency: transitive
     description:
@@ -123,6 +144,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.8.0"
+  provider:
+    dependency: "direct main"
+    description:
+      name: provider
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.0.0"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -156,6 +184,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.1.0"
+  styled_widget:
+    dependency: "direct main"
+    description:
+      name: styled_widget
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.3.1+2"
   term_glyph:
     dependency: transitive
     description:
@@ -169,7 +204,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   textstyle_extensions:
     dependency: "direct main"
     description:

+ 3 - 0
app_flowy/packages/flowy_style/pubspec.yaml

@@ -17,6 +17,9 @@ dependencies:
   textstyle_extensions: '2.0.0-nullsafety'
   animations: ^2.0.0
   dartz: '0.10.0-nullsafety.2'
+  provider: ^5.0.0
+  styled_widget: '>=0.3.1'
+  equatable: '>=2.0.2'
 
 dev_dependencies:
   flutter_test:

+ 36 - 1
app_flowy/pubspec.lock

@@ -197,6 +197,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.0.3"
+  expandable:
+    dependency: "direct main"
+    description:
+      name: expandable
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.1.4"
   fake_async:
     dependency: transitive
     description:
@@ -233,7 +240,7 @@ packages:
     source: path
     version: "0.0.1"
   flowy_logger:
-    dependency: transitive
+    dependency: "direct main"
     description:
       path: "packages/flowy_logger"
       relative: true
@@ -401,6 +408,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "4.0.1"
+  lint:
+    dependency: transitive
+    description:
+      name: lint
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.5.3"
   lints:
     dependency: transitive
     description:
@@ -599,6 +613,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.1"
+  sized_context:
+    dependency: "direct main"
+    description:
+      name: sized_context
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0+1"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -653,6 +674,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.3.0"
+  styled_widget:
+    dependency: "direct main"
+    description:
+      name: styled_widget
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.3.1+2"
   term_glyph:
     dependency: transitive
     description:
@@ -702,6 +730,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.3.0"
+  universal_platform:
+    dependency: transitive
+    description:
+      name: universal_platform
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0+1"
   url_launcher:
     dependency: transitive
     description:

+ 5 - 0
app_flowy/pubspec.yaml

@@ -35,6 +35,8 @@ dependencies:
     path: packages/flowy_style
   flowy_editor:
     path: packages/flowy_editor
+  flowy_logger:
+    path: packages/flowy_logger
   
   #  third party packages
   time: '>=2.0.0'
@@ -48,6 +50,9 @@ dependencies:
       url: git://github.com/google/flutter-desktop-embedding.git
       path: plugins/window_size
       ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
+  sized_context: ^1.0.0+1
+  styled_widget: '>=0.3.1'
+  expandable: ^4.1.4
 
 
   # The following adds the Cupertino Icons font to your application.

+ 2 - 2
rust-lib/flowy-user/src/entities/mod.rs

@@ -1,15 +1,15 @@
 mod sign_in;
 mod sign_up;
+mod user_detail;
 mod user_email;
 mod user_id;
 mod user_name;
 mod user_password;
-mod user_status;
 
 pub use sign_in::*;
 pub use sign_up::*;
+pub use user_detail::*;
 pub use user_email::*;
 pub use user_id::*;
 pub use user_name::*;
 pub use user_password::*;
-pub use user_status::*;

+ 0 - 0
rust-lib/flowy-user/src/entities/user_status.rs → rust-lib/flowy-user/src/entities/user_detail.rs


+ 3 - 3
rust-lib/flowy-user/src/protobuf/model/mod.rs

@@ -1,8 +1,5 @@
 // Auto-generated, do not edit 
 
-mod user_status; 
-pub use user_status::*; 
-
 mod sign_up; 
 pub use sign_up::*; 
 
@@ -15,5 +12,8 @@ pub use user_table::*;
 mod errors; 
 pub use errors::*; 
 
+mod user_detail; 
+pub use user_detail::*; 
+
 mod event; 
 pub use event::*; 

+ 20 - 20
rust-lib/flowy-user/src/protobuf/model/user_status.rs → rust-lib/flowy-user/src/protobuf/model/user_detail.rs

@@ -17,7 +17,7 @@
 #![allow(trivial_casts)]
 #![allow(unused_imports)]
 #![allow(unused_results)]
-//! Generated file from `user_status.proto`
+//! Generated file from `user_detail.proto`
 
 /// Generated files are compatible only with the same version
 /// of protobuf runtime.
@@ -309,28 +309,28 @@ impl ::protobuf::reflect::ProtobufValue for UserStatus {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x11user_status.proto\"[\n\nUserDetail\x12\x14\n\x05email\x18\x01\x20\
+    \n\x11user_detail.proto\"[\n\nUserDetail\x12\x14\n\x05email\x18\x01\x20\
     \x01(\tR\x05email\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12#\n\
     \x06status\x18\x03\x20\x01(\x0e2\x0b.UserStatusR\x06status*1\n\nUserStat\
     us\x12\x0b\n\x07Unknown\x10\0\x12\t\n\x05Login\x10\x01\x12\x0b\n\x07Expi\
-    red\x10\x02J\xe2\x02\n\x06\x12\x04\0\0\x0b\x01\n\x08\n\x01\x0c\x12\x03\0\
-    \0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\
-    \x02\x08\x12\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x15\n\x0c\n\x05\x04\
-    \0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\
-    \x0b\x10\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x13\x14\n\x0b\n\x04\x04\
-    \0\x02\x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\
-    \x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\
-    \0\x02\x01\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\
-    \x04\x1a\n\x0c\n\x05\x04\0\x02\x02\x06\x12\x03\x05\x04\x0e\n\x0c\n\x05\
-    \x04\0\x02\x02\x01\x12\x03\x05\x0f\x15\n\x0c\n\x05\x04\0\x02\x02\x03\x12\
-    \x03\x05\x18\x19\n\n\n\x02\x05\0\x12\x04\x07\0\x0b\x01\n\n\n\x03\x05\0\
-    \x01\x12\x03\x07\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\x08\x04\x10\n\
-    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x08\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
-    \x02\x12\x03\x08\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\t\x04\x0e\n\
-    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\t\x04\t\n\x0c\n\x05\x05\0\x02\x01\
-    \x02\x12\x03\t\x0c\r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\n\x04\x10\n\x0c\n\
-    \x05\x05\0\x02\x02\x01\x12\x03\n\x04\x0b\n\x0c\n\x05\x05\0\x02\x02\x02\
-    \x12\x03\n\x0e\x0fb\x06proto3\
+    red\x10\x02J\xe2\x02\n\x06\x12\x04\0\0\n\x01\n\x08\n\x01\x0c\x12\x03\0\0\
+    \x12\n\n\n\x02\x04\0\x12\x04\x01\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\
+    \x01\x08\x12\n\x0b\n\x04\x04\0\x02\0\x12\x03\x02\x04\x15\n\x0c\n\x05\x04\
+    \0\x02\0\x05\x12\x03\x02\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x02\
+    \x0b\x10\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x02\x13\x14\n\x0b\n\x04\x04\
+    \0\x02\x01\x12\x03\x03\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x03\
+    \x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x03\x0b\x0f\n\x0c\n\x05\x04\
+    \0\x02\x01\x03\x12\x03\x03\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x04\
+    \x04\x1a\n\x0c\n\x05\x04\0\x02\x02\x06\x12\x03\x04\x04\x0e\n\x0c\n\x05\
+    \x04\0\x02\x02\x01\x12\x03\x04\x0f\x15\n\x0c\n\x05\x04\0\x02\x02\x03\x12\
+    \x03\x04\x18\x19\n\n\n\x02\x05\0\x12\x04\x06\0\n\x01\n\n\n\x03\x05\0\x01\
+    \x12\x03\x06\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\
+    \x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\
+    \x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x0e\n\x0c\n\
+    \x05\x05\0\x02\x01\x01\x12\x03\x08\x04\t\n\x0c\n\x05\x05\0\x02\x01\x02\
+    \x12\x03\x08\x0c\r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x10\n\x0c\n\
+    \x05\x05\0\x02\x02\x01\x12\x03\t\x04\x0b\n\x0c\n\x05\x05\0\x02\x02\x02\
+    \x12\x03\t\x0e\x0fb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 0 - 1
rust-lib/flowy-user/src/protobuf/proto/user_status.proto → rust-lib/flowy-user/src/protobuf/proto/user_detail.proto

@@ -1,5 +1,4 @@
 syntax = "proto3";
-
 message UserDetail {
     string email = 1;
     string name = 2;

+ 2 - 2
rust-lib/flowy-user/src/sql_tables/user_table.rs

@@ -11,10 +11,10 @@ pub struct User {
     pub(crate) name: String,
 
     #[pb(index = 3)]
-    pub(crate) email: String,
+    password: String,
 
     #[pb(index = 4)]
-    password: String,
+    pub(crate) email: String,
 }
 
 impl User {