date_reference.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import 'package:appflowy/date/date_service.dart';
  2. import 'package:appflowy/generated/locale_keys.g.dart';
  3. import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
  4. import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
  5. import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
  6. import 'package:appflowy_editor/appflowy_editor.dart';
  7. import 'package:easy_localization/easy_localization.dart';
  8. import 'package:flutter/material.dart';
  9. final _keywords = [
  10. LocaleKeys.inlineActions_date.tr().toLowerCase(),
  11. ];
  12. class DateReferenceService {
  13. DateReferenceService(this.context) {
  14. // Initialize locale
  15. _locale = context.locale.toLanguageTag();
  16. // Initializes options
  17. _setOptions();
  18. }
  19. final BuildContext context;
  20. late String _locale;
  21. late List<InlineActionsMenuItem> _allOptions;
  22. List<InlineActionsMenuItem> options = [];
  23. Future<InlineActionsResult> dateReferenceDelegate([
  24. String? search,
  25. ]) async {
  26. // Checks if Locale has changed since last
  27. _setLocale();
  28. // Filters static options
  29. _filterOptions(search);
  30. // Searches for date by pattern
  31. _searchDate(search);
  32. // Searches for date by natural language prompt
  33. await _searchDateNLP(search);
  34. return InlineActionsResult(
  35. title: LocaleKeys.inlineActions_date.tr(),
  36. results: options,
  37. );
  38. }
  39. void _filterOptions(String? search) {
  40. if (search == null || search.isEmpty) {
  41. options = _allOptions;
  42. return;
  43. }
  44. options = _allOptions
  45. .where(
  46. (option) =>
  47. option.keywords != null &&
  48. option.keywords!.isNotEmpty &&
  49. option.keywords!.any(
  50. (keyword) => keyword.contains(search.toLowerCase()),
  51. ),
  52. )
  53. .toList();
  54. if (options.isEmpty && _keywords.any((k) => search.startsWith(k))) {
  55. options = _allOptions;
  56. }
  57. }
  58. void _searchDate(String? search) {
  59. if (search == null || search.isEmpty) {
  60. return;
  61. }
  62. try {
  63. final date = DateFormat.yMd(_locale).parse(search);
  64. options.insert(0, _itemFromDate(date));
  65. } catch (_) {
  66. return;
  67. }
  68. }
  69. Future<void> _searchDateNLP(String? search) async {
  70. if (search == null || search.isEmpty) {
  71. return;
  72. }
  73. final result = await DateService.queryDate(search);
  74. result.fold(
  75. (l) {},
  76. (date) => options.insert(0, _itemFromDate(date)),
  77. );
  78. }
  79. Future<void> _insertDateReference(
  80. EditorState editorState,
  81. DateTime date,
  82. int start,
  83. int end,
  84. ) async {
  85. final selection = editorState.selection;
  86. if (selection == null || !selection.isCollapsed) {
  87. return;
  88. }
  89. final node = editorState.getNodeAtPath(selection.end.path);
  90. final delta = node?.delta;
  91. if (node == null || delta == null) {
  92. return;
  93. }
  94. final transaction = editorState.transaction
  95. ..replaceText(
  96. node,
  97. start,
  98. end,
  99. '\$',
  100. attributes: {
  101. MentionBlockKeys.mention: {
  102. MentionBlockKeys.type: MentionType.date.name,
  103. MentionBlockKeys.date: date.toIso8601String(),
  104. }
  105. },
  106. );
  107. await editorState.apply(transaction);
  108. }
  109. void _setOptions() {
  110. final today = DateTime.now();
  111. final tomorrow = today.add(const Duration(days: 1));
  112. final yesterday = today.subtract(const Duration(days: 1));
  113. _allOptions = [
  114. _itemFromDate(
  115. today,
  116. LocaleKeys.relativeDates_today.tr(),
  117. [DateFormat.yMd(_locale).format(today)],
  118. ),
  119. _itemFromDate(
  120. tomorrow,
  121. LocaleKeys.relativeDates_tomorrow.tr(),
  122. [DateFormat.yMd(_locale).format(tomorrow)],
  123. ),
  124. _itemFromDate(
  125. yesterday,
  126. LocaleKeys.relativeDates_yesterday.tr(),
  127. [DateFormat.yMd(_locale).format(yesterday)],
  128. ),
  129. ];
  130. }
  131. /// Sets Locale on each search to make sure
  132. /// keywords are localized
  133. void _setLocale() {
  134. final locale = context.locale.toLanguageTag();
  135. if (locale != _locale) {
  136. _locale = locale;
  137. _setOptions();
  138. }
  139. }
  140. InlineActionsMenuItem _itemFromDate(
  141. DateTime date, [
  142. String? label,
  143. List<String>? keywords,
  144. ]) {
  145. final labelStr = label ?? DateFormat.yMd(_locale).format(date);
  146. return InlineActionsMenuItem(
  147. label: labelStr.capitalize(),
  148. keywords: [labelStr.toLowerCase(), ...?keywords],
  149. onSelected: (context, editorState, menuService, replace) =>
  150. _insertDateReference(
  151. editorState,
  152. date,
  153. replace.$1,
  154. replace.$2,
  155. ),
  156. );
  157. }
  158. }