fix(assistant): chips show their text, and the field sits on the card
The chips above the assistant's field cut their text at 24 characters and scrolled sideways out of view, so they helped little. They now flow onto as many lines as they need (about three before they scroll), each showing as much of its text as its line holds, the whole of it on hovering or a long press. In a study session the button and its field now belong to the card: the field unfolds across the card's width, inside its edges, where it was anchored to the window's right edge and overhung the card on wide windows. The send button keeps clear of the field's ring, more so in the corner the chips' rounded box leaves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
96c2dc1be8
commit
8ab67b8a27
2 changed files with 92 additions and 84 deletions
|
|
@ -215,7 +215,11 @@ class _AssistantFieldState() extends State<AssistantField> {
|
|||
color: theme.colorScheme.surfaceContainerHigh,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
// The send button clear of the ring, more so in the corner the
|
||||
// chips' rounded box leaves.
|
||||
padding: offered.isEmpty
|
||||
? const EdgeInsets.fromLTRB(4, 0, 6, 0)
|
||||
: const EdgeInsets.fromLTRB(4, 0, 8, 8),
|
||||
child: offered.isEmpty
|
||||
? row
|
||||
: Column(
|
||||
|
|
@ -276,62 +280,74 @@ class const _TargetSwitch({
|
|||
}
|
||||
}
|
||||
|
||||
/// The parts that can go along, as chips in a row that scrolls sideways,
|
||||
/// each with the start of its text.
|
||||
/// The parts that can go along, as chips flowing onto as many lines as
|
||||
/// they need, each showing as much of its text as its line holds; the whole
|
||||
/// of it on hovering or a long press.
|
||||
class const _Parts({
|
||||
required final List<AssistantPart> parts,
|
||||
required final Set<AssistantPart> chosen,
|
||||
required final ValueChanged<AssistantPart> onToggle,
|
||||
}) extends StatelessWidget {
|
||||
/// How much of a part's text its chip shows.
|
||||
static const _preview = 24;
|
||||
/// How tall the chips grow before they scroll: about three lines.
|
||||
static const _maxHeight = 132.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
String preview(String text) {
|
||||
final line = text.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
return line.length <= _preview
|
||||
? line
|
||||
: '${line.substring(0, _preview - 1)}…';
|
||||
}
|
||||
|
||||
String line(String text) => text.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
|
||||
padding: const EdgeInsets.fromLTRB(8, 10, 8, 2),
|
||||
// At most about three lines of chips; more scroll.
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: _maxHeight),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final part in parts) ...[
|
||||
FilterChip(
|
||||
for (final part in parts)
|
||||
Tooltip(
|
||||
message: part.sent,
|
||||
waitDuration: const Duration(milliseconds: 400),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: constraints.maxWidth,
|
||||
),
|
||||
child: FilterChip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
label: Text(switch (part.kind) {
|
||||
label: Text(
|
||||
switch (part.kind) {
|
||||
AssistantPartKind.front => l10n.assistantLineFront(
|
||||
preview(part.text),
|
||||
line(part.text),
|
||||
),
|
||||
AssistantPartKind.back => l10n.assistantLineBack(
|
||||
preview(part.text),
|
||||
line(part.text),
|
||||
),
|
||||
AssistantPartKind.hint => l10n.assistantLineHint(
|
||||
part.number ?? 1,
|
||||
preview(part.text),
|
||||
line(part.text),
|
||||
),
|
||||
AssistantPartKind.explanation =>
|
||||
l10n.assistantLineExplanation(preview(part.text)),
|
||||
AssistantPartKind.studyset => l10n.assistantChipStudyset(
|
||||
preview(part.text),
|
||||
),
|
||||
l10n.assistantLineExplanation(line(part.text)),
|
||||
AssistantPartKind.studyset =>
|
||||
l10n.assistantChipStudyset(line(part.text)),
|
||||
AssistantPartKind.description =>
|
||||
l10n.assistantLineDescription(preview(part.text)),
|
||||
}),
|
||||
l10n.assistantLineDescription(line(part.text)),
|
||||
},
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
selected: chosen.contains(part),
|
||||
onSelected: (_) => onToggle(part),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
|
@ -331,8 +329,9 @@ class const _Showing(
|
|||
/// The studyset's title, for questions to Claude about the card.
|
||||
required final String title,
|
||||
}) extends StatelessWidget {
|
||||
/// The widest the field to ask Claude unfolds.
|
||||
static const _fieldMaxWidth = 640.0;
|
||||
/// How far inside the card's edges the assistant's button floats, and
|
||||
/// its field unfolds.
|
||||
static const _fieldInset = 8.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -621,48 +620,41 @@ class const _Showing(
|
|||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 1100,
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: 1100),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
16,
|
||||
12,
|
||||
16,
|
||||
0,
|
||||
),
|
||||
child: body,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Asking Claude about the card, in the card's lower
|
||||
// right corner, above the ratings. Unfolded, the field
|
||||
// spans the card, the keyboard pushing it up.
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
body,
|
||||
// Asking about the card, in the card's
|
||||
// lower right corner, above the ratings.
|
||||
// Unfolded, the field spans the card, the
|
||||
// keyboard pushing it up.
|
||||
Positioned(
|
||||
right: 28,
|
||||
bottom: 24,
|
||||
right: _fieldInset,
|
||||
bottom: _fieldInset + 16,
|
||||
child: AssistantButton(
|
||||
topic: AssistantTopicCard(
|
||||
studysetTitle: title,
|
||||
card: card.content,
|
||||
),
|
||||
small: true,
|
||||
// Within the card's sides.
|
||||
expandedWidth: math.min(
|
||||
MediaQuery.sizeOf(context).width - 56,
|
||||
_fieldMaxWidth,
|
||||
),
|
||||
expandedWidth:
|
||||
constraints.maxWidth -
|
||||
2 * _fieldInset,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (ratedBelow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
|
|
|
|||
Loading…
Reference in a new issue