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