part of '../channel_detail_page.dart'; double _scaledTextHeight(BuildContext context, TextStyle style) { final scaledFontSize = MediaQuery.textScalerOf( context, ).scale(style.fontSize ?? 0); return scaledFontSize * (style.height ?? 1); } double _dmAppBarTitleContentHeight(BuildContext context) { const titleStyle = channelTitleTextStyle; final presenceStyle = context.textTheme.bodySmall; if (presenceStyle == null) { return 30; } final textHeight = _scaledTextHeight(context, titleStyle) + _scaledTextHeight(context, presenceStyle); return textHeight > 30 ? textHeight : 30; } class _MembersButton extends ConsumerWidget { final String channelId; final Channel channel; final String? currentPubkey; const _MembersButton({ required this.channelId, required this.channel, required this.currentPubkey, }); @override Widget build(BuildContext context, WidgetRef ref) { final hasWorkingBot = ref .watch(workingBotPubkeysProvider(channelId)) .isNotEmpty; return IconButton( color: context.colors.primary, onPressed: () { showBuzzModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, builder: (_) => MembersSheet(channel: channel, currentPubkey: currentPubkey), ); }, tooltip: context.l10n.viewMembers, icon: Stack( clipBehavior: Clip.none, children: [ const Icon(LucideIcons.users, size: 22), if (hasWorkingBot) Positioned( top: -2, right: -2, child: Container( width: 8, height: 8, decoration: BoxDecoration( color: context.appColors.success, shape: BoxShape.circle, border: Border.all(color: context.colors.surface, width: 1.5), ), ), ), ], ), ); } } class _DmAppBarTitle extends ConsumerWidget { final Channel channel; final String? currentPubkey; const _DmAppBarTitle({required this.channel, required this.currentPubkey}); @override Widget build(BuildContext context, WidgetRef ref) { final profiles = ref.watch(userCacheProvider); final presenceMap = ref.watch(presenceCacheProvider); final normalizedCurrent = currentPubkey?.toLowerCase(); String? otherPubkey; for (final pk in channel.participantPubkeys) { if (pk.toLowerCase() != normalizedCurrent) { otherPubkey = pk.toLowerCase(); break; } } final profile = otherPubkey != null ? profiles[otherPubkey] : null; if (otherPubkey != null) { if (profile == null) { ref.read(userCacheProvider.notifier).preload([otherPubkey]); } ref.read(presenceCacheProvider.notifier).track([otherPubkey]); } final avatarUrl = profile?.avatarUrl; final initial = profile?.initial ?? (channel.participants.isNotEmpty ? channel.participants.first[0].toUpperCase() : '?'); final presence = otherPubkey != null ? (presenceMap[otherPubkey] ?? 'offline') : 'offline'; final presenceLabel = switch (presence) { 'online' => context.l10n.online, 'away' => context.l10n.away, _ => context.l10n.offline, }; return Row( children: [ SizedBox( width: 30, height: 30, child: Stack( clipBehavior: Clip.none, children: [ AvatarImage( imageUrl: avatarUrl, radius: 14, backgroundColor: context.colors.primaryContainer, fallback: Text( initial, style: context.textTheme.labelSmall?.copyWith( color: context.colors.onPrimaryContainer, fontWeight: FontWeight.w600, ), ), ), Positioned( right: -1, bottom: -1, child: Container( width: 10, height: 10, decoration: BoxDecoration( color: switch (presence) { 'online' => context.appColors.success, 'away' => context.appColors.warning, _ => context.colors.outline, }, shape: BoxShape.circle, border: Border.all( color: context.colors.surface, width: 1.5, ), ), ), ), ], ), ), const SizedBox(width: Grid.xxs), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: Text( resolveDmChannelDisplayLabel( channel, currentPubkey: currentPubkey, ), maxLines: 1, overflow: TextOverflow.ellipsis, style: channelTitleTextStyle, ), ), if (channel.isEphemeral) ...[ const SizedBox(width: Grid.quarter), _HeaderEphemeralBadge(channel: channel), ], ], ), Text( presenceLabel, style: context.textTheme.bodySmall?.copyWith( color: context.colors.onSurfaceVariant, ), ), ], ), ), ], ); } }