flutter-quill icon indicating copy to clipboard operation
flutter-quill copied to clipboard

Mobile [Web] - Editor loses focus when toolbar buttons are pressed

Open azambuilds opened this issue 2 years ago • 4 comments

My issue is about [Web]

On mobile web, specifically chrome on IOS, clicking on a toolbar button will hide the keyboard if it is open, and open the keyboard if it is closed. It creates an annoying interaction where I click on the editor and it pops up the keyboard, and then I click on a toolbar button and suddenly the keyboard closes. It's even more annoying with the bold button, because clicking that button will hide the keyboard and if the cursor is on a new line it resets the bold attribute.

flutter_quill version: ^6.1.12, but I've had this problem on earlier version too.

[√] I have tried running example directory successfully before creating an issue here.

Flutter: 3.3.10 • channel stable Flutter doctor: [√] Android Studio (version 2021.3) [!] Android Studio (version 4.2) X Unable to determine bundled Java version. [√] VS Code (version 1.70.0) [√] Connected device (3 available) [√] HTTP Host Availability

Demonstration https://user-images.githubusercontent.com/26692217/213832013-68919fce-1cb4-4e61-a1f4-f0c9d250f8f6.mov

My page code:


class EditProjectDescriptionPage extends ConsumerStatefulWidget {
  const EditProjectDescriptionPage({Key? key}) : super(key: key);

  @override
  ConsumerState<EditProjectDescriptionPage> createState() =>
      _EditProjectDescriptionPageState();
}

class _EditProjectDescriptionPageState
    extends ConsumerState<EditProjectDescriptionPage> {
  late QuillController? _controller;
  final FocusNode _focusNode = FocusNode();

  final int _limit = 3000;

  @override
  void initState() {
    var initialState = ref.read(projectManagerStateNotifierProvider);
    _loadDoc(
        initialState.projectWithWorkingChanges!.projectDescriptionDeltaJson);
    _controller!.document.changes.listen(_onDocumentChange);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    if (_controller == null) {
      return WillPopScope(
        onWillPop: () async => !Navigator.of(context).userGestureInProgress,
        child: Scaffold(
            appBar: AppBar(
              title: Text("Edit Project Description"),
            ),
            body: Center(child: CircularProgressIndicator())),
      );
    }

    return WillPopScope(
      onWillPop: () async => !Navigator.of(context).userGestureInProgress,
      child: Scaffold(
          appBar: AppBar(
            title: Text("Edit Project Description"),
            actions: [
              IconButton(
                  onPressed: () {
                    _saveDoc();
                  },
                  icon: Icon(Icons.save))
            ],
          ),
          body: _buildWelcomeEditor(context)),
    );
  }
  //// So there is an issue with selection, when on mobile
  //// web you select, it creates 2 selection highlights, one in the toolbar's
  //// style, and one in ios default style I guess? Well the one in the toolbar
  //// works fine. I need to hide the ios one
  //// ALSO, clicking a toolbar option (e.g. bold, italic)
  //// causes the keyboard to go away. I'm guessing it's to do with focus...
  Widget _buildWelcomeEditor(BuildContext context) {
    Widget quillEditor = MouseRegion(
      // cursor: SystemMouseCursors.text,
      child: QuillEditor(
        controller: _controller!,
        scrollController: ScrollController(),
        scrollable: true,
        focusNode: _focusNode,
        autoFocus: true,
        readOnly: false,
        placeholder: 'Project Description',
        expands: false,
        padding: EdgeInsets.zero,
        enableSelectionToolbar: false,
      ),
    );

    var toolbar = QuillToolbar.basic(
      afterButtonPressed: _focusNode.requestFocus,
      controller: _controller!,
      showFontFamily: false,
      showRedo: false,
      showUndo: false,
      showListBullets: true,
      showListCheck: false,
      showFontSize: false,
      showHeaderStyle: true,
      showClearFormat: false,
      showCodeBlock: false,
      showColorButton: false,
      showBackgroundColorButton: false,
      showInlineCode: false,
      showStrikeThrough: false,
      showQuote: false,
      showAlignmentButtons: false,
      showCenterAlignment: false,
      showDirection: false,
      showIndent: false,
      showSearchButton: false,
      showLink: false,
      showJustifyAlignment: false,
      showRightAlignment: false,
      showLeftAlignment: false,
      multiRowsDisplay: false,
    );

    return SafeArea(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Expanded(
            flex: 15,
            child: Container(
              color: Colors.white,
              padding: const EdgeInsets.only(left: 16, right: 16),
              child: quillEditor,
            ),
          ),
          SizedBox(
              child: Container(
            padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
            child: toolbar,
          )),
        ],
      ),
    );
  }

  /// https://stackoverflow.com/questions/71294770/how-can-i-set-character-limit-in-flutter-quill-editor
  void _onDocumentChange(Tuple3<Delta, Delta, ChangeSource> tuple) {
    final documentLength = _controller!.document.length;
    if (documentLength > _limit) {
      final latestIndex = _limit - 1;
      _controller!.replaceText(
        latestIndex,
        documentLength - _limit,
        '',
        TextSelection.collapsed(offset: latestIndex),
      );
      ScaffoldMessenger.of(context).removeCurrentSnackBar();
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text("Character Limited Exceeded")));
    }
  }

  void _saveDoc() async {
    final _deltaJsonString =
        jsonEncode(_controller!.document.toDelta().toJson());
    final provider = ref.read(projectManagerStateNotifierProvider.notifier);
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text('Updating Project. Please Wait')));

    try {
      await provider.updateProjectDescription(jsonListString: _deltaJsonString);
      await provider.saveProject(null);
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('Project Updated!')));
    } catch (e) {
      ScaffoldMessenger.of(context).removeCurrentSnackBar();
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('Failed to update: $e')));
    }
  }

  void _loadDoc(String _json) {
    var _decodedJson = <dynamic>[""];

    var _doc = Document();
    try {
      _decodedJson = json.decode(_json);
      _doc = Document.fromJson(_decodedJson);
    } catch (e) {
      print(e.toString());
    }

    _controller = QuillController(
        document: _doc, selection: TextSelection.collapsed(offset: 0));
  }

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

azambuilds avatar Jan 21 '23 01:01 azambuilds

@Tameflame Did you fix this issue? I'm also getting same issue while using web build on mobile.

SC8885 avatar Feb 26 '24 17:02 SC8885

@EchoEllet does this issue persists?

CatHood0 avatar Sep 22 '24 08:09 CatHood0

I'm not sure, but there are similar issues related to keyboard focus on mobile, making the editor unusable. They might be fixed recently.

Keeping it open and will check it once we reach it.

EchoEllet avatar Sep 22 '24 12:09 EchoEllet

I too have this issue. On web (mobile or desktop), clicking down on the toolbar will remove focus from the editor and upon clicking up the focus is restored. On mobile, there is no focus change.

This is a bit of a problem because I'm wanting to only show the toolbar when the editor is in focus but when you go to click the toolbar it disappears because the editor loses focus.

Below is a minimal example where the print statement showcases the problem.

import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final QuillController _controller = QuillController.basic();
  late final FocusNode _focusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(() {
      print('Focus node has focus: ${_focusNode.hasFocus}');
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Column(
        children: [
          QuillSimpleToolbar(
            controller: _controller,
            configurations: const QuillSimpleToolbarConfigurations(),
          ),
          Expanded(
            child: QuillEditor.basic(
              focusNode: _focusNode,
              controller: _controller,
              configurations: const QuillEditorConfigurations(),
            ),
          )
        ],
      ),
    );
  }
}

Zambrella avatar Oct 08 '24 12:10 Zambrella