diff --git a/apps/chanora_flutter/lib/main.dart b/apps/chanora_flutter/lib/main.dart index a35c50e..b1a51b4 100644 --- a/apps/chanora_flutter/lib/main.dart +++ b/apps/chanora_flutter/lib/main.dart @@ -823,10 +823,22 @@ class _BetaHomeState extends State<_BetaHome> { ), body: Padding( padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( + child: LayoutBuilder( + builder: (ctx, bodyConstraints) { + // Shared breakpoint with the inner snapshot LayoutBuilder + // below — when the UI is wide enough to split into two + // columns AND we are showing the snapshot, the + // not-production-ready banner is moved into the left + // column above the Voice Bar so it doesn't span the + // wider channel-tree area. In every other state (narrow, + // or idle / connecting at any width) the banner stays + // pinned to the top of the body. + const wideBreakpoint = 840.0; + final isWideSnapshot = + bodyConstraints.maxWidth >= wideBreakpoint && + _phase == _Phase.connected && + _snapshot != null; + final banner = Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: theme.colorScheme.tertiaryContainer, @@ -836,8 +848,14 @@ class _BetaHomeState extends State<_BetaHome> { l10n.homeNotProductionReadyBanner, style: TextStyle(color: theme.colorScheme.onTertiaryContainer), ), - ), - const SizedBox(height: 12), + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isWideSnapshot) ...[ + banner, + const SizedBox(height: 12), + ], Text(statusText(), style: theme.textTheme.titleMedium), if (_lostReason != null || _reconnectAttempt != null) ...[ const SizedBox(height: 8), @@ -943,7 +961,17 @@ class _BetaHomeState extends State<_BetaHome> { return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SizedBox(width: voiceBarWidthWide, child: voiceBar), + SizedBox( + width: voiceBarWidthWide, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + banner, + const SizedBox(height: 12), + voiceBar, + ], + ), + ), const SizedBox(width: 12), Expanded(child: snapshotView), ], @@ -962,6 +990,8 @@ class _BetaHomeState extends State<_BetaHome> { ), ], ], + ); + }, ), ), ); diff --git a/apps/chanora_flutter/lib/widgets/voice_bar.dart b/apps/chanora_flutter/lib/widgets/voice_bar.dart index 27e8f18..78f764a 100644 --- a/apps/chanora_flutter/lib/widgets/voice_bar.dart +++ b/apps/chanora_flutter/lib/widgets/voice_bar.dart @@ -110,39 +110,54 @@ class VoiceBar extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // Row 1: channel pill + mute toggle + // Row 1: channel pill + mute toggle. The pill is + // wrapped in `Flexible` so very long channel names + // truncate with an ellipsis instead of overflowing the + // Voice Bar's column width (320 dp in the wide layout) + // and pushing the mute icons under the adjacent channel + // tree. Row( children: [ if (inChannel && channelName.isNotEmpty) ...[ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.tag, - size: 14, - color: theme.colorScheme.onPrimaryContainer, - ), - const SizedBox(width: 4), - Text( - channelName, - style: TextStyle( + Flexible( + flex: 100, + fit: FlexFit.loose, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.tag, + size: 14, color: theme.colorScheme.onPrimaryContainer, - fontWeight: FontWeight.w600, ), - ), - ], + const SizedBox(width: 4), + Flexible( + child: Text( + channelName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: false, + style: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), ), ), ], + const SizedBox(width: 8), const Spacer(), IconButton( tooltip: l10n.voiceOutputMuteLabel,