Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 29 additions & 31 deletions lib/src/modules/room/ui/chunk_visualization_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:soliplex_logging/soliplex_logging.dart';

import '../../../design/design.dart';
import '../../../shared/failed_image.dart';
import 'pager_dots.dart';

final _logger =
LogManager.instance.getLogger('soliplex_frontend.chunk_visualization');
Expand Down Expand Up @@ -110,15 +111,25 @@ class ChunkVisualizationPage extends StatefulWidget {
);

if (useDialog) {
return showDialog<void>(
// Zero-duration transition — the default fade adds a visible
// flash when the user is expecting an immediate jump from the
// citation to its rendered pages.
return showGeneralDialog<void>(
context: context,
barrierDismissible: true,
builder: (_) => child,
barrierLabel:
MaterialLocalizations.of(context).modalBarrierDismissLabel,
transitionDuration: Duration.zero,
pageBuilder: (_, __, ___) => child,
);
}

return Navigator.of(context).push<void>(
MaterialPageRoute(builder: (_) => child),
PageRouteBuilder<void>(
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
pageBuilder: (_, __, ___) => child,
),
);
}

Expand Down Expand Up @@ -154,10 +165,11 @@ class _ChunkVisualizationPageState extends State<ChunkVisualizationPage> {
await widget.api.getChunkVisualization(widget.roomId, widget.chunkId);
return viz.imagesBase64.map(_decodePageImage).toList();
} catch (error, stack) {
_logger.warning(
_logger.error(
'chunk visualization fetch failed',
error: error,
stackTrace: stack,
attributes: {'errorType': error.runtimeType.toString()},
);
rethrow;
}
Expand Down Expand Up @@ -246,7 +258,10 @@ class _ChunkVisualizationPageState extends State<ChunkVisualizationPage> {
title: Text(widget.documentTitle),
titleTextStyle: Theme.of(context).textTheme.titleMedium,
),
body: _buildContent(context),
// Without SafeArea, the system gesture inset (iOS home indicator,
// Android nav bar) sits on top of the dots row at the bottom of
// the page strip — making them effectively untappable.
body: SafeArea(top: false, child: _buildContent(context)),
);
}

Expand Down Expand Up @@ -290,14 +305,6 @@ class _ChunkVisualizationPageState extends State<ChunkVisualizationPage> {
'Failed to load visualization',
style: theme.textTheme.bodyMedium,
),
const SizedBox(height: SoliplexSpacing.s1),
Text(
error.toString(),
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
const SizedBox(height: SoliplexSpacing.s4),
FilledButton.icon(
onPressed: _retry,
Expand Down Expand Up @@ -417,24 +424,15 @@ class _ChunkVisualizationPageState extends State<ChunkVisualizationPage> {
),
if (pages.length > 1) ...[
const SizedBox(height: SoliplexSpacing.s1),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(pages.length, (index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: SoliplexSpacing.s1,
),
child: CircleAvatar(
radius: 4,
backgroundColor: index == _currentPage
? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurfaceVariant
.withValues(alpha: 0.3),
),
);
}),
PagerDots(
itemCount: pages.length,
currentIndex: _currentPage,
onGoTo: (index) => _pageController.animateToPage(
index,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
),
labelForIndex: (i) => 'Page ${i + 1}',
),
],
],
Expand Down
29 changes: 13 additions & 16 deletions lib/src/modules/room/ui/citations_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import 'package:flutter/material.dart';
import 'package:soliplex_agent/soliplex_agent.dart' hide State;
import 'package:url_launcher/url_launcher.dart';

import '../../../design/design.dart';
import '../../../shared/copy_button.dart';
import '../../../shared/preview_icon_button.dart';
import 'markdown/flutter_markdown_plus_renderer.dart';
import '../../../design/design.dart';

class CitationsSection extends StatefulWidget {
const CitationsSection({
Expand Down Expand Up @@ -187,6 +188,17 @@ class _SourceReferenceRow extends StatelessWidget {
),
),
),
if (sourceReference.isPdf && onShowChunkVisualization != null)
SizedBox(
width: 32,
height: 32,
child: Center(
child: PreviewIconButton(
onTap: () => onShowChunkVisualization!(sourceReference),
tooltip: 'View source PDF',
),
),
),
SizedBox(
width: 32,
height: 32,
Expand Down Expand Up @@ -267,21 +279,6 @@ class _SourceReferenceRow extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
),
if (sourceReference.isPdf && onShowChunkVisualization != null)
Padding(
padding: const EdgeInsets.only(top: SoliplexSpacing.s1),
child: TextButton.icon(
onPressed: () => onShowChunkVisualization!(sourceReference),
icon: const Icon(Icons.picture_as_pdf, size: 16),
label: const Text('View in PDF'),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: SoliplexSpacing.s2),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:flutter_markdown_plus_latex/flutter_markdown_plus_latex.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:soliplex_logging/soliplex_logging.dart';

import '../../../../shared/failed_image.dart';
Expand Down Expand Up @@ -51,6 +52,7 @@ class FlutterMarkdownPlusRenderer extends MarkdownRenderer {
styleSheet: markdownTheme?.toMarkdownStyleSheet(
codeFontStyle: monoStyle,
),
extensionSet: md.ExtensionSet.gitHubFlavored,
blockSyntaxes: [LatexBlockSyntax()],
inlineSyntaxes: [LatexInlineSyntax()],
onTapLink: onLinkTap == null
Expand Down
65 changes: 65 additions & 0 deletions lib/src/modules/room/ui/pager_dots.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';

import '../../../design/design.dart';

/// Tappable dots indicator for a `PageView`. Each dot maps to one
/// item; tapping a dot calls [onGoTo] with that index.
///
/// Hidden entirely for single-item lists and for lists above
/// [maxVisible] — past that threshold the dots stop being a useful
/// affordance and the consumer should rely on other navigation
/// (chevrons, "N / M" text, swipe).
class PagerDots extends StatelessWidget {
const PagerDots({
super.key,
required this.itemCount,
required this.currentIndex,
required this.onGoTo,
this.maxVisible = 12,
this.labelForIndex,
}) : assert(itemCount >= 0, 'itemCount must be non-negative'),
assert(maxVisible > 0, 'maxVisible must be positive'),
assert(
itemCount == 0 || (currentIndex >= 0 && currentIndex < itemCount),
'currentIndex out of range for itemCount',
);

final int itemCount;
final int currentIndex;
final ValueChanged<int> onGoTo;
final int maxVisible;

/// Optional per-dot tooltip text (e.g. filename, page label). When
/// null the dots render without tooltips.
final String Function(int index)? labelForIndex;

@override
Widget build(BuildContext context) {
if (itemCount <= 1 || itemCount > maxVisible) {
return const SizedBox.shrink();
}
final theme = Theme.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: List.generate(itemCount, (index) {
final dot = InkResponse(
onTap: () => onGoTo(index),
radius: 16,
child: Padding(
padding: const EdgeInsets.all(SoliplexSpacing.s1),
child: CircleAvatar(
radius: 4,
backgroundColor: index == currentIndex
? theme.colorScheme.primary
: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.3),
),
),
);
final label = labelForIndex?.call(index);
if (label == null) return dot;
return Tooltip(message: label, child: dot);
}),
);
}
}
Loading
Loading