diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 1031265da1..0fe9008ee6 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -155,7 +155,15 @@ "numberFieldName": "Numbers", "singleSelectFieldName": "Select", "multiSelectFieldName": "Multiselect", - "numberFormat": " Number format" + "numberFormat": " Number format", + "dateFormat": " Date format", + "dateFormatFriendly": "Month Day,Year", + "dateFormatISO": "Year-Month-Day", + "dateFormatLocal": "Month/Month/Day", + "dateFormatUS": "Month/Month/Day", + "timeFormat": " Time format", + "timeFormatTwelveHour": "12 hour", + "timeFormatTwentyFourHour": "24 hour" } } } diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index e277bc6a20..5d78cbdf2a 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -18,6 +18,7 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.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'; +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'; import 'package:get_it/get_it.dart'; @@ -210,16 +211,16 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam<SwitchFieldTypeBloc, SwitchFieldContext, void>( - (context, _) => SwitchFieldTypeBloc(context), + getIt.registerFactoryParam<FieldTypeSwitchBloc, SwitchFieldContext, void>( + (context, _) => FieldTypeSwitchBloc(context), ); getIt.registerFactory<SelectionTypeOptionBloc>( () => SelectionTypeOptionBloc(), ); - getIt.registerFactory<DateTypeOptionBloc>( - () => DateTypeOptionBloc(), + getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>( + (typeOption, _) => DateTypeOptionBloc(typeOption: typeOption), ); getIt.registerFactoryParam<NumberTypeOptionBloc, NumberTypeOption, void>( diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart index a2f7438434..ba8371c3a3 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart @@ -9,9 +9,9 @@ import 'field_service.dart'; part 'switch_field_type_bloc.freezed.dart'; -class SwitchFieldTypeBloc extends Bloc<SwitchFieldTypeEvent, SwitchFieldTypeState> { - SwitchFieldTypeBloc(SwitchFieldContext editContext) : super(SwitchFieldTypeState.initial(editContext)) { - on<SwitchFieldTypeEvent>( +class FieldTypeSwitchBloc extends Bloc<FieldTypeSwitchEvent, FieldTypeSwitchState> { + FieldTypeSwitchBloc(SwitchFieldContext editContext) : super(FieldTypeSwitchState.initial(editContext)) { + on<FieldTypeSwitchEvent>( (event, emit) async { await event.map( toFieldType: (_ToFieldType value) async { @@ -44,20 +44,20 @@ class SwitchFieldTypeBloc extends Bloc<SwitchFieldTypeEvent, SwitchFieldTypeStat } @freezed -class SwitchFieldTypeEvent with _$SwitchFieldTypeEvent { - const factory SwitchFieldTypeEvent.toFieldType(FieldType fieldType) = _ToFieldType; - const factory SwitchFieldTypeEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData; +class FieldTypeSwitchEvent with _$FieldTypeSwitchEvent { + const factory FieldTypeSwitchEvent.toFieldType(FieldType fieldType) = _ToFieldType; + const factory FieldTypeSwitchEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData; } @freezed -class SwitchFieldTypeState with _$SwitchFieldTypeState { - const factory SwitchFieldTypeState({ +class FieldTypeSwitchState with _$FieldTypeSwitchState { + const factory FieldTypeSwitchState({ required String gridId, required Field field, required Uint8List typeOptionData, - }) = _SwitchFieldTypeState; + }) = _FieldTypeSwitchState; - factory SwitchFieldTypeState.initial(SwitchFieldContext switchContext) => SwitchFieldTypeState( + factory FieldTypeSwitchState.initial(SwitchFieldContext switchContext) => FieldTypeSwitchState( gridId: switchContext.gridId, field: switchContext.field, typeOptionData: Uint8List.fromList(switchContext.typeOptionData), diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart index 78b22b078b..e2e7f2f0e2 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart @@ -1,20 +1,23 @@ -import 'dart:typed_data'; - -import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; -import 'package:dartz/dartz.dart'; part 'date_bloc.freezed.dart'; class DateTypeOptionBloc extends Bloc<DateTypeOptionEvent, DateTypeOptionState> { - DateTypeOptionBloc() : super(DateTypeOptionState.initial()) { + DateTypeOptionBloc({required DateTypeOption typeOption}) : super(DateTypeOptionState.initial(typeOption)) { on<DateTypeOptionEvent>( (event, emit) async { - await event.map( - initial: (_InitialField value) async {}, + event.map( + didSelectDateFormat: (_DidSelectDateFormat value) { + state.typeOption.dateFormat = value.format; + emit(state); + }, + didSelectTimeFormat: (_DidSelectTimeFormat value) { + state.typeOption.timeFormat = value.format; + emit(state); + }, ); }, ); @@ -28,12 +31,15 @@ class DateTypeOptionBloc extends Bloc<DateTypeOptionEvent, DateTypeOptionState> @freezed class DateTypeOptionEvent with _$DateTypeOptionEvent { - const factory DateTypeOptionEvent.initial(Uint8List? typeOptionData) = _InitialField; + const factory DateTypeOptionEvent.didSelectDateFormat(DateFormat format) = _DidSelectDateFormat; + const factory DateTypeOptionEvent.didSelectTimeFormat(TimeFormat format) = _DidSelectTimeFormat; } @freezed class DateTypeOptionState with _$DateTypeOptionState { - const factory DateTypeOptionState() = _DateTypeOptionState; + const factory DateTypeOptionState({ + required DateTypeOption typeOption, + }) = _DateTypeOptionState; - factory DateTypeOptionState.initial() => DateTypeOptionState(); + factory DateTypeOptionState.initial(DateTypeOption typeOption) => DateTypeOptionState(typeOption: typeOption); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart index 5b63a7aee4..a396454671 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart @@ -9,8 +9,7 @@ class NumberTypeOptionBloc extends Bloc<NumberTypeOptionEvent, NumberTypeOptionS NumberTypeOptionBloc({required NumberTypeOption typeOption}) : super(NumberTypeOptionState.initial(typeOption)) { on<NumberTypeOptionEvent>( (event, emit) async { - await event.map( - initial: (_InitialField value) async {}, + event.map( didSelectFormat: (_DidSelectFormat value) { state.typeOption.format = value.format; emit(state); @@ -28,7 +27,6 @@ class NumberTypeOptionBloc extends Bloc<NumberTypeOptionEvent, NumberTypeOptionS @freezed class NumberTypeOptionEvent with _$NumberTypeOptionEvent { - const factory NumberTypeOptionEvent.initial() = _InitialField; const factory NumberTypeOptionEvent.didSelectFormat(NumberFormat format) = _DidSelectFormat; } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart index e94507ab52..c24bdb4326 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart @@ -10,6 +10,8 @@ class GridSize { static double get trailHeaderPadding => 140 * scale; static double get headerContainerPadding => 0 * scale; static double get cellContentPadding => 10 * scale; + static double get typeOptionItemHeight => 30 * scale; + static double get typeOptionSeparatorHeight => 6 * scale; // static EdgeInsets get headerContentInsets => EdgeInsets.symmetric( @@ -26,6 +28,11 @@ class GridSize { vertical: GridSize.cellContentPadding, ); + static EdgeInsets get typeOptionContentInsets => const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ); + static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB( 0, GridSize.headerContainerPadding, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart index dd477dd8bb..8b2b74d9ae 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart @@ -4,7 +4,6 @@ import 'package:app_flowy/workspace/application/grid/field/switch_field_type_blo 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/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'field_name_input.dart'; @@ -61,7 +60,6 @@ class _CreateFieldPannelWidget extends StatelessWidget { const _FieldNameTextField(), const VSpace(10), _FieldTypeSwitcher(SwitchFieldContext(state.gridId, field, state.typeOptionData)), - const VSpace(10), ], ), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart index 1fee89aecb..6e03b4243d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart @@ -1,26 +1,29 @@ import 'dart:typed_data'; -import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/application/grid/prelude.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/selection.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/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart'; + import 'type_option/number.dart'; typedef SelectFieldCallback = void Function(Field, Uint8List); -class FieldTypeSwitcher extends StatelessWidget { +class FieldTypeSwitcher extends StatefulWidget { final SwitchFieldContext switchContext; final SelectFieldCallback onSelected; @@ -30,25 +33,27 @@ class FieldTypeSwitcher extends StatelessWidget { Key? key, }) : super(key: key); + @override + State<FieldTypeSwitcher> createState() => _FieldTypeSwitcherState(); +} + +class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> { + String? currentOverlayIdentifier; + @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => getIt<SwitchFieldTypeBloc>(param1: switchContext), - child: BlocBuilder<SwitchFieldTypeBloc, SwitchFieldTypeState>( + create: (context) => getIt<FieldTypeSwitchBloc>(param1: widget.switchContext), + child: BlocBuilder<FieldTypeSwitchBloc, FieldTypeSwitchState>( builder: (context, state) { - List<Widget> children = [ - _switchFieldTypeButton(context, state.field), - ]; + List<Widget> children = [_switchFieldTypeButton(context, state.field)]; - final builder = _makeTypeOptionBuild( + final typeOptionWidget = _typeOptionWidget( + context: context, fieldType: state.field.fieldType, - typeOptionData: state.typeOptionData, - typeOptionDataCallback: (newTypeOptionData) { - context.read<SwitchFieldTypeBloc>().add(SwitchFieldTypeEvent.didUpdateTypeOptionData(newTypeOptionData)); - }, + data: state.typeOptionData, ); - final typeOptionWidget = builder.customWidget; if (typeOptionWidget != null) { children.add(typeOptionWidget); } @@ -65,53 +70,100 @@ class FieldTypeSwitcher extends StatelessWidget { Widget _switchFieldTypeButton(BuildContext context, Field field) { final theme = context.watch<AppTheme>(); return SizedBox( - height: 36, + height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(field.fieldType.title(), fontSize: 12), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), hoverColor: theme.hover, - onTap: () => FieldTypeList.show(context, (fieldType) { - context.read<SwitchFieldTypeBloc>().add(SwitchFieldTypeEvent.toFieldType(fieldType)); - }), + onTap: () { + final list = FieldTypeList(onSelectField: (fieldType) { + context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.toFieldType(fieldType)); + }); + _showOverlay(context, FieldTypeList.identifier(), list); + }, leftIcon: svg(field.fieldType.iconName(), color: theme.iconColor), rightIcon: svg("grid/more", color: theme.iconColor), ), ); } + + Widget? _typeOptionWidget({ + required BuildContext context, + required FieldType fieldType, + required TypeOptionData data, + }) { + final delegate = TypeOptionOperationDelegate( + didUpdateTypeOptionData: (data) { + context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.didUpdateTypeOptionData(data)); + }, + requireToShowOverlay: _showOverlay, + ); + final builder = _makeTypeOptionBuild(fieldType: fieldType, data: data, delegate: delegate); + return builder.customWidget; + } + + void _showOverlay(BuildContext context, String identifier, Widget child) { + if (currentOverlayIdentifier != null) { + FlowyOverlay.of(context).remove(currentOverlayIdentifier!); + } + + currentOverlayIdentifier = identifier; + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + child: child, + constraints: BoxConstraints.loose(const Size(240, 400)), + ), + identifier: identifier, + anchorContext: context, + anchorDirection: AnchorDirection.leftWithCenterAligned, + style: FlowyOverlayStyle(blur: false), + anchorOffset: const Offset(-20, 0), + ); + } } abstract class TypeOptionBuilder { Widget? get customWidget; } +TypeOptionBuilder _makeTypeOptionBuild({ + required FieldType fieldType, + required TypeOptionData data, + required TypeOptionOperationDelegate delegate, +}) { + switch (fieldType) { + case FieldType.Checkbox: + return CheckboxTypeOptionBuilder(data); + case FieldType.DateTime: + return DateTypeOptionBuilder(data, delegate); + case FieldType.MultiSelect: + return MultiSelectTypeOptionBuilder(data); + case FieldType.Number: + return NumberTypeOptionBuilder(data, delegate); + case FieldType.RichText: + return RichTextTypeOptionBuilder(data); + case FieldType.SingleSelect: + return SingleSelectTypeOptionBuilder(data); + default: + throw UnimplementedError; + } +} + abstract class TypeOptionWidget extends StatelessWidget { const TypeOptionWidget({Key? key}) : super(key: key); } typedef TypeOptionData = Uint8List; typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData); +typedef ShowOverlayCallback = void Function(BuildContext anchorContext, String overlayIdentifier, Widget child); -TypeOptionBuilder _makeTypeOptionBuild({ - required FieldType fieldType, - required TypeOptionData typeOptionData, - required TypeOptionDataCallback typeOptionDataCallback, -}) { - switch (fieldType) { - case FieldType.Checkbox: - return CheckboxTypeOptionBuilder(typeOptionData); - case FieldType.DateTime: - return DateTypeOptionBuilder(typeOptionData); - case FieldType.MultiSelect: - return MultiSelectTypeOptionBuilder(typeOptionData); - case FieldType.Number: - return NumberTypeOptionBuilder(typeOptionData, typeOptionDataCallback); - case FieldType.RichText: - return RichTextTypeOptionBuilder(typeOptionData); - case FieldType.SingleSelect: - return SingleSelectTypeOptionBuilder(typeOptionData); - default: - throw UnimplementedError; - } +class TypeOptionOperationDelegate { + TypeOptionDataCallback didUpdateTypeOptionData; + ShowOverlayCallback requireToShowOverlay; + TypeOptionOperationDelegate({ + required this.didUpdateTypeOptionData, + required this.requireToShowOverlay, + }); } class RichTextTypeOptionBuilder extends TypeOptionBuilder { @@ -123,27 +175,6 @@ class RichTextTypeOptionBuilder extends TypeOptionBuilder { Widget? get customWidget => null; } -class DateTypeOptionBuilder extends TypeOptionBuilder { - DateTypeOption typeOption; - - DateTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = DateTypeOption.fromBuffer(typeOptionData); - - @override - Widget? get customWidget => const DateTypeOptionWidget(); -} - -class DateTypeOptionWidget extends TypeOptionWidget { - const DateTypeOptionWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt<DateTypeOptionBloc>(), - child: Container(height: 80, color: Colors.red), - ); - } -} - class CheckboxTypeOptionBuilder extends TypeOptionBuilder { CheckboxTypeOption typeOption; @@ -152,47 +183,3 @@ class CheckboxTypeOptionBuilder extends TypeOptionBuilder { @override Widget? get customWidget => null; } - -class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { - SingleSelectTypeOption typeOption; - - SingleSelectTypeOptionBuilder(TypeOptionData typeOptionData) - : typeOption = SingleSelectTypeOption.fromBuffer(typeOptionData); - - @override - Widget? get customWidget => const SingleSelectTypeOptionWidget(); -} - -class SingleSelectTypeOptionWidget extends TypeOptionWidget { - const SingleSelectTypeOptionWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt<SelectionTypeOptionBloc>(), - child: Container(height: 100, color: Colors.yellow), - ); - } -} - -class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { - MultiSelectTypeOption typeOption; - - MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData) - : typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData); - - @override - Widget? get customWidget => const MultiSelectTypeOptionWidget(); -} - -class MultiSelectTypeOptionWidget extends TypeOptionWidget { - const MultiSelectTypeOptionWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt<SelectionTypeOptionBloc>(), - child: Container(height: 100, color: Colors.blue), - ); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart index 25481fce09..dfc5c007c6 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart @@ -19,12 +19,13 @@ class FieldTypeList extends StatelessWidget { static void show(BuildContext context, SelectFieldCallback onSelectField) { final list = FieldTypeList(onSelectField: onSelectField); + FieldTypeList.hide(context); FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( child: list, constraints: BoxConstraints.loose(const Size(140, 300)), ), - identifier: list.identifier(), + identifier: FieldTypeList.identifier(), anchorContext: context, anchorDirection: AnchorDirection.leftWithCenterAligned, style: FlowyOverlayStyle(blur: false), @@ -32,6 +33,10 @@ class FieldTypeList extends StatelessWidget { ); } + static void hide(BuildContext context) { + FlowyOverlay.of(context).remove(FieldTypeList.identifier()); + } + @override Widget build(BuildContext context) { final cells = FieldType.values.map((fieldType) { @@ -39,7 +44,7 @@ class FieldTypeList extends StatelessWidget { fieldType: fieldType, onSelectField: (fieldType) { onSelectField(fieldType); - FlowyOverlay.of(context).remove(identifier()); + FlowyOverlay.of(context).remove(FieldTypeList.identifier()); }, ); }).toList(); @@ -58,8 +63,8 @@ class FieldTypeList extends StatelessWidget { ); } - String identifier() { - return toString(); + static String identifier() { + return (FieldTypeList).toString(); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart new file mode 100644 index 0000000000..493ab0e02e --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart @@ -0,0 +1,229 @@ +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_tyep_switcher.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'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DateTypeOptionBuilder extends TypeOptionBuilder { + DateTypeOption typeOption; + TypeOptionOperationDelegate delegate; + + DateTypeOptionBuilder(TypeOptionData typeOptionData, this.delegate) + : typeOption = DateTypeOption.fromBuffer(typeOptionData); + + @override + Widget? get customWidget => DateTypeOptionWidget( + typeOption: typeOption, + operationDelegate: delegate, + ); +} + +class DateTypeOptionWidget extends TypeOptionWidget { + final DateTypeOption typeOption; + final TypeOptionOperationDelegate operationDelegate; + const DateTypeOptionWidget({required this.typeOption, required this.operationDelegate, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => getIt<DateTypeOptionBloc>(param1: typeOption), + child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>( + listener: (context, state) => operationDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), + builder: (context, state) { + return Column(children: [ + _dateFormatButton(context), + _timeFormatButton(context), + ]); + }, + ), + ); + } + + Widget _dateFormatButton(BuildContext context) { + final theme = context.watch<AppTheme>(); + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr(), fontSize: 12), + padding: GridSize.typeOptionContentInsets, + hoverColor: theme.hover, + onTap: () { + final list = DateFormatList(onSelected: (format) { + context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectDateFormat(format)); + }); + operationDelegate.requireToShowOverlay(context, list.identifier(), list); + }, + rightIcon: svg("grid/more", color: theme.iconColor), + ), + ); + } + + Widget _timeFormatButton(BuildContext context) { + final theme = context.watch<AppTheme>(); + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr(), fontSize: 12), + padding: GridSize.typeOptionContentInsets, + hoverColor: theme.hover, + onTap: () { + final list = TimeFormatList(onSelected: (format) { + context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectTimeFormat(format)); + }); + operationDelegate.requireToShowOverlay(context, list.identifier(), list); + }, + rightIcon: svg("grid/more", color: theme.iconColor), + ), + ); + } +} + +class DateFormatList extends StatelessWidget { + final Function(DateFormat format) onSelected; + const DateFormatList({required this.onSelected, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final formatItems = DateFormat.values.map((format) { + return DateFormatItem( + dateFormat: format, + onSelected: (format) { + onSelected(format); + FlowyOverlay.of(context).remove(identifier()); + }); + }).toList(); + + return SizedBox( + width: 180, + child: ListView.separated( + shrinkWrap: true, + controller: ScrollController(), + separatorBuilder: (context, index) { + return VSpace(GridSize.typeOptionSeparatorHeight); + }, + itemCount: formatItems.length, + itemBuilder: (BuildContext context, int index) { + return formatItems[index]; + }, + ), + ); + } + + String identifier() { + return toString(); + } +} + +class DateFormatItem extends StatelessWidget { + final DateFormat dateFormat; + final Function(DateFormat format) onSelected; + const DateFormatItem({required this.dateFormat, required this.onSelected, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch<AppTheme>(); + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(dateFormat.title(), fontSize: 12), + hoverColor: theme.hover, + onTap: () => onSelected(dateFormat), + ), + ); + } +} + +extension DateFormatExtension on DateFormat { + String title() { + switch (this) { + case DateFormat.Friendly: + return LocaleKeys.grid_field_dateFormatFriendly.tr(); + case DateFormat.ISO: + return LocaleKeys.grid_field_dateFormatISO.tr(); + case DateFormat.Local: + return LocaleKeys.grid_field_dateFormatLocal.tr(); + case DateFormat.US: + return LocaleKeys.grid_field_dateFormatUS.tr(); + default: + throw UnimplementedError; + } + } +} + +class TimeFormatList extends StatelessWidget { + final Function(TimeFormat format) onSelected; + const TimeFormatList({required this.onSelected, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final formatItems = TimeFormat.values.map((format) { + return TimeFormatItem( + timeFormat: format, + onSelected: (format) { + onSelected(format); + FlowyOverlay.of(context).remove(identifier()); + }); + }).toList(); + + return SizedBox( + width: 120, + child: ListView.separated( + shrinkWrap: true, + controller: ScrollController(), + separatorBuilder: (context, index) { + return VSpace(GridSize.typeOptionSeparatorHeight); + }, + itemCount: formatItems.length, + itemBuilder: (BuildContext context, int index) { + return formatItems[index]; + }, + ), + ); + } + + String identifier() { + return toString(); + } +} + +class TimeFormatItem extends StatelessWidget { + final TimeFormat timeFormat; + final Function(TimeFormat format) onSelected; + const TimeFormatItem({required this.timeFormat, required this.onSelected, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch<AppTheme>(); + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(timeFormat.title(), fontSize: 12), + hoverColor: theme.hover, + onTap: () => onSelected(timeFormat), + ), + ); + } +} + +extension TimeFormatExtension on TimeFormat { + String title() { + switch (this) { + case TimeFormat.TwelveHour: + return LocaleKeys.grid_field_timeFormatTwelveHour.tr(); + case TimeFormat.TwentyFourHour: + return LocaleKeys.grid_field_timeFormatTwentyFourHour.tr(); + default: + throw UnimplementedError; + } + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart index f7016510b7..6aeb73ac48 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart @@ -1,5 +1,6 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/number_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_tyep_switcher.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; @@ -15,24 +16,24 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; class NumberTypeOptionBuilder extends TypeOptionBuilder { NumberTypeOption typeOption; - TypeOptionDataCallback typeOptionDataCallback; + TypeOptionOperationDelegate delegate; NumberTypeOptionBuilder( TypeOptionData typeOptionData, - this.typeOptionDataCallback, + this.delegate, ) : typeOption = NumberTypeOption.fromBuffer(typeOptionData); @override Widget? get customWidget => NumberTypeOptionWidget( typeOption: typeOption, - updateCallback: typeOptionDataCallback, + operationDelegate: delegate, ); } class NumberTypeOptionWidget extends TypeOptionWidget { - final TypeOptionDataCallback updateCallback; + final TypeOptionOperationDelegate operationDelegate; final NumberTypeOption typeOption; - const NumberTypeOptionWidget({required this.typeOption, required this.updateCallback, Key? key}) : super(key: key); + const NumberTypeOptionWidget({required this.typeOption, required this.operationDelegate, Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -40,18 +41,19 @@ class NumberTypeOptionWidget extends TypeOptionWidget { return BlocProvider( create: (context) => getIt<NumberTypeOptionBloc>(param1: typeOption), child: SizedBox( - height: 36, + height: GridSize.typeOptionItemHeight, child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>( - listener: (context, state) => updateCallback(state.typeOption.writeToBuffer()), + listener: (context, state) => operationDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), builder: (context, state) { return FlowyButton( text: FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(), fontSize: 12), - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + padding: GridSize.typeOptionContentInsets, hoverColor: theme.hover, onTap: () { - NumberFormatList.show(context, (format) { + final list = NumberFormatList(onSelected: (format) { context.read<NumberTypeOptionBloc>().add(NumberTypeOptionEvent.didSelectFormat(format)); }); + operationDelegate.requireToShowOverlay(context, list.identifier(), list); }, rightIcon: svg("grid/more", color: theme.iconColor), ); @@ -68,21 +70,6 @@ class NumberFormatList extends StatelessWidget { final _SelectNumberFormatCallback onSelected; const NumberFormatList({required this.onSelected, Key? key}) : super(key: key); - static void show(BuildContext context, _SelectNumberFormatCallback onSelected) { - final list = NumberFormatList(onSelected: onSelected); - FlowyOverlay.of(context).insertWithAnchor( - widget: OverlayContainer( - child: list, - constraints: BoxConstraints.loose(const Size(140, 300)), - ), - identifier: list.identifier(), - anchorContext: context, - anchorDirection: AnchorDirection.leftWithCenterAligned, - style: FlowyOverlayStyle(blur: false), - anchorOffset: const Offset(-20, 0), - ); - } - @override Widget build(BuildContext context) { final formatItems = NumberFormat.values.map((format) { @@ -94,16 +81,19 @@ class NumberFormatList extends StatelessWidget { }); }).toList(); - return ListView.separated( - shrinkWrap: true, - controller: ScrollController(), - separatorBuilder: (context, index) { - return const VSpace(10); - }, - itemCount: formatItems.length, - itemBuilder: (BuildContext context, int index) { - return formatItems[index]; - }, + return SizedBox( + width: 120, + child: ListView.separated( + shrinkWrap: true, + controller: ScrollController(), + separatorBuilder: (context, index) { + return VSpace(GridSize.typeOptionSeparatorHeight); + }, + itemCount: formatItems.length, + itemBuilder: (BuildContext context, int index) { + return formatItems[index]; + }, + ), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/selection.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/selection.dart new file mode 100644 index 0000000000..b5737fd219 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/selection.dart @@ -0,0 +1,50 @@ +import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/grid/field/type_option/selection_bloc.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { + SingleSelectTypeOption typeOption; + + SingleSelectTypeOptionBuilder(TypeOptionData typeOptionData) + : typeOption = SingleSelectTypeOption.fromBuffer(typeOptionData); + + @override + Widget? get customWidget => const SingleSelectTypeOptionWidget(); +} + +class SingleSelectTypeOptionWidget extends TypeOptionWidget { + const SingleSelectTypeOptionWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => getIt<SelectionTypeOptionBloc>(), + child: Container(height: 100, color: Colors.yellow), + ); + } +} + +class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { + MultiSelectTypeOption typeOption; + + MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData) + : typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData); + + @override + Widget? get customWidget => const MultiSelectTypeOptionWidget(); +} + +class MultiSelectTypeOptionWidget extends TypeOptionWidget { + const MultiSelectTypeOptionWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => getIt<SelectionTypeOptionBloc>(), + child: Container(height: 100, color: Colors.blue), + ); + } +} diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart index 26760dc411..651971d17f 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart @@ -182,7 +182,9 @@ class FlowyOverlayState extends State<FlowyOverlay> { void remove(String identifier) { setState(() { final index = _overlayList.indexWhere((ele) => ele.value2 == identifier); - _overlayList.removeAt(index).value3?.didRemove(); + if (index != -1) { + _overlayList.removeAt(index).value3?.didRemove(); + } }); }