feat: date picker ui revamp (#4044)

* feat: date picker editor

* feat: refactor date picker on mobile

* chore: update background color

* feat: date picker UI revamp

* feat: optimize the scroll behavior

* chore: remove unused code
This commit is contained in:
Lucas.Xu 2023-11-29 21:02:04 +08:00 committed by GitHub
parent 551e012147
commit 8665a25b39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 640 additions and 335 deletions

View File

@ -202,4 +202,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 8c681999c7764593c94846b2a64b44d86f7a27ac
COCOAPODS: 1.11.3
COCOAPODS: 1.12.1

View File

@ -2,6 +2,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -18,7 +19,13 @@ Future<void> showMobileBottomSheet({
isScrollControlled: true,
enableDrag: isDragEnabled,
useSafeArea: true,
shape: shape,
clipBehavior: Clip.antiAlias,
shape: shape ??
const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Corners.s12Radius,
),
),
builder: builder,
);
}

View File

@ -10,7 +10,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_access
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,7 +1,5 @@
export 'mobile_text_cell.dart';
export 'mobile_number_cell.dart';
export 'mobile_timestamp_cell.dart';
export 'mobile_checkbox_cell.dart';
export 'mobile_number_cell.dart';
export 'mobile_text_cell.dart';
export 'mobile_timestamp_cell.dart';
export 'mobile_url_cell.dart';
export 'date_cell/mobile_date_cell.dart';
export 'date_cell/mobile_date_cell_edit_screen.dart';

View File

@ -1,103 +0,0 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_cell_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'mobile_date_cell_edit_screen.dart';
class MobileDateCell extends GridCellWidget {
MobileDateCell({
super.key,
required this.cellControllerBuilder,
required this.hintText,
});
final CellControllerBuilder cellControllerBuilder;
final String? hintText;
@override
GridCellState<MobileDateCell> createState() => _DateCellState();
}
class _DateCellState extends GridCellState<MobileDateCell> {
late final DateCellBloc _cellBloc;
@override
void initState() {
final cellController =
widget.cellControllerBuilder.build() as DateCellController;
_cellBloc = DateCellBloc(cellController: cellController)
..add(const DateCellEvent.initial());
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<DateCellBloc, DateCellState>(
builder: (context, state) {
// full screen show the date edit screen
return GestureDetector(
onTap: () => context.push(
MobileDateCellEditScreen.routeName,
extra: {
MobileDateCellEditScreen.argCellController:
widget.cellControllerBuilder.build() as DateCellController,
},
),
child: SizedBox(
width: double.infinity,
child: MobileDateCellText(
dateStr: state.dateStr,
placeholder: widget.hintText ?? "",
),
),
);
},
),
);
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
@override
void requestBeginFocus() {}
@override
String? onCopy() => _cellBloc.state.dateStr;
}
class MobileDateCellText extends StatelessWidget {
const MobileDateCellText({
super.key,
required this.dateStr,
required this.placeholder,
});
final String dateStr;
final String placeholder;
@override
Widget build(BuildContext context) {
final isPlaceholder = dateStr.isEmpty;
final text = isPlaceholder ? placeholder : dateStr;
return Align(
alignment: Alignment.centerLeft,
child: Text(
text,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: isPlaceholder
? Theme.of(context).hintColor
: Theme.of(context).colorScheme.onBackground,
),
),
);
}
}

View File

@ -1,10 +1,11 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/mobile_date_editor.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -13,19 +14,24 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'widgets/widgets.dart';
import 'package:go_router/go_router.dart';
class MobileDateCellEditScreen extends StatefulWidget {
static const routeName = '/MobileDateCellEditScreen';
static const argCellController = 'cellController';
static const routeName = '/edit_date_cell';
const MobileDateCellEditScreen(
this.cellController, {
// the type is DateCellController
static const dateCellController = 'date_cell_controller';
// bool value, default is true
static const fullScreen = 'full_screen';
const MobileDateCellEditScreen({
super.key,
required this.controller,
this.showAsFullScreen = true,
});
final DateCellController cellController;
final DateCellController controller;
final bool showAsFullScreen;
@override
State<MobileDateCellEditScreen> createState() =>
@ -33,42 +39,116 @@ class MobileDateCellEditScreen extends StatefulWidget {
}
class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
late final Future<Either<dynamic, FlowyError>> typeOptionFuture;
@override
void initState() {
super.initState();
typeOptionFuture = widget.controller.getTypeOption(
DateTypeOptionDataParser(),
);
}
@override
Widget build(BuildContext context) {
return widget.showAsFullScreen ? _buildFullScreen() : _buildNotFullScreen();
}
Widget _buildFullScreen() {
return Scaffold(
appBar: AppBar(
title: Text(LocaleKeys.button_edit.tr()),
title: Text(LocaleKeys.titleBar_date.tr()),
),
body: FutureBuilder<Either<dynamic, FlowyError>>(
future: widget.cellController.getTypeOption(
DateTypeOptionDataParser(),
body: _buildBody(),
);
}
Widget _buildNotFullScreen() {
return DraggableScrollableSheet(
expand: false,
snap: true,
initialChildSize: 0.6,
minChildSize: 0.6,
builder: (_, controller) => Material(
child: SingleChildScrollView(
controller: controller,
child: Column(
children: [
const DragHandler(),
_buildHeader(),
const Divider(),
_buildBody(),
],
),
),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!.fold(
(dateTypeOptionPB) {
return _DateCellEditBody(
dateCellController: widget.cellController,
dateTypeOptionPB: dateTypeOptionPB,
);
},
(err) {
Log.error(err);
return FlowyMobileStateContainer.error(
title: LocaleKeys.grid_field_failedToLoadDate.tr(),
errorMsg: err.toString(),
);
},
),
);
}
Widget _buildBody() {
return FutureBuilder<Either<dynamic, FlowyError>>(
future: typeOptionFuture,
builder: (context, snapshot) {
final data = snapshot.data;
if (data == null) {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
return data.fold(
(dateTypeOptionPB) {
return _DateCellEditBody(
dateCellController: widget.controller,
dateTypeOptionPB: dateTypeOptionPB,
);
}
return const Center(child: CircularProgressIndicator.adaptive());
},
},
(err) {
Log.error(err);
return FlowyMobileStateContainer.error(
title: LocaleKeys.grid_field_failedToLoadDate.tr(),
errorMsg: err.toString(),
);
},
);
},
);
}
Widget _buildHeader() {
const iconWidth = 36.0;
const height = 44.0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Stack(
children: [
Align(
alignment: Alignment.centerLeft,
child: FlowyIconButton(
icon: const FlowySvg(
FlowySvgs.close_s,
size: Size.square(iconWidth),
),
width: iconWidth,
iconPadding: EdgeInsets.zero,
onPressed: () => context.pop(),
),
),
Align(
alignment: Alignment.center,
child: FlowyText.medium(
LocaleKeys.grid_field_dateFieldName.tr(),
fontSize: 18,
),
),
].map((e) => SizedBox(height: height, child: e)).toList(),
),
);
}
}
class _DateCellEditBody extends StatefulWidget {
class _DateCellEditBody extends StatelessWidget {
const _DateCellEditBody({
required this.dateCellController,
required this.dateTypeOptionPB,
@ -77,48 +157,48 @@ class _DateCellEditBody extends StatefulWidget {
final DateCellController dateCellController;
final DateTypeOptionPB dateTypeOptionPB;
@override
State<_DateCellEditBody> createState() => _DateCellEditBodyState();
}
class _DateCellEditBodyState extends State<_DateCellEditBody> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DateCellCalendarBloc(
dateTypeOptionPB: widget.dateTypeOptionPB,
cellData: widget.dateCellController.getCellData(),
cellController: widget.dateCellController,
dateTypeOptionPB: dateTypeOptionPB,
cellData: dateCellController.getCellData(),
cellController: dateCellController,
)..add(const DateCellCalendarEvent.initial()),
child: BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
builder: (context, state) {
final widgetsList = [
DateAndTimeDisplay(state),
const DatePicker(),
const _EndDateSwitch(),
const _IncludeTimeSwitch(),
const _StartDayTime(),
const _EndDayTime(),
const Divider(),
const _DateFormatOption(),
const _TimeFormatOption(),
const Divider(),
const _ClearDateButton(),
];
return Padding(
padding: const EdgeInsets.all(16),
child: ListView.separated(
itemBuilder: (context, index) => widgetsList[index],
separatorBuilder: (_, __) => const VSpace(8),
itemCount: widgetsList.length,
),
);
},
child: const Column(
children: [
FlowyOptionDecorateBox(
showTopBorder: false,
child: MobileDatePicker(),
),
_ColoredDivider(),
_EndDateSwitch(),
_IncludeTimeSwitch(),
_StartDayTime(),
_EndDayTime(),
_ColoredDivider(),
_DateFormatOption(),
_TimeFormatOption(),
_ClearDateButton(),
_ColoredDivider(),
],
),
);
}
}
class _ColoredDivider extends StatelessWidget {
const _ColoredDivider();
@override
Widget build(BuildContext context) {
return VSpace(
20.0,
color: Theme.of(context).colorScheme.secondaryContainer,
);
}
}
class _EndDateSwitch extends StatelessWidget {
const _EndDateSwitch();
@ -127,23 +207,17 @@ class _EndDateSwitch extends StatelessWidget {
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
selector: (state) => state.isRange,
builder: (context, isRange) {
return Row(
children: [
Text(
LocaleKeys.grid_field_isRange.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
const Spacer(),
Switch.adaptive(
value: isRange,
activeColor: Theme.of(context).colorScheme.primary,
onChanged: (value) {
context
.read<DateCellCalendarBloc>()
.add(DateCellCalendarEvent.setIsRange(value));
},
),
],
return FlowyOptionTile(
text: LocaleKeys.grid_field_isRange.tr(),
leftIcon: const FlowySvg(FlowySvgs.date_s),
leading: _Switcher(
value: isRange,
onChanged: (value) {
context
.read<DateCellCalendarBloc>()
.add(DateCellCalendarEvent.setIsRange(value));
},
),
);
},
);
@ -158,13 +232,18 @@ class _IncludeTimeSwitch extends StatelessWidget {
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
selector: (state) => state.includeTime,
builder: (context, includeTime) {
return IncludeTimeSwitch(
switchValue: includeTime,
onChanged: (value) {
context
.read<DateCellCalendarBloc>()
.add(DateCellCalendarEvent.setIncludeTime(value));
},
return FlowyOptionTile(
showTopBorder: false,
text: LocaleKeys.grid_field_includeTime.tr(),
leftIcon: const FlowySvg(FlowySvgs.clock_alarm_s),
leading: _Switcher(
value: includeTime,
onChanged: (value) {
context
.read<DateCellCalendarBloc>()
.add(DateCellCalendarEvent.setIncludeTime(value));
},
),
);
},
);
@ -300,14 +379,9 @@ class _ClearDateButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
LocaleKeys.grid_field_clearDate.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
),
return FlowyOptionTile(
showTopBorder: false,
text: LocaleKeys.grid_field_clearDate.tr(),
onTap: () => context
.read<DateCellCalendarBloc>()
.add(const DateCellCalendarEvent.clearDate()),
@ -324,20 +398,25 @@ class _TimeFormatOption extends StatelessWidget {
TimeFormatPB>(
selector: (state) => state.dateTypeOptionPB.timeFormat,
builder: (context, state) {
return TimeFormatListTile(
currentFormatStr: state.title(),
groupValue: context
.watch<DateCellCalendarBloc>()
.state
.dateTypeOptionPB
.timeFormat,
onChanged: (newFormat) {
if (newFormat == null) return;
context
.read<DateCellCalendarBloc>()
.add(DateCellCalendarEvent.setTimeFormat(newFormat));
},
return FlowyOptionTile(
showTopBorder: false,
text: LocaleKeys.settings_appearance_timeFormat_label.tr(),
leftIcon: const FlowySvg(FlowySvgs.time_s),
);
// TimeFormatListTile(
// currentFormatStr: state.title(),
// groupValue: context
// .watch<DateCellCalendarBloc>()
// .state
// .dateTypeOptionPB
// .timeFormat,
// onChanged: (newFormat) {
// if (newFormat == null) return;
// context
// .read<DateCellCalendarBloc>()
// .add(DateCellCalendarEvent.setTimeFormat(newFormat));
// },
// );
},
);
}
@ -352,21 +431,50 @@ class _DateFormatOption extends StatelessWidget {
DateFormatPB>(
selector: (state) => state.dateTypeOptionPB.dateFormat,
builder: (context, state) {
return DateFormatListTile(
currentFormatStr: state.title(),
groupValue: context
.watch<DateCellCalendarBloc>()
.state
.dateTypeOptionPB
.dateFormat,
onChanged: (newFormat) {
if (newFormat == null) return;
context
.read<DateCellCalendarBloc>()
.add(DateCellCalendarEvent.setDateFormat(newFormat));
},
return FlowyOptionTile(
text: LocaleKeys.settings_appearance_dateFormat_label.tr(),
leftIcon: const FlowySvg(FlowySvgs.clock_alarm_s),
);
// DateFormatListTile(
// currentFormatStr: state.title(),
// groupValue: context
// .watch<DateCellCalendarBloc>()
// .state
// .dateTypeOptionPB
// .dateFormat,
// onChanged: (newFormat) {
// if (newFormat == null) return;
// context
// .read<DateCellCalendarBloc>()
// .add(DateCellCalendarEvent.setDateFormat(newFormat));
// },
// );
},
);
}
}
class _Switcher extends StatelessWidget {
const _Switcher({
required this.value,
required this.onChanged,
});
final bool value;
final void Function(bool value) onChanged;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 48,
child: FittedBox(
fit: BoxFit.fill,
child: Switch.adaptive(
value: value,
activeColor: const Color(0xFF00BCF0),
onChanged: onChanged,
),
),
);
}
}

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class FlowyOptionDecorateBox extends StatelessWidget {
const FlowyOptionDecorateBox({
super.key,
this.showTopBorder = true,
this.showBottomBorder = true,
required this.child,
});
final bool showTopBorder;
final bool showBottomBorder;
final Widget child;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
border: Border(
top: showTopBorder
? BorderSide(
color: Theme.of(context).dividerColor,
)
: BorderSide.none,
bottom: showBottomBorder
? BorderSide(
color: Theme.of(context).dividerColor,
)
: BorderSide.none,
),
),
child: child,
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
// used in cell editor
class FlowyOptionTile extends StatelessWidget {
const FlowyOptionTile({
super.key,
this.showTopBorder = true,
this.showBottomBorder = true,
required this.text,
this.leftIcon,
this.onTap,
this.leading,
});
final bool showTopBorder;
final bool showBottomBorder;
final String text;
final void Function()? onTap;
final Widget? leftIcon;
final Widget? leading;
@override
Widget build(BuildContext context) {
return FlowyOptionDecorateBox(
showTopBorder: showTopBorder,
showBottomBorder: showBottomBorder,
child: Row(
children: [
FlowyButton(
useIntrinsicWidth: true,
text: FlowyText(
text,
fontSize: 16.0,
),
margin: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 16.0,
),
leftIcon: leftIcon,
leftIconSize: const Size.square(24.0),
iconPadding: 8.0,
onTap: onTap,
),
const Spacer(),
leading ?? const SizedBox.shrink(),
const HSpace(12.0),
],
),
);
}
}

View File

@ -1,3 +1,5 @@
export 'show_flowy_mobile_confirm_dialog.dart';
export 'show_flowy_mobile_bottom_sheet.dart';
export 'flowy_mobile_option_decorate_box.dart';
export 'flowy_mobile_state_container.dart';
export 'flowy_option_tile.dart';
export 'show_flowy_mobile_bottom_sheet.dart';
export 'show_flowy_mobile_confirm_dialog.dart';

View File

@ -155,10 +155,9 @@ GridCellWidget _getMobileCardCellWidget(
);
case FieldType.DateTime:
style as DateCellStyle?;
return MobileDateCell(
return GridDateCell(
cellControllerBuilder: cellControllerBuilder,
hintText: style?.placeholder,
key: key,
style: style,
);
case FieldType.URL:
style as GridURLCellStyle?;
@ -167,7 +166,6 @@ GridCellWidget _getMobileCardCellWidget(
hintText: style?.placeholder,
key: key,
);
// TODO(yijing): implement the following mobile select option cell
case FieldType.SingleSelect:
return GridSingleSelectCell(
cellControllerBuilder: cellControllerBuilder,

View File

@ -1,4 +1,7 @@
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -66,34 +69,55 @@ class _DateCellState extends GridCellState<GridDateCell> {
final alignment = widget.cellStyle != null
? widget.cellStyle!.alignment
: Alignment.centerLeft;
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<DateCellBloc, DateCellState>(
builder: (context, state) {
return AppFlowyPopover(
controller: _popover,
triggerActions: PopoverTriggerFlags.none,
direction: PopoverDirection.bottomWithLeftAligned,
constraints: BoxConstraints.loose(const Size(260, 620)),
margin: EdgeInsets.zero,
child: GridDateCellText(
dateStr: state.dateStr,
placeholder: widget.cellStyle?.placeholder ?? "",
alignment: alignment,
cellPadding:
widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets,
),
popupBuilder: (BuildContext popoverContent) {
return DateCellEditor(
cellController:
widget.cellControllerBuilder.build() as DateCellController,
onDismissed: () => widget.cellContainerNotifier.isFocus = false,
);
},
onClose: () {
widget.cellContainerNotifier.isFocus = false;
},
final child = GridDateCellText(
dateStr: state.dateStr,
placeholder: widget.cellStyle?.placeholder ?? "",
alignment: alignment,
cellPadding:
widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets,
);
if (PlatformExtension.isDesktopOrWeb) {
return AppFlowyPopover(
controller: _popover,
triggerActions: PopoverTriggerFlags.none,
direction: PopoverDirection.bottomWithLeftAligned,
constraints: BoxConstraints.loose(const Size(260, 620)),
margin: EdgeInsets.zero,
child: child,
popupBuilder: (BuildContext popoverContent) {
return DateCellEditor(
cellController: widget.cellControllerBuilder.build()
as DateCellController,
onDismissed: () =>
widget.cellContainerNotifier.isFocus = false,
);
},
onClose: () {
widget.cellContainerNotifier.isFocus = false;
},
);
} else {
return FlowyButton(
text: child,
onTap: () {
showMobileBottomSheet(
context: context,
builder: (context) {
return MobileDateCellEditScreen(
controller: widget.cellControllerBuilder.build()
as DateCellController,
showAsFullScreen: false,
);
},
);
},
);
}
},
),
);

View File

@ -0,0 +1,193 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/appflowy_calendar.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart';
import 'date_cal_bloc.dart';
class MobileDatePicker extends StatefulWidget {
const MobileDatePicker({
super.key,
});
@override
State<MobileDatePicker> createState() => _MobileDatePickerState();
}
class _MobileDatePickerState extends State<MobileDatePicker> {
DateTime _focusedDay = DateTime.now();
CalendarFormat _calendarFormat = CalendarFormat.month;
final ValueNotifier<(DateTime, dynamic)> _currentDateNotifier = ValueNotifier(
(DateTime.now(), null),
);
PageController? _pageController;
@override
Widget build(BuildContext context) {
return Column(
children: [
const VSpace(8.0),
_buildHeader(context),
const VSpace(8.0),
_buildCalendar(context),
const VSpace(16.0),
],
);
}
Widget _buildCalendar(BuildContext context) {
const selectedColor = Color(0xFF00BCF0);
final textStyle = Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16.0,
);
const boxDecoration = BoxDecoration(
shape: BoxShape.circle,
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
builder: (context, state) {
return TableCalendar(
firstDay: kFirstDay,
lastDay: kLastDay,
focusedDay: _focusedDay,
rowHeight: 48.0,
calendarFormat: _calendarFormat,
daysOfWeekHeight: 48.0,
rangeSelectionMode: state.isRange
? RangeSelectionMode.enforced
: RangeSelectionMode.disabled,
rangeStartDay: state.isRange ? state.startDay : null,
rangeEndDay: state.isRange ? state.endDay : null,
onCalendarCreated: (pageController) =>
_pageController = pageController,
headerVisible: false,
availableGestures: AvailableGestures.horizontalSwipe,
calendarStyle: CalendarStyle(
cellMargin: const EdgeInsets.all(3.5),
defaultDecoration: boxDecoration,
selectedDecoration: boxDecoration.copyWith(
color: selectedColor,
),
todayDecoration: boxDecoration.copyWith(
color: Colors.transparent,
border: Border.all(color: selectedColor),
),
weekendDecoration: boxDecoration,
outsideDecoration: boxDecoration,
rangeStartDecoration: boxDecoration.copyWith(
color: selectedColor,
),
rangeEndDecoration: boxDecoration.copyWith(
color: selectedColor,
),
defaultTextStyle: textStyle,
weekendTextStyle: textStyle,
selectedTextStyle: textStyle.copyWith(
color: Theme.of(context).colorScheme.surface,
),
rangeStartTextStyle: textStyle.copyWith(
color: Theme.of(context).colorScheme.surface,
),
rangeEndTextStyle: textStyle.copyWith(
color: Theme.of(context).colorScheme.surface,
),
todayTextStyle: textStyle,
outsideTextStyle: textStyle.copyWith(
color: Theme.of(context).disabledColor,
),
rangeHighlightColor:
Theme.of(context).colorScheme.secondaryContainer,
),
calendarBuilders: CalendarBuilders(
dowBuilder: (context, day) {
final locale = context.locale.toLanguageTag();
final label = DateFormat.E(locale).format(day).substring(0, 2);
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Center(
child: Text(
label,
style: textStyle.copyWith(
color: Theme.of(context).hintColor,
fontSize: 14.0,
),
),
),
);
},
),
selectedDayPredicate: (day) =>
state.isRange ? false : isSameDay(state.dateTime, day),
onDaySelected: (selectedDay, focusedDay) {
context.read<DateCellCalendarBloc>().add(
DateCellCalendarEvent.selectDay(selectedDay),
);
},
onRangeSelected: (start, end, focusedDay) {
context.read<DateCellCalendarBloc>().add(
DateCellCalendarEvent.selectDateRange(start, end),
);
},
onFormatChanged: (calendarFormat) => setState(() {
_calendarFormat = calendarFormat;
}),
onPageChanged: (focusedDay) => setState(() {
_focusedDay = focusedDay;
_currentDateNotifier.value = (focusedDay, null);
}),
);
},
),
);
}
Widget _buildHeader(BuildContext context) {
return Row(
children: [
const HSpace(16.0),
ValueListenableBuilder(
valueListenable: _currentDateNotifier,
builder: (_, value, ___) {
return FlowyText(
DateFormat.yMMMM(value.$2).format(value.$1),
fontSize: 16.0,
);
},
),
const Spacer(),
FlowyButton(
useIntrinsicWidth: true,
text: FlowySvg(
FlowySvgs.arrow_left_s,
color: Theme.of(context).iconTheme.color,
size: const Size.square(24.0),
),
onTap: () => _pageController?.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
),
),
const HSpace(24.0),
FlowyButton(
useIntrinsicWidth: true,
text: FlowySvg(
FlowySvgs.arrow_right_s,
color: Theme.of(context).iconTheme.color,
size: const Size.square(24.0),
),
onTap: () => _pageController?.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
),
),
const HSpace(8.0),
],
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
@ -49,7 +50,7 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
@override
Widget build(BuildContext context) {
return Container(
return ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 420),
child: BlocProvider(
create: (context) => SelectOptionCellEditorBloc(
@ -504,23 +505,36 @@ class _MoreOptions extends StatelessWidget {
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.secondaryContainer;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const VSpace(8.0),
VSpace(8.0, color: color),
_buildRenameTextField(context),
const VSpace(16.0),
VSpace(
16.0,
color: color,
),
_buildDeleteButton(context),
const VSpace(16.0),
VSpace(
16.0,
color: color,
),
Padding(
padding: const EdgeInsets.only(left: 12.0),
child: FlowyText(
LocaleKeys.grid_field_optionTitle.tr(),
color: Theme.of(context).hintColor,
child: ColoredBox(
color: color,
child: FlowyText(
LocaleKeys.grid_field_optionTitle.tr(),
color: Theme.of(context).hintColor,
),
),
),
const VSpace(4.0),
VSpace(
4.0,
color: color,
),
_buildColorOptions(context),
],
),
@ -530,7 +544,7 @@ class _MoreOptions extends StatelessWidget {
Widget _buildRenameTextField(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 52.0),
child: _DefaultDecorateBox(
child: FlowyOptionDecorateBox(
showTopBorder: true,
showBottomBorder: true,
child: TextField(
@ -549,28 +563,15 @@ class _MoreOptions extends StatelessWidget {
}
Widget _buildDeleteButton(BuildContext context) {
return _DefaultDecorateBox(
showTopBorder: true,
showBottomBorder: true,
child: FlowyButton(
text: FlowyText(
LocaleKeys.button_delete.tr(),
fontSize: 16.0,
),
margin: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 16.0,
),
leftIcon: const FlowySvg(FlowySvgs.delete_s),
leftIconSize: const Size.square(24.0),
iconPadding: 8.0,
onTap: onDelete,
),
return FlowyOptionTile(
text: LocaleKeys.button_delete.tr(),
leftIcon: const FlowySvg(FlowySvgs.delete_s),
onTap: onDelete,
);
}
Widget _buildColorOptions(BuildContext context) {
return _DefaultDecorateBox(
return FlowyOptionDecorateBox(
showTopBorder: true,
showBottomBorder: true,
child: Padding(
@ -606,33 +607,3 @@ class _MoreOptions extends StatelessWidget {
);
}
}
class _DefaultDecorateBox extends StatelessWidget {
const _DefaultDecorateBox({
this.showTopBorder = true,
this.showBottomBorder = true,
required this.child,
});
final bool showTopBorder;
final bool showBottomBorder;
final Widget child;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
border: Border(
top: BorderSide(
color: Theme.of(context).dividerColor,
),
bottom: BorderSide(
color: Theme.of(context).dividerColor,
),
),
),
child: child,
);
}
}

View File

@ -4,7 +4,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_c
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -209,11 +208,6 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
onTap: () {
showMobileBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Corners.s12Radius,
),
),
builder: (context) {
return MobileSelectOptionEditor(
cellController: cellController,

View File

@ -1,7 +1,7 @@
import 'package:appflowy/mobile/presentation/database/card/card.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/row/cells/cells.dart';
import 'package:appflowy/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_board_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_events_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
@ -539,11 +539,18 @@ GoRoute _mobileDateCellEditScreenRoute() {
path: MobileDateCellEditScreen.routeName,
pageBuilder: (context, state) {
final args = state.extra as Map<String, dynamic>;
final cellController = args[MobileDateCellEditScreen.argCellController];
return MaterialPage(
child: MobileDateCellEditScreen(cellController),
final controller = args[MobileDateCellEditScreen.dateCellController];
final fullScreen = args[MobileDateCellEditScreen.fullScreen];
return CustomTransitionPage(
transitionsBuilder: (_, __, ___, child) => child,
fullscreenDialog: true,
opaque: false,
barrierDismissible: true,
barrierColor: Theme.of(context).bottomSheetTheme.modalBarrierColor,
child: MobileDateCellEditScreen(
controller: controller,
showAsFullScreen: fullScreen ?? true,
),
);
},
);

View File

@ -62,6 +62,7 @@ class FlowyButton extends StatelessWidget {
return InkWell(
onTap: disable ? null : onTap,
onSecondaryTap: disable ? null : onSecondaryTap,
borderRadius: radius ?? Corners.s6Border,
child: _render(context),
);
}

View File

@ -11,12 +11,29 @@ class Space extends StatelessWidget {
}
class VSpace extends StatelessWidget {
final double size;
const VSpace(
this.size, {
super.key,
this.color,
});
const VSpace(this.size, {Key? key}) : super(key: key);
final double size;
final Color? color;
@override
Widget build(BuildContext context) => Space(0, size);
Widget build(BuildContext context) {
if (color != null) {
return SizedBox(
height: size,
width: double.infinity,
child: ColoredBox(
color: color!,
),
);
} else {
return Space(0, size);
}
}
}
class HSpace extends StatelessWidget {

View File

@ -1124,6 +1124,7 @@
"pageIcon": "Page icon",
"language": "Language",
"font": "Font",
"actions": "Actions"
"actions": "Actions",
"date": "Date"
}
}