mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: launch review reminder (#4514)
* fix: dismiss menu on enter on no result * fix: add drag handle to mobile reminder dialog * fix: show reminder icon in date cell in grid * fix: auto select day when selecting reminder * fix: increase height of notification hub * fix: let some reminder options require time and show time * fix: handling of non-time reminder options * test: fix edit date time cell test * fix: close popover when pressing child again * fix: add time of now when setting include time * fix: clean logic * fix: tests * fix: add test and include time in notification hub --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
parent
d27e2179cc
commit
247405ff51
@ -32,13 +32,13 @@ void main() {
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Select date
|
||||
await tester.selectLastDateInPicker();
|
||||
final isToday = await tester.selectLastDateInPicker();
|
||||
|
||||
// Select Time of event reminder
|
||||
await tester.selectReminderOption(ReminderOption.atTimeOfEvent);
|
||||
// Select "On day of event" reminder
|
||||
await tester.selectReminderOption(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Expect Time of event to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
@ -47,14 +47,20 @@ void main() {
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect Time of event to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
int tabIndex = 1;
|
||||
final now = DateTime.now();
|
||||
if (isToday && now.hour >= 9) {
|
||||
tabIndex = 0;
|
||||
}
|
||||
|
||||
// Open "Upcoming" in Notification hub
|
||||
await tester.openNotificationHub(tabIndex: 1);
|
||||
await tester.openNotificationHub(tabIndex: tabIndex);
|
||||
|
||||
// Expect 1 notification
|
||||
tester.expectNotificationItems(1);
|
||||
@ -80,13 +86,13 @@ void main() {
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Select date
|
||||
await tester.selectLastDateInPicker();
|
||||
final isToday = await tester.selectLastDateInPicker();
|
||||
|
||||
// Select Time of event reminder
|
||||
await tester.selectReminderOption(ReminderOption.atTimeOfEvent);
|
||||
// Select "On day of event"-reminder
|
||||
await tester.selectReminderOption(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Expect Time of event to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
@ -95,8 +101,8 @@ void main() {
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect Time of event to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
@ -105,8 +111,14 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Open "Upcoming" in Notification hub
|
||||
await tester.openNotificationHub(tabIndex: 1);
|
||||
int tabIndex = 1;
|
||||
final now = DateTime.now();
|
||||
if (isToday && now.hour >= 9) {
|
||||
tabIndex = 0;
|
||||
}
|
||||
|
||||
// Open correct tab in Notification hub
|
||||
await tester.openNotificationHub(tabIndex: tabIndex);
|
||||
|
||||
// Expect 1 notification
|
||||
tester.expectNotificationItems(1);
|
||||
@ -118,5 +130,77 @@ void main() {
|
||||
// Expect to see Row Editor Dialog
|
||||
tester.expectToSeeRowDetailsPageDialog();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'toggle include time sets reminder option correctly',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Grid,
|
||||
);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// Change to date type
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.DateTime);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// Open date picker
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Select date
|
||||
await tester.selectLastDateInPicker();
|
||||
|
||||
// Select "On day of event"-reminder
|
||||
await tester.selectReminderOption(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Open date picker again
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Toggle include time on
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
// Expect "At time of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Open date picker again
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect "At time of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
|
||||
// Select "One hour before"-reminder
|
||||
await tester.selectReminderOption(ReminderOption.oneHourBefore);
|
||||
|
||||
// Expect "One hour before" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.oneHourBefore);
|
||||
|
||||
// Toggle include time off
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -322,20 +322,23 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> selectReminderOption(ReminderOption option) async {
|
||||
await hoverOnWidget(find.byType(ReminderSelector));
|
||||
await tapButton(find.byType(ReminderSelector));
|
||||
|
||||
final finder = find.descendant(
|
||||
of: find.byType(FlowyButton),
|
||||
matching: find.text(option.label),
|
||||
matching: find.textContaining(option.label),
|
||||
);
|
||||
|
||||
await tapButton(finder);
|
||||
}
|
||||
|
||||
Future<void> selectLastDateInPicker() async {
|
||||
Future<bool> selectLastDateInPicker() async {
|
||||
final finder = find.byType(CellContent).last;
|
||||
final w = widget(finder) as CellContent;
|
||||
|
||||
await tapButton(finder);
|
||||
|
||||
return w.isToday;
|
||||
}
|
||||
|
||||
Future<void> toggleDateRange() async {
|
||||
|
@ -191,4 +191,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 8c681999c7764593c94846b2a64b44d86f7a27ac
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.14.3
|
||||
|
@ -95,6 +95,7 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
includeTime: state.includeTime,
|
||||
use24hFormat: state.dateTypeOptionPB.timeFormat ==
|
||||
TimeFormatPB.TwentyFourHour,
|
||||
timeFormat: state.dateTypeOptionPB.timeFormat,
|
||||
selectedReminderOption: state.reminderOption,
|
||||
onStartTimeChanged: (String? time) {
|
||||
if (time != null) {
|
||||
@ -125,9 +126,14 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
onClearDate: () => context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(const DateCellEditorEvent.clearDate()),
|
||||
onReminderSelected: (option) => context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.setReminderOption(option: option)),
|
||||
onReminderSelected: (option) =>
|
||||
context.read<DateCellEditorBloc>().add(
|
||||
DateCellEditorEvent.setReminderOption(
|
||||
option: option,
|
||||
selectedDay:
|
||||
state.dateTime == null ? DateTime.now() : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -13,6 +13,7 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart'
|
||||
show StringTranslateExtension;
|
||||
@ -56,20 +57,28 @@ class DateCellEditorBloc
|
||||
dateCellData.isRange == state.isRange && dateCellData.isRange
|
||||
? dateCellData.endDateTime
|
||||
: null;
|
||||
ReminderOption option = state.reminderOption;
|
||||
|
||||
if (dateCellData.dateTime != null &&
|
||||
(state.reminderId?.isEmpty ?? true) &&
|
||||
(dateCellData.reminderId?.isNotEmpty ?? false) &&
|
||||
state.reminderOption != ReminderOption.none) {
|
||||
final date = state.reminderOption.withoutTime
|
||||
? dateCellData.dateTime!.withoutTime
|
||||
: dateCellData.dateTime!;
|
||||
|
||||
// Add Reminder
|
||||
_reminderBloc.add(
|
||||
ReminderEvent.addById(
|
||||
reminderId: dateCellData.reminderId!,
|
||||
objectId: cellController.viewId,
|
||||
meta: {ReminderMetaKeys.rowId: cellController.rowId},
|
||||
meta: {
|
||||
ReminderMetaKeys.includeTime: true.toString(),
|
||||
ReminderMetaKeys.rowId: cellController.rowId,
|
||||
},
|
||||
scheduledAt: Int64(
|
||||
dateCellData.dateTime!
|
||||
.subtract(state.reminderOption.time)
|
||||
state.reminderOption
|
||||
.fromDate(date)
|
||||
.millisecondsSinceEpoch ~/
|
||||
1000,
|
||||
),
|
||||
@ -79,13 +88,25 @@ class DateCellEditorBloc
|
||||
|
||||
if ((dateCellData.reminderId?.isNotEmpty ?? false) &&
|
||||
dateCellData.dateTime != null) {
|
||||
if (option.requiresNoTime && dateCellData.includeTime) {
|
||||
option = ReminderOption.atTimeOfEvent;
|
||||
} else if (!option.withoutTime && !dateCellData.includeTime) {
|
||||
option = ReminderOption.onDayOfEvent;
|
||||
}
|
||||
|
||||
final date = option.withoutTime
|
||||
? dateCellData.dateTime!.withoutTime
|
||||
: dateCellData.dateTime!;
|
||||
|
||||
final scheduledAt = option.fromDate(date);
|
||||
|
||||
// Update Reminder
|
||||
_reminderBloc.add(
|
||||
ReminderEvent.update(
|
||||
ReminderUpdate(
|
||||
id: state.reminderId!,
|
||||
scheduledAt: dateCellData.dateTime!
|
||||
.subtract(state.reminderOption.time),
|
||||
id: dateCellData.reminderId!,
|
||||
scheduledAt: scheduledAt,
|
||||
includeTime: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -104,6 +125,7 @@ class DateCellEditorBloc
|
||||
dateStr: dateCellData.dateStr,
|
||||
endDateStr: dateCellData.endDateStr,
|
||||
reminderId: dateCellData.reminderId,
|
||||
reminderOption: option,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -185,16 +207,21 @@ class DateCellEditorBloc
|
||||
|
||||
await _clearDate();
|
||||
},
|
||||
setReminderOption: (ReminderOption option) async {
|
||||
setReminderOption: (
|
||||
ReminderOption option,
|
||||
DateTime? selectedDay,
|
||||
) async {
|
||||
if (state.reminderId?.isEmpty ??
|
||||
true &&
|
||||
state.dateTime != null &&
|
||||
(state.dateTime != null || selectedDay != null) &&
|
||||
option != ReminderOption.none) {
|
||||
// New Reminder
|
||||
final reminderId = nanoid();
|
||||
await _updateDateData(reminderId: reminderId);
|
||||
await _updateDateData(reminderId: reminderId, date: selectedDay);
|
||||
|
||||
emit(state.copyWith(reminderOption: option));
|
||||
emit(
|
||||
state.copyWith(reminderOption: option, dateTime: selectedDay),
|
||||
);
|
||||
} else if (option == ReminderOption.none &&
|
||||
(state.reminderId?.isNotEmpty ?? false)) {
|
||||
// Remove reminder
|
||||
@ -204,12 +231,15 @@ class DateCellEditorBloc
|
||||
emit(state.copyWith(reminderOption: option));
|
||||
} else if (state.dateTime != null &&
|
||||
(state.reminderId?.isNotEmpty ?? false)) {
|
||||
final scheduledAt = option.fromDate(state.dateTime!);
|
||||
|
||||
// Update reminder
|
||||
_reminderBloc.add(
|
||||
ReminderEvent.update(
|
||||
ReminderUpdate(
|
||||
id: state.reminderId!,
|
||||
scheduledAt: state.dateTime!.subtract(option.time),
|
||||
scheduledAt: scheduledAt,
|
||||
includeTime: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -427,6 +457,7 @@ class DateCellEditorEvent with _$DateCellEditorEvent {
|
||||
|
||||
const factory DateCellEditorEvent.setReminderOption({
|
||||
required ReminderOption option,
|
||||
@Default(null) DateTime? selectedDay,
|
||||
}) = _SetReminderOption;
|
||||
|
||||
const factory DateCellEditorEvent.removeReminder() = _RemoveReminder;
|
||||
@ -483,8 +514,11 @@ class DateCellEditorState with _$DateCellEditorState {
|
||||
final reminder = reminderBloc.state.reminders
|
||||
.firstWhereOrNull((r) => r.id == dateCellData.reminderId);
|
||||
if (reminder != null) {
|
||||
final eventDate = dateCellData.includeTime
|
||||
? dateCellData.dateTime!
|
||||
: dateCellData.dateTime!.withoutTime;
|
||||
reminderOption = ReminderOption.fromDateDifference(
|
||||
dateCellData.dateTime!,
|
||||
eventDate,
|
||||
reminder.scheduledAt.toDateTime(),
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart';
|
||||
@ -5,7 +8,6 @@ import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileGridDateCellSkin extends IEditableDateCellSkin {
|
||||
@override
|
||||
@ -25,9 +27,17 @@ class MobileGridDateCellSkin extends IEditableDateCellSkin {
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: FlowyText(
|
||||
state.dateStr,
|
||||
fontSize: 15,
|
||||
child: Row(
|
||||
children: [
|
||||
if (state.data?.reminderId.isNotEmpty ?? false) ...[
|
||||
const FlowySvg(FlowySvgs.clock_alarm_s),
|
||||
const HSpace(6),
|
||||
],
|
||||
FlowyText(
|
||||
state.dateStr,
|
||||
fontSize: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -67,8 +67,12 @@ class _DateCellEditor extends State<DateCellEditor> {
|
||||
parseEndTimeError: state.parseEndTimeError,
|
||||
parseTimeError: state.parseTimeError,
|
||||
popoverMutex: popoverMutex,
|
||||
onReminderSelected: (option) => dateCellBloc
|
||||
.add(DateCellEditorEvent.setReminderOption(option: option)),
|
||||
onReminderSelected: (option) => dateCellBloc.add(
|
||||
DateCellEditorEvent.setReminderOption(
|
||||
option: option,
|
||||
selectedDay: state.dateTime == null ? DateTime.now() : null,
|
||||
),
|
||||
),
|
||||
selectedReminderOption: state.reminderOption,
|
||||
options: [
|
||||
OptionGroup(
|
||||
|
@ -12,6 +12,7 @@ import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/mobile_appflowy_date_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/utils/user_time_format_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/mobile_date_header.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||
@ -192,6 +193,7 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
|
||||
UserTimeFormatPB.TwentyFourHour,
|
||||
rebuildOnDaySelected: true,
|
||||
rebuildOnTimeChanged: true,
|
||||
timeFormat: options.timeFormat.simplified,
|
||||
selectedReminderOption: widget.reminderOption,
|
||||
onDaySelected: options.onDaySelected,
|
||||
onStartTimeChanged: (time) => options
|
||||
@ -342,7 +344,7 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
|
||||
ReminderEvent.update(
|
||||
ReminderUpdate(
|
||||
id: widget.reminderId!,
|
||||
scheduledAt: parsedDate!.subtract(reminderOption.time),
|
||||
scheduledAt: reminderOption.fromDate(parsedDate!),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -104,6 +104,9 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
||||
: 0;
|
||||
|
||||
if (invalidCounter >= _invalidSearchesAmount) {
|
||||
// Workaround to bring focus back to editor
|
||||
await widget.editorState
|
||||
.updateSelectionWithReason(widget.editorState.selection);
|
||||
return widget.onDismiss();
|
||||
}
|
||||
|
||||
@ -192,7 +195,8 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
||||
|
||||
int get groupLength => results.length;
|
||||
|
||||
int lengthOfGroup(int index) => results[index].results.length;
|
||||
int lengthOfGroup(int index) =>
|
||||
results.length > index ? results[index].results.length : -1;
|
||||
|
||||
InlineActionsMenuItem handlerOf(int groupIndex, int handlerIndex) =>
|
||||
results[groupIndex].results[handlerIndex];
|
||||
@ -224,7 +228,21 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
||||
widget.onDismiss();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
if (noResults) {
|
||||
// Workaround to bring focus back to editor
|
||||
widget.editorState
|
||||
.updateSelectionWithReason(widget.editorState.selection);
|
||||
widget.editorState.insertNewLine();
|
||||
|
||||
widget.onDismiss();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||
// Workaround to bring focus back to editor
|
||||
widget.editorState
|
||||
.updateSelectionWithReason(widget.editorState.selection);
|
||||
|
||||
widget.onDismiss();
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
|
||||
if (_search.isEmpty) {
|
||||
|
@ -31,7 +31,7 @@ class NotificationButton extends StatelessWidget {
|
||||
child: AppFlowyPopover(
|
||||
mutex: mutex,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: const BoxConstraints(maxHeight: 250, maxWidth: 425),
|
||||
constraints: const BoxConstraints(maxHeight: 500, maxWidth: 425),
|
||||
windowPadding: EdgeInsets.zero,
|
||||
margin: EdgeInsets.zero,
|
||||
popupBuilder: (_) =>
|
||||
|
@ -62,10 +62,24 @@ class _NotificationItemState extends State<NotificationItem> {
|
||||
bool _isHovering = false;
|
||||
int? path;
|
||||
|
||||
late final String infoString;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.block?.then((b) => path = b?.path.first);
|
||||
infoString = _buildInfoString();
|
||||
}
|
||||
|
||||
String _buildInfoString() {
|
||||
String scheduledString =
|
||||
_scheduledString(widget.scheduled, widget.includeTime);
|
||||
|
||||
if (widget.view != null) {
|
||||
scheduledString = '$scheduledString - ${widget.view!.name}';
|
||||
}
|
||||
|
||||
return scheduledString;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -135,10 +149,7 @@ class _NotificationItemState extends State<NotificationItem> {
|
||||
),
|
||||
// TODO(Xazin): Relative time
|
||||
FlowyText.regular(
|
||||
'${_scheduledString(
|
||||
widget.scheduled,
|
||||
widget.includeTime,
|
||||
)}${widget.view != null ? " - ${widget.view!.name}" : ""}',
|
||||
infoString,
|
||||
fontSize:
|
||||
PlatformExtension.isMobile ? 12 : 10,
|
||||
),
|
||||
|
@ -135,6 +135,18 @@ class _AppFlowyDatePickerState extends State<AppFlowyDatePicker> {
|
||||
late DateTime? _selectedDay = widget.selectedDay;
|
||||
late ReminderOption _selectedReminderOption = widget.selectedReminderOption;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AppFlowyDatePicker oldWidget) {
|
||||
_selectedDay = oldWidget.selectedDay != widget.selectedDay
|
||||
? widget.selectedDay
|
||||
: _selectedDay;
|
||||
_selectedReminderOption =
|
||||
oldWidget.selectedReminderOption != widget.selectedReminderOption
|
||||
? widget.selectedReminderOption
|
||||
: _selectedReminderOption;
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
PlatformExtension.isMobile ? buildMobilePicker() : buildDesktopPicker();
|
||||
@ -222,6 +234,8 @@ class _AppFlowyDatePickerState extends State<AppFlowyDatePicker> {
|
||||
const _GroupSeparator(),
|
||||
ReminderSelector(
|
||||
mutex: widget.popoverMutex,
|
||||
hasTime: widget.includeTime,
|
||||
timeFormat: widget.timeFormat,
|
||||
selectedOption: _selectedReminderOption,
|
||||
onOptionSelected: (option) {
|
||||
setState(() => _selectedReminderOption = option);
|
||||
|
@ -7,9 +7,11 @@ import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_option_decorate_box.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/appflowy_date_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/mobile_date_editor.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:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
@ -31,6 +33,7 @@ class MobileAppFlowyDatePicker extends StatefulWidget {
|
||||
this.rebuildOnTimeChanged = false,
|
||||
required this.includeTime,
|
||||
required this.use24hFormat,
|
||||
required this.timeFormat,
|
||||
this.selectedReminderOption,
|
||||
required this.onStartTimeChanged,
|
||||
this.onEndTimeChanged,
|
||||
@ -59,6 +62,8 @@ class MobileAppFlowyDatePicker extends StatefulWidget {
|
||||
final bool rebuildOnTimeChanged;
|
||||
final bool use24hFormat;
|
||||
|
||||
final TimeFormatPB timeFormat;
|
||||
|
||||
final ReminderOption? selectedReminderOption;
|
||||
|
||||
final Function(String? time) onStartTimeChanged;
|
||||
@ -143,6 +148,8 @@ class _MobileAppFlowyDatePickerState extends State<MobileAppFlowyDatePicker> {
|
||||
widget.onReminderSelected!.call(option);
|
||||
setState(() => _reminderOption = option);
|
||||
},
|
||||
timeFormat: widget.timeFormat,
|
||||
hasTime: widget.includeTime,
|
||||
),
|
||||
],
|
||||
if (widget.onClearDate != null) ...[
|
||||
@ -166,10 +173,14 @@ class _ReminderSelector extends StatelessWidget {
|
||||
const _ReminderSelector({
|
||||
this.selectedReminderOption,
|
||||
required this.onReminderSelected,
|
||||
required this.timeFormat,
|
||||
this.hasTime = false,
|
||||
});
|
||||
|
||||
final ReminderOption? selectedReminderOption;
|
||||
final OnReminderSelected onReminderSelected;
|
||||
final TimeFormatPB timeFormat;
|
||||
final bool hasTime;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -180,8 +191,12 @@ class _ReminderSelector extends StatelessWidget {
|
||||
availableOptions.remove(ReminderOption.custom);
|
||||
}
|
||||
|
||||
availableOptions.removeWhere(
|
||||
(o) => !o.timeExempt && (!hasTime ? !o.withoutTime : o.requiresNoTime),
|
||||
);
|
||||
|
||||
return FlowyOptionTile.text(
|
||||
text: 'Reminder',
|
||||
text: LocaleKeys.datePicker_reminderLabel.tr(),
|
||||
trailing: Row(
|
||||
children: [
|
||||
const HSpace(6.0),
|
||||
@ -200,39 +215,51 @@ class _ReminderSelector extends StatelessWidget {
|
||||
onTap: () => showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (context) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 0.7,
|
||||
minChildSize: 0.7,
|
||||
builder: (context, controller) => Column(
|
||||
children: [
|
||||
const _ReminderSelectHeader(),
|
||||
const VSpace(12.0),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Column(
|
||||
children: availableOptions
|
||||
.map(
|
||||
(o) => FlowyOptionTile.text(
|
||||
text: o.label,
|
||||
showTopBorder: o == ReminderOption.none,
|
||||
onTap: () {
|
||||
onReminderSelected(o);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
builder: (_) => DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 0.7,
|
||||
minChildSize: 0.7,
|
||||
builder: (context, controller) => Column(
|
||||
children: [
|
||||
ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Center(child: DragHandler()),
|
||||
),
|
||||
const _ReminderSelectHeader(),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Column(
|
||||
children: availableOptions.map<Widget>(
|
||||
(o) {
|
||||
String label = o.label;
|
||||
if (o.withoutTime && !o.timeExempt) {
|
||||
const time = "09:00";
|
||||
final t = timeFormat == TimeFormatPB.TwelveHour
|
||||
? "$time AM"
|
||||
: time;
|
||||
|
||||
label = "$label ($t)";
|
||||
}
|
||||
|
||||
return FlowyOptionTile.text(
|
||||
text: label,
|
||||
showTopBorder: o == ReminderOption.none,
|
||||
onTap: () {
|
||||
onReminderSelected(o);
|
||||
context.pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
).toList()
|
||||
..insert(0, const _Divider()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -243,8 +270,16 @@ class _ReminderSelectHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
return Container(
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -252,8 +287,8 @@ class _ReminderSelectHeader extends StatelessWidget {
|
||||
width: 120,
|
||||
child: AppBarCancelButton(onTap: context.pop),
|
||||
),
|
||||
const FlowyText.medium(
|
||||
'Select reminder',
|
||||
FlowyText.medium(
|
||||
LocaleKeys.datePicker_selectReminder.tr(),
|
||||
fontSize: 17.0,
|
||||
),
|
||||
const HSpace(120),
|
||||
|
@ -31,7 +31,6 @@ class DateTypeOptionButton extends StatelessWidget {
|
||||
"${LocaleKeys.datePicker_dateFormat.tr()} & ${LocaleKeys.datePicker_timeFormat.tr()}";
|
||||
return AppFlowyPopover(
|
||||
mutex: popoverMutex,
|
||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
offset: const Offset(8, 0),
|
||||
margin: EdgeInsets.zero,
|
||||
constraints: BoxConstraints.loose(const Size(140, 100)),
|
||||
|
@ -3,7 +3,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/utils/layout.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
@ -15,11 +17,15 @@ class ReminderSelector extends StatelessWidget {
|
||||
required this.mutex,
|
||||
required this.selectedOption,
|
||||
required this.onOptionSelected,
|
||||
required this.timeFormat,
|
||||
this.hasTime = false,
|
||||
});
|
||||
|
||||
final PopoverMutex? mutex;
|
||||
final ReminderOption selectedOption;
|
||||
final OnReminderSelected? onOptionSelected;
|
||||
final TimeFormatPB timeFormat;
|
||||
final bool hasTime;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -28,39 +34,53 @@ class ReminderSelector extends StatelessWidget {
|
||||
options.remove(ReminderOption.custom);
|
||||
}
|
||||
|
||||
final optionWidgets = options
|
||||
.map(
|
||||
(o) => SizedBox(
|
||||
height: DatePickerSize.itemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(o.label),
|
||||
rightIcon: o == selectedOption
|
||||
? const FlowySvg(FlowySvgs.check_s)
|
||||
: null,
|
||||
onTap: () {
|
||||
if (o != selectedOption) {
|
||||
onOptionSelected?.call(o);
|
||||
mutex?.close();
|
||||
}
|
||||
},
|
||||
),
|
||||
options.removeWhere(
|
||||
(o) => !o.timeExempt && (!hasTime ? !o.withoutTime : o.requiresNoTime),
|
||||
);
|
||||
|
||||
final optionWidgets = options.map(
|
||||
(o) {
|
||||
String label = o.label;
|
||||
if (o.withoutTime && !o.timeExempt) {
|
||||
const time = "09:00";
|
||||
final t = timeFormat == TimeFormatPB.TwelveHour ? "$time AM" : time;
|
||||
|
||||
label = "$label ($t)";
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: DatePickerSize.itemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(label),
|
||||
rightIcon:
|
||||
o == selectedOption ? const FlowySvg(FlowySvgs.check_s) : null,
|
||||
onTap: () {
|
||||
if (o != selectedOption) {
|
||||
onOptionSelected?.call(o);
|
||||
mutex?.close();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
||||
return AppFlowyPopover(
|
||||
mutex: mutex,
|
||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
offset: const Offset(8, -155),
|
||||
offset: const Offset(8, 0),
|
||||
margin: EdgeInsets.zero,
|
||||
constraints: BoxConstraints.loose(const Size(150, 310)),
|
||||
popupBuilder: (_) => Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: ListView.separated(
|
||||
itemCount: options.length,
|
||||
separatorBuilder: (_, __) => VSpace(DatePickerSize.seperatorHeight),
|
||||
itemBuilder: (_, index) => optionWidgets[index],
|
||||
),
|
||||
constraints: const BoxConstraints(maxHeight: 400, maxWidth: 205),
|
||||
popupBuilder: (_) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: SeparatedColumn(
|
||||
children: optionWidgets,
|
||||
separatorBuilder: () => VSpace(DatePickerSize.seperatorHeight),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
@ -90,13 +110,29 @@ enum ReminderOption {
|
||||
thirtyMinsBefore(time: Duration(minutes: 30)),
|
||||
oneHourBefore(time: Duration(hours: 1)),
|
||||
twoHoursBefore(time: Duration(hours: 2)),
|
||||
oneDayBefore(time: Duration(days: 1)),
|
||||
twoDaysBefore(time: Duration(days: 2)),
|
||||
onDayOfEvent(
|
||||
time: Duration(hours: 9),
|
||||
withoutTime: true,
|
||||
requiresNoTime: true,
|
||||
),
|
||||
// 9:00 AM the day before (24-9)
|
||||
oneDayBefore(time: Duration(hours: 15), withoutTime: true),
|
||||
twoDaysBefore(time: Duration(days: 1, hours: 15), withoutTime: true),
|
||||
oneWeekBefore(time: Duration(days: 6, hours: 15), withoutTime: true),
|
||||
custom(time: Duration());
|
||||
|
||||
const ReminderOption({required this.time});
|
||||
const ReminderOption({
|
||||
required this.time,
|
||||
this.withoutTime = false,
|
||||
this.requiresNoTime = false,
|
||||
});
|
||||
|
||||
final Duration time;
|
||||
final bool withoutTime;
|
||||
final bool requiresNoTime;
|
||||
|
||||
bool get timeExempt =>
|
||||
[ReminderOption.none, ReminderOption.custom].contains(this);
|
||||
|
||||
String get label => switch (this) {
|
||||
ReminderOption.none => LocaleKeys.datePicker_reminderOptions_none.tr(),
|
||||
@ -114,10 +150,14 @@ enum ReminderOption {
|
||||
LocaleKeys.datePicker_reminderOptions_oneHourBefore.tr(),
|
||||
ReminderOption.twoHoursBefore =>
|
||||
LocaleKeys.datePicker_reminderOptions_twoHoursBefore.tr(),
|
||||
ReminderOption.onDayOfEvent =>
|
||||
LocaleKeys.datePicker_reminderOptions_onDayOfEvent.tr(),
|
||||
ReminderOption.oneDayBefore =>
|
||||
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
|
||||
ReminderOption.twoDaysBefore =>
|
||||
LocaleKeys.datePicker_reminderOptions_twoDaysBefore.tr(),
|
||||
ReminderOption.oneWeekBefore =>
|
||||
LocaleKeys.datePicker_reminderOptions_oneWeekBefore.tr(),
|
||||
ReminderOption.custom =>
|
||||
LocaleKeys.datePicker_reminderOptions_custom.tr(),
|
||||
};
|
||||
@ -125,8 +165,15 @@ enum ReminderOption {
|
||||
static ReminderOption fromDateDifference(
|
||||
DateTime eventDate,
|
||||
DateTime reminderDate,
|
||||
) =>
|
||||
fromMinutes(eventDate.difference(reminderDate).inMinutes);
|
||||
) {
|
||||
final def = fromMinutes(eventDate.difference(reminderDate).inMinutes);
|
||||
if (def != ReminderOption.custom) {
|
||||
return def;
|
||||
}
|
||||
|
||||
final diff = eventDate.withoutTime.difference(reminderDate).inMinutes;
|
||||
return fromMinutes(diff);
|
||||
}
|
||||
|
||||
static ReminderOption fromMinutes(int minutes) => switch (minutes) {
|
||||
0 => ReminderOption.atTimeOfEvent,
|
||||
@ -136,8 +183,18 @@ enum ReminderOption {
|
||||
30 => ReminderOption.thirtyMinsBefore,
|
||||
60 => ReminderOption.oneHourBefore,
|
||||
120 => ReminderOption.twoHoursBefore,
|
||||
1440 => ReminderOption.oneDayBefore,
|
||||
2880 => ReminderOption.twoDaysBefore,
|
||||
// Negative because Event Day Today + 940 minutes
|
||||
-540 => ReminderOption.onDayOfEvent,
|
||||
900 => ReminderOption.oneDayBefore,
|
||||
2340 => ReminderOption.twoDaysBefore,
|
||||
9540 => ReminderOption.oneWeekBefore,
|
||||
_ => ReminderOption.custom,
|
||||
};
|
||||
|
||||
DateTime fromDate(DateTime date) => switch (withoutTime) {
|
||||
true => requiresNoTime
|
||||
? date.withoutTime.add(time)
|
||||
: date.withoutTime.subtract(time),
|
||||
_ => date.subtract(time),
|
||||
};
|
||||
}
|
||||
|
@ -235,16 +235,24 @@ class PopoverState extends State<Popover> {
|
||||
switch (widget.clickHandler) {
|
||||
case PopoverClickHandler.listener:
|
||||
return Listener(
|
||||
onPointerDown: (_) => handler(),
|
||||
onPointerDown: (_) => _callHandler(handler),
|
||||
child: child,
|
||||
);
|
||||
case PopoverClickHandler.gestureDetector:
|
||||
return GestureDetector(
|
||||
onTap: handler,
|
||||
onTap: () => _callHandler(handler),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _callHandler(VoidCallback handler) {
|
||||
if (_rootEntry.contains(this)) {
|
||||
close();
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverContainer extends StatefulWidget {
|
||||
|
@ -1037,6 +1037,7 @@
|
||||
"timeFormat": "Time format",
|
||||
"clearDate": "Clear date",
|
||||
"reminderLabel": "Reminder",
|
||||
"selectReminder": "Select reminder",
|
||||
"reminderOptions": {
|
||||
"none": "None",
|
||||
"atTimeOfEvent": "Time of event",
|
||||
@ -1046,8 +1047,10 @@
|
||||
"thirtyMinsBefore": "30 mins before",
|
||||
"oneHourBefore": "1 hour before",
|
||||
"twoHoursBefore": "2 hours before",
|
||||
"onDayOfEvent": "On day of event",
|
||||
"oneDayBefore": "1 day before",
|
||||
"twoDaysBefore": "2 days before",
|
||||
"oneWeekBefore": "1 week before",
|
||||
"custom": "Custom"
|
||||
}
|
||||
},
|
||||
@ -1247,4 +1250,4 @@
|
||||
"userIcon": "User icon"
|
||||
},
|
||||
"noLogFiles": "There're no log files"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user