chore: date field

This commit is contained in:
appflowy 2022-03-28 22:47:30 +08:00
parent 35e27b579f
commit d2080a6c03
13 changed files with 458 additions and 177 deletions

View File

@ -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"
}
}
}

View File

@ -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>(

View File

@ -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),

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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),
],
),
);

View File

@ -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),
);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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];
},
),
);
}

View File

@ -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),
);
}
}

View File

@ -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();
}
});
}