Improve connection status cards

This commit is contained in:
2026-05-14 11:54:28 +09:00
parent f9596a2033
commit aa2336358b
2 changed files with 242 additions and 20 deletions

View File

@@ -127,8 +127,10 @@
<Grid ColumnSpacing="10"> <Grid ColumnSpacing="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="1.05*" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="1.25*" />
<ColumnDefinition Width="1.15*" />
<ColumnDefinition Width="1.15*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border Padding="10,8" Background="#132338" BorderBrush="{StaticResource ControlRoomPanelStrokeBrush}" BorderThickness="1" CornerRadius="14"> <Border Padding="10,8" Background="#132338" BorderBrush="{StaticResource ControlRoomPanelStrokeBrush}" BorderThickness="1" CornerRadius="14">
@@ -145,7 +147,12 @@
</StackPanel> </StackPanel>
</Border> </Border>
<Border Grid.Column="1" Padding="10,8" Background="#132338" BorderBrush="{StaticResource ControlRoomPanelStrokeBrush}" BorderThickness="1" CornerRadius="14"> <Border Grid.Column="1"
Padding="12,10"
Background="{x:Bind ViewModel.CgIntegrationCardBackgroundBrush, Mode=OneWay}"
BorderBrush="{x:Bind ViewModel.CgIntegrationCardBorderBrush, Mode=OneWay}"
BorderThickness="2"
CornerRadius="14">
<StackPanel Spacing="2"> <StackPanel Spacing="2">
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}" Text="CG 연결 상태" /> <TextBlock Style="{StaticResource ConsoleLabelTextStyle}" Text="CG 연결 상태" />
@@ -167,21 +174,73 @@
</Button.Flyout> </Button.Flyout>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<Ellipse Width="10"
Height="10"
VerticalAlignment="Center"
Fill="{x:Bind ViewModel.CgIntegrationBrush, Mode=OneWay}" />
<TextBlock FontFamily="Bahnschrift SemiBold" <TextBlock FontFamily="Bahnschrift SemiBold"
FontSize="15" FontSize="22"
Foreground="{StaticResource ControlRoomTextPrimaryBrush}" Foreground="{StaticResource ControlRoomTextPrimaryBrush}"
Text="{x:Bind ViewModel.CgIntegrationSummary, Mode=OneWay}" /> Text="{x:Bind ViewModel.CgIntegrationSignalText, Mode=OneWay}"
</StackPanel> TextTrimming="CharacterEllipsis" />
<TextBlock Style="{StaticResource ConsoleBodyTextStyle}"
Text="{x:Bind ViewModel.CgIntegrationOperatorMessage, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}" <TextBlock Style="{StaticResource ConsoleLabelTextStyle}"
Text="{x:Bind ViewModel.CgIntegrationDetail, Mode=OneWay}" Text="{x:Bind ViewModel.CgIntegrationDetail, Mode=OneWay}"
TextTrimming="CharacterEllipsis" /> TextTrimming="CharacterEllipsis" />
</StackPanel> </StackPanel>
</Border> </Border>
<Border Grid.Column="2"
Padding="12,10"
Background="{x:Bind ViewModel.SbsDataConnectionCardBackgroundBrush, Mode=OneWay}"
BorderBrush="{x:Bind ViewModel.SbsDataConnectionCardBorderBrush, Mode=OneWay}"
BorderThickness="2"
CornerRadius="14">
<StackPanel Spacing="2">
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}" Text="SBS 데이터" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Ellipse Width="12"
Height="12"
VerticalAlignment="Center"
Fill="{x:Bind ViewModel.SbsDataConnectionBrush, Mode=OneWay}" />
<TextBlock FontFamily="Bahnschrift SemiBold"
FontSize="20"
Foreground="{StaticResource ControlRoomTextPrimaryBrush}"
Text="{x:Bind ViewModel.SbsDataConnectionSummary, Mode=OneWay}" />
</StackPanel>
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}"
Text="광역단체장 포함"
TextTrimming="CharacterEllipsis" />
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}"
Text="{x:Bind ViewModel.SbsDataConnectionDetail, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</Border>
<Border Grid.Column="3"
Padding="12,10"
Background="{x:Bind ViewModel.MbcCniDataConnectionCardBackgroundBrush, Mode=OneWay}"
BorderBrush="{x:Bind ViewModel.MbcCniDataConnectionCardBorderBrush, Mode=OneWay}"
BorderThickness="2"
CornerRadius="14">
<StackPanel Spacing="2">
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}" Text="MBC CNI 데이터" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Ellipse Width="12"
Height="12"
VerticalAlignment="Center"
Fill="{x:Bind ViewModel.MbcCniDataConnectionBrush, Mode=OneWay}" />
<TextBlock FontFamily="Bahnschrift SemiBold"
FontSize="20"
Foreground="{StaticResource ControlRoomTextPrimaryBrush}"
Text="{x:Bind ViewModel.MbcCniDataConnectionSummary, Mode=OneWay}" />
</StackPanel>
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}"
Text="광역의원 포함"
TextTrimming="CharacterEllipsis" />
<TextBlock Style="{StaticResource ConsoleLabelTextStyle}"
Text="{x:Bind ViewModel.MbcCniDataConnectionDetail, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</Border>
</Grid> </Grid>
<Border Visibility="{x:Bind ViewModel.SituationRoomBodyVisibility, Mode=OneWay}" <Border Visibility="{x:Bind ViewModel.SituationRoomBodyVisibility, Mode=OneWay}"

View File

@@ -22,6 +22,12 @@ public sealed class MainViewModel : ObservableObject
{ {
private static readonly Brush ConnectedStatusBrush = new SolidColorBrush(Colors.LimeGreen); private static readonly Brush ConnectedStatusBrush = new SolidColorBrush(Colors.LimeGreen);
private static readonly Brush DisconnectedStatusBrush = new SolidColorBrush(Colors.OrangeRed); private static readonly Brush DisconnectedStatusBrush = new SolidColorBrush(Colors.OrangeRed);
private static readonly Brush WaitingStatusBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 148, 163, 184));
private static readonly Brush ReceivingStatusBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 56, 189, 248));
private static readonly Brush ConnectedCardBackgroundBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 8, 52, 34));
private static readonly Brush DisconnectedCardBackgroundBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 64, 24, 28));
private static readonly Brush WaitingCardBackgroundBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 19, 35, 56));
private static readonly Brush ReceivingCardBackgroundBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 12, 42, 66));
private static readonly Brush DataReceivingNavigationBrush = new SolidColorBrush(Colors.LimeGreen); private static readonly Brush DataReceivingNavigationBrush = new SolidColorBrush(Colors.LimeGreen);
private static readonly Brush DataWaitingNavigationBrush = new SolidColorBrush(Colors.White); private static readonly Brush DataWaitingNavigationBrush = new SolidColorBrush(Colors.White);
private static readonly TimeSpan AutomaticSaveDelay = TimeSpan.FromMilliseconds(500); private static readonly TimeSpan AutomaticSaveDelay = TimeSpan.FromMilliseconds(500);
@@ -190,7 +196,13 @@ public sealed class MainViewModel : ObservableObject
nameof(BottomVisibility), nameof(BottomVisibility),
nameof(VideoWallVisibility), nameof(VideoWallVisibility),
nameof(HeaderStatus), nameof(HeaderStatus),
nameof(IsCgConnected),
nameof(CgIntegrationSummary), nameof(CgIntegrationSummary),
nameof(CgIntegrationBrush),
nameof(CgIntegrationCardBackgroundBrush),
nameof(CgIntegrationCardBorderBrush),
nameof(CgIntegrationSignalText),
nameof(CgIntegrationOperatorMessage),
nameof(CgIntegrationDetail), nameof(CgIntegrationDetail),
nameof(TornadoConnectionSummary), nameof(TornadoConnectionSummary),
nameof(TornadoConnectionDetail)); nameof(TornadoConnectionDetail));
@@ -408,10 +420,38 @@ public sealed class MainViewModel : ObservableObject
public Brush CgIntegrationBrush => IsCgConnected ? ConnectedStatusBrush : DisconnectedStatusBrush; public Brush CgIntegrationBrush => IsCgConnected ? ConnectedStatusBrush : DisconnectedStatusBrush;
public Brush CgIntegrationCardBackgroundBrush => IsCgConnected ? ConnectedCardBackgroundBrush : DisconnectedCardBackgroundBrush;
public Brush CgIntegrationCardBorderBrush => CgIntegrationBrush;
public string CgIntegrationSignalText => IsCgConnected ? "CG 연결됨" : "CG 끊김";
public string CgIntegrationOperatorMessage => IsCgConnected ? "송출 가능" : "연결 확인 필요";
public Brush DataNavigationIconBrush => Data.HasLiveDataSignal public Brush DataNavigationIconBrush => Data.HasLiveDataSignal
? DataReceivingNavigationBrush ? DataReceivingNavigationBrush
: DataWaitingNavigationBrush; : DataWaitingNavigationBrush;
public Brush SbsDataConnectionBrush => ResolveDataConnectionBrush(isMbcCni: false);
public Brush SbsDataConnectionCardBackgroundBrush => ResolveDataConnectionCardBackgroundBrush(isMbcCni: false);
public Brush SbsDataConnectionCardBorderBrush => SbsDataConnectionBrush;
public string SbsDataConnectionSummary => ResolveDataConnectionSummary(isMbcCni: false);
public string SbsDataConnectionDetail => ResolveDataConnectionDetail(isMbcCni: false);
public Brush MbcCniDataConnectionBrush => ResolveDataConnectionBrush(isMbcCni: true);
public Brush MbcCniDataConnectionCardBackgroundBrush => ResolveDataConnectionCardBackgroundBrush(isMbcCni: true);
public Brush MbcCniDataConnectionCardBorderBrush => MbcCniDataConnectionBrush;
public string MbcCniDataConnectionSummary => ResolveDataConnectionSummary(isMbcCni: true);
public string MbcCniDataConnectionDetail => ResolveDataConnectionDetail(isMbcCni: true);
public string CgIntegrationDetail public string CgIntegrationDetail
{ {
get get
@@ -504,6 +544,93 @@ public sealed class MainViewModel : ObservableObject
public string HeaderStatus => $"{Settings.SelectedStation.Name} / {CurrentPageTitle} / {Data.BroadcastPhaseBadgeText} / {OperationModeLabel}"; public string HeaderStatus => $"{Settings.SelectedStation.Name} / {CurrentPageTitle} / {Data.BroadcastPhaseBadgeText} / {OperationModeLabel}";
private Brush ResolveDataConnectionBrush(bool isMbcCni)
{
if (!IsCurrentDataSource(isMbcCni))
{
return WaitingStatusBrush;
}
if (Data.IsRefreshing)
{
return ReceivingStatusBrush;
}
if (Data.HasLiveDataSignal)
{
return ConnectedStatusBrush;
}
return DisconnectedStatusBrush;
}
private Brush ResolveDataConnectionCardBackgroundBrush(bool isMbcCni)
{
if (!IsCurrentDataSource(isMbcCni))
{
return WaitingCardBackgroundBrush;
}
if (Data.IsRefreshing)
{
return ReceivingCardBackgroundBrush;
}
return Data.HasLiveDataSignal
? ConnectedCardBackgroundBrush
: DisconnectedCardBackgroundBrush;
}
private string ResolveDataConnectionSummary(bool isMbcCni)
{
if (!IsCurrentDataSource(isMbcCni))
{
return "대기";
}
if (Data.IsRefreshing)
{
return "수신 중";
}
if (Data.HasLiveDataSignal)
{
return "연결됨";
}
if (!Data.IsCurrentApiSelectionSupported)
{
return "미지원";
}
return Data.LastRefreshAt == DateTimeOffset.MinValue ? "미수신" : "확인 필요";
}
private string ResolveDataConnectionDetail(bool isMbcCni)
{
if (!IsCurrentDataSource(isMbcCni))
{
return isMbcCni
? "광역의원 포함 MBC CNI 데이터"
: "광역단체장 포함 SBS 데이터";
}
return $"{Data.ElectionType} / {Data.BroadcastPhaseLabel} / {Data.StatusText}";
}
private bool IsCurrentDataSource(bool isMbcCni)
{
return IsMbcCniDataSource(Data.ElectionType) == isMbcCni;
}
private static bool IsMbcCniDataSource(string electionType)
{
return string.Equals(electionType, "광역의원", StringComparison.Ordinal) ||
string.Equals(electionType, "기초의원", StringComparison.Ordinal) ||
string.Equals(electionType, "비례대표광역의원", StringComparison.Ordinal) ||
string.Equals(electionType, "비례대표기초의원", StringComparison.Ordinal);
}
public void Navigate(string tag) public void Navigate(string tag)
{ {
var targetPage = tag switch var targetPage = tag switch
@@ -797,6 +924,17 @@ public sealed class MainViewModel : ObservableObject
OnPropertyChanged(nameof(DataNavigationIconBrush)); OnPropertyChanged(nameof(DataNavigationIconBrush));
} }
if (args.PropertyName is nameof(DataViewModel.HasLiveDataSignal)
or nameof(DataViewModel.IsRefreshing)
or nameof(DataViewModel.LastRefreshAt)
or nameof(DataViewModel.StatusText)
or nameof(DataViewModel.ElectionType)
or nameof(DataViewModel.BroadcastPhase)
or nameof(DataViewModel.IsPollingEnabled))
{
NotifyDataConnectionCardsChanged();
}
if (args.PropertyName is nameof(DataViewModel.IsPollingEnabled) if (args.PropertyName is nameof(DataViewModel.IsPollingEnabled)
or nameof(DataViewModel.BroadcastPhase) or nameof(DataViewModel.BroadcastPhase)
or nameof(DataViewModel.ElectionType) or nameof(DataViewModel.ElectionType)
@@ -812,11 +950,42 @@ public sealed class MainViewModel : ObservableObject
} }
} }
private void NotifyDataConnectionCardsChanged()
{
OnPropertyChanged(
nameof(DataNavigationIconBrush),
nameof(SbsDataConnectionBrush),
nameof(SbsDataConnectionCardBackgroundBrush),
nameof(SbsDataConnectionCardBorderBrush),
nameof(SbsDataConnectionSummary),
nameof(SbsDataConnectionDetail),
nameof(MbcCniDataConnectionBrush),
nameof(MbcCniDataConnectionCardBackgroundBrush),
nameof(MbcCniDataConnectionCardBorderBrush),
nameof(MbcCniDataConnectionSummary),
nameof(MbcCniDataConnectionDetail));
}
private void RestoreSelection_PropertyChanged(object? sender, PropertyChangedEventArgs e) private void RestoreSelection_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
QueueAutomaticSave(); QueueAutomaticSave();
} }
private void NotifyCgConnectionStatusChanged()
{
OnPropertyChanged(
nameof(IsCgConnected),
nameof(CgIntegrationSummary),
nameof(CgIntegrationBrush),
nameof(CgIntegrationCardBackgroundBrush),
nameof(CgIntegrationCardBorderBrush),
nameof(CgIntegrationSignalText),
nameof(CgIntegrationOperatorMessage),
nameof(CgIntegrationDetail),
nameof(TornadoConnectionSummary),
nameof(TornadoConnectionDetail));
}
private void Channel_PropertyChanged(object? sender, PropertyChangedEventArgs e) private void Channel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName is nameof(ChannelScheduleViewModel.LoopEnabled) if (e.PropertyName is nameof(ChannelScheduleViewModel.LoopEnabled)
@@ -829,13 +998,7 @@ public sealed class MainViewModel : ObservableObject
or nameof(ChannelScheduleViewModel.AdapterStateLabel) or nameof(ChannelScheduleViewModel.AdapterStateLabel)
or nameof(ChannelScheduleViewModel.IsCgConnected)) or nameof(ChannelScheduleViewModel.IsCgConnected))
{ {
OnPropertyChanged( NotifyCgConnectionStatusChanged();
nameof(IsCgConnected),
nameof(CgIntegrationSummary),
nameof(CgIntegrationBrush),
nameof(CgIntegrationDetail),
nameof(TornadoConnectionSummary),
nameof(TornadoConnectionDetail));
} }
} }