Skip to content
2 changes: 1 addition & 1 deletion mobile/lib/features/channels/channel_detail_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:math' show min;
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show ScrollDirection;
Expand Down Expand Up @@ -40,6 +39,7 @@ import 'compose_bar.dart';
import 'composer_dock_size_reporter.dart';
import 'date_formatters.dart';
import 'day_divider.dart';
import 'jump_to_latest_button.dart';
import 'dm_channel_labels.dart';
import 'ephemeral_channel_display.dart';
import 'members_sheet.dart';
Expand Down
64 changes: 2 additions & 62 deletions mobile/lib/features/channels/channel_detail_page/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,9 @@ class _MessageList extends HookConsumerWidget {
right: 0,
bottom: composerBottomInset + Grid.xs,
child: Center(
child: _JumpToLatestButton(
child: JumpToLatestButton(
key: const ValueKey('channel-jump-to-latest'),
surfaceKey: const ValueKey('channel-jump-to-latest-surface'),
onPressed: scrollToLatest,
),
),
Expand All @@ -561,67 +562,6 @@ class _MessageList extends HookConsumerWidget {
}
}

class _JumpToLatestButton extends StatelessWidget {
final VoidCallback onPressed;

const _JumpToLatestButton({required this.onPressed, super.key});

@override
Widget build(BuildContext context) {
final borderRadius = BorderRadius.circular(Radii.full);
return Semantics(
button: true,
child: ClipRRect(
borderRadius: borderRadius,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(
key: const ValueKey('channel-jump-to-latest-surface'),
decoration: BoxDecoration(
color: context.colors.surface.withValues(alpha: 0.5),
borderRadius: borderRadius,
border: Border.all(
color: Colors.black.withValues(alpha: 0.04),
width: 1,
),
),
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: onPressed,
borderRadius: borderRadius,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Grid.gutter,
vertical: Grid.xxs,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LucideIcons.arrowDown,
size: 16,
color: context.colors.onSurface,
),
const SizedBox(width: Grid.half),
Text(
'Latest',
style: context.textTheme.labelLarge?.copyWith(
color: context.colors.onSurface,
),
),
],
),
),
),
),
),
),
),
);
}
}

class _ChannelLatestMetricsObserver with WidgetsBindingObserver {
final VoidCallback onMetricsChanged;

Expand Down
78 changes: 78 additions & 0 deletions mobile/lib/features/channels/jump_to_latest_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';

import '../../shared/theme/theme.dart';

/// Frosted "Latest" pill shown when the reader has scrolled away from the
/// newest message.
///
/// Shared by the channel timeline and the thread detail page so both surfaces
/// stay visually identical. [surfaceKey] keys the blurred container so each
/// surface can be targeted independently in widget tests.
class JumpToLatestButton extends StatelessWidget {
final VoidCallback onPressed;
final Key? surfaceKey;

const JumpToLatestButton({
required this.onPressed,
this.surfaceKey,
super.key,
});

@override
Widget build(BuildContext context) {
final borderRadius = BorderRadius.circular(Radii.full);
return Semantics(
button: true,
child: ClipRRect(
borderRadius: borderRadius,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(
key: surfaceKey,
decoration: BoxDecoration(
color: context.colors.surface.withValues(alpha: 0.5),
borderRadius: borderRadius,
border: Border.all(
color: Colors.black.withValues(alpha: 0.04),
width: 1,
),
),
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: onPressed,
borderRadius: borderRadius,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Grid.gutter,
vertical: Grid.xxs,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LucideIcons.arrowDown,
size: 16,
color: context.colors.onSurface,
),
const SizedBox(width: Grid.half),
Text(
'Latest',
style: context.textTheme.labelLarge?.copyWith(
color: context.colors.onSurface,
),
),
],
),
),
),
),
),
),
),
);
}
}
Loading
Loading