Merge pull request #485 from AppFlowy-IO/fix_0.0.4_beta3

Fix 0.0.4 beta3
This commit is contained in:
Nathan.fooo 2022-05-07 17:14:42 +08:00 committed by GitHub
commit cb0a86893b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 640 additions and 342 deletions

View File

@ -181,6 +181,7 @@
"textPlaceholder": "Empty"
},
"selectOption": {
"create": "Create",
"purpleColor": "Purple",
"pinkColor": "Pink",
"lightPinkColor": "Light Pink",

View File

@ -7,9 +7,7 @@ import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/application/trash/prelude.dart';
import 'package:app_flowy/workspace/application/workspace/prelude.dart';
import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
import 'package:app_flowy/workspace/application/view/prelude.dart';
import 'package:app_flowy/workspace/application/home/prelude.dart';
import 'package:app_flowy/workspace/application/menu/prelude.dart';
import 'package:app_flowy/user/application/prelude.dart';
import 'package:app_flowy/user/presentation/router.dart';
@ -17,6 +15,7 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show EditFieldContext;
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
@ -44,7 +43,6 @@ void _resolveUserDeps(GetIt getIt) {
getIt.registerFactory<SignUpBloc>(() => SignUpBloc(getIt<AuthService>()));
getIt.registerFactory<SplashRoute>(() => SplashRoute());
getIt.registerFactory<HomeBloc>(() => HomeBloc());
getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
getIt.registerFactory<SplashBloc>(() => SplashBloc());
getIt.registerLazySingleton<NetworkListener>(() => NetworkListener());
@ -57,10 +55,6 @@ void _resolveHomeDeps(GetIt getIt) {
(user, _) => UserListener(user: user),
);
getIt.registerFactoryParam<HomeListenBloc, UserProfile, void>(
(user, _) => HomeListenBloc(getIt<UserListener>(param1: user)),
);
//
getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager());
@ -201,8 +195,8 @@ void _resolveGridDeps(GetIt getIt) {
),
);
getIt.registerFactoryParam<FieldSwitcherBloc, SwitchFieldContext, void>(
(context, _) => FieldSwitcherBloc(context),
getIt.registerFactoryParam<FieldEditorPannelBloc, EditFieldContext, void>(
(context, _) => FieldEditorPannelBloc(context),
);
getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(

View File

@ -12,26 +12,55 @@ import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user;
import 'package:flowy_sdk/rust_stream.dart';
typedef UserProfileUpdatedNotifierValue = Either<UserProfile, FlowyError>;
typedef AuthNotifierValue = Either<Unit, FlowyError>;
typedef WorkspaceUpdatedNotifierValue = Either<List<Workspace>, FlowyError>;
typedef UserProfileNotifyValue = Either<UserProfile, FlowyError>;
typedef AuthNotifyValue = Either<Unit, FlowyError>;
typedef WorkspaceListNotifyValue = Either<List<Workspace>, FlowyError>;
typedef WorkspaceSettingNotifyValue = Either<CurrentWorkspaceSetting, FlowyError>;
class UserListener {
StreamSubscription<SubscribeObject>? _subscription;
final profileUpdatedNotifier = PublishNotifier<UserProfileUpdatedNotifierValue>();
final authDidChangedNotifier = PublishNotifier<AuthNotifierValue>();
final workspaceUpdatedNotifier = PublishNotifier<WorkspaceUpdatedNotifierValue>();
final _profileNotifier = PublishNotifier<UserProfileNotifyValue>();
final _authNotifier = PublishNotifier<AuthNotifyValue>();
final _workspaceListNotifier = PublishNotifier<WorkspaceListNotifyValue>();
final _workSettingNotifier = PublishNotifier<WorkspaceSettingNotifyValue>();
FolderNotificationParser? _workspaceParser;
UserNotificationParser? _userParser;
late UserProfile _user;
final UserProfile _user;
UserListener({
required UserProfile user,
}) {
_user = user;
}
}) : _user = user;
void start({
void Function(AuthNotifyValue)? onAuthChanged,
void Function(UserProfileNotifyValue)? onProfileUpdated,
void Function(WorkspaceListNotifyValue)? onWorkspaceListUpdated,
void Function(WorkspaceSettingNotifyValue)? onWorkspaceSettingUpdated,
}) {
if (onAuthChanged != null) {
_authNotifier.addListener(() {
onAuthChanged(_authNotifier.currentValue!);
});
}
if (onProfileUpdated != null) {
_profileNotifier.addListener(() {
onProfileUpdated(_profileNotifier.currentValue!);
});
}
if (onWorkspaceListUpdated != null) {
_workspaceListNotifier.addListener(() {
onWorkspaceListUpdated(_workspaceListNotifier.currentValue!);
});
}
if (onWorkspaceSettingUpdated != null) {
_workSettingNotifier.addListener(() {
onWorkspaceSettingUpdated(_workSettingNotifier.currentValue!);
});
}
void start() {
_workspaceParser = FolderNotificationParser(id: _user.token, callback: _notificationCallback);
_userParser = UserNotificationParser(id: _user.token, callback: _userNotificationCallback);
_subscription = RustStreamReceiver.listen((observable) {
@ -44,9 +73,9 @@ class UserListener {
_workspaceParser = null;
_userParser = null;
await _subscription?.cancel();
profileUpdatedNotifier.dispose();
authDidChangedNotifier.dispose();
workspaceUpdatedNotifier.dispose();
_profileNotifier.dispose();
_authNotifier.dispose();
_workspaceListNotifier.dispose();
}
void _notificationCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) {
@ -55,16 +84,23 @@ class UserListener {
case FolderNotification.UserDeleteWorkspace:
case FolderNotification.WorkspaceListUpdated:
result.fold(
(payload) => workspaceUpdatedNotifier.value = left(RepeatedWorkspace.fromBuffer(payload).items),
(error) => workspaceUpdatedNotifier.value = right(error),
(payload) => _workspaceListNotifier.value = left(RepeatedWorkspace.fromBuffer(payload).items),
(error) => _workspaceListNotifier.value = right(error),
);
break;
case FolderNotification.WorkspaceSetting:
result.fold(
(payload) => _workSettingNotifier.value = left(CurrentWorkspaceSetting.fromBuffer(payload)),
(error) => _workSettingNotifier.value = right(error),
);
break;
case FolderNotification.UserUnauthorized:
result.fold(
(_) {},
(error) => authDidChangedNotifier.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
(error) => _authNotifier.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
);
break;
default:
break;
}
@ -74,8 +110,8 @@ class UserListener {
switch (ty) {
case user.UserNotification.UserUnauthorized:
result.fold(
(payload) => profileUpdatedNotifier.value = left(UserProfile.fromBuffer(payload)),
(error) => profileUpdatedNotifier.value = right(error),
(payload) => _profileNotifier.value = left(UserProfile.fromBuffer(payload)),
(error) => _profileNotifier.value = right(error),
);
break;
default:

View File

@ -41,7 +41,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
void _startListening() {
appListener.start(
viewsChanged: (result) {
onViewsChanged: (result) {
result.fold(
(views) {
if (!isClosed) {
@ -51,7 +51,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
(error) => Log.error(error),
);
},
appUpdated: (app) {
onAppUpdated: (app) {
if (!isClosed) {
add(AppEvent.appDidUpdate(app));
}
@ -97,7 +97,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
@override
Future<void> close() async {
await appListener.close();
await appListener.stop();
return super.close();
}

View File

@ -24,9 +24,9 @@ class AppListener {
required this.appId,
});
void start({ViewsDidChangeCallback? viewsChanged, AppDidUpdateCallback? appUpdated}) {
_viewsChanged = viewsChanged;
_updated = appUpdated;
void start({ViewsDidChangeCallback? onViewsChanged, AppDidUpdateCallback? onAppUpdated}) {
_viewsChanged = onViewsChanged;
_updated = onAppUpdated;
_parser = FolderNotificationParser(id: appId, callback: _bservableCallback);
_subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
}
@ -60,7 +60,7 @@ class AppListener {
}
}
Future<void> close() async {
Future<void> stop() async {
_parser = null;
await _subscription?.cancel();
_viewsChanged = null;

View File

@ -59,7 +59,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
@override
Future<void> close() async {
await listener.close();
await listener.stop();
if (_subscription != null) {
await _subscription?.cancel();
@ -70,21 +70,20 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
}
Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
listener.deletedNotifier.addPublishListener((result) {
result.fold(
(view) => add(const DocumentEvent.deleted()),
(error) {},
);
});
listener.restoredNotifier.addPublishListener((result) {
result.fold(
(view) => add(const DocumentEvent.restore()),
(error) {},
);
});
listener.start();
listener.start(
onViewDeleted: (result) {
result.fold(
(view) => add(const DocumentEvent.deleted()),
(error) {},
);
},
onViewRestored: (result) {
result.fold(
(view) => add(const DocumentEvent.restore()),
(error) {},
);
},
);
final result = await service.openDocument(docId: view.id);
result.fold(
(block) {

View File

@ -1,9 +1,13 @@
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
import 'select_option_service.dart';
part 'selection_editor_bloc.freezed.dart';
@ -24,14 +28,19 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
_startListening();
},
didReceiveOptions: (_DidReceiveOptions value) {
final result = _makeOptions(state.filter, value.options);
emit(state.copyWith(
allOptions: value.options,
options: _makeOptions(state.filter, value.options),
options: result.options,
createOption: result.createOption,
selectedOptions: value.selectedOptions,
));
},
newOption: (_NewOption value) {
_createOption(value.optionName);
emit(state.copyWith(
filter: none(),
));
},
deleteOption: (_DeleteOption value) {
_deleteOption(value.option);
@ -91,16 +100,37 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
}
void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) {
emit(state.copyWith(filter: optionName, options: _makeOptions(optionName, state.allOptions)));
final _MakeOptionResult result = _makeOptions(Some(optionName), state.allOptions);
emit(state.copyWith(
filter: Some(optionName),
options: result.options,
createOption: result.createOption,
));
}
List<SelectOption> _makeOptions(String filter, List<SelectOption> allOptions) {
_MakeOptionResult _makeOptions(Option<String> filter, List<SelectOption> allOptions) {
final List<SelectOption> options = List.from(allOptions);
if (filter.isNotEmpty) {
options.retainWhere((option) => option.name.toLowerCase().contains(filter.toLowerCase()));
}
Option<String> createOption = filter;
return options;
filter.foldRight(null, (filter, previous) {
if (filter.isNotEmpty) {
options.retainWhere((option) {
final name = option.name.toLowerCase();
final lFilter = filter.toLowerCase();
if (name == lFilter) {
createOption = none();
}
return name.contains(lFilter);
});
}
});
return _MakeOptionResult(
options: options,
createOption: createOption,
);
}
void _startListening() {
@ -135,7 +165,8 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
required List<SelectOption> options,
required List<SelectOption> allOptions,
required List<SelectOption> selectedOptions,
required String filter,
required Option<String> createOption,
required Option<String> filter,
}) = _SelectOptionEditorState;
factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
@ -144,7 +175,18 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
options: data?.options ?? [],
allOptions: data?.options ?? [],
selectedOptions: data?.selectOptions ?? [],
filter: "",
createOption: none(),
filter: none(),
);
}
}
class _MakeOptionResult {
List<SelectOption> options;
Option<String> createOption;
_MakeOptionResult({
required this.options,
required this.createOption,
});
}

View File

@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
import 'package:dartz/dartz.dart';
import 'package:protobuf/protobuf.dart';
part 'field_editor_bloc.freezed.dart';
@ -25,10 +26,13 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
await _getEditFieldContext(emit);
},
updateName: (_UpdateName value) {
emit(state.copyWith(fieldName: value.name));
final newContext = _updateEditContext(name: value.name);
emit(state.copyWith(editFieldContext: newContext));
},
switchField: (_SwitchField value) {
emit(state.copyWith(field: Some(value.field), typeOptionData: value.typeOptionData));
updateField: (_UpdateField value) {
final newContext = _updateEditContext(field: value.field, typeOptionData: value.typeOptionData);
emit(state.copyWith(editFieldContext: newContext));
},
done: (_Done value) async {
await _saveField(emit);
@ -43,14 +47,49 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
return super.close();
}
Option<EditFieldContext> _updateEditContext({
String? name,
Field? field,
List<int>? typeOptionData,
}) {
return state.editFieldContext.fold(
() => none(),
(context) {
context.freeze();
final newContext = context.rebuild((newContext) {
newContext.gridField.rebuild((newField) {
if (name != null) {
newField.name = name;
}
newContext.gridField = newField;
});
if (field != null) {
newContext.gridField = field;
}
if (typeOptionData != null) {
newContext.typeOptionData = typeOptionData;
}
});
service.insertField(
field: newContext.gridField,
typeOptionData: newContext.typeOptionData,
);
return Some(newContext);
},
);
}
Future<void> _saveField(Emitter<FieldEditorState> emit) async {
await state.field.fold(
await state.editFieldContext.fold(
() async => null,
(field) async {
field.name = state.fieldName;
(context) async {
final result = await service.insertField(
field: field,
typeOptionData: state.typeOptionData,
field: context.gridField,
typeOptionData: context.typeOptionData,
);
result.fold((l) => null, (r) => null);
},
@ -60,11 +99,9 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
Future<void> _getEditFieldContext(Emitter<FieldEditorState> emit) async {
final result = await _loader.load();
result.fold(
(editContext) {
(context) {
emit(state.copyWith(
field: Some(editContext.gridField),
typeOptionData: editContext.typeOptionData,
fieldName: editContext.gridField.name,
editFieldContext: Some(context),
));
},
(err) => Log.error(err),
@ -76,25 +113,21 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
class FieldEditorEvent with _$FieldEditorEvent {
const factory FieldEditorEvent.initial() = _InitialField;
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
const factory FieldEditorEvent.switchField(Field field, Uint8List typeOptionData) = _SwitchField;
const factory FieldEditorEvent.updateField(Field field, Uint8List typeOptionData) = _UpdateField;
const factory FieldEditorEvent.done() = _Done;
}
@freezed
class FieldEditorState with _$FieldEditorState {
const factory FieldEditorState({
required String fieldName,
required String gridId,
required String errorText,
required Option<Field> field,
required List<int> typeOptionData,
required Option<EditFieldContext> editFieldContext,
}) = _FieldEditorState;
factory FieldEditorState.initial(String gridId) => FieldEditorState(
gridId: gridId,
fieldName: '',
field: none(),
editFieldContext: none(),
errorText: '',
typeOptionData: List<int>.empty(),
);
}

View File

@ -0,0 +1,53 @@
import 'dart:typed_data';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
part 'field_editor_pannel_bloc.freezed.dart';
class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPannelState> {
FieldEditorPannelBloc(EditFieldContext editContext) : super(FieldEditorPannelState.initial(editContext)) {
on<FieldEditorPannelEvent>(
(event, emit) async {
await event.map(
toFieldType: (_ToFieldType value) async {
emit(state.copyWith(
field: value.field,
typeOptionData: Uint8List.fromList(value.typeOptionData),
));
},
didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) {
emit(state.copyWith(typeOptionData: value.typeOptionData));
},
);
},
);
}
@override
Future<void> close() async {
return super.close();
}
}
@freezed
class FieldEditorPannelEvent with _$FieldEditorPannelEvent {
const factory FieldEditorPannelEvent.toFieldType(Field field, List<int> typeOptionData) = _ToFieldType;
const factory FieldEditorPannelEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData;
}
@freezed
class FieldEditorPannelState with _$FieldEditorPannelState {
const factory FieldEditorPannelState({
required String gridId,
required Field field,
required Uint8List typeOptionData,
}) = _FieldEditorPannelState;
factory FieldEditorPannelState.initial(EditFieldContext context) => FieldEditorPannelState(
gridId: context.gridId,
field: context.gridField,
typeOptionData: Uint8List.fromList(context.typeOptionData),
);
}

View File

@ -1,61 +0,0 @@
import 'dart:typed_data';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
part 'field_switch_bloc.freezed.dart';
class FieldSwitcherBloc extends Bloc<FieldSwitchEvent, FieldSwitchState> {
FieldSwitcherBloc(SwitchFieldContext editContext) : super(FieldSwitchState.initial(editContext)) {
on<FieldSwitchEvent>(
(event, emit) async {
await event.map(
toFieldType: (_ToFieldType value) async {
emit(state.copyWith(
field: value.field,
typeOptionData: Uint8List.fromList(value.typeOptionData),
));
},
didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) {
emit(state.copyWith(typeOptionData: value.typeOptionData));
},
);
},
);
}
@override
Future<void> close() async {
return super.close();
}
}
@freezed
class FieldSwitchEvent with _$FieldSwitchEvent {
const factory FieldSwitchEvent.toFieldType(Field field, List<int> typeOptionData) = _ToFieldType;
const factory FieldSwitchEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData;
}
@freezed
class FieldSwitchState with _$FieldSwitchState {
const factory FieldSwitchState({
required String gridId,
required Field field,
required Uint8List typeOptionData,
}) = _FieldSwitchState;
factory FieldSwitchState.initial(SwitchFieldContext switchContext) => FieldSwitchState(
gridId: switchContext.gridId,
field: switchContext.field,
typeOptionData: Uint8List.fromList(switchContext.typeOptionData),
);
}
class SwitchFieldContext {
final String gridId;
final Field field;
final List<int> typeOptionData;
SwitchFieldContext(this.gridId, this.field, this.typeOptionData);
}

View File

@ -8,7 +8,7 @@ export 'grid_header_bloc.dart';
export 'field/field_service.dart';
export 'field/field_action_sheet_bloc.dart';
export 'field/field_editor_bloc.dart';
export 'field/field_switch_bloc.dart';
export 'field/field_editor_pannel_bloc.dart';
// Field Type Option
export 'field/type_option/date_bloc.dart';

View File

@ -1,13 +1,36 @@
import 'package:app_flowy/user/application/user_listener.dart';
import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/workspace.pb.dart' show CurrentWorkspaceSetting;
import 'package:flowy_sdk/protobuf/flowy-user-data-model/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart';
part 'home_bloc.freezed.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
HomeBloc() : super(HomeState.initial()) {
final UserListener _listener;
HomeBloc(UserProfile user, CurrentWorkspaceSetting workspaceSetting)
: _listener = UserListener(user: user),
super(HomeState.initial(workspaceSetting)) {
on<HomeEvent>((event, emit) async {
await event.map(
initial: (_Initial value) {
_listener.start(
onAuthChanged: (result) {
_authDidChanged(result);
},
onWorkspaceSettingUpdated: (result) {
result.fold(
(setting) => add(HomeEvent.didReceiveWorkspaceSetting(setting)),
(r) => Log.error(r),
);
},
);
},
showLoading: (e) async {
emit(state.copyWith(isLoading: e.isLoading));
},
@ -20,22 +43,40 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
forceCollapse: (e) async {
emit(state.copyWith(forceCollapse: e.forceCollapse));
},
didReceiveWorkspaceSetting: (_DidReceiveWorkspaceSetting value) {
emit(state.copyWith(workspaceSetting: value.setting));
},
unauthorized: (_Unauthorized value) {
emit(state.copyWith(unauthorized: true));
},
);
});
}
@override
Future<void> close() {
Future<void> close() async {
await _listener.stop();
return super.close();
}
void _authDidChanged(Either<Unit, FlowyError> errorOrNothing) {
errorOrNothing.fold((_) {}, (error) {
if (error.code == ErrorCode.UserUnauthorized.value) {
add(HomeEvent.unauthorized(error.msg));
}
});
}
}
@freezed
class HomeEvent with _$HomeEvent {
const factory HomeEvent.initial() = _Initial;
const factory HomeEvent.showLoading(bool isLoading) = _ShowLoading;
const factory HomeEvent.forceCollapse(bool forceCollapse) = _ForceCollapse;
const factory HomeEvent.setEditPannel(EditPannelContext editContext) = _ShowEditPannel;
const factory HomeEvent.dismissEditPannel() = _DismissEditPannel;
const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting;
const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
}
@freezed
@ -44,11 +85,15 @@ class HomeState with _$HomeState {
required bool isLoading,
required bool forceCollapse,
required Option<EditPannelContext> pannelContext,
required CurrentWorkspaceSetting workspaceSetting,
required bool unauthorized,
}) = _HomeState;
factory HomeState.initial() => HomeState(
factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState(
isLoading: false,
forceCollapse: false,
pannelContext: none(),
workspaceSetting: workspaceSetting,
unauthorized: false,
);
}

View File

@ -1,54 +0,0 @@
import 'package:app_flowy/user/application/user_listener.dart';
import 'package:flowy_sdk/protobuf/error-code/error_code.pbenum.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:dartz/dartz.dart';
part 'home_listen_bloc.freezed.dart';
class HomeListenBloc extends Bloc<HomeListenEvent, HomeListenState> {
final UserListener listener;
HomeListenBloc(this.listener) : super(const HomeListenState.loading()) {
on<HomeListenEvent>((event, emit) async {
await event.map(
started: (_) async {
listener.authDidChangedNotifier.addPublishListener((result) {
_authDidChanged(result);
});
listener.start();
},
stop: (_) async {},
unauthorized: (e) async {
emit(HomeListenState.unauthorized(e.msg));
},
);
});
}
@override
Future<void> close() async {
await listener.stop();
super.close();
}
void _authDidChanged(Either<Unit, FlowyError> errorOrNothing) {
errorOrNothing.fold((_) {}, (error) {
if (error.code == ErrorCode.UserUnauthorized.value) {
add(HomeListenEvent.unauthorized(error.msg));
}
});
}
}
@freezed
class HomeListenEvent with _$HomeListenEvent {
const factory HomeListenEvent.started() = _Started;
const factory HomeListenEvent.stop() = _Stop;
const factory HomeListenEvent.unauthorized(String msg) = _Unauthorized;
}
@freezed
class HomeListenState with _$HomeListenState {
const factory HomeListenState.loading() = Loading;
const factory HomeListenState.unauthorized(String msg) = Unauthorized;
}

View File

@ -1 +1 @@
export 'home_listen_bloc.dart';

View File

@ -19,9 +19,10 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
on<MenuUserEvent>((event, emit) async {
await event.map(
initial: (_) async {
userListener.profileUpdatedNotifier.addPublishListener(_profileUpdated);
userListener.workspaceUpdatedNotifier.addPublishListener(_workspacesUpdated);
userListener.start();
userListener.start(
onProfileUpdated: _profileUpdated,
onWorkspaceListUpdated: _workspaceListUpdated,
);
await _initUser();
},
fetchWorkspaces: (_FetchWorkspaces value) async {},
@ -41,7 +42,7 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
}
void _profileUpdated(Either<UserProfile, FlowyError> userOrFailed) {}
void _workspacesUpdated(Either<List<Workspace>, FlowyError> workspacesOrFailed) {
void _workspaceListUpdated(Either<List<Workspace>, FlowyError> workspacesOrFailed) {
// fetch workspaces
// iUserImpl.fetchWorkspaces().then((result) {
// result.fold(

View File

@ -21,12 +21,9 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
on<ViewEvent>((event, emit) async {
await event.map(
initial: (e) {
// TODO: Listener can be refactored to a stream.
listener.updatedNotifier.addPublishListener((result) {
// emit.forEach(stream, onData: onData)
listener.start(onViewUpdated: (result) {
add(ViewEvent.viewDidUpdate(result));
});
listener.start();
emit(state);
},
setIsEditing: (e) {
@ -34,14 +31,12 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
},
viewDidUpdate: (e) {
e.result.fold(
(view) =>
emit(state.copyWith(view: view, successOrFailure: left(unit))),
(view) => emit(state.copyWith(view: view, successOrFailure: left(unit))),
(error) => emit(state.copyWith(successOrFailure: right(error))),
);
},
rename: (e) async {
final result =
await service.updateView(viewId: view.id, name: e.newName);
final result = await service.updateView(viewId: view.id, name: e.newName);
emit(
result.fold(
(l) => state.copyWith(successOrFailure: left(unit)),
@ -74,7 +69,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
@override
Future<void> close() async {
await listener.close();
await listener.stop();
return super.close();
}
}
@ -86,8 +81,7 @@ class ViewEvent with _$ViewEvent {
const factory ViewEvent.rename(String newName) = Rename;
const factory ViewEvent.delete() = Delete;
const factory ViewEvent.duplicate() = Duplicate;
const factory ViewEvent.viewDidUpdate(Either<View, FlowyError> result) =
ViewDidUpdate;
const factory ViewEvent.viewDidUpdate(Either<View, FlowyError> result) = ViewDidUpdate;
}
@freezed

View File

@ -15,9 +15,9 @@ typedef RestoreViewNotifiedValue = Either<View, FlowyError>;
class ViewListener {
StreamSubscription<SubscribeObject>? _subscription;
PublishNotifier<UpdateViewNotifiedValue> updatedNotifier = PublishNotifier<UpdateViewNotifiedValue>();
PublishNotifier<DeleteViewNotifyValue> deletedNotifier = PublishNotifier<DeleteViewNotifyValue>();
PublishNotifier<RestoreViewNotifiedValue> restoredNotifier = PublishNotifier<RestoreViewNotifiedValue>();
final PublishNotifier<UpdateViewNotifiedValue> _updatedViewNotifier = PublishNotifier();
final PublishNotifier<DeleteViewNotifyValue> _deletedNotifier = PublishNotifier();
final PublishNotifier<RestoreViewNotifiedValue> _restoredNotifier = PublishNotifier();
FolderNotificationParser? _parser;
View view;
@ -25,7 +25,29 @@ class ViewListener {
required this.view,
});
void start() {
void start({
void Function(UpdateViewNotifiedValue)? onViewUpdated,
void Function(DeleteViewNotifyValue)? onViewDeleted,
void Function(RestoreViewNotifiedValue)? onViewRestored,
}) {
if (onViewUpdated != null) {
_updatedViewNotifier.addListener(() {
onViewUpdated(_updatedViewNotifier.currentValue!);
});
}
if (onViewDeleted != null) {
_deletedNotifier.addListener(() {
onViewDeleted(_deletedNotifier.currentValue!);
});
}
if (onViewRestored != null) {
_restoredNotifier.addListener(() {
onViewRestored(_restoredNotifier.currentValue!);
});
}
_parser = FolderNotificationParser(
id: view.id,
callback: (ty, result) {
@ -40,20 +62,20 @@ class ViewListener {
switch (ty) {
case FolderNotification.ViewUpdated:
result.fold(
(payload) => updatedNotifier.value = left(View.fromBuffer(payload)),
(error) => updatedNotifier.value = right(error),
(payload) => _updatedViewNotifier.value = left(View.fromBuffer(payload)),
(error) => _updatedViewNotifier.value = right(error),
);
break;
case FolderNotification.ViewDeleted:
result.fold(
(payload) => deletedNotifier.value = left(View.fromBuffer(payload)),
(error) => deletedNotifier.value = right(error),
(payload) => _deletedNotifier.value = left(View.fromBuffer(payload)),
(error) => _deletedNotifier.value = right(error),
);
break;
case FolderNotification.ViewRestored:
result.fold(
(payload) => restoredNotifier.value = left(View.fromBuffer(payload)),
(error) => restoredNotifier.value = right(error),
(payload) => _restoredNotifier.value = left(View.fromBuffer(payload)),
(error) => _restoredNotifier.value = right(error),
);
break;
default:
@ -61,11 +83,11 @@ class ViewListener {
}
}
Future<void> close() async {
Future<void> stop() async {
_parser = null;
await _subscription?.cancel();
updatedNotifier.dispose();
deletedNotifier.dispose();
restoredNotifier.dispose();
_updatedViewNotifier.dispose();
_deletedNotifier.dispose();
_restoredNotifier.dispose();
}
}

View File

@ -16,8 +16,9 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
on<WelcomeEvent>(
(event, emit) async {
await event.map(initial: (e) async {
userListener.workspaceUpdatedNotifier.addPublishListener(_workspacesUpdated);
userListener.start();
userListener.start(
onWorkspaceListUpdated: (result) => add(WelcomeEvent.workspacesReveived(result)),
);
//
await _fetchWorkspaces(emit);
}, openWorkspace: (e) async {
@ -74,10 +75,6 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
},
));
}
void _workspacesUpdated(Either<List<Workspace>, FlowyError> workspacesOrFail) {
add(WelcomeEvent.workspacesReveived(workspacesOrFail));
}
}
@freezed

View File

@ -1,6 +1,5 @@
import 'package:app_flowy/plugin/plugin.dart';
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
import 'package:app_flowy/workspace/application/home/home_listen_bloc.dart';
import 'package:app_flowy/workspace/presentation/widgets/edit_pannel/pannel_animation.dart';
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
import 'package:app_flowy/startup/startup.dart';
@ -46,22 +45,20 @@ class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<HomeListenBloc>(
create: (context) => getIt<HomeListenBloc>(param1: widget.user)..add(const HomeListenEvent.started()),
BlocProvider<HomeBloc>(
create: (context) {
return HomeBloc(widget.user, widget.workspaceSetting)..add(const HomeEvent.initial());
},
),
BlocProvider<HomeBloc>(create: (context) => getIt<HomeBloc>()),
],
child: Scaffold(
key: HomeScreen.scaffoldKey,
body: BlocListener<HomeListenBloc, HomeListenState>(
body: BlocListener<HomeBloc, HomeState>(
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
listener: (context, state) {
state.map(
loading: (_) {},
unauthorized: (unauthorized) {
// TODO: push to login screen when user token was invalid
Log.error("Push to login screen when user token was invalid");
},
);
if (state.unauthorized) {
Log.error("Push to login screen when user token was invalid");
}
},
child: BlocBuilder<HomeBloc, HomeState>(
buildWhen: (previous, current) => previous != current,
@ -73,7 +70,7 @@ class _HomeScreenState extends State<HomeScreen> {
return FlowyContainer(
Theme.of(context).colorScheme.surface,
// Colors.white,
child: _buildBody(state, context.read<HomeBloc>().state.forceCollapse),
child: _buildBody(state),
);
},
),
@ -82,14 +79,15 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
Widget _buildBody(HomeState state, bool forceCollapse) {
Widget _buildBody(HomeState state) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final layout = HomeLayout(context, constraints, forceCollapse);
final layout = HomeLayout(context, constraints, state.forceCollapse);
const homeStack = HomeStack();
final menu = _buildHomeMenu(
layout: layout,
context: context,
state: state,
);
final editPannel = _buildEditPannel(
homeState: state,
@ -108,20 +106,21 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context}) {
if (initialView == null && widget.workspaceSetting.hasLatestView()) {
initialView = widget.workspaceSetting.latestView;
Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context, required HomeState state}) {
final workspaceSetting = state.workspaceSetting;
if (initialView == null && workspaceSetting.hasLatestView()) {
initialView = workspaceSetting.latestView;
final plugin = makePlugin(pluginType: initialView!.pluginType, data: initialView);
getIt<HomeStackManager>().setPlugin(plugin);
}
HomeMenu homeMenu = HomeMenu(
user: widget.user,
workspaceSetting: widget.workspaceSetting,
workspaceSetting: workspaceSetting,
collapsedNotifier: getIt<HomeStackManager>().collapsedNotifier,
);
final latestView = widget.workspaceSetting.hasLatestView() ? widget.workspaceSetting.latestView : null;
final latestView = workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null;
getIt<MenuSharedState>().latestOpenView = latestView;
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));

View File

@ -60,7 +60,7 @@ class DocumentPlugin implements Plugin {
DocumentPlugin({required PluginType pluginType, required View view, Key? key}) : _view = view {
_pluginType = pluginType;
_listener = getIt<ViewListener>(param1: view);
_listener?.updatedNotifier.addPublishListener((result) {
_listener?.start(onViewUpdated: (result) {
result.fold(
(newView) {
_view = newView;
@ -69,12 +69,11 @@ class DocumentPlugin implements Plugin {
(error) {},
);
});
_listener?.start();
}
@override
void dispose() {
_listener?.close();
_listener?.stop();
_listener = null;
}

View File

@ -23,7 +23,7 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {
case FieldType.Checkbox:
return CheckboxCell(cellContextBuilder: cellContextBuilder, key: key);
case FieldType.DateTime:
return DateCell(cellContextBuilder: cellContextBuilder, key: key);
return DateCell(cellContextBuilder: cellContextBuilder, key: key, style: style);
case FieldType.SingleSelect:
return SingleSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
case FieldType.MultiSelect:

View File

@ -8,6 +8,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart';
import 'cell_builder.dart';
class DateCellStyle extends GridCellStyle {
Alignment alignment;
DateCellStyle({this.alignment = Alignment.center});
}
abstract class GridCellDelegate {
void onFocus(bool isFocus);
GridCellDelegate get delegate;
@ -15,11 +21,19 @@ abstract class GridCellDelegate {
class DateCell extends GridCellWidget {
final GridCellContextBuilder cellContextBuilder;
late final DateCellStyle? cellStyle;
DateCell({
GridCellStyle? style,
required this.cellContextBuilder,
Key? key,
}) : super(key: key);
}) : super(key: key) {
if (style != null) {
cellStyle = (style as DateCellStyle);
} else {
cellStyle = null;
}
}
@override
State<DateCell> createState() => _DateCellState();
@ -37,6 +51,7 @@ class _DateCellState extends State<DateCell> {
@override
Widget build(BuildContext context) {
final alignment = widget.cellStyle != null ? widget.cellStyle!.alignment : Alignment.center;
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<DateCellBloc, DateCellState>(
@ -57,7 +72,7 @@ class _DateCellState extends State<DateCell> {
child: MouseRegion(
opaque: false,
cursor: SystemMouseCursors.click,
child: Center(child: FlowyText.medium(state.content, fontSize: 12)),
child: Align(alignment: alignment, child: FlowyText.medium(state.content, fontSize: 12)),
),
),
);

View File

@ -60,17 +60,35 @@ extension SelectOptionColorExtension on SelectOptionColor {
}
class SelectOptionTag extends StatelessWidget {
final SelectOption option;
final String name;
final Color color;
final bool isSelected;
const SelectOptionTag({required this.option, this.isSelected = false, Key? key}) : super(key: key);
const SelectOptionTag({
required this.name,
required this.color,
this.isSelected = false,
Key? key,
}) : super(key: key);
factory SelectOptionTag.fromSelectOption({
required BuildContext context,
required SelectOption option,
bool isSelected = false,
}) {
return SelectOptionTag(
name: option.name,
color: option.color.make(context),
isSelected: isSelected,
);
}
@override
Widget build(BuildContext context) {
return ChoiceChip(
pressElevation: 1,
label: FlowyText.medium(option.name, fontSize: 12),
selectedColor: option.color.make(context),
backgroundColor: option.color.make(context),
label: FlowyText.medium(name, fontSize: 12),
selectedColor: color,
backgroundColor: color,
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
selected: true,
onSelected: (_) {},

View File

@ -150,7 +150,14 @@ class _SelectOptionCell extends StatelessWidget {
child: FlowyText.medium(cellStyle!.placeholder, fontSize: 14, color: theme.shader3),
);
} else {
final tags = selectOptions.map((option) => SelectOptionTag(option: option)).toList();
final tags = selectOptions
.map(
(option) => SelectOptionTag.fromSelectOption(
context: context,
option: option,
),
)
.toList();
child = Align(
alignment: Alignment.centerLeft,
child: Wrap(children: tags, spacing: 4, runSpacing: 4),

View File

@ -104,9 +104,18 @@ class _OptionList extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
builder: (context, state) {
final cells = state.options.map((option) {
List<Widget> cells = [];
cells.addAll(state.options.map((option) {
return _SelectOptionCell(option, state.selectedOptions.contains(option));
}).toList();
}).toList());
state.createOption.fold(
() => null,
(createOption) {
cells.add(_CreateOptionCell(name: createOption));
},
);
final list = ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
@ -119,7 +128,11 @@ class _OptionList extends StatelessWidget {
return cells[index];
},
);
return list;
return Padding(
padding: const EdgeInsets.all(3.0),
child: list,
);
},
);
}
@ -177,6 +190,30 @@ class _Title extends StatelessWidget {
}
}
class _CreateOptionCell extends StatelessWidget {
final String name;
const _CreateOptionCell({required this.name, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return Row(
children: [
FlowyText.medium(
LocaleKeys.grid_selectOption_create.tr(),
fontSize: 12,
color: theme.shader3,
),
const HSpace(10),
SelectOptionTag(
name: name,
color: theme.shader6,
),
],
);
}
}
class _SelectOptionCell extends StatelessWidget {
final SelectOption option;
final bool isSelected;
@ -206,7 +243,11 @@ class _SelectOptionCell extends StatelessWidget {
style: HoverStyle(hoverColor: theme.hover),
builder: (_, onHover) {
List<Widget> children = [
SelectOptionTag(option: option, isSelected: isSelected),
SelectOptionTag(
name: option.name,
color: option.color.make(context),
isSelected: isSelected,
),
const Spacer(),
];
@ -223,10 +264,7 @@ class _SelectOptionCell extends StatelessWidget {
));
}
return Padding(
padding: const EdgeInsets.all(3.0),
child: Row(children: children),
);
return Row(children: children);
},
);
}

View File

@ -76,7 +76,7 @@ class SelectOptionTextField extends StatelessWidget {
borderRadius: Corners.s10Border,
),
isDense: true,
prefixIcon: _renderTags(sc),
prefixIcon: _renderTags(context, sc),
hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
focusedBorder: OutlineInputBorder(
@ -90,12 +90,14 @@ class SelectOptionTextField extends StatelessWidget {
);
}
Widget? _renderTags(ScrollController sc) {
Widget? _renderTags(BuildContext context, ScrollController sc) {
if (selectedOptionMap.isEmpty) {
return null;
}
final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList();
final children = selectedOptionMap.values
.map((option) => SelectOptionTag.fromSelectOption(context: context, option: option))
.toList();
return Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(

View File

@ -1,18 +1,15 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart';
import 'field_switcher.dart';
import 'field_editor_pannel.dart';
class FieldEditor extends FlowyOverlayDelegate {
final String gridId;
@ -30,7 +27,6 @@ class FieldEditor extends FlowyOverlayDelegate {
BuildContext context, {
AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned,
}) {
Log.trace("Show $identifier()");
FlowyOverlay.of(context).remove(identifier());
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
@ -69,16 +65,24 @@ class _FieldEditorWidget extends StatelessWidget {
value: editorBloc,
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) {
return state.field.fold(
return state.editFieldContext.fold(
() => const SizedBox(),
(field) => ListView(
(editFieldContext) => ListView(
shrinkWrap: true,
children: [
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
const VSpace(10),
const _FieldNameTextField(),
const VSpace(10),
_renderSwitchButton(context, field, state),
FieldEditorPannel(
editFieldContext: editFieldContext,
onSwitchToField: (fieldId, fieldType) {
return fieldContextLoader.switchToField(fieldId, fieldType);
},
onUpdated: (field, typeOptionData) {
context.read<FieldEditorBloc>().add(FieldEditorEvent.updateField(field, typeOptionData));
},
),
],
),
);
@ -86,18 +90,6 @@ class _FieldEditorWidget extends StatelessWidget {
),
);
}
Widget _renderSwitchButton(BuildContext context, Field field, FieldEditorState state) {
return FieldSwitcher(
switchContext: SwitchFieldContext(state.gridId, field, state.typeOptionData),
onSwitchToField: (fieldId, fieldType) {
return fieldContextLoader.switchToField(fieldId, fieldType);
},
onUpdated: (field, typeOptionData) {
context.read<FieldEditorBloc>().add(FieldEditorEvent.switchField(field, typeOptionData));
},
);
}
}
class _FieldNameTextField extends StatelessWidget {
@ -105,11 +97,16 @@ class _FieldNameTextField extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
buildWhen: (previous, current) => previous.fieldName != current.fieldName,
builder: (context, state) {
return BlocSelector<FieldEditorBloc, FieldEditorState, String>(
selector: (state) {
return state.editFieldContext.fold(
() => "",
(editFieldContext) => editFieldContext.gridField.name,
);
},
builder: (context, name) {
return FieldNameTextField(
name: state.fieldName,
name: name,
errorText: context.read<FieldEditorBloc>().state.errorText,
onNameChanged: (newName) {
context.read<FieldEditorBloc>().add(FieldEditorEvent.updateName(newName));

View File

@ -30,30 +30,30 @@ typedef SwitchToFieldCallback = Future<Either<EditFieldContext, FlowyError>> Fun
FieldType fieldType,
);
class FieldSwitcher extends StatefulWidget {
final SwitchFieldContext switchContext;
class FieldEditorPannel extends StatefulWidget {
final EditFieldContext editFieldContext;
final UpdateFieldCallback onUpdated;
final SwitchToFieldCallback onSwitchToField;
const FieldSwitcher({
required this.switchContext,
const FieldEditorPannel({
required this.editFieldContext,
required this.onUpdated,
required this.onSwitchToField,
Key? key,
}) : super(key: key);
@override
State<FieldSwitcher> createState() => _FieldSwitcherState();
State<FieldEditorPannel> createState() => _FieldEditorPannelState();
}
class _FieldSwitcherState extends State<FieldSwitcher> {
class _FieldEditorPannelState extends State<FieldEditorPannel> {
String? currentOverlayIdentifier;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<FieldSwitcherBloc>(param1: widget.switchContext),
child: BlocConsumer<FieldSwitcherBloc, FieldSwitchState>(
create: (context) => getIt<FieldEditorPannelBloc>(param1: widget.editFieldContext),
child: BlocConsumer<FieldEditorPannelBloc, FieldEditorPannelState>(
listener: (context, state) {
widget.onUpdated(state.field, state.typeOptionData);
},
@ -87,8 +87,8 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
widget.onSwitchToField(field.id, newFieldType).then((result) {
result.fold(
(editFieldContext) {
context.read<FieldSwitcherBloc>().add(
FieldSwitchEvent.toFieldType(
context.read<FieldEditorPannelBloc>().add(
FieldEditorPannelEvent.toFieldType(
editFieldContext.gridField,
editFieldContext.typeOptionData,
),
@ -108,7 +108,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
Widget? _typeOptionWidget({
required BuildContext context,
required FieldSwitchState state,
required FieldEditorPannelState state,
}) {
final overlayDelegate = TypeOptionOverlayDelegate(
showOverlay: _showOverlay,
@ -116,7 +116,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
);
final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) {
context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
context.read<FieldEditorPannelBloc>().add(FieldEditorPannelEvent.didUpdateTypeOptionData(data));
});
final typeOptionContext = TypeOptionContext(

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/date_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
import 'package:easy_localization/easy_localization.dart' hide DateFormat;
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -3,7 +3,7 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/number_bl
import 'package:app_flowy/workspace/application/grid/field/type_option/number_format_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'field_option_pannel.dart';

View File

@ -6,9 +6,11 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.d
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
@ -66,12 +68,36 @@ class _RowDetailPageState extends State<RowDetailPage> {
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40),
child: _PropertyList(cellCache: widget.cellCache),
child: Column(
children: [
SizedBox(
height: 40,
child: Row(
children: const [Spacer(), _CloseButton()],
)),
Expanded(child: _PropertyList(cellCache: widget.cellCache)),
],
),
),
);
}
}
class _CloseButton extends StatelessWidget {
const _CloseButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyIconButton(
width: 24,
onPressed: () => FlowyOverlay.of(context).remove(RowDetailPage.identifier()),
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
icon: svgWidget("home/close", color: theme.iconColor),
);
}
}
class _PropertyList extends StatelessWidget {
final GridCellCache cellCache;
final ScrollController _scrollController;
@ -165,7 +191,9 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
case FieldType.Checkbox:
return null;
case FieldType.DateTime:
return null;
return DateCellStyle(
alignment: Alignment.centerLeft,
);
case FieldType.MultiSelect:
return SelectOptionCellStyle(
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),

View File

@ -1,5 +1,7 @@
import 'package:app_flowy/workspace/application/view/view_listener.dart';
import 'package:app_flowy/workspace/application/view/view_service.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -16,12 +18,26 @@ class ViewLeftBarItem extends StatefulWidget {
class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
final _controller = TextEditingController();
final _focusNode = FocusNode();
late ViewService serviceService;
late ViewService _viewService;
late ViewListener _viewListener;
late View view;
@override
void initState() {
serviceService = ViewService(/*view: widget.view*/);
view = widget.view;
_viewService = ViewService();
_focusNode.addListener(_handleFocusChanged);
_viewListener = ViewListener(view: widget.view);
_viewListener.start(onViewUpdated: (result) {
result.fold(
(updatedView) {
if (mounted) {
setState(() => view = updatedView);
}
},
(err) => Log.error(err),
);
});
super.initState();
}
@ -30,12 +46,13 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
_controller.dispose();
_focusNode.removeListener(_handleFocusChanged);
_focusNode.dispose();
_viewListener.stop();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.text = widget.view.name;
_controller.text = view.name;
final theme = context.watch<AppTheme>();
return IntrinsicWidth(
@ -63,12 +80,12 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
void _handleFocusChanged() {
if (_controller.text.isEmpty) {
_controller.text = widget.view.name;
_controller.text = view.name;
return;
}
if (_controller.text != widget.view.name) {
serviceService.updateView(viewId: widget.view.id, name: _controller.text);
if (_controller.text != view.name) {
_viewService.updateView(viewId: view.id, name: _controller.text);
}
}
}

View File

@ -176,7 +176,6 @@ class FlowyOverlayState extends State<FlowyOverlay> {
FlowyOverlayStyle? style,
Offset? anchorOffset,
}) {
debugPrint("Show overlay: $identifier");
this.style = style ?? FlowyOverlayStyle();
_showOverlay(
@ -245,6 +244,7 @@ class FlowyOverlayState extends State<FlowyOverlay> {
OverlapBehaviour? overlapBehaviour,
FlowyOverlayDelegate? delegate,
}) {
debugPrint("Show overlay: $identifier");
Widget overlay = widget;
final offset = anchorOffset ?? Offset.zero;

View File

@ -16,6 +16,7 @@ class FolderNotification extends $pb.ProtobufEnum {
static const FolderNotification WorkspaceUpdated = FolderNotification._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceUpdated');
static const FolderNotification WorkspaceListUpdated = FolderNotification._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceListUpdated');
static const FolderNotification WorkspaceAppsChanged = FolderNotification._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceAppsChanged');
static const FolderNotification WorkspaceSetting = FolderNotification._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceSetting');
static const FolderNotification AppUpdated = FolderNotification._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppUpdated');
static const FolderNotification AppViewsChanged = FolderNotification._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppViewsChanged');
static const FolderNotification ViewUpdated = FolderNotification._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewUpdated');
@ -31,6 +32,7 @@ class FolderNotification extends $pb.ProtobufEnum {
WorkspaceUpdated,
WorkspaceListUpdated,
WorkspaceAppsChanged,
WorkspaceSetting,
AppUpdated,
AppViewsChanged,
ViewUpdated,

View File

@ -18,6 +18,7 @@ const FolderNotification$json = const {
const {'1': 'WorkspaceUpdated', '2': 12},
const {'1': 'WorkspaceListUpdated', '2': 13},
const {'1': 'WorkspaceAppsChanged', '2': 14},
const {'1': 'WorkspaceSetting', '2': 15},
const {'1': 'AppUpdated', '2': 21},
const {'1': 'AppViewsChanged', '2': 24},
const {'1': 'ViewUpdated', '2': 31},
@ -29,4 +30,4 @@ const FolderNotification$json = const {
};
/// Descriptor for `FolderNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List folderNotificationDescriptor = $convert.base64Decode('ChJGb2xkZXJOb3RpZmljYXRpb24SCwoHVW5rbm93bhAAEhcKE1VzZXJDcmVhdGVXb3Jrc3BhY2UQChIXChNVc2VyRGVsZXRlV29ya3NwYWNlEAsSFAoQV29ya3NwYWNlVXBkYXRlZBAMEhgKFFdvcmtzcGFjZUxpc3RVcGRhdGVkEA0SGAoUV29ya3NwYWNlQXBwc0NoYW5nZWQQDhIOCgpBcHBVcGRhdGVkEBUSEwoPQXBwVmlld3NDaGFuZ2VkEBgSDwoLVmlld1VwZGF0ZWQQHxIPCgtWaWV3RGVsZXRlZBAgEhAKDFZpZXdSZXN0b3JlZBAhEhQKEFVzZXJVbmF1dGhvcml6ZWQQZBIRCgxUcmFzaFVwZGF0ZWQQ6Ac=');
final $typed_data.Uint8List folderNotificationDescriptor = $convert.base64Decode('ChJGb2xkZXJOb3RpZmljYXRpb24SCwoHVW5rbm93bhAAEhcKE1VzZXJDcmVhdGVXb3Jrc3BhY2UQChIXChNVc2VyRGVsZXRlV29ya3NwYWNlEAsSFAoQV29ya3NwYWNlVXBkYXRlZBAMEhgKFFdvcmtzcGFjZUxpc3RVcGRhdGVkEA0SGAoUV29ya3NwYWNlQXBwc0NoYW5nZWQQDhIUChBXb3Jrc3BhY2VTZXR0aW5nEA8SDgoKQXBwVXBkYXRlZBAVEhMKD0FwcFZpZXdzQ2hhbmdlZBAYEg8KC1ZpZXdVcGRhdGVkEB8SDwoLVmlld0RlbGV0ZWQQIBIQCgxWaWV3UmVzdG9yZWQQIRIUChBVc2VyVW5hdXRob3JpemVkEGQSEQoMVHJhc2hVcGRhdGVkEOgH');

View File

@ -10,6 +10,7 @@ pub(crate) enum FolderNotification {
WorkspaceUpdated = 12,
WorkspaceListUpdated = 13,
WorkspaceAppsChanged = 14,
WorkspaceSetting = 15,
AppUpdated = 21,
AppViewsChanged = 24,
ViewUpdated = 31,

View File

@ -31,6 +31,7 @@ pub enum FolderNotification {
WorkspaceUpdated = 12,
WorkspaceListUpdated = 13,
WorkspaceAppsChanged = 14,
WorkspaceSetting = 15,
AppUpdated = 21,
AppViewsChanged = 24,
ViewUpdated = 31,
@ -53,6 +54,7 @@ impl ::protobuf::ProtobufEnum for FolderNotification {
12 => ::std::option::Option::Some(FolderNotification::WorkspaceUpdated),
13 => ::std::option::Option::Some(FolderNotification::WorkspaceListUpdated),
14 => ::std::option::Option::Some(FolderNotification::WorkspaceAppsChanged),
15 => ::std::option::Option::Some(FolderNotification::WorkspaceSetting),
21 => ::std::option::Option::Some(FolderNotification::AppUpdated),
24 => ::std::option::Option::Some(FolderNotification::AppViewsChanged),
31 => ::std::option::Option::Some(FolderNotification::ViewUpdated),
@ -72,6 +74,7 @@ impl ::protobuf::ProtobufEnum for FolderNotification {
FolderNotification::WorkspaceUpdated,
FolderNotification::WorkspaceListUpdated,
FolderNotification::WorkspaceAppsChanged,
FolderNotification::WorkspaceSetting,
FolderNotification::AppUpdated,
FolderNotification::AppViewsChanged,
FolderNotification::ViewUpdated,
@ -107,14 +110,15 @@ impl ::protobuf::reflect::ProtobufValue for FolderNotification {
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x17dart_notification.proto*\x9f\x02\n\x12FolderNotification\x12\x0b\n\
\n\x17dart_notification.proto*\xb5\x02\n\x12FolderNotification\x12\x0b\n\
\x07Unknown\x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13Us\
erDeleteWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x18\
\n\x14WorkspaceListUpdated\x10\r\x12\x18\n\x14WorkspaceAppsChanged\x10\
\x0e\x12\x0e\n\nAppUpdated\x10\x15\x12\x13\n\x0fAppViewsChanged\x10\x18\
\x12\x0f\n\x0bViewUpdated\x10\x1f\x12\x0f\n\x0bViewDeleted\x10\x20\x12\
\x10\n\x0cViewRestored\x10!\x12\x14\n\x10UserUnauthorized\x10d\x12\x11\n\
\x0cTrashUpdated\x10\xe8\x07b\x06proto3\
\x0e\x12\x14\n\x10WorkspaceSetting\x10\x0f\x12\x0e\n\nAppUpdated\x10\x15\
\x12\x13\n\x0fAppViewsChanged\x10\x18\x12\x0f\n\x0bViewUpdated\x10\x1f\
\x12\x0f\n\x0bViewDeleted\x10\x20\x12\x10\n\x0cViewRestored\x10!\x12\x14\
\n\x10UserUnauthorized\x10d\x12\x11\n\x0cTrashUpdated\x10\xe8\x07b\x06pr\
oto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -7,6 +7,7 @@ enum FolderNotification {
WorkspaceUpdated = 12;
WorkspaceListUpdated = 13;
WorkspaceAppsChanged = 14;
WorkspaceSetting = 15;
AppUpdated = 21;
AppViewsChanged = 24;
ViewUpdated = 31;

View File

@ -1,4 +1,5 @@
use crate::services::AppController;
use crate::manager::FolderManager;
use crate::services::{notify_workspace_setting_did_change, AppController};
use crate::{
entities::{
trash::Trash,
@ -69,10 +70,12 @@ pub(crate) async fn delete_view_handler(
pub(crate) async fn set_latest_view_handler(
data: Data<ViewId>,
folder: AppData<Arc<FolderManager>>,
controller: AppData<Arc<ViewController>>,
) -> Result<(), FlowyError> {
let view_id: ViewId = data.into_inner();
let _ = controller.set_latest_view(&view_id.value)?;
let _ = notify_workspace_setting_did_change(&folder, &view_id).await?;
Ok(())
}

View File

@ -1,3 +1,4 @@
use crate::manager::FolderManager;
use crate::{
dart_notification::*,
errors::*,
@ -190,6 +191,44 @@ impl WorkspaceController {
}
}
pub async fn notify_workspace_setting_did_change(
folder_manager: &Arc<FolderManager>,
view_id: &str,
) -> FlowyResult<()> {
let user_id = folder_manager.user.user_id()?;
let token = folder_manager.user.token()?;
let workspace_id = get_current_workspace()?;
let workspace_setting = folder_manager
.persistence
.begin_transaction(|transaction| {
let workspace = folder_manager.workspace_controller.read_local_workspace(
workspace_id.clone(),
&user_id,
&transaction,
)?;
let setting = match transaction.read_view(view_id) {
Ok(latest_view) => CurrentWorkspaceSetting {
workspace,
latest_view: Some(latest_view),
},
Err(_) => CurrentWorkspaceSetting {
workspace,
latest_view: None,
},
};
Ok(setting)
})
.await?;
send_dart_notification(&token, FolderNotification::WorkspaceSetting)
.payload(workspace_setting)
.send();
Ok(())
}
const CURRENT_WORKSPACE_ID: &str = "current_workspace_id";
pub fn set_current_workspace(workspace_id: &str) {

View File

@ -85,7 +85,17 @@ impl CellDataOperation for NumberTypeOption {
let cell_data = type_option_cell_data.data;
match self.format {
NumberFormat::Number => cell_data.parse::<i64>().map_or(String::new(), |v| v.to_string()),
NumberFormat::Number => {
if let Ok(v) = cell_data.parse::<f64>() {
return v.to_string();
}
if let Ok(v) = cell_data.parse::<i64>() {
return v.to_string();
}
return String::new();
}
NumberFormat::Percent => cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string()),
_ => self.money_from_str(&cell_data),
}
@ -100,10 +110,13 @@ impl CellDataOperation for NumberTypeOption {
_cell_meta: Option<CellMeta>,
) -> Result<String, FlowyError> {
let changeset = changeset.into();
let data = self.strip_symbol(changeset);
let mut data = changeset.trim().to_string();
if !data.chars().all(char::is_numeric) {
return Err(FlowyError::invalid_data().context("Should only contain numbers"));
if self.format != NumberFormat::Number {
data = self.strip_symbol(data);
if !data.chars().all(char::is_numeric) {
return Err(FlowyError::invalid_data().context("Should only contain numbers"));
}
}
Ok(TypeOptionCellData::new(&data, self.field_type()).json())
@ -157,7 +170,7 @@ impl NumberTypeOption {
}
}
#[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
pub enum NumberFormat {
Number = 0,
USD = 1,

View File

@ -316,7 +316,11 @@ impl ClientGridEditor {
let cell_data_changeset = changeset.data.unwrap();
let cell_meta = self.get_cell_meta(&changeset.row_id, &changeset.field_id).await?;
tracing::trace!("{}: {:?}", &changeset.field_id, cell_meta);
tracing::trace!(
"field changeset: id:{} / value:{}",
&changeset.field_id,
cell_data_changeset
);
match self.grid_pad.read().await.get_field_meta(&changeset.field_id) {
None => {
let msg = format!("Field not found with id: {}", &changeset.field_id);

View File

@ -201,6 +201,14 @@ impl std::convert::From<&str> for ViewId {
}
}
impl std::ops::Deref for ViewId {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.value
}
}
#[derive(Default, ProtoBuf)]
pub struct RepeatedViewId {
#[pb(index = 1)]