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" "textPlaceholder": "Empty"
}, },
"selectOption": { "selectOption": {
"create": "Create",
"purpleColor": "Purple", "purpleColor": "Purple",
"pinkColor": "Pink", "pinkColor": "Pink",
"lightPinkColor": "Light 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/trash/prelude.dart';
import 'package:app_flowy/workspace/application/workspace/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/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/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/workspace/application/menu/prelude.dart';
import 'package:app_flowy/user/application/prelude.dart'; import 'package:app_flowy/user/application/prelude.dart';
import 'package:app_flowy/user/presentation/router.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: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/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.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/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_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'; 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<SignUpBloc>(() => SignUpBloc(getIt<AuthService>()));
getIt.registerFactory<SplashRoute>(() => SplashRoute()); getIt.registerFactory<SplashRoute>(() => SplashRoute());
getIt.registerFactory<HomeBloc>(() => HomeBloc());
getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc()); getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
getIt.registerFactory<SplashBloc>(() => SplashBloc()); getIt.registerFactory<SplashBloc>(() => SplashBloc());
getIt.registerLazySingleton<NetworkListener>(() => NetworkListener()); getIt.registerLazySingleton<NetworkListener>(() => NetworkListener());
@ -57,10 +55,6 @@ void _resolveHomeDeps(GetIt getIt) {
(user, _) => UserListener(user: user), (user, _) => UserListener(user: user),
); );
getIt.registerFactoryParam<HomeListenBloc, UserProfile, void>(
(user, _) => HomeListenBloc(getIt<UserListener>(param1: user)),
);
// //
getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager()); getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager());
@ -201,8 +195,8 @@ void _resolveGridDeps(GetIt getIt) {
), ),
); );
getIt.registerFactoryParam<FieldSwitcherBloc, SwitchFieldContext, void>( getIt.registerFactoryParam<FieldEditorPannelBloc, EditFieldContext, void>(
(context, _) => FieldSwitcherBloc(context), (context, _) => FieldEditorPannelBloc(context),
); );
getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>( 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/protobuf/flowy-user/dart_notification.pb.dart' as user;
import 'package:flowy_sdk/rust_stream.dart'; import 'package:flowy_sdk/rust_stream.dart';
typedef UserProfileUpdatedNotifierValue = Either<UserProfile, FlowyError>; typedef UserProfileNotifyValue = Either<UserProfile, FlowyError>;
typedef AuthNotifierValue = Either<Unit, FlowyError>; typedef AuthNotifyValue = Either<Unit, FlowyError>;
typedef WorkspaceUpdatedNotifierValue = Either<List<Workspace>, FlowyError>; typedef WorkspaceListNotifyValue = Either<List<Workspace>, FlowyError>;
typedef WorkspaceSettingNotifyValue = Either<CurrentWorkspaceSetting, FlowyError>;
class UserListener { class UserListener {
StreamSubscription<SubscribeObject>? _subscription; StreamSubscription<SubscribeObject>? _subscription;
final profileUpdatedNotifier = PublishNotifier<UserProfileUpdatedNotifierValue>(); final _profileNotifier = PublishNotifier<UserProfileNotifyValue>();
final authDidChangedNotifier = PublishNotifier<AuthNotifierValue>(); final _authNotifier = PublishNotifier<AuthNotifyValue>();
final workspaceUpdatedNotifier = PublishNotifier<WorkspaceUpdatedNotifierValue>(); final _workspaceListNotifier = PublishNotifier<WorkspaceListNotifyValue>();
final _workSettingNotifier = PublishNotifier<WorkspaceSettingNotifyValue>();
FolderNotificationParser? _workspaceParser; FolderNotificationParser? _workspaceParser;
UserNotificationParser? _userParser; UserNotificationParser? _userParser;
late UserProfile _user; final UserProfile _user;
UserListener({ UserListener({
required UserProfile user, 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); _workspaceParser = FolderNotificationParser(id: _user.token, callback: _notificationCallback);
_userParser = UserNotificationParser(id: _user.token, callback: _userNotificationCallback); _userParser = UserNotificationParser(id: _user.token, callback: _userNotificationCallback);
_subscription = RustStreamReceiver.listen((observable) { _subscription = RustStreamReceiver.listen((observable) {
@ -44,9 +73,9 @@ class UserListener {
_workspaceParser = null; _workspaceParser = null;
_userParser = null; _userParser = null;
await _subscription?.cancel(); await _subscription?.cancel();
profileUpdatedNotifier.dispose(); _profileNotifier.dispose();
authDidChangedNotifier.dispose(); _authNotifier.dispose();
workspaceUpdatedNotifier.dispose(); _workspaceListNotifier.dispose();
} }
void _notificationCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) { void _notificationCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) {
@ -55,16 +84,23 @@ class UserListener {
case FolderNotification.UserDeleteWorkspace: case FolderNotification.UserDeleteWorkspace:
case FolderNotification.WorkspaceListUpdated: case FolderNotification.WorkspaceListUpdated:
result.fold( result.fold(
(payload) => workspaceUpdatedNotifier.value = left(RepeatedWorkspace.fromBuffer(payload).items), (payload) => _workspaceListNotifier.value = left(RepeatedWorkspace.fromBuffer(payload).items),
(error) => workspaceUpdatedNotifier.value = right(error), (error) => _workspaceListNotifier.value = right(error),
);
break;
case FolderNotification.WorkspaceSetting:
result.fold(
(payload) => _workSettingNotifier.value = left(CurrentWorkspaceSetting.fromBuffer(payload)),
(error) => _workSettingNotifier.value = right(error),
); );
break; break;
case FolderNotification.UserUnauthorized: case FolderNotification.UserUnauthorized:
result.fold( result.fold(
(_) {}, (_) {},
(error) => authDidChangedNotifier.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value), (error) => _authNotifier.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
); );
break; break;
default: default:
break; break;
} }
@ -74,8 +110,8 @@ class UserListener {
switch (ty) { switch (ty) {
case user.UserNotification.UserUnauthorized: case user.UserNotification.UserUnauthorized:
result.fold( result.fold(
(payload) => profileUpdatedNotifier.value = left(UserProfile.fromBuffer(payload)), (payload) => _profileNotifier.value = left(UserProfile.fromBuffer(payload)),
(error) => profileUpdatedNotifier.value = right(error), (error) => _profileNotifier.value = right(error),
); );
break; break;
default: default:

View File

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

View File

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

View File

@ -59,7 +59,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
@override @override
Future<void> close() async { Future<void> close() async {
await listener.close(); await listener.stop();
if (_subscription != null) { if (_subscription != null) {
await _subscription?.cancel(); await _subscription?.cancel();
@ -70,21 +70,20 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
} }
Future<void> _initial(Initial value, Emitter<DocumentState> emit) async { Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
listener.deletedNotifier.addPublishListener((result) { listener.start(
result.fold( onViewDeleted: (result) {
(view) => add(const DocumentEvent.deleted()), result.fold(
(error) {}, (view) => add(const DocumentEvent.deleted()),
); (error) {},
}); );
},
listener.restoredNotifier.addPublishListener((result) { onViewRestored: (result) {
result.fold( result.fold(
(view) => add(const DocumentEvent.restore()), (view) => add(const DocumentEvent.restore()),
(error) {}, (error) {},
); );
}); },
);
listener.start();
final result = await service.openDocument(docId: view.id); final result = await service.openDocument(docId: view.id);
result.fold( result.fold(
(block) { (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/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.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'; import 'select_option_service.dart';
part 'selection_editor_bloc.freezed.dart'; part 'selection_editor_bloc.freezed.dart';
@ -24,14 +28,19 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
_startListening(); _startListening();
}, },
didReceiveOptions: (_DidReceiveOptions value) { didReceiveOptions: (_DidReceiveOptions value) {
final result = _makeOptions(state.filter, value.options);
emit(state.copyWith( emit(state.copyWith(
allOptions: value.options, allOptions: value.options,
options: _makeOptions(state.filter, value.options), options: result.options,
createOption: result.createOption,
selectedOptions: value.selectedOptions, selectedOptions: value.selectedOptions,
)); ));
}, },
newOption: (_NewOption value) { newOption: (_NewOption value) {
_createOption(value.optionName); _createOption(value.optionName);
emit(state.copyWith(
filter: none(),
));
}, },
deleteOption: (_DeleteOption value) { deleteOption: (_DeleteOption value) {
_deleteOption(value.option); _deleteOption(value.option);
@ -91,16 +100,37 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
} }
void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) { 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); final List<SelectOption> options = List.from(allOptions);
if (filter.isNotEmpty) { Option<String> createOption = filter;
options.retainWhere((option) => option.name.toLowerCase().contains(filter.toLowerCase()));
}
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() { void _startListening() {
@ -135,7 +165,8 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
required List<SelectOption> options, required List<SelectOption> options,
required List<SelectOption> allOptions, required List<SelectOption> allOptions,
required List<SelectOption> selectedOptions, required List<SelectOption> selectedOptions,
required String filter, required Option<String> createOption,
required Option<String> filter,
}) = _SelectOptionEditorState; }) = _SelectOptionEditorState;
factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) { factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
@ -144,7 +175,18 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
options: data?.options ?? [], options: data?.options ?? [],
allOptions: data?.options ?? [], allOptions: data?.options ?? [],
selectedOptions: data?.selectOptions ?? [], 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 'dart:async';
import 'field_service.dart'; import 'field_service.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:protobuf/protobuf.dart';
part 'field_editor_bloc.freezed.dart'; part 'field_editor_bloc.freezed.dart';
@ -25,10 +26,13 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
await _getEditFieldContext(emit); await _getEditFieldContext(emit);
}, },
updateName: (_UpdateName value) { updateName: (_UpdateName value) {
emit(state.copyWith(fieldName: value.name)); final newContext = _updateEditContext(name: value.name);
emit(state.copyWith(editFieldContext: newContext));
}, },
switchField: (_SwitchField value) { updateField: (_UpdateField value) {
emit(state.copyWith(field: Some(value.field), typeOptionData: value.typeOptionData)); final newContext = _updateEditContext(field: value.field, typeOptionData: value.typeOptionData);
emit(state.copyWith(editFieldContext: newContext));
}, },
done: (_Done value) async { done: (_Done value) async {
await _saveField(emit); await _saveField(emit);
@ -43,14 +47,49 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
return super.close(); 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 { Future<void> _saveField(Emitter<FieldEditorState> emit) async {
await state.field.fold( await state.editFieldContext.fold(
() async => null, () async => null,
(field) async { (context) async {
field.name = state.fieldName;
final result = await service.insertField( final result = await service.insertField(
field: field, field: context.gridField,
typeOptionData: state.typeOptionData, typeOptionData: context.typeOptionData,
); );
result.fold((l) => null, (r) => null); result.fold((l) => null, (r) => null);
}, },
@ -60,11 +99,9 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
Future<void> _getEditFieldContext(Emitter<FieldEditorState> emit) async { Future<void> _getEditFieldContext(Emitter<FieldEditorState> emit) async {
final result = await _loader.load(); final result = await _loader.load();
result.fold( result.fold(
(editContext) { (context) {
emit(state.copyWith( emit(state.copyWith(
field: Some(editContext.gridField), editFieldContext: Some(context),
typeOptionData: editContext.typeOptionData,
fieldName: editContext.gridField.name,
)); ));
}, },
(err) => Log.error(err), (err) => Log.error(err),
@ -76,25 +113,21 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
class FieldEditorEvent with _$FieldEditorEvent { class FieldEditorEvent with _$FieldEditorEvent {
const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.initial() = _InitialField;
const factory FieldEditorEvent.updateName(String name) = _UpdateName; 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; const factory FieldEditorEvent.done() = _Done;
} }
@freezed @freezed
class FieldEditorState with _$FieldEditorState { class FieldEditorState with _$FieldEditorState {
const factory FieldEditorState({ const factory FieldEditorState({
required String fieldName,
required String gridId, required String gridId,
required String errorText, required String errorText,
required Option<Field> field, required Option<EditFieldContext> editFieldContext,
required List<int> typeOptionData,
}) = _FieldEditorState; }) = _FieldEditorState;
factory FieldEditorState.initial(String gridId) => FieldEditorState( factory FieldEditorState.initial(String gridId) => FieldEditorState(
gridId: gridId, gridId: gridId,
fieldName: '', editFieldContext: none(),
field: none(),
errorText: '', 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_service.dart';
export 'field/field_action_sheet_bloc.dart'; export 'field/field_action_sheet_bloc.dart';
export 'field/field_editor_bloc.dart'; export 'field/field_editor_bloc.dart';
export 'field/field_switch_bloc.dart'; export 'field/field_editor_pannel_bloc.dart';
// Field Type Option // Field Type Option
export 'field/type_option/date_bloc.dart'; 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: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:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
part 'home_bloc.freezed.dart'; part 'home_bloc.freezed.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> { 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 { on<HomeEvent>((event, emit) async {
await event.map( 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 { showLoading: (e) async {
emit(state.copyWith(isLoading: e.isLoading)); emit(state.copyWith(isLoading: e.isLoading));
}, },
@ -20,22 +43,40 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
forceCollapse: (e) async { forceCollapse: (e) async {
emit(state.copyWith(forceCollapse: e.forceCollapse)); emit(state.copyWith(forceCollapse: e.forceCollapse));
}, },
didReceiveWorkspaceSetting: (_DidReceiveWorkspaceSetting value) {
emit(state.copyWith(workspaceSetting: value.setting));
},
unauthorized: (_Unauthorized value) {
emit(state.copyWith(unauthorized: true));
},
); );
}); });
} }
@override @override
Future<void> close() { Future<void> close() async {
await _listener.stop();
return super.close(); return super.close();
} }
void _authDidChanged(Either<Unit, FlowyError> errorOrNothing) {
errorOrNothing.fold((_) {}, (error) {
if (error.code == ErrorCode.UserUnauthorized.value) {
add(HomeEvent.unauthorized(error.msg));
}
});
}
} }
@freezed @freezed
class HomeEvent with _$HomeEvent { class HomeEvent with _$HomeEvent {
const factory HomeEvent.initial() = _Initial;
const factory HomeEvent.showLoading(bool isLoading) = _ShowLoading; const factory HomeEvent.showLoading(bool isLoading) = _ShowLoading;
const factory HomeEvent.forceCollapse(bool forceCollapse) = _ForceCollapse; const factory HomeEvent.forceCollapse(bool forceCollapse) = _ForceCollapse;
const factory HomeEvent.setEditPannel(EditPannelContext editContext) = _ShowEditPannel; const factory HomeEvent.setEditPannel(EditPannelContext editContext) = _ShowEditPannel;
const factory HomeEvent.dismissEditPannel() = _DismissEditPannel; const factory HomeEvent.dismissEditPannel() = _DismissEditPannel;
const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting;
const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
} }
@freezed @freezed
@ -44,11 +85,15 @@ class HomeState with _$HomeState {
required bool isLoading, required bool isLoading,
required bool forceCollapse, required bool forceCollapse,
required Option<EditPannelContext> pannelContext, required Option<EditPannelContext> pannelContext,
required CurrentWorkspaceSetting workspaceSetting,
required bool unauthorized,
}) = _HomeState; }) = _HomeState;
factory HomeState.initial() => HomeState( factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState(
isLoading: false, isLoading: false,
forceCollapse: false, forceCollapse: false,
pannelContext: none(), 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 { on<MenuUserEvent>((event, emit) async {
await event.map( await event.map(
initial: (_) async { initial: (_) async {
userListener.profileUpdatedNotifier.addPublishListener(_profileUpdated); userListener.start(
userListener.workspaceUpdatedNotifier.addPublishListener(_workspacesUpdated); onProfileUpdated: _profileUpdated,
userListener.start(); onWorkspaceListUpdated: _workspaceListUpdated,
);
await _initUser(); await _initUser();
}, },
fetchWorkspaces: (_FetchWorkspaces value) async {}, fetchWorkspaces: (_FetchWorkspaces value) async {},
@ -41,7 +42,7 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
} }
void _profileUpdated(Either<UserProfile, FlowyError> userOrFailed) {} void _profileUpdated(Either<UserProfile, FlowyError> userOrFailed) {}
void _workspacesUpdated(Either<List<Workspace>, FlowyError> workspacesOrFailed) { void _workspaceListUpdated(Either<List<Workspace>, FlowyError> workspacesOrFailed) {
// fetch workspaces // fetch workspaces
// iUserImpl.fetchWorkspaces().then((result) { // iUserImpl.fetchWorkspaces().then((result) {
// result.fold( // result.fold(

View File

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

View File

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

View File

@ -16,8 +16,9 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
on<WelcomeEvent>( on<WelcomeEvent>(
(event, emit) async { (event, emit) async {
await event.map(initial: (e) async { await event.map(initial: (e) async {
userListener.workspaceUpdatedNotifier.addPublishListener(_workspacesUpdated); userListener.start(
userListener.start(); onWorkspaceListUpdated: (result) => add(WelcomeEvent.workspacesReveived(result)),
);
// //
await _fetchWorkspaces(emit); await _fetchWorkspaces(emit);
}, openWorkspace: (e) async { }, 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 @freezed

View File

@ -1,6 +1,5 @@
import 'package:app_flowy/plugin/plugin.dart'; 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_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/edit_pannel/pannel_animation.dart';
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart'; import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
@ -46,22 +45,20 @@ class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<HomeListenBloc>( BlocProvider<HomeBloc>(
create: (context) => getIt<HomeListenBloc>(param1: widget.user)..add(const HomeListenEvent.started()), create: (context) {
return HomeBloc(widget.user, widget.workspaceSetting)..add(const HomeEvent.initial());
},
), ),
BlocProvider<HomeBloc>(create: (context) => getIt<HomeBloc>()),
], ],
child: Scaffold( child: Scaffold(
key: HomeScreen.scaffoldKey, key: HomeScreen.scaffoldKey,
body: BlocListener<HomeListenBloc, HomeListenState>( body: BlocListener<HomeBloc, HomeState>(
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
listener: (context, state) { listener: (context, state) {
state.map( if (state.unauthorized) {
loading: (_) {}, Log.error("Push to login screen when user token was invalid");
unauthorized: (unauthorized) { }
// TODO: push to login screen when user token was invalid
Log.error("Push to login screen when user token was invalid");
},
);
}, },
child: BlocBuilder<HomeBloc, HomeState>( child: BlocBuilder<HomeBloc, HomeState>(
buildWhen: (previous, current) => previous != current, buildWhen: (previous, current) => previous != current,
@ -73,7 +70,7 @@ class _HomeScreenState extends State<HomeScreen> {
return FlowyContainer( return FlowyContainer(
Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surface,
// Colors.white, // 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( return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
final layout = HomeLayout(context, constraints, forceCollapse); final layout = HomeLayout(context, constraints, state.forceCollapse);
const homeStack = HomeStack(); const homeStack = HomeStack();
final menu = _buildHomeMenu( final menu = _buildHomeMenu(
layout: layout, layout: layout,
context: context, context: context,
state: state,
); );
final editPannel = _buildEditPannel( final editPannel = _buildEditPannel(
homeState: state, homeState: state,
@ -108,20 +106,21 @@ class _HomeScreenState extends State<HomeScreen> {
); );
} }
Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context}) { Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context, required HomeState state}) {
if (initialView == null && widget.workspaceSetting.hasLatestView()) { final workspaceSetting = state.workspaceSetting;
initialView = widget.workspaceSetting.latestView; if (initialView == null && workspaceSetting.hasLatestView()) {
initialView = workspaceSetting.latestView;
final plugin = makePlugin(pluginType: initialView!.pluginType, data: initialView); final plugin = makePlugin(pluginType: initialView!.pluginType, data: initialView);
getIt<HomeStackManager>().setPlugin(plugin); getIt<HomeStackManager>().setPlugin(plugin);
} }
HomeMenu homeMenu = HomeMenu( HomeMenu homeMenu = HomeMenu(
user: widget.user, user: widget.user,
workspaceSetting: widget.workspaceSetting, workspaceSetting: workspaceSetting,
collapsedNotifier: getIt<HomeStackManager>().collapsedNotifier, collapsedNotifier: getIt<HomeStackManager>().collapsedNotifier,
); );
final latestView = widget.workspaceSetting.hasLatestView() ? widget.workspaceSetting.latestView : null; final latestView = workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null;
getIt<MenuSharedState>().latestOpenView = latestView; getIt<MenuSharedState>().latestOpenView = latestView;
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu)); 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 { DocumentPlugin({required PluginType pluginType, required View view, Key? key}) : _view = view {
_pluginType = pluginType; _pluginType = pluginType;
_listener = getIt<ViewListener>(param1: view); _listener = getIt<ViewListener>(param1: view);
_listener?.updatedNotifier.addPublishListener((result) { _listener?.start(onViewUpdated: (result) {
result.fold( result.fold(
(newView) { (newView) {
_view = newView; _view = newView;
@ -69,12 +69,11 @@ class DocumentPlugin implements Plugin {
(error) {}, (error) {},
); );
}); });
_listener?.start();
} }
@override @override
void dispose() { void dispose() {
_listener?.close(); _listener?.stop();
_listener = null; _listener = null;
} }

View File

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

View File

@ -8,6 +8,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart'; import 'package:table_calendar/table_calendar.dart';
import 'cell_builder.dart'; import 'cell_builder.dart';
class DateCellStyle extends GridCellStyle {
Alignment alignment;
DateCellStyle({this.alignment = Alignment.center});
}
abstract class GridCellDelegate { abstract class GridCellDelegate {
void onFocus(bool isFocus); void onFocus(bool isFocus);
GridCellDelegate get delegate; GridCellDelegate get delegate;
@ -15,11 +21,19 @@ abstract class GridCellDelegate {
class DateCell extends GridCellWidget { class DateCell extends GridCellWidget {
final GridCellContextBuilder cellContextBuilder; final GridCellContextBuilder cellContextBuilder;
late final DateCellStyle? cellStyle;
DateCell({ DateCell({
GridCellStyle? style,
required this.cellContextBuilder, required this.cellContextBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key) {
if (style != null) {
cellStyle = (style as DateCellStyle);
} else {
cellStyle = null;
}
}
@override @override
State<DateCell> createState() => _DateCellState(); State<DateCell> createState() => _DateCellState();
@ -37,6 +51,7 @@ class _DateCellState extends State<DateCell> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final alignment = widget.cellStyle != null ? widget.cellStyle!.alignment : Alignment.center;
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<DateCellBloc, DateCellState>( child: BlocBuilder<DateCellBloc, DateCellState>(
@ -57,7 +72,7 @@ class _DateCellState extends State<DateCell> {
child: MouseRegion( child: MouseRegion(
opaque: false, opaque: false,
cursor: SystemMouseCursors.click, 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 { class SelectOptionTag extends StatelessWidget {
final SelectOption option; final String name;
final Color color;
final bool isSelected; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChoiceChip( return ChoiceChip(
pressElevation: 1, pressElevation: 1,
label: FlowyText.medium(option.name, fontSize: 12), label: FlowyText.medium(name, fontSize: 12),
selectedColor: option.color.make(context), selectedColor: color,
backgroundColor: option.color.make(context), backgroundColor: color,
labelPadding: const EdgeInsets.symmetric(horizontal: 6), labelPadding: const EdgeInsets.symmetric(horizontal: 6),
selected: true, selected: true,
onSelected: (_) {}, onSelected: (_) {},

View File

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

View File

@ -104,9 +104,18 @@ class _OptionList extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>( return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
builder: (context, state) { 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)); return _SelectOptionCell(option, state.selectedOptions.contains(option));
}).toList(); }).toList());
state.createOption.fold(
() => null,
(createOption) {
cells.add(_CreateOptionCell(name: createOption));
},
);
final list = ListView.separated( final list = ListView.separated(
shrinkWrap: true, shrinkWrap: true,
controller: ScrollController(), controller: ScrollController(),
@ -119,7 +128,11 @@ class _OptionList extends StatelessWidget {
return cells[index]; 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 { class _SelectOptionCell extends StatelessWidget {
final SelectOption option; final SelectOption option;
final bool isSelected; final bool isSelected;
@ -206,7 +243,11 @@ class _SelectOptionCell extends StatelessWidget {
style: HoverStyle(hoverColor: theme.hover), style: HoverStyle(hoverColor: theme.hover),
builder: (_, onHover) { builder: (_, onHover) {
List<Widget> children = [ List<Widget> children = [
SelectOptionTag(option: option, isSelected: isSelected), SelectOptionTag(
name: option.name,
color: option.color.make(context),
isSelected: isSelected,
),
const Spacer(), const Spacer(),
]; ];
@ -223,10 +264,7 @@ class _SelectOptionCell extends StatelessWidget {
)); ));
} }
return Padding( return Row(children: children);
padding: const EdgeInsets.all(3.0),
child: Row(children: children),
);
}, },
); );
} }

View File

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

View File

@ -1,18 +1,15 @@
import 'package:app_flowy/startup/startup.dart'; 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_editor_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.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:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart'; import 'field_name_input.dart';
import 'field_switcher.dart'; import 'field_editor_pannel.dart';
class FieldEditor extends FlowyOverlayDelegate { class FieldEditor extends FlowyOverlayDelegate {
final String gridId; final String gridId;
@ -30,7 +27,6 @@ class FieldEditor extends FlowyOverlayDelegate {
BuildContext context, { BuildContext context, {
AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned, AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned,
}) { }) {
Log.trace("Show $identifier()");
FlowyOverlay.of(context).remove(identifier()); FlowyOverlay.of(context).remove(identifier());
FlowyOverlay.of(context).insertWithAnchor( FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer( widget: OverlayContainer(
@ -69,16 +65,24 @@ class _FieldEditorWidget extends StatelessWidget {
value: editorBloc, value: editorBloc,
child: BlocBuilder<FieldEditorBloc, FieldEditorState>( child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) { builder: (context, state) {
return state.field.fold( return state.editFieldContext.fold(
() => const SizedBox(), () => const SizedBox(),
(field) => ListView( (editFieldContext) => ListView(
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12), FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
const VSpace(10), const VSpace(10),
const _FieldNameTextField(), const _FieldNameTextField(),
const VSpace(10), 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 { class _FieldNameTextField extends StatelessWidget {
@ -105,11 +97,16 @@ class _FieldNameTextField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<FieldEditorBloc, FieldEditorState>( return BlocSelector<FieldEditorBloc, FieldEditorState, String>(
buildWhen: (previous, current) => previous.fieldName != current.fieldName, selector: (state) {
builder: (context, state) { return state.editFieldContext.fold(
() => "",
(editFieldContext) => editFieldContext.gridField.name,
);
},
builder: (context, name) {
return FieldNameTextField( return FieldNameTextField(
name: state.fieldName, name: name,
errorText: context.read<FieldEditorBloc>().state.errorText, errorText: context.read<FieldEditorBloc>().state.errorText,
onNameChanged: (newName) { onNameChanged: (newName) {
context.read<FieldEditorBloc>().add(FieldEditorEvent.updateName(newName)); context.read<FieldEditorBloc>().add(FieldEditorEvent.updateName(newName));

View File

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

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/startup/startup.dart'; 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/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/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:easy_localization/easy_localization.dart' hide DateFormat;
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.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/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/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/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/image.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.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/multi_select_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.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/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/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/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/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/image.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.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/single_select_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'field_option_pannel.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/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_cell.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.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/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.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/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/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType; 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( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), 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 { class _PropertyList extends StatelessWidget {
final GridCellCache cellCache; final GridCellCache cellCache;
final ScrollController _scrollController; final ScrollController _scrollController;
@ -165,7 +191,9 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
return null; return null;
case FieldType.DateTime: case FieldType.DateTime:
return null; return DateCellStyle(
alignment: Alignment.centerLeft,
);
case FieldType.MultiSelect: case FieldType.MultiSelect:
return SelectOptionCellStyle( return SelectOptionCellStyle(
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), 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:app_flowy/workspace/application/view/view_service.dart';
import 'package:flowy_infra/theme.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:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -16,12 +18,26 @@ class ViewLeftBarItem extends StatefulWidget {
class _ViewLeftBarItemState extends State<ViewLeftBarItem> { class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
final _controller = TextEditingController(); final _controller = TextEditingController();
final _focusNode = FocusNode(); final _focusNode = FocusNode();
late ViewService serviceService; late ViewService _viewService;
late ViewListener _viewListener;
late View view;
@override @override
void initState() { void initState() {
serviceService = ViewService(/*view: widget.view*/); view = widget.view;
_viewService = ViewService();
_focusNode.addListener(_handleFocusChanged); _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(); super.initState();
} }
@ -30,12 +46,13 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
_controller.dispose(); _controller.dispose();
_focusNode.removeListener(_handleFocusChanged); _focusNode.removeListener(_handleFocusChanged);
_focusNode.dispose(); _focusNode.dispose();
_viewListener.stop();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_controller.text = widget.view.name; _controller.text = view.name;
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
return IntrinsicWidth( return IntrinsicWidth(
@ -63,12 +80,12 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
void _handleFocusChanged() { void _handleFocusChanged() {
if (_controller.text.isEmpty) { if (_controller.text.isEmpty) {
_controller.text = widget.view.name; _controller.text = view.name;
return; return;
} }
if (_controller.text != widget.view.name) { if (_controller.text != view.name) {
serviceService.updateView(viewId: widget.view.id, name: _controller.text); _viewService.updateView(viewId: view.id, name: _controller.text);
} }
} }
} }

View File

@ -176,7 +176,6 @@ class FlowyOverlayState extends State<FlowyOverlay> {
FlowyOverlayStyle? style, FlowyOverlayStyle? style,
Offset? anchorOffset, Offset? anchorOffset,
}) { }) {
debugPrint("Show overlay: $identifier");
this.style = style ?? FlowyOverlayStyle(); this.style = style ?? FlowyOverlayStyle();
_showOverlay( _showOverlay(
@ -245,6 +244,7 @@ class FlowyOverlayState extends State<FlowyOverlay> {
OverlapBehaviour? overlapBehaviour, OverlapBehaviour? overlapBehaviour,
FlowyOverlayDelegate? delegate, FlowyOverlayDelegate? delegate,
}) { }) {
debugPrint("Show overlay: $identifier");
Widget overlay = widget; Widget overlay = widget;
final offset = anchorOffset ?? Offset.zero; 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 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 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 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 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 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'); 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, WorkspaceUpdated,
WorkspaceListUpdated, WorkspaceListUpdated,
WorkspaceAppsChanged, WorkspaceAppsChanged,
WorkspaceSetting,
AppUpdated, AppUpdated,
AppViewsChanged, AppViewsChanged,
ViewUpdated, ViewUpdated,

View File

@ -18,6 +18,7 @@ const FolderNotification$json = const {
const {'1': 'WorkspaceUpdated', '2': 12}, const {'1': 'WorkspaceUpdated', '2': 12},
const {'1': 'WorkspaceListUpdated', '2': 13}, const {'1': 'WorkspaceListUpdated', '2': 13},
const {'1': 'WorkspaceAppsChanged', '2': 14}, const {'1': 'WorkspaceAppsChanged', '2': 14},
const {'1': 'WorkspaceSetting', '2': 15},
const {'1': 'AppUpdated', '2': 21}, const {'1': 'AppUpdated', '2': 21},
const {'1': 'AppViewsChanged', '2': 24}, const {'1': 'AppViewsChanged', '2': 24},
const {'1': 'ViewUpdated', '2': 31}, const {'1': 'ViewUpdated', '2': 31},
@ -29,4 +30,4 @@ const FolderNotification$json = const {
}; };
/// Descriptor for `FolderNotification`. Decode as a `google.protobuf.EnumDescriptorProto`. /// 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, WorkspaceUpdated = 12,
WorkspaceListUpdated = 13, WorkspaceListUpdated = 13,
WorkspaceAppsChanged = 14, WorkspaceAppsChanged = 14,
WorkspaceSetting = 15,
AppUpdated = 21, AppUpdated = 21,
AppViewsChanged = 24, AppViewsChanged = 24,
ViewUpdated = 31, ViewUpdated = 31,

View File

@ -31,6 +31,7 @@ pub enum FolderNotification {
WorkspaceUpdated = 12, WorkspaceUpdated = 12,
WorkspaceListUpdated = 13, WorkspaceListUpdated = 13,
WorkspaceAppsChanged = 14, WorkspaceAppsChanged = 14,
WorkspaceSetting = 15,
AppUpdated = 21, AppUpdated = 21,
AppViewsChanged = 24, AppViewsChanged = 24,
ViewUpdated = 31, ViewUpdated = 31,
@ -53,6 +54,7 @@ impl ::protobuf::ProtobufEnum for FolderNotification {
12 => ::std::option::Option::Some(FolderNotification::WorkspaceUpdated), 12 => ::std::option::Option::Some(FolderNotification::WorkspaceUpdated),
13 => ::std::option::Option::Some(FolderNotification::WorkspaceListUpdated), 13 => ::std::option::Option::Some(FolderNotification::WorkspaceListUpdated),
14 => ::std::option::Option::Some(FolderNotification::WorkspaceAppsChanged), 14 => ::std::option::Option::Some(FolderNotification::WorkspaceAppsChanged),
15 => ::std::option::Option::Some(FolderNotification::WorkspaceSetting),
21 => ::std::option::Option::Some(FolderNotification::AppUpdated), 21 => ::std::option::Option::Some(FolderNotification::AppUpdated),
24 => ::std::option::Option::Some(FolderNotification::AppViewsChanged), 24 => ::std::option::Option::Some(FolderNotification::AppViewsChanged),
31 => ::std::option::Option::Some(FolderNotification::ViewUpdated), 31 => ::std::option::Option::Some(FolderNotification::ViewUpdated),
@ -72,6 +74,7 @@ impl ::protobuf::ProtobufEnum for FolderNotification {
FolderNotification::WorkspaceUpdated, FolderNotification::WorkspaceUpdated,
FolderNotification::WorkspaceListUpdated, FolderNotification::WorkspaceListUpdated,
FolderNotification::WorkspaceAppsChanged, FolderNotification::WorkspaceAppsChanged,
FolderNotification::WorkspaceSetting,
FolderNotification::AppUpdated, FolderNotification::AppUpdated,
FolderNotification::AppViewsChanged, FolderNotification::AppViewsChanged,
FolderNotification::ViewUpdated, FolderNotification::ViewUpdated,
@ -107,14 +110,15 @@ impl ::protobuf::reflect::ProtobufValue for FolderNotification {
} }
static file_descriptor_proto_data: &'static [u8] = b"\ 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\ \x07Unknown\x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13Us\
erDeleteWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x18\ erDeleteWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x18\
\n\x14WorkspaceListUpdated\x10\r\x12\x18\n\x14WorkspaceAppsChanged\x10\ \n\x14WorkspaceListUpdated\x10\r\x12\x18\n\x14WorkspaceAppsChanged\x10\
\x0e\x12\x0e\n\nAppUpdated\x10\x15\x12\x13\n\x0fAppViewsChanged\x10\x18\ \x0e\x12\x14\n\x10WorkspaceSetting\x10\x0f\x12\x0e\n\nAppUpdated\x10\x15\
\x12\x0f\n\x0bViewUpdated\x10\x1f\x12\x0f\n\x0bViewDeleted\x10\x20\x12\ \x12\x13\n\x0fAppViewsChanged\x10\x18\x12\x0f\n\x0bViewUpdated\x10\x1f\
\x10\n\x0cViewRestored\x10!\x12\x14\n\x10UserUnauthorized\x10d\x12\x11\n\ \x12\x0f\n\x0bViewDeleted\x10\x20\x12\x10\n\x0cViewRestored\x10!\x12\x14\
\x0cTrashUpdated\x10\xe8\x07b\x06proto3\ \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; 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; WorkspaceUpdated = 12;
WorkspaceListUpdated = 13; WorkspaceListUpdated = 13;
WorkspaceAppsChanged = 14; WorkspaceAppsChanged = 14;
WorkspaceSetting = 15;
AppUpdated = 21; AppUpdated = 21;
AppViewsChanged = 24; AppViewsChanged = 24;
ViewUpdated = 31; 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::{ use crate::{
entities::{ entities::{
trash::Trash, trash::Trash,
@ -69,10 +70,12 @@ pub(crate) async fn delete_view_handler(
pub(crate) async fn set_latest_view_handler( pub(crate) async fn set_latest_view_handler(
data: Data<ViewId>, data: Data<ViewId>,
folder: AppData<Arc<FolderManager>>,
controller: AppData<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let view_id: ViewId = data.into_inner(); let view_id: ViewId = data.into_inner();
let _ = controller.set_latest_view(&view_id.value)?; let _ = controller.set_latest_view(&view_id.value)?;
let _ = notify_workspace_setting_did_change(&folder, &view_id).await?;
Ok(()) Ok(())
} }

View File

@ -1,3 +1,4 @@
use crate::manager::FolderManager;
use crate::{ use crate::{
dart_notification::*, dart_notification::*,
errors::*, 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"; const CURRENT_WORKSPACE_ID: &str = "current_workspace_id";
pub fn set_current_workspace(workspace_id: &str) { 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; let cell_data = type_option_cell_data.data;
match self.format { 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()), NumberFormat::Percent => cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string()),
_ => self.money_from_str(&cell_data), _ => self.money_from_str(&cell_data),
} }
@ -100,10 +110,13 @@ impl CellDataOperation for NumberTypeOption {
_cell_meta: Option<CellMeta>, _cell_meta: Option<CellMeta>,
) -> Result<String, FlowyError> { ) -> Result<String, FlowyError> {
let changeset = changeset.into(); let changeset = changeset.into();
let data = self.strip_symbol(changeset); let mut data = changeset.trim().to_string();
if !data.chars().all(char::is_numeric) { if self.format != NumberFormat::Number {
return Err(FlowyError::invalid_data().context("Should only contain numbers")); 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()) 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 { pub enum NumberFormat {
Number = 0, Number = 0,
USD = 1, USD = 1,

View File

@ -316,7 +316,11 @@ impl ClientGridEditor {
let cell_data_changeset = changeset.data.unwrap(); let cell_data_changeset = changeset.data.unwrap();
let cell_meta = self.get_cell_meta(&changeset.row_id, &changeset.field_id).await?; 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) { match self.grid_pad.read().await.get_field_meta(&changeset.field_id) {
None => { None => {
let msg = format!("Field not found with id: {}", &changeset.field_id); 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)] #[derive(Default, ProtoBuf)]
pub struct RepeatedViewId { pub struct RepeatedViewId {
#[pb(index = 1)] #[pb(index = 1)]