fluffychat/lib/pangea/analytics_practice/grammar_error_example_widget.dart
ggurdin 117a03089e
5720 vocab practice should have feedback flag (#5761)
* chore: split up analytics activity page widgets into separate files

* started analytics practice refactor

* refactor how UI updates are triggered in analytics practice page

* some fixes
2026-02-20 13:25:21 -05:00

138 lines
4.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
class GrammarErrorExampleWidget extends StatelessWidget {
final GrammarErrorPracticeActivityModel activity;
final bool showTranslation;
const GrammarErrorExampleWidget({
super.key,
required this.activity,
required this.showTranslation,
});
@override
Widget build(BuildContext context) {
final text = activity.text;
final errorOffset = activity.errorOffset;
final errorLength = activity.errorLength;
const maxContextChars = 50;
final chars = text.characters;
final totalLength = chars.length;
// ---------- BEFORE ----------
int beforeStart = 0;
bool trimmedBefore = false;
if (errorOffset > maxContextChars) {
int desiredStart = errorOffset - maxContextChars;
// Snap left to nearest whitespace to avoid cutting words
while (desiredStart > 0 && chars.elementAt(desiredStart) != ' ') {
desiredStart--;
}
beforeStart = desiredStart;
trimmedBefore = true;
}
final before = chars
.skip(beforeStart)
.take(errorOffset - beforeStart)
.toString();
// ---------- AFTER ----------
int afterEnd = totalLength;
bool trimmedAfter = false;
final errorEnd = errorOffset + errorLength;
final afterChars = totalLength - errorEnd;
if (afterChars > maxContextChars) {
int desiredEnd = errorEnd + maxContextChars;
// Snap right to nearest whitespace
while (desiredEnd < totalLength && chars.elementAt(desiredEnd) != ' ') {
desiredEnd++;
}
afterEnd = desiredEnd;
trimmedAfter = true;
}
final after = chars.skip(errorEnd).take(afterEnd - errorEnd).toString();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Color.alphaBlend(
Colors.white.withAlpha(180),
ThemeData.dark().colorScheme.primary,
),
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RichText(
text: TextSpan(
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimaryFixed,
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
children: [
if (trimmedBefore) const TextSpan(text: ''),
if (before.isNotEmpty) TextSpan(text: before),
WidgetSpan(
child: Container(
height: 4.0,
width: (errorLength * 8).toDouble(),
padding: const EdgeInsets.only(bottom: 2.0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
),
),
if (after.isNotEmpty) TextSpan(text: after),
if (trimmedAfter) const TextSpan(text: ''),
],
),
),
AnimatedSize(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
alignment: Alignment.topCenter,
child: showTranslation
? Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
Text(
activity.translation,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimaryFixed,
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
fontStyle: FontStyle.italic,
),
textAlign: TextAlign.center,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
)
: const SizedBox.shrink(),
),
],
),
);
}
}