Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
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 @@ -42,6 +41,7 @@ import 'date_formatters.dart';
import 'day_divider.dart';
import 'dm_channel_labels.dart';
import 'ephemeral_channel_display.dart';
import 'jump_to_latest_button.dart';
import 'members_sheet.dart';
import 'message_actions.dart';
import 'message_long_press_region.dart';
Expand Down
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
80 changes: 80 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,80 @@
import 'dart:ui';

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

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

/// Frosted pill that jumps a message timeline back to its newest entry.
///
/// Shared by the channel timeline and the thread detail page. Callers show
/// it only while the newest message is scrolled out of view and hide it once
/// the tail is visible again.
class JumpToLatestButton extends StatelessWidget {
final VoidCallback onPressed;

/// Optional key for the frosted surface container, kept separate from the
/// widget [key] so tests can target the surface decoration directly.
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