mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: optimize date picker & mention block (#5954)
* chore: optimize rename button on mobile * fix: mention block id empty error * chore: optimize mention block style * feat: add confirm button in date picker
This commit is contained in:
parent
463c8c7ee4
commit
b3a0119c18
@ -64,7 +64,7 @@ class _MobileBottomSheetRenameWidgetState
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
),
|
||||
fontColor: Colors.white,
|
||||
textColor: Colors.white,
|
||||
fillColor: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
widget.onRename(controller.text);
|
||||
|
@ -18,7 +18,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
Node,
|
||||
Path,
|
||||
Delta,
|
||||
composeAttributes;
|
||||
composeAttributes,
|
||||
blockComponentDelta;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:nanoid/nanoid.dart';
|
||||
|
||||
@ -81,6 +82,15 @@ class TransactionAdapter {
|
||||
}
|
||||
final blockActions =
|
||||
actions.map((e) => e.blockActionPB).toList(growable: false);
|
||||
|
||||
for (final action in blockActions) {
|
||||
if (enableDocumentInternalLog) {
|
||||
Log.debug(
|
||||
'[editor_transaction_adapter] action => ${action.toProto3Json()}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await documentService.applyAction(
|
||||
documentId: documentId,
|
||||
actions: blockActions,
|
||||
@ -164,6 +174,7 @@ extension on InsertOperation {
|
||||
childrenId: nanoid(6),
|
||||
externalId: textId,
|
||||
externalType: textId != null ? _kExternalTextType : null,
|
||||
attributes: {...node.attributes}..remove(blockComponentDelta),
|
||||
)
|
||||
..parentId = parentId
|
||||
..prevId = prevId;
|
||||
@ -234,10 +245,13 @@ extension on UpdateOperation {
|
||||
)
|
||||
: null;
|
||||
|
||||
final composedAttributes = composeAttributes(oldAttributes, attributes);
|
||||
composedAttributes?.remove(blockComponentDelta);
|
||||
|
||||
final payload = BlockActionPayloadPB()
|
||||
..block = node.toBlock(
|
||||
parentId: parentId,
|
||||
attributes: composeAttributes(oldAttributes, attributes),
|
||||
attributes: composedAttributes,
|
||||
)
|
||||
..parentId = parentId;
|
||||
final blockActionPB = BlockActionPB()
|
||||
|
@ -73,7 +73,11 @@ class MentionBlock extends StatelessWidget {
|
||||
|
||||
switch (type) {
|
||||
case MentionType.page:
|
||||
final String pageId = mention[MentionBlockKeys.pageId];
|
||||
final String? pageId = mention[MentionBlockKeys.pageId] as String?;
|
||||
if (pageId == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return MentionPageBlock(
|
||||
key: ValueKey(pageId),
|
||||
editorState: editorState,
|
||||
|
@ -26,6 +26,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:nanoid/non_secure.dart';
|
||||
|
||||
@ -182,68 +183,13 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
|
||||
|
||||
return GestureDetector(
|
||||
onTapDown: (details) {
|
||||
if (widget.editorState.editable) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
builder: (_) => DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 0.7,
|
||||
minChildSize: 0.4,
|
||||
snapSizes: const [0.4, 0.7, 1.0],
|
||||
builder: (_, controller) => Material(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Center(child: DragHandle()),
|
||||
),
|
||||
const MobileDateHeader(),
|
||||
MobileAppFlowyDatePicker(
|
||||
selectedDay: parsedDate,
|
||||
timeStr: timeStr,
|
||||
dateStr: parsedDate != null
|
||||
? options.dateFormat
|
||||
.formatDate(parsedDate!, _includeTime)
|
||||
: null,
|
||||
includeTime: options.includeTime,
|
||||
use24hFormat: options.timeFormat ==
|
||||
UserTimeFormatPB.TwentyFourHour,
|
||||
rebuildOnDaySelected: true,
|
||||
rebuildOnTimeChanged: true,
|
||||
timeFormat: options.timeFormat.simplified,
|
||||
selectedReminderOption: widget.reminderOption,
|
||||
onDaySelected: options.onDaySelected,
|
||||
onStartTimeChanged: (time) => options
|
||||
.onStartTimeChanged
|
||||
?.call(time ?? ""),
|
||||
onIncludeTimeChanged:
|
||||
options.onIncludeTimeChanged,
|
||||
liveDateFormatter: (selected) =>
|
||||
appearance.dateFormat.formatDate(
|
||||
selected,
|
||||
false,
|
||||
appearance.timeFormat,
|
||||
),
|
||||
onReminderSelected: (option) =>
|
||||
_updateReminder(option, reminder),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
DatePickerMenu(
|
||||
context: context,
|
||||
editorState: widget.editorState,
|
||||
).show(details.globalPosition, options: options);
|
||||
}
|
||||
}
|
||||
_showDatePicker(
|
||||
context: context,
|
||||
offset: details.globalPosition,
|
||||
reminder: reminder,
|
||||
timeStr: timeStr,
|
||||
options: options,
|
||||
);
|
||||
},
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
@ -251,15 +197,10 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.reminderId != null
|
||||
? '@$formattedDate'
|
||||
: formattedDate,
|
||||
style: widget.textStyle?.copyWith(
|
||||
color: color,
|
||||
leadingDistribution: TextLeadingDistribution.even,
|
||||
),
|
||||
strutStyle: widget.textStyle != null
|
||||
? StrutStyle.fromTextStyle(widget.textStyle!)
|
||||
'@$formattedDate',
|
||||
style: textStyle,
|
||||
strutStyle: textStyle != null
|
||||
? StrutStyle.fromTextStyle(textStyle)
|
||||
: null,
|
||||
),
|
||||
const HSpace(4),
|
||||
@ -402,4 +343,109 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDatePicker({
|
||||
required BuildContext context,
|
||||
required DatePickerOptions options,
|
||||
required Offset offset,
|
||||
String? timeStr,
|
||||
ReminderPB? reminder,
|
||||
}) {
|
||||
if (!widget.editorState.editable) {
|
||||
return;
|
||||
}
|
||||
if (PlatformExtension.isMobile) {
|
||||
SystemChannels.textInput.invokeMethod('TextInput.hide');
|
||||
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
builder: (_) => DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 0.7,
|
||||
minChildSize: 0.4,
|
||||
snapSizes: const [0.4, 0.7, 1.0],
|
||||
builder: (_, controller) => _DatePickerBottomSheet(
|
||||
controller: controller,
|
||||
parsedDate: parsedDate,
|
||||
timeStr: timeStr,
|
||||
options: options,
|
||||
includeTime: _includeTime,
|
||||
reminderOption: widget.reminderOption,
|
||||
onReminderSelected: (option) => _updateReminder(
|
||||
option,
|
||||
reminder,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
DatePickerMenu(
|
||||
context: context,
|
||||
editorState: widget.editorState,
|
||||
).show(offset, options: options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DatePickerBottomSheet extends StatelessWidget {
|
||||
const _DatePickerBottomSheet({
|
||||
required this.controller,
|
||||
required this.parsedDate,
|
||||
required this.timeStr,
|
||||
required this.options,
|
||||
required this.includeTime,
|
||||
this.reminderOption,
|
||||
required this.onReminderSelected,
|
||||
});
|
||||
|
||||
final ScrollController controller;
|
||||
final DateTime? parsedDate;
|
||||
final String? timeStr;
|
||||
final DatePickerOptions options;
|
||||
final bool includeTime;
|
||||
final ReminderOption? reminderOption;
|
||||
final void Function(ReminderOption) onReminderSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appearance = context.read<AppearanceSettingsCubit>().state;
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Center(child: DragHandle()),
|
||||
),
|
||||
const MobileDateHeader(),
|
||||
MobileAppFlowyDatePicker(
|
||||
selectedDay: parsedDate,
|
||||
timeStr: timeStr,
|
||||
dateStr: parsedDate != null
|
||||
? options.dateFormat.formatDate(parsedDate!, includeTime)
|
||||
: null,
|
||||
includeTime: options.includeTime,
|
||||
use24hFormat: options.timeFormat == UserTimeFormatPB.TwentyFourHour,
|
||||
rebuildOnDaySelected: true,
|
||||
rebuildOnTimeChanged: true,
|
||||
timeFormat: options.timeFormat.simplified,
|
||||
selectedReminderOption: reminderOption,
|
||||
onDaySelected: options.onDaySelected,
|
||||
onStartTimeChanged: (time) =>
|
||||
options.onStartTimeChanged?.call(time ?? ""),
|
||||
onIncludeTimeChanged: options.onIncludeTimeChanged,
|
||||
liveDateFormatter: (selected) => appearance.dateFormat.formatDate(
|
||||
selected,
|
||||
false,
|
||||
appearance.timeFormat,
|
||||
),
|
||||
onReminderSelected: onReminderSelected,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
@ -13,8 +10,9 @@ import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/mobi
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileAppFlowyDatePicker extends StatefulWidget {
|
||||
@ -389,57 +387,107 @@ class _IncludeTimePickerState extends State<_IncludeTimePicker> {
|
||||
children.addAll([
|
||||
Expanded(child: FlowyText(dateStr, textAlign: TextAlign.center)),
|
||||
Container(width: 1, height: 16, color: Colors.grey),
|
||||
Expanded(child: FlowyText(timeStr ?? '', textAlign: TextAlign.center)),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => _showTimePicker(
|
||||
context,
|
||||
use24hFormat: use24hFormat,
|
||||
isStartDay: isStartDay,
|
||||
),
|
||||
child: FlowyText(timeStr ?? '', textAlign: TextAlign.center),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: !isIncludeTime
|
||||
? null
|
||||
: () async {
|
||||
await showMobileBottomSheet(
|
||||
context,
|
||||
builder: (context) => ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.time,
|
||||
use24hFormat: use24hFormat,
|
||||
onDateTimeChanged: (dateTime) {
|
||||
final selectedTime = use24hFormat
|
||||
? DateFormat('HH:mm').format(dateTime)
|
||||
: DateFormat('hh:mm a').format(dateTime);
|
||||
|
||||
if (isStartDay) {
|
||||
widget.onStartTimeChanged(selectedTime);
|
||||
|
||||
if (widget.rebuildOnTimeChanged && mounted) {
|
||||
setState(() => _timeStr = selectedTime);
|
||||
}
|
||||
} else {
|
||||
widget.onEndTimeChanged?.call(selectedTime);
|
||||
|
||||
if (widget.rebuildOnTimeChanged && mounted) {
|
||||
setState(() => _endTimeStr = selectedTime);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(minHeight: 36),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 36),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
child: Row(children: children),
|
||||
),
|
||||
child: Row(children: children),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showTimePicker(
|
||||
BuildContext context, {
|
||||
required bool use24hFormat,
|
||||
required bool isStartDay,
|
||||
}) async {
|
||||
String? selectedTime = isStartDay ? _timeStr : _endTimeStr;
|
||||
final initialDateTime = selectedTime != null
|
||||
? _convertTimeStringToDateTime(selectedTime)
|
||||
: null;
|
||||
|
||||
return showMobileBottomSheet(
|
||||
context,
|
||||
builder: (context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.time,
|
||||
initialDateTime: initialDateTime,
|
||||
use24hFormat: use24hFormat,
|
||||
onDateTimeChanged: (dateTime) {
|
||||
selectedTime = use24hFormat
|
||||
? DateFormat('HH:mm').format(dateTime)
|
||||
: DateFormat('hh:mm a').format(dateTime);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 36),
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.button_confirm.tr(),
|
||||
constraints: const BoxConstraints.tightFor(height: 42),
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
fillColor: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
if (isStartDay) {
|
||||
widget.onStartTimeChanged(selectedTime);
|
||||
|
||||
if (widget.rebuildOnTimeChanged && mounted) {
|
||||
setState(() => _timeStr = selectedTime);
|
||||
}
|
||||
} else {
|
||||
widget.onEndTimeChanged?.call(selectedTime);
|
||||
|
||||
if (widget.rebuildOnTimeChanged && mounted) {
|
||||
setState(() => _endTimeStr = selectedTime);
|
||||
}
|
||||
}
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const VSpace(18.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DateTime _convertTimeStringToDateTime(String timeString) {
|
||||
final DateTime now = DateTime.now();
|
||||
|
||||
final List<String> timeParts = timeString.split(':');
|
||||
|
||||
if (timeParts.length != 2) {
|
||||
return now;
|
||||
}
|
||||
|
||||
final int hour = int.parse(timeParts[0]);
|
||||
final int minute = int.parse(timeParts[1]);
|
||||
|
||||
return DateTime(now.year, now.month, now.day, hour, minute);
|
||||
}
|
||||
}
|
||||
|
||||
class _EndDateSwitch extends StatelessWidget {
|
||||
|
Loading…
Reference in New Issue
Block a user