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(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16.0,
|
horizontal: 16.0,
|
||||||
),
|
),
|
||||||
fontColor: Colors.white,
|
textColor: Colors.white,
|
||||||
fillColor: Theme.of(context).primaryColor,
|
fillColor: Theme.of(context).primaryColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.onRename(controller.text);
|
widget.onRename(controller.text);
|
||||||
|
@ -18,7 +18,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'
|
|||||||
Node,
|
Node,
|
||||||
Path,
|
Path,
|
||||||
Delta,
|
Delta,
|
||||||
composeAttributes;
|
composeAttributes,
|
||||||
|
blockComponentDelta;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:nanoid/nanoid.dart';
|
import 'package:nanoid/nanoid.dart';
|
||||||
|
|
||||||
@ -81,6 +82,15 @@ class TransactionAdapter {
|
|||||||
}
|
}
|
||||||
final blockActions =
|
final blockActions =
|
||||||
actions.map((e) => e.blockActionPB).toList(growable: false);
|
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(
|
await documentService.applyAction(
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
actions: blockActions,
|
actions: blockActions,
|
||||||
@ -164,6 +174,7 @@ extension on InsertOperation {
|
|||||||
childrenId: nanoid(6),
|
childrenId: nanoid(6),
|
||||||
externalId: textId,
|
externalId: textId,
|
||||||
externalType: textId != null ? _kExternalTextType : null,
|
externalType: textId != null ? _kExternalTextType : null,
|
||||||
|
attributes: {...node.attributes}..remove(blockComponentDelta),
|
||||||
)
|
)
|
||||||
..parentId = parentId
|
..parentId = parentId
|
||||||
..prevId = prevId;
|
..prevId = prevId;
|
||||||
@ -234,10 +245,13 @@ extension on UpdateOperation {
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
final composedAttributes = composeAttributes(oldAttributes, attributes);
|
||||||
|
composedAttributes?.remove(blockComponentDelta);
|
||||||
|
|
||||||
final payload = BlockActionPayloadPB()
|
final payload = BlockActionPayloadPB()
|
||||||
..block = node.toBlock(
|
..block = node.toBlock(
|
||||||
parentId: parentId,
|
parentId: parentId,
|
||||||
attributes: composeAttributes(oldAttributes, attributes),
|
attributes: composedAttributes,
|
||||||
)
|
)
|
||||||
..parentId = parentId;
|
..parentId = parentId;
|
||||||
final blockActionPB = BlockActionPB()
|
final blockActionPB = BlockActionPB()
|
||||||
|
@ -73,7 +73,11 @@ class MentionBlock extends StatelessWidget {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MentionType.page:
|
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(
|
return MentionPageBlock(
|
||||||
key: ValueKey(pageId),
|
key: ValueKey(pageId),
|
||||||
editorState: editorState,
|
editorState: editorState,
|
||||||
|
@ -26,6 +26,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:nanoid/non_secure.dart';
|
import 'package:nanoid/non_secure.dart';
|
||||||
|
|
||||||
@ -182,68 +183,13 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTapDown: (details) {
|
onTapDown: (details) {
|
||||||
if (widget.editorState.editable) {
|
_showDatePicker(
|
||||||
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,
|
context: context,
|
||||||
editorState: widget.editorState,
|
offset: details.globalPosition,
|
||||||
).show(details.globalPosition, options: options);
|
reminder: reminder,
|
||||||
}
|
timeStr: timeStr,
|
||||||
}
|
options: options,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
@ -251,15 +197,10 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.reminderId != null
|
'@$formattedDate',
|
||||||
? '@$formattedDate'
|
style: textStyle,
|
||||||
: formattedDate,
|
strutStyle: textStyle != null
|
||||||
style: widget.textStyle?.copyWith(
|
? StrutStyle.fromTextStyle(textStyle)
|
||||||
color: color,
|
|
||||||
leadingDistribution: TextLeadingDistribution.even,
|
|
||||||
),
|
|
||||||
strutStyle: widget.textStyle != null
|
|
||||||
? StrutStyle.fromTextStyle(widget.textStyle!)
|
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
const HSpace(4),
|
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/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.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/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class MobileAppFlowyDatePicker extends StatefulWidget {
|
class MobileAppFlowyDatePicker extends StatefulWidget {
|
||||||
@ -389,26 +387,69 @@ class _IncludeTimePickerState extends State<_IncludeTimePicker> {
|
|||||||
children.addAll([
|
children.addAll([
|
||||||
Expanded(child: FlowyText(dateStr, textAlign: TextAlign.center)),
|
Expanded(child: FlowyText(dateStr, textAlign: TextAlign.center)),
|
||||||
Container(width: 1, height: 16, color: Colors.grey),
|
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(
|
return Container(
|
||||||
onTap: !isIncludeTime
|
constraints: const BoxConstraints(minHeight: 36),
|
||||||
? null
|
decoration: BoxDecoration(
|
||||||
: () async {
|
borderRadius: BorderRadius.circular(6),
|
||||||
await showMobileBottomSheet(
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,
|
context,
|
||||||
builder: (context) => ConstrainedBox(
|
builder: (context) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxHeight: 300),
|
constraints: const BoxConstraints(maxHeight: 300),
|
||||||
child: CupertinoDatePicker(
|
child: CupertinoDatePicker(
|
||||||
mode: CupertinoDatePickerMode.time,
|
mode: CupertinoDatePickerMode.time,
|
||||||
|
initialDateTime: initialDateTime,
|
||||||
use24hFormat: use24hFormat,
|
use24hFormat: use24hFormat,
|
||||||
onDateTimeChanged: (dateTime) {
|
onDateTimeChanged: (dateTime) {
|
||||||
final selectedTime = use24hFormat
|
selectedTime = use24hFormat
|
||||||
? DateFormat('HH:mm').format(dateTime)
|
? DateFormat('HH:mm').format(dateTime)
|
||||||
: DateFormat('hh:mm a').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) {
|
if (isStartDay) {
|
||||||
widget.onStartTimeChanged(selectedTime);
|
widget.onStartTimeChanged(selectedTime);
|
||||||
|
|
||||||
@ -422,24 +463,31 @@ class _IncludeTimePickerState extends State<_IncludeTimePicker> {
|
|||||||
setState(() => _endTimeStr = selectedTime);
|
setState(() => _endTimeStr = selectedTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
const VSpace(18.0),
|
||||||
},
|
],
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(children: children),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
class _EndDateSwitch extends StatelessWidget {
|
||||||
|
Loading…
Reference in New Issue
Block a user