Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SuperTextField] [iOS] Overriding imeController.performAction() does not behave as expected #2004

Closed
miguelcmedeiros opened this issue May 16, 2024 · 6 comments · Fixed by #2012
Assignees
Labels
area_supertextfield Pertains to SuperTextField bounty_junior f:superlist Funded by Superlist time:2 type_bug Something isn't working

Comments

@miguelcmedeiros
Copy link
Collaborator

Package Version
Using main on e20eb1b.

To Reproduce
Steps to reproduce the behavior:

  1. Run the iOS SuperTextField with the code below
  2. Write "Run tom"
  3. Press the return button

Minimal Reproduction Code

Minimal, Runnable Code Sample
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:super_editor/super_editor.dart';
import 'package:super_text_layout/super_text_layout.dart';

import '../_mobile_textfield_demo.dart';

/// Demo of [SuperIOSTextField].
class SuperIOSTextFieldDemo extends StatefulWidget {
  @override
  State<SuperIOSTextFieldDemo> createState() => _SuperIOSTextFieldDemoState();
}

class _SuperIOSTextFieldDemoState extends State<SuperIOSTextFieldDemo> {
  final String _tapRegionGroupId = "iOS";
  final _controller = _TextController();

  late final FocusNode _focusNode;

  @override
  void initState() {
    super.initState();
    initLoggers(Level.FINE, {
      // iosTextFieldLog,
      // imeTextFieldLog,
    });

    _focusNode = FocusNode();
  }

  @override
  void dispose() {
    _focusNode.dispose();

    deactivateLoggers({iosTextFieldLog, imeTextFieldLog});
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MobileSuperTextFieldDemo(
      initialText: AttributedText(
        'This is a custom textfield implementation called SuperIOSTextfield. It is super long so that we can mess with scrolling. This drags it out even further so that we can get multiline scrolling, too. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin tempor sapien est, in eleifend purus rhoncus fringilla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla varius libero lorem, eget tincidunt ante porta accumsan. Morbi quis ante at nunc molestie ullamcorper.',
      ),
      textFieldFocusNode: _focusNode,
      textFieldTapRegionGroupId: _tapRegionGroupId,
      createTextField: _buildTextField,
    );
  }

  Widget _buildTextField(MobileTextFieldDemoConfig config) {
    final genericTextStyle = config.styleBuilder({});
    final lineHeight =
        genericTextStyle.fontSize! * (genericTextStyle.height ?? 1.0);

    return DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(color: Colors.black),
      ),
      child: SuperIOSTextField(
        textController: _controller,
        focusNode: _focusNode,
        tapRegionGroupId: _tapRegionGroupId,
        textStyleBuilder: config.styleBuilder,
        padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 24),
        hintBehavior: HintBehavior.displayHintUntilTextEntered,
        hintBuilder: StyledHintBuilder(
          hintText: AttributedText("Enter text"),
          hintTextStyleBuilder: (attributions) {
            return config
                .styleBuilder(attributions)
                .copyWith(color: Colors.grey);
          },
        ).build,
        selectionColor: Colors.blue.withOpacity(0.4),
        caretStyle: const CaretStyle(color: Colors.blue),
        blinkTimingMode: BlinkTimingMode.timer,
        handlesColor: Colors.blue,
        minLines: config.minLines,
        maxLines: config.maxLines,
        lineHeight: lineHeight,
        textInputAction: TextInputAction.done,
        showDebugPaint: config.showDebugPaint,
      ),
    );
  }
}

class _TextController extends ImeAttributedTextEditingController {
  @override
  void performAction(TextInputAction action) {
    // super.performAction(action);
  }
}

Actual behavior
With the text mentioned in the reproduction steps ("Run tom"), a suggestion ("Tom") is applied.

Expected behavior
Nothing should happen since _TextController.performAction() does not call super.performAction(action).

Platform
Only on iOS

Flutter version
Master channel with commit d5bcd587aa72570faf12875bdfb326ab51eba961.

Screenshots

Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-05-16.at.17.52.22.mp4

Additional context
With SuperEditor we can extend DeltaTextInputClientDecorator to override the behaviour of performAction() and there we don't have the problem where the IME suggestion is applied when super.performAction() is not called, as expected.

@miguelcmedeiros miguelcmedeiros added type_bug Something isn't working f:superlist Funded by Superlist labels May 16, 2024
@miguelcmedeiros
Copy link
Collaborator Author

@matthew-carroll
Copy link
Contributor

I've tried this on my end. For both super text field and super editor, I'm seeing the suggested "tom" -> "Tom" applied in both cases.

Unfortunately, Miguel's machine is currently hitting an error when running Super Editor. We don't know why.

@angelosilvestre can you please try these repro steps for both super text field and super editor and report back whether "tom" becomes "Tom" in both cases, or just in super text field?

@matthew-carroll
Copy link
Contributor

I noticed a different behavior on my end when I started using an iOS simulator on 17.5 instead of 17.0. I think Miguel said he was on 17.4. So it looks like some combination of iOS versions and Flutter may have broken us.

I have the following problems on 17.5...

Super Editor error when typing "run tom" and pressing "return":

======== Exception caught by services library ======================================================
The following _Exception was thrown during method call TextInputClient.updateEditingStateWithDeltas:
Exception: Couldn't map an IME position to a document position. 
TextEditingValue: '. '
IME position: TextPosition(offset: 6, affinity: TextAffinity.downstream)

When the exception was thrown, this was the stack: 
#0      DocumentImeSerializer._imeToDocumentPosition (package:super_editor/src/default_editor/document_ime/document_serialization.dart:309:5)
#1      DocumentImeSerializer.imeToDocumentSelection (package:super_editor/src/default_editor/document_ime/document_serialization.dart:166:18)
#2      TextDeltasDocumentEditor.replace (package:super_editor/src/default_editor/document_ime/document_delta_editing.dart:360:49)
#3      TextDeltasDocumentEditor._applyReplacement (package:super_editor/src/default_editor/document_ime/document_delta_editing.dart:220:5)
#4      TextDeltasDocumentEditor.applyDeltas (package:super_editor/src/default_editor/document_ime/document_delta_editing.dart:83:9)
#5      DocumentImeInputClient.updateEditingValueWithDeltas (package:super_editor/src/default_editor/document_ime/document_ime_communication.dart:219:30)
#6      DeltaTextInputClientDecorator.updateEditingValueWithDeltas (package:super_editor/src/default_editor/document_ime/ime_decoration.dart:129:14)
#7      TextInput._handleTextInputInvocation (package:flutter/src/services/text_input.dart:1904:63)
#8      TextInput._loudlyHandleTextInputInvocation (package:flutter/src/services/text_input.dart:1795:20)
#9      MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:571:55)
#10     MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:564:34)
#11     _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:597:35)
#12     _invoke2 (dart:ui/hooks.dart:344:13)
#13     _ChannelCallbackRecord.invoke (dart:ui/channel_buffers.dart:45:5)
#14     _Channel.push (dart:ui/channel_buffers.dart:135:31)
#15     ChannelBuffers.push (dart:ui/channel_buffers.dart:343:17)
#16     PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:750:22)
#17     _dispatchPlatformMessage (dart:ui/hooks.dart:257:31)
call: MethodCall(TextInputClient.updateEditingStateWithDeltas, [3, {deltas: [{selectionBase: 9, oldText: . Run tom, selectionAffinity: TextAffinity.downstream, deltaEnd: 9, deltaText: Tom, deltaStart: 6, composingExtent: -1, selectionIsDirectional: false, selectionExtent: 9, composingBase: -1}]}])
====================================================================================================

Super Text Field problem in the demo app - we lose all content when pressing "done".

Screen.Recording.2024-05-16.at.12.51.44.PM.mov

I'm gonna try to figure out the exact iOS simulator versions where this happens. Then @angelosilvestre I'll probably file two separate tickets and assign them to you. These should be done ASAP.

@matthew-carroll
Copy link
Contributor

I've tested 17.5, 17.4, 17.2, and both of these issues are present on all those versions. It looks like XCode doesn't even consider 17.0 to be supported any more, so we can probably consider these bugs to be present in all versions of iOS. I'll file new tickets for each issue.

@angelosilvestre
Copy link
Collaborator

@matthew-carroll In SuperTextField with iOS 17.5 I'm seeing the same behavior that Miguel reported and the same exception you reported for SuperEditor.

@matthew-carroll
Copy link
Contributor

I filed the two issues for @angelosilvestre - @miguelcmedeiros I think we should hold off on this one until Angelo is at least able to fix the super editor problem. At that point we can compare SuperEditor behavior to SuperTextField behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area_supertextfield Pertains to SuperTextField bounty_junior f:superlist Funded by Superlist time:2 type_bug Something isn't working
Projects
None yet
3 participants