fluffychat/lib/pages/chat/input_bar.dart
ggurdin e8428783e6
Fluffychat merge 2 (#5590)
* build: Reenable shrink resources and minify in gradle

* build: (deps): bump image from 4.6.0 to 4.7.1

Bumps [image](https://github.com/brendan-duncan/image) from 4.6.0 to 4.7.1.
- [Changelog](https://github.com/brendan-duncan/image/blob/main/CHANGELOG.md)
- [Commits](https://github.com/brendan-duncan/image/commits)

---
updated-dependencies:
- dependency-name: image
  dependency-version: 4.7.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build: (deps): bump file_picker from 10.3.7 to 10.3.8

Bumps [file_picker](https://github.com/miguelpruivo/flutter_file_picker) from 10.3.7 to 10.3.8.
- [Release notes](https://github.com/miguelpruivo/flutter_file_picker/releases)
- [Changelog](https://github.com/miguelpruivo/flutter_file_picker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/miguelpruivo/flutter_file_picker/compare/v10.3.7...v10.3.8)

---
updated-dependencies:
- dependency-name: file_picker
  dependency-version: 10.3.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: Improved search

* build: Use matrix sdk vom pub.dev again

* chore: Follow up better search

* build: (deps): bump image from 4.7.1 to 4.7.2

Bumps [image](https://github.com/brendan-duncan/image) from 4.7.1 to 4.7.2.
- [Changelog](https://github.com/brendan-duncan/image/blob/main/CHANGELOG.md)
- [Commits](https://github.com/brendan-duncan/image/commits)

---
updated-dependencies:
- dependency-name: image
  dependency-version: 4.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: Make cross signing self sign mandatory for bootstrap

* chore: Update user device keys before creating bootstrap

* fix: Better wait for secrets after verification bootstrap

* refactor: Remove native imaging and enable web worker

* refactor: Remove unused html onfocus streams

* build: (deps): bump flutter_foreground_task from 9.1.0 to 9.2.0

Bumps [flutter_foreground_task](https://github.com/Dev-hwang/flutter_foreground_task) from 9.1.0 to 9.2.0.
- [Changelog](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Dev-hwang/flutter_foreground_task/commits)

---
updated-dependencies:
- dependency-name: flutter_foreground_task
  dependency-version: 9.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(translations): Translated using Weblate (Uzbek)

Currently translated at 99.7% (823 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uz/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 99.8% (824 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* chore(translations): Translated using Weblate (Norwegian Bokmål)

Currently translated at 90.9% (750 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nb_NO/

* chore(translations): Translated using Weblate (Galician)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/

* chore(translations): Translated using Weblate (Basque)

Currently translated at 99.7% (823 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/

* chore(translations): Translated using Weblate (Ukrainian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/

* chore(translations): Translated using Weblate (Estonian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/

* chore(translations): Translated using Weblate (Dutch)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* chore(translations): Translated using Weblate (Spanish)

Currently translated at 95.2% (788 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/

* chore(translations): Translated using Weblate (Spanish)

Currently translated at 96.3% (797 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* fix: Broken ruzzian plurals

* chore(translations): Translated using Weblate (Norwegian Bokmål)

Currently translated at 91.2% (753 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nb_NO/

* chore(translations): Translated using Weblate (Bengali)

Currently translated at 4.5% (38 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/bn/

* chore(translations): Translated using Weblate (French)

Currently translated at 82.3% (679 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/fr/

* build: (deps): bump translations_cleaner from 0.0.5 to 0.1.0

Bumps [translations_cleaner](https://github.com/Chinmay-KB/translations_cleaner) from 0.0.5 to 0.1.0.
- [Changelog](https://github.com/Chinmay-KB/translations_cleaner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Chinmay-KB/translations_cleaner/commits)

---
updated-dependencies:
- dependency-name: translations_cleaner
  dependency-version: 0.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(translations): Translated using Weblate (German)

Currently translated at 99.2% (821 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/

* chore(translations): Translated using Weblate (Estonian)

Currently translated at 100.0% (827 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/

* build: Bump version to 2.4.0

* build: (deps): bump sqflite_common_ffi from 2.3.6 to 2.3.7+1

Bumps [sqflite_common_ffi](https://github.com/tekartik/sqflite) from 2.3.6 to 2.3.7+1.
- [Commits](https://github.com/tekartik/sqflite/compare/sqflite_common_ffi_v2.3.6...sqflite_common_ffi/v2.3.7)

---
updated-dependencies:
- dependency-name: sqflite_common_ffi
  dependency-version: 2.3.7+1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(translations): Translated using Weblate (Czech)

Currently translated at 66.1% (547 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/cs/

* chore(translations): Translated using Weblate (Czech)

Currently translated at 72.7% (602 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/cs/

* chore(translations): Translated using Weblate (German)

Currently translated at 99.8% (826 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/

* chore: Add security.md file

* fix: Locale unlocalized strings

* build: (deps): bump matrix from 4.1.0 to 5.0.0

Bumps [matrix](https://github.com/famedly/matrix-dart-sdk) from 4.1.0 to 5.0.0.
- [Release notes](https://github.com/famedly/matrix-dart-sdk/releases)
- [Changelog](https://github.com/famedly/matrix-dart-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/famedly/matrix-dart-sdk/compare/v4.1.0...v5.0.0)

---
updated-dependencies:
- dependency-name: matrix
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: Notifications on web correctly managed when tab not focused

* chore: Add changelog for android

* chore: Remove duplicated localization

* fix: Sign in label

* chore: Versionize fcm shared isolate

* build: Remove unused packag

* build: (deps): bump package_info_plus from 8.3.1 to 9.0.0

Bumps [package_info_plus](https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus) from 8.3.1 to 9.0.0.
- [Release notes](https://github.com/fluttercommunity/plus_plugins/releases)
- [Commits](https://github.com/fluttercommunity/plus_plugins/commits/package_info_plus-v9.0.0/packages/package_info_plus)

---
updated-dependencies:
- dependency-name: package_info_plus
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: Display particle animation on login page

* chore: Use fixed version of fcm shared isolate

* fix: apk crash on some platforms due new flutter version

* chore: Correct kotlin format

* fix iOS notifications

* fluffychat merge

* fluffychat merge

* fluffychat merge

* fluffychat merge

* fluffychat merge

* fluffychat merge

* add missing type annotations

* update matrix version

* fluffychat merge

* fluffychat merge

* fix notification on click actions

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Christian Kußowski <c.kussowski@famedly.com>
Co-authored-by: Krille-chan <christian-kussowski@posteo.de>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: BeMeritus <bemerituss@gmail.com>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: josé m. <correoxm@disroot.org>
Co-authored-by: xabirequejo <xabi.rn@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Jelv <post@jelv.nl>
Co-authored-by: Дмитрий Михирев <bizdelnick@gmail.com>
Co-authored-by: Kimby <kimbyqs@gmail.com>
Co-authored-by: Christian <christian-pauly@posteo.de>
Co-authored-by: Kom nake <kominak310@svcache.com>
Co-authored-by: hugues de keyzer <komputilisto@hugues.info>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: Šebestová <ka.sebestova.cz@gmail.com>
2026-02-10 08:01:12 -05:00

592 lines
20 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:matrix/matrix.dart';
import 'package:slugify/slugify.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
import 'package:fluffychat/pangea/choreographer/choreo_mode_enum.dart';
import 'package:fluffychat/pangea/choreographer/choreographer.dart';
import 'package:fluffychat/pangea/choreographer/text_editing/edit_type_enum.dart';
import 'package:fluffychat/pangea/choreographer/text_editing/pangea_text_controller.dart';
import 'package:fluffychat/pangea/common/utils/overlay.dart';
import 'package:fluffychat/pangea/common/widgets/shrinkable_text.dart';
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/subscription/widgets/paywall_card.dart';
import 'package:fluffychat/utils/markdown_context_builder.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import '../../widgets/avatar.dart';
import '../../widgets/matrix.dart';
import 'command_hints.dart';
class InputBar extends StatelessWidget {
final Room room;
final int? minLines;
final int? maxLines;
final TextInputType? keyboardType;
final TextInputAction? textInputAction;
final ValueChanged<String>? onSubmitted;
final ValueChanged<Uint8List?>? onSubmitImage;
final FocusNode? focusNode;
// #Pangea
// final TextEditingController? controller;
final PangeaTextController? controller;
final Choreographer choreographer;
final VoidCallback showNextMatch;
final Future Function(String) onFeedbackSubmitted;
// Pangea#
final InputDecoration decoration;
final ValueChanged<String>? onChanged;
final bool? autofocus;
final bool readOnly;
final List<Emoji> suggestionEmojis;
const InputBar({
required this.room,
this.minLines,
this.maxLines,
this.keyboardType,
this.onSubmitted,
this.onSubmitImage,
this.focusNode,
this.controller,
required this.decoration,
this.onChanged,
this.autofocus,
this.textInputAction,
this.readOnly = false,
required this.suggestionEmojis,
// #Pangea
required this.choreographer,
required this.showNextMatch,
required this.onFeedbackSubmitted,
// Pangea#
super.key,
});
List<Map<String, String?>> getSuggestions(TextEditingValue text) {
if (text.selection.baseOffset != text.selection.extentOffset ||
text.selection.baseOffset < 0) {
return []; // no entries if there is selected text
}
final searchText = text.text.substring(0, text.selection.baseOffset);
final ret = <Map<String, String?>>[];
const maxResults = 30;
// #Pangea
// final commandMatch = RegExp(r'^/(\w*)$').firstMatch(searchText);
// if (commandMatch != null) {
// final commandSearch = commandMatch[1]!.toLowerCase();
// for (final command in room.client.commands.keys) {
// if (command.contains(commandSearch)) {
// ret.add({'type': 'command', 'name': command});
// }
// if (ret.length > maxResults) return ret;
// }
// }
// Pangea#
final emojiMatch = RegExp(
r'(?:\s|^):(?:([\p{L}\p{N}_-]+)~)?([\p{L}\p{N}_-]+)$',
unicode: true,
).firstMatch(searchText);
if (emojiMatch != null) {
final packSearch = emojiMatch[1];
final emoteSearch = emojiMatch[2]!.toLowerCase();
final emotePacks = room.getImagePacks(ImagePackUsage.emoticon);
if (packSearch == null || packSearch.isEmpty) {
for (final pack in emotePacks.entries) {
for (final emote in pack.value.images.entries) {
if (emote.key.toLowerCase().contains(emoteSearch)) {
ret.add({
'type': 'emote',
'name': emote.key,
'pack': pack.key,
'pack_avatar_url': pack.value.pack.avatarUrl?.toString(),
'pack_display_name': pack.value.pack.displayName ?? pack.key,
'mxc': emote.value.url.toString(),
});
}
if (ret.length > maxResults) {
break;
}
}
if (ret.length > maxResults) {
break;
}
}
} else if (emotePacks[packSearch] != null) {
for (final emote in emotePacks[packSearch]!.images.entries) {
if (emote.key.toLowerCase().contains(emoteSearch)) {
ret.add({
'type': 'emote',
'name': emote.key,
'pack': packSearch,
'pack_avatar_url': emotePacks[packSearch]!.pack.avatarUrl
?.toString(),
'pack_display_name':
emotePacks[packSearch]!.pack.displayName ?? packSearch,
'mxc': emote.value.url.toString(),
});
}
if (ret.length > maxResults) {
break;
}
}
}
// aside of emote packs, also propose normal (tm) unicode emojis
final matchingUnicodeEmojis = suggestionEmojis
.where((emoji) => emoji.name.toLowerCase().contains(emoteSearch))
.toList();
// sort by the index of the search term in the name in order to have
// best matches first
// (thanks for the hint by github.com/nextcloud/circles devs)
matchingUnicodeEmojis.sort((a, b) {
final indexA = a.name.indexOf(emoteSearch);
final indexB = b.name.indexOf(emoteSearch);
if (indexA == -1 || indexB == -1) {
if (indexA == indexB) return 0;
if (indexA == -1) {
return 1;
} else {
return 0;
}
}
return indexA.compareTo(indexB);
});
for (final emoji in matchingUnicodeEmojis) {
ret.add({
'type': 'emoji',
'emoji': emoji.emoji,
'label': emoji.name,
'current_word': ':$emoteSearch',
});
if (ret.length > maxResults) {
break;
}
}
}
final userMatch = RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText);
if (userMatch != null) {
final userSearch = userMatch[1]!.toLowerCase();
for (final user in room.getParticipants()) {
if ((user.displayName != null &&
(user.displayName!.toLowerCase().contains(userSearch) ||
slugify(
user.displayName!.toLowerCase(),
).contains(userSearch))) ||
user.id.split(':')[0].toLowerCase().contains(userSearch)) {
ret.add({
'type': 'user',
'mxid': user.id,
'mention': user.mention,
'displayname': user.displayName,
'avatar_url': user.avatarUrl?.toString(),
});
}
if (ret.length > maxResults) {
break;
}
}
}
final roomMatch = RegExp(r'(?:\s|^)#([-\w]+)$').firstMatch(searchText);
if (roomMatch != null) {
final roomSearch = roomMatch[1]!.toLowerCase();
for (final r in room.client.rooms) {
if (r.getState(EventTypes.RoomTombstone) != null) {
continue; // we don't care about tombstoned rooms
}
final state = r.getState(EventTypes.RoomCanonicalAlias);
if ((state != null &&
((state.content['alias'] is String &&
state.content
.tryGet<String>('alias')!
.split(':')[0]
.toLowerCase()
.contains(roomSearch)) ||
(state.content['alt_aliases'] is List &&
(state.content['alt_aliases'] as List).any(
(l) =>
l is String &&
l
.split(':')[0]
.toLowerCase()
.contains(roomSearch),
)))) ||
(r.name.toLowerCase().contains(roomSearch))) {
ret.add({
'type': 'room',
'mxid': (r.canonicalAlias.isNotEmpty) ? r.canonicalAlias : r.id,
'displayname': r.getLocalizedDisplayname(),
'avatar_url': r.avatar?.toString(),
});
}
if (ret.length > maxResults) {
break;
}
}
}
return ret;
}
Widget buildSuggestion(
BuildContext context,
Map<String, String?> suggestion,
void Function(Map<String, String?>) onSelected,
Client? client,
) {
final theme = Theme.of(context);
const size = 30.0;
if (suggestion['type'] == 'command') {
final command = suggestion['name']!;
final hint = commandHint(L10n.of(context), command);
return Tooltip(
message: hint,
waitDuration: const Duration(days: 1), // don't show on hover
child: ListTile(
onTap: () => onSelected(suggestion),
title: Text(
commandExample(command),
style: const TextStyle(fontFamily: 'RobotoMono'),
),
subtitle: Text(
hint,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall,
),
),
);
}
if (suggestion['type'] == 'emoji') {
final label = suggestion['label']!;
return Tooltip(
message: label,
waitDuration: const Duration(days: 1), // don't show on hover
child: ListTile(
onTap: () => onSelected(suggestion),
leading: SizedBox.square(
dimension: size,
child: Text(
suggestion['emoji']!,
style: const TextStyle(fontSize: 16),
),
),
title: Text(label, maxLines: 1, overflow: TextOverflow.ellipsis),
),
);
}
if (suggestion['type'] == 'emote') {
return ListTile(
onTap: () => onSelected(suggestion),
leading: MxcImage(
// ensure proper ordering ...
key: ValueKey(suggestion['name']),
uri: suggestion['mxc'] is String
? Uri.parse(suggestion['mxc'] ?? '')
: null,
width: size,
height: size,
isThumbnail: false,
),
title: Row(
crossAxisAlignment: .center,
children: <Widget>[
Text(suggestion['name']!),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Opacity(
opacity: suggestion['pack_avatar_url'] != null ? 0.8 : 0.5,
child: suggestion['pack_avatar_url'] != null
? Avatar(
mxContent: Uri.tryParse(
suggestion.tryGet<String>('pack_avatar_url') ?? '',
),
name: suggestion.tryGet<String>('pack_display_name'),
size: size * 0.9,
client: client,
)
: Text(suggestion['pack_display_name']!),
),
),
),
],
),
);
}
if (suggestion['type'] == 'user' || suggestion['type'] == 'room') {
final url = Uri.parse(suggestion['avatar_url'] ?? '');
return ListTile(
onTap: () => onSelected(suggestion),
leading: Avatar(
mxContent: url,
name:
suggestion.tryGet<String>('displayname') ??
suggestion.tryGet<String>('mxid'),
size: size,
client: client,
),
title: Text(suggestion['displayname'] ?? suggestion['mxid']!),
);
}
return const SizedBox.shrink();
}
String insertSuggestion(Map<String, String?> suggestion) {
final replaceText = controller!.text.substring(
0,
controller!.selection.baseOffset,
);
var startText = '';
final afterText = replaceText == controller!.text
? ''
: controller!.text.substring(controller!.selection.baseOffset + 1);
var insertText = '';
if (suggestion['type'] == 'command') {
insertText = '${suggestion['name']!} ';
startText = replaceText.replaceAllMapped(
RegExp(r'^(/\w*)$'),
(Match m) => '/$insertText',
);
}
if (suggestion['type'] == 'emoji') {
insertText = '${suggestion['emoji']!} ';
startText = replaceText.replaceAllMapped(
// #Pangea
RegExp(suggestion['current_word']!, caseSensitive: false),
// suggestion['current_word']!,
// Pangea#
(Match m) => insertText,
);
}
if (suggestion['type'] == 'emote') {
var isUnique = true;
final insertEmote = suggestion['name'];
final insertPack = suggestion['pack'];
final emotePacks = room.getImagePacks(ImagePackUsage.emoticon);
for (final pack in emotePacks.entries) {
if (pack.key == insertPack) {
continue;
}
for (final emote in pack.value.images.entries) {
if (emote.key == insertEmote) {
isUnique = false;
break;
}
}
if (!isUnique) {
break;
}
}
insertText = ':${isUnique ? '' : '${insertPack!}~'}$insertEmote: ';
startText = replaceText.replaceAllMapped(
RegExp(r'(\s|^)(:(?:[-\w]+~)?[-\w]+)$'),
(Match m) => '${m[1]}$insertText',
);
}
if (suggestion['type'] == 'user') {
insertText = '${suggestion['mention']!} ';
startText = replaceText.replaceAllMapped(
RegExp(r'(\s|^)(@[-\w]+)$'),
(Match m) => '${m[1]}$insertText',
);
}
if (suggestion['type'] == 'room') {
insertText = '${suggestion['mxid']!} ';
startText = replaceText.replaceAllMapped(
RegExp(r'(\s|^)(#[-\w]+)$'),
(Match m) => '${m[1]}$insertText',
);
}
return startText + afterText;
}
// #Pangea
SubscriptionStatus get _subscriptionStatus =>
MatrixState.pangeaController.subscriptionController.subscriptionStatus;
String _defaultHintText(BuildContext context) {
return MatrixState.pangeaController.userController.languagesSet
? L10n.of(context).writeAMessageLangCodes(
MatrixState.pangeaController.userController.userL1!.displayName,
MatrixState.pangeaController.userController.userL2!.displayName,
)
: L10n.of(context).writeAMessage;
}
void _onInputTap(BuildContext context) {
if (_shouldShowPaywall(context)) return;
final baseOffset = controller!.selection.baseOffset;
final adjustedOffset = _adjustOffsetForNormalization(baseOffset);
final match = choreographer.igcController.getMatchByOffset(adjustedOffset);
if (match == null) return;
if (match.updatedMatch.isITStart) {
choreographer.itController.openIT(controller!.text);
} else {
OverlayUtil.showIGCMatch(
match,
choreographer,
context,
showNextMatch,
onFeedbackSubmitted,
);
// rebuild the text field to highlight the newly selected match
choreographer.textController.setSystemText(
choreographer.textController.text,
EditTypeEnum.other,
);
choreographer.textController.selection = TextSelection.collapsed(
offset: baseOffset,
);
}
}
bool _shouldShowPaywall(BuildContext context) {
if (_subscriptionStatus == SubscriptionStatus.shouldShowPaywall) {
PaywallCard.show(context, ChoreoConstants.inputTransformTargetKey);
return true;
}
return false;
}
int _adjustOffsetForNormalization(int baseOffset) {
int adjustedOffset = baseOffset;
final corrections = choreographer.igcController.recentAutomaticCorrections;
for (final correction in corrections) {
final match = correction.updatedMatch.match;
if (match.offset < adjustedOffset && match.length > 0) {
adjustedOffset += (match.length - 1);
}
}
return adjustedOffset;
}
// Pangea#
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Autocomplete<Map<String, String?>>(
focusNode: focusNode,
textEditingController: controller,
optionsBuilder: getSuggestions,
// #Pangea
// fieldViewBuilder: (context, controller, focusNode, _) => TextField(
fieldViewBuilder: (context, _, focusNode, _) => ListenableBuilder(
listenable: choreographer,
builder: (context, _) {
return TextField(
// Pangea#
controller: controller,
focusNode: focusNode,
// #Pangea
// readOnly: readOnly,
// contextMenuBuilder: (c, e) =>
// markdownContextBuilder(c, e, controller),
contextMenuBuilder: (c, e) =>
markdownContextBuilder(c, e, controller!),
onTap: () => _onInputTap(context),
readOnly: choreographer.choreoMode == ChoreoModeEnum.it,
autocorrect: MatrixState.pangeaController.userController
.isToolEnabled(ToolSetting.enableAutocorrect),
// Pangea#
contentInsertionConfiguration: ContentInsertionConfiguration(
onContentInserted: (KeyboardInsertedContent content) {
final data = content.data;
if (data == null) return;
final file = MatrixFile(
mimeType: content.mimeType,
bytes: data,
name: content.uri.split('/').last,
);
room.sendFileEvent(file, shrinkImageMaxDimension: 1600);
},
),
minLines: minLines,
maxLines: maxLines,
keyboardType: keyboardType!,
textInputAction: textInputAction,
autofocus: autofocus!,
inputFormatters: [
// #Pangea
//LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()),
//setting max character count to 1000
//after max, nothing else can be typed
LengthLimitingTextInputFormatter(1000),
// Pangea#
],
onSubmitted: (text) {
// fix for library for now
// it sets the types for the callback incorrectly
onSubmitted!(text);
},
// #Pangea
// maxLength: AppSettings.textMessageMaxLength.value,
// decoration: decoration,
decoration: decoration.copyWith(
hint: StreamBuilder(
stream: MatrixState
.pangeaController
.userController
.languageStream
.stream,
builder: (context, _) => SizedBox(
height: 24,
child: ShrinkableText(
text: choreographer.itController.open.value
? L10n.of(context).buildTranslation
: _defaultHintText(context),
maxWidth: double.infinity,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).disabledColor,
),
),
),
),
),
// Pangea#
onChanged: (text) {
// fix for the library for now
// it sets the types for the callback incorrectly
onChanged!(text);
},
textCapitalization: TextCapitalization.sentences,
);
},
),
optionsViewBuilder: (c, onSelected, s) {
final suggestions = s.toList();
return Material(
elevation: theme.appBarTheme.scrolledUnderElevation ?? 4,
shadowColor: theme.appBarTheme.shadowColor,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
clipBehavior: Clip.hardEdge,
child: ListView.builder(
shrinkWrap: true,
itemCount: suggestions.length,
itemBuilder: (context, i) => buildSuggestion(
c,
suggestions[i],
onSelected,
Matrix.of(context).client,
),
),
);
},
displayStringForOption: insertSuggestion,
optionsViewOpenDirection: OptionsViewOpenDirection.up,
);
}
}