mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #485 from AppFlowy-IO/fix_0.0.4_beta3
Fix 0.0.4 beta3
This commit is contained in:
commit
cb0a86893b
@ -181,6 +181,7 @@
|
||||
"textPlaceholder": "Empty"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Create",
|
||||
"purpleColor": "Purple",
|
||||
"pinkColor": "Pink",
|
||||
"lightPinkColor": "Light Pink",
|
||||
|
@ -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>(
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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';
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -1 +1 @@
|
||||
export 'home_listen_bloc.dart';
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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: (_) {},
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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));
|
||||
|
@ -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(
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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');
|
||||
|
@ -10,6 +10,7 @@ pub(crate) enum FolderNotification {
|
||||
WorkspaceUpdated = 12,
|
||||
WorkspaceListUpdated = 13,
|
||||
WorkspaceAppsChanged = 14,
|
||||
WorkspaceSetting = 15,
|
||||
AppUpdated = 21,
|
||||
AppViewsChanged = 24,
|
||||
ViewUpdated = 31,
|
||||
|
@ -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;
|
||||
|
@ -7,6 +7,7 @@ enum FolderNotification {
|
||||
WorkspaceUpdated = 12;
|
||||
WorkspaceListUpdated = 13;
|
||||
WorkspaceAppsChanged = 14;
|
||||
WorkspaceSetting = 15;
|
||||
AppUpdated = 21;
|
||||
AppViewsChanged = 24;
|
||||
ViewUpdated = 31;
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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)]
|
||||
|
Loading…
Reference in New Issue
Block a user