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

Fixed popup overflow by suggestions #35

Merged
merged 12 commits into from
May 29, 2023
11 changes: 6 additions & 5 deletions lib/domain/typedefs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import 'package:languagetool_textfield/domain/mistake.dart';
import 'package:languagetool_textfield/utils/popup_overlay_renderer.dart';

/// Callback used to build popup body
typedef MistakeBuilderCallback = Widget Function(
PopupOverlayRenderer popupRenderer,
Mistake mistake,
ColoredTextEditingController controller,
);
typedef MistakeBuilderCallback = Widget Function({
required PopupOverlayRenderer popupRenderer,
required Mistake mistake,
required ColoredTextEditingController controller,
required Offset mistakePosition,
});

/// Function called after mistake was clicked
typedef ShowPopupCallback = void Function(
Expand Down
134 changes: 90 additions & 44 deletions lib/utils/mistake_popup.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:languagetool_textfield/domain/typedefs.dart';
import 'package:languagetool_textfield/languagetool_textfield.dart';
Expand All @@ -21,16 +23,18 @@ class MistakePopup {
Offset popupPosition,
ColoredTextEditingController controller,
) {
final MistakeBuilderCallback builder =
mistakeBuilder ?? LanguageToolMistakePopup.new;

popupRenderer.render(
context,
position: popupPosition,
popupBuilder: (context) =>
mistakeBuilder?.call(popupRenderer, mistake, controller) ??
LanguageToolMistakePopup(
popupRenderer: popupRenderer,
mistake: mistake,
controller: controller,
),
popupBuilder: (context) => builder.call(
popupRenderer: popupRenderer,
mistake: mistake,
controller: controller,
mistakePosition: popupPosition,
),
);
}
}
Expand All @@ -43,6 +47,8 @@ class LanguageToolMistakePopup extends StatelessWidget {
required this.popupRenderer,
required this.mistake,
required this.controller,
required this.mistakePosition,
this.maxHeight = double.infinity,
});

/// Renderer used to display this window.
Expand All @@ -54,60 +60,100 @@ class LanguageToolMistakePopup extends StatelessWidget {
/// Controller of the text where mistake was found
final ColoredTextEditingController controller;

/// An on-screen position of the mistake
final Offset mistakePosition;

/// A maximum height of the popup.
/// If infinity, the popup will use all the available height between the
/// [mistakePosition] and the furthest border of the layout constraints.
final double maxHeight;

@override
Widget build(BuildContext context) {
const _borderRadius = 10.0;
const _mistakeNameFontSize = 13.0;
const _mistakeMessageFontSize = 15.0;
const _replacementButtonsSpacing = 10.0;

const padding = 10.0;
const paddingCount = 4;
const paddingSum = padding * paddingCount;

final availableSpace = _calculateAvailableSpace(
context,
paddings: paddingSum,
);

return Container(
padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(padding),
decoration: BoxDecoration(
boxShadow: const [BoxShadow(color: Colors.grey, blurRadius: 20)],
color: Colors.white,
borderRadius: BorderRadius.circular(_borderRadius),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// mistake type
Text(
mistake.type.name,
style: TextStyle(
color: Colors.grey.shade700,
fontSize: _mistakeNameFontSize,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 10),
constraints: BoxConstraints(maxHeight: availableSpace),
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// mistake type
Text(
mistake.type.name,
style: TextStyle(
color: Colors.grey.shade700,
fontSize: _mistakeNameFontSize,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: padding),

// mistake message
Text(
mistake.message,
style: const TextStyle(fontSize: _mistakeMessageFontSize),
// mistake message
Text(
mistake.message,
style: const TextStyle(fontSize: _mistakeMessageFontSize),
),
const SizedBox(height: padding),
],
),
),
const SizedBox(height: 10),

// replacements
Wrap(
spacing: _replacementButtonsSpacing,
direction: Axis.horizontal,
children: mistake.replacements
.map(
(replacement) => ElevatedButton(
onPressed: () {
controller.replaceMistake(mistake, replacement);
popupRenderer.dismiss();
},
child: Text(replacement),
),
)
.toList(growable: false),
SliverList.builder(
itemCount: mistake.replacements.length,
itemBuilder: _suggestionsListBuilder,
),
],
),
);
}

Widget _suggestionsListBuilder(BuildContext _, int index) {
const replacementButtonsSpacing = 10.0;

final replacement = mistake.replacements[index];

return Padding(
padding: const EdgeInsets.all(replacementButtonsSpacing / 2),
child: ElevatedButton(
onPressed: () {
controller.replaceMistake(mistake, replacement);
popupRenderer.dismiss();
},
child: Text(replacement),
),
);
}
mitryp marked this conversation as resolved.
Show resolved Hide resolved

double _calculateAvailableSpace(
BuildContext context, {
required double paddings,
}) {
final mediaQuery = MediaQuery.of(context);

final availableSpaceBottom =
mediaQuery.size.height - mistakePosition.dy - paddings;
final availableSpaceTop = mistakePosition.dy - paddings;

return min(max(availableSpaceBottom, availableSpaceTop), maxHeight);
}
mitryp marked this conversation as resolved.
Show resolved Hide resolved
}