date_reference.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. _setOptions();
  56. options = _allOptions;
  57. }
  58. }
  59. void _searchDate(String? search) {
  60. if (search == null || search.isEmpty) {
  61. return;
  62. }
  63. try {
  64. final date = DateFormat.yMd(_locale).parse(search);
  65. options.insert(0, _itemFromDate(date));
  66. } catch (_) {
  67. return;
  68. }
  69. }
  70. Future<void> _searchDateNLP(String? search) async {
  71. if (search == null || search.isEmpty) {
  72. return;
  73. }
  74. final result = await DateService.queryDate(search);
  75. result.fold(
  76. (l) {},
  77. (date) => options.insert(0, _itemFromDate(date)),
  78. );
  79. }
  80. Future<void> _insertDateReference(
  81. EditorState editorState,
  82. DateTime date,
  83. int start,
  84. int end,
  85. ) async {
  86. final selection = editorState.selection;
  87. if (selection == null || !selection.isCollapsed) {
  88. return;
  89. }
  90. final node = editorState.getNodeAtPath(selection.end.path);
  91. final delta = node?.delta;
  92. if (node == null || delta == null) {
  93. return;
  94. }
  95. final transaction = editorState.transaction
  96. ..replaceText(
  97. node,
  98. start,
  99. end,
  100. '\$',
  101. attributes: {
  102. MentionBlockKeys.mention: {
  103. MentionBlockKeys.type: MentionType.date.name,
  104. MentionBlockKeys.date: date.toIso8601String(),
  105. }
  106. },
  107. );
  108. await editorState.apply(transaction);
  109. }
  110. void _setOptions() {
  111. final today = DateTime.now();
  112. final tomorrow = today.add(const Duration(days: 1));
  113. final yesterday = today.subtract(const Duration(days: 1));
  114. _allOptions = [
  115. _itemFromDate(
  116. today,
  117. LocaleKeys.relativeDates_today.tr(),
  118. [DateFormat.yMd(_locale).format(today)],
  119. ),
  120. _itemFromDate(
  121. tomorrow,
  122. LocaleKeys.relativeDates_tomorrow.tr(),
  123. [DateFormat.yMd(_locale).format(tomorrow)],
  124. ),
  125. _itemFromDate(
  126. yesterday,
  127. LocaleKeys.relativeDates_yesterday.tr(),
  128. [DateFormat.yMd(_locale).format(yesterday)],
  129. ),
  130. ];
  131. }
  132. /// Sets Locale on each search to make sure
  133. /// keywords are localized
  134. void _setLocale() {
  135. final locale = context.locale.toLanguageTag();
  136. if (locale != _locale) {
  137. _locale = locale;
  138. _setOptions();
  139. }
  140. }
  141. InlineActionsMenuItem _itemFromDate(
  142. DateTime date, [
  143. String? label,
  144. List<String>? keywords,
  145. ]) {
  146. final labelStr = label ?? DateFormat.yMd(_locale).format(date);
  147. return InlineActionsMenuItem(
  148. label: labelStr.capitalize(),
  149. keywords: [labelStr.toLowerCase(), ...?keywords],
  150. onSelected: (context, editorState, menuService, replace) =>
  151. _insertDateReference(
  152. editorState,
  153. date,
  154. replace.$1,
  155. replace.$2,
  156. ),
  157. );
  158. }
  159. }