데이터 소스 자동 수신 상태 분리
This commit is contained in:
10
Tornado3_2026Election/Domain/DataSourceConnectionState.cs
Normal file
10
Tornado3_2026Election/Domain/DataSourceConnectionState.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Tornado3_2026Election.Domain;
|
||||||
|
|
||||||
|
public enum DataSourceConnectionState
|
||||||
|
{
|
||||||
|
Waiting,
|
||||||
|
Receiving,
|
||||||
|
Connected,
|
||||||
|
Warning,
|
||||||
|
Disconnected
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
private const string PanseDemocraticPartyLabel = "더불어민주당";
|
private const string PanseDemocraticPartyLabel = "더불어민주당";
|
||||||
private const string PansePeoplePowerPartyLabel = "국민의힘";
|
private const string PansePeoplePowerPartyLabel = "국민의힘";
|
||||||
private const string PanseOtherPartyLabel = "무·기타";
|
private const string PanseOtherPartyLabel = "무·기타";
|
||||||
|
private const string SbsProbeElectionType = "광역단체장";
|
||||||
|
private const string MbcCniProbeElectionType = "광역의원";
|
||||||
public const int FixedPollingIntervalSeconds = 60;
|
public const int FixedPollingIntervalSeconds = 60;
|
||||||
private static readonly Regex TopRankSlotCountPattern = new(@"1-(\d+)위", RegexOptions.Compiled);
|
private static readonly Regex TopRankSlotCountPattern = new(@"1-(\d+)위", RegexOptions.Compiled);
|
||||||
private static readonly Regex PeopleSlotCountPattern = new(@"(\d+)인", RegexOptions.Compiled);
|
private static readonly Regex PeopleSlotCountPattern = new(@"(\d+)인", RegexOptions.Compiled);
|
||||||
@@ -138,6 +140,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
private readonly SbsElectionApiClient _apiClient;
|
private readonly SbsElectionApiClient _apiClient;
|
||||||
private readonly PreElectionHistoryService _preElectionHistoryService;
|
private readonly PreElectionHistoryService _preElectionHistoryService;
|
||||||
private readonly CareerPromiseService _careerPromiseService;
|
private readonly CareerPromiseService _careerPromiseService;
|
||||||
|
private readonly SemaphoreSlim _dataSourceRefreshLock = new(1, 1);
|
||||||
private readonly Dictionary<string, string> _districtCodeMap = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, string> _districtCodeMap = new(StringComparer.Ordinal);
|
||||||
private readonly Dictionary<string, SbsElectionApiClient.DistrictSelectionOption> _districtOptionMap = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, SbsElectionApiClient.DistrictSelectionOption> _districtOptionMap = new(StringComparer.Ordinal);
|
||||||
private CancellationTokenSource? _pollingCts;
|
private CancellationTokenSource? _pollingCts;
|
||||||
@@ -150,6 +153,12 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
private bool _isApplyingRefreshResult;
|
private bool _isApplyingRefreshResult;
|
||||||
private DateTimeOffset _lastRefreshAt = DateTimeOffset.MinValue;
|
private DateTimeOffset _lastRefreshAt = DateTimeOffset.MinValue;
|
||||||
private DateTimeOffset _lastManualRefreshAt = DateTimeOffset.MinValue;
|
private DateTimeOffset _lastManualRefreshAt = DateTimeOffset.MinValue;
|
||||||
|
private DataSourceConnectionState _sbsDataSourceState = DataSourceConnectionState.Waiting;
|
||||||
|
private DataSourceConnectionState _mbcCniDataSourceState = DataSourceConnectionState.Waiting;
|
||||||
|
private DateTimeOffset _sbsDataSourceLastRefreshAt = DateTimeOffset.MinValue;
|
||||||
|
private DateTimeOffset _mbcCniDataSourceLastRefreshAt = DateTimeOffset.MinValue;
|
||||||
|
private string _sbsDataSourceDetail = "아직 자동 수신 전입니다.";
|
||||||
|
private string _mbcCniDataSourceDetail = "아직 자동 수신 전입니다.";
|
||||||
private string _electionType = "광역단체장";
|
private string _electionType = "광역단체장";
|
||||||
private string _districtName = "부산광역시";
|
private string _districtName = "부산광역시";
|
||||||
private string _districtCode = "26";
|
private string _districtCode = "26";
|
||||||
@@ -332,6 +341,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
if (SetProperty(ref _broadcastPhase, value))
|
if (SetProperty(ref _broadcastPhase, value))
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(StatusText), nameof(HasLiveDataSignal));
|
OnPropertyChanged(nameof(StatusText), nameof(HasLiveDataSignal));
|
||||||
|
OnPropertyChanged(nameof(HasAnyLiveDataSignal));
|
||||||
NotifyModePresentationChanged();
|
NotifyModePresentationChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,6 +655,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(StatusText), nameof(PollingCountdownText), nameof(PollingModeLabel), nameof(PollingStateDetail));
|
OnPropertyChanged(nameof(StatusText), nameof(PollingCountdownText), nameof(PollingModeLabel), nameof(PollingStateDetail));
|
||||||
OnPropertyChanged(nameof(HasLiveDataSignal));
|
OnPropertyChanged(nameof(HasLiveDataSignal));
|
||||||
|
OnPropertyChanged(nameof(HasAnyLiveDataSignal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -678,10 +689,27 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(StatusText), nameof(LastRefreshDisplay));
|
OnPropertyChanged(nameof(StatusText), nameof(LastRefreshDisplay));
|
||||||
OnPropertyChanged(nameof(HasLiveDataSignal));
|
OnPropertyChanged(nameof(HasLiveDataSignal));
|
||||||
|
OnPropertyChanged(nameof(HasAnyLiveDataSignal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataSourceConnectionState SbsDataSourceState => _sbsDataSourceState;
|
||||||
|
|
||||||
|
public DataSourceConnectionState MbcCniDataSourceState => _mbcCniDataSourceState;
|
||||||
|
|
||||||
|
public DateTimeOffset SbsDataSourceLastRefreshAt => _sbsDataSourceLastRefreshAt;
|
||||||
|
|
||||||
|
public DateTimeOffset MbcCniDataSourceLastRefreshAt => _mbcCniDataSourceLastRefreshAt;
|
||||||
|
|
||||||
|
public string SbsDataSourceSummary => ResolveDataSourceSummary(SbsDataSourceState);
|
||||||
|
|
||||||
|
public string MbcCniDataSourceSummary => ResolveDataSourceSummary(MbcCniDataSourceState);
|
||||||
|
|
||||||
|
public string SbsDataSourceDetail => _sbsDataSourceDetail;
|
||||||
|
|
||||||
|
public string MbcCniDataSourceDetail => _mbcCniDataSourceDetail;
|
||||||
|
|
||||||
public string ElectionType
|
public string ElectionType
|
||||||
{
|
{
|
||||||
get => _electionType;
|
get => _electionType;
|
||||||
@@ -692,6 +720,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
if (SetProperty(ref _electionType, normalizedValue))
|
if (SetProperty(ref _electionType, normalizedValue))
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(StatusText), nameof(CareerPromiseContextText), nameof(HasLiveDataSignal));
|
OnPropertyChanged(nameof(StatusText), nameof(CareerPromiseContextText), nameof(HasLiveDataSignal));
|
||||||
|
OnPropertyChanged(nameof(HasAnyLiveDataSignal));
|
||||||
NotifyModePresentationChanged();
|
NotifyModePresentationChanged();
|
||||||
RefreshPreElectionHistoryPresentation();
|
RefreshPreElectionHistoryPresentation();
|
||||||
_ = RefreshDistrictOptionsForElectionTypeAsync();
|
_ = RefreshDistrictOptionsForElectionTypeAsync();
|
||||||
@@ -911,6 +940,11 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
LastRefreshAt != DateTimeOffset.MinValue &&
|
LastRefreshAt != DateTimeOffset.MinValue &&
|
||||||
string.IsNullOrWhiteSpace(_lastRefreshWarningMessage));
|
string.IsNullOrWhiteSpace(_lastRefreshWarningMessage));
|
||||||
|
|
||||||
|
public bool HasAnyLiveDataSignal =>
|
||||||
|
HasLiveDataSignal ||
|
||||||
|
SbsDataSourceState == DataSourceConnectionState.Connected ||
|
||||||
|
MbcCniDataSourceState == DataSourceConnectionState.Connected;
|
||||||
|
|
||||||
public string PollingModeLabel
|
public string PollingModeLabel
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -984,6 +1018,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pollingCts = new CancellationTokenSource();
|
_pollingCts = new CancellationTokenSource();
|
||||||
|
_ = RefreshAllDataSourcesAsync(_pollingCts.Token);
|
||||||
_ = RunPollingLoopAsync(_pollingCts.Token);
|
_ = RunPollingLoopAsync(_pollingCts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2501,6 +2536,218 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
|
|
||||||
_lastRefreshWarningMessage = normalized;
|
_lastRefreshWarningMessage = normalized;
|
||||||
OnPropertyChanged(nameof(StatusText), nameof(HasLiveDataSignal));
|
OnPropertyChanged(nameof(StatusText), nameof(HasLiveDataSignal));
|
||||||
|
OnPropertyChanged(nameof(HasAnyLiveDataSignal));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshAllDataSourcesAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!await _dataSourceRefreshLock.WaitAsync(0, cancellationToken).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await RefreshSbsDataSourceAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
await RefreshMbcCniDataSourceAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_dataSourceRefreshLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshSbsDataSourceAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: false,
|
||||||
|
DataSourceConnectionState.Receiving,
|
||||||
|
"광역단체장 개표 데이터 수신 중");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var districts = await _apiClient
|
||||||
|
.GetDistrictOptionsAsync(SbsProbeElectionType, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (districts.Count == 0)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: false,
|
||||||
|
DataSourceConnectionState.Warning,
|
||||||
|
"광역단체장 선거구 목록이 비어 있습니다.");
|
||||||
|
_logService.Warning("SBS 데이터 상태 확인: 광역단체장 선거구 목록이 비어 있습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshots = await _apiClient
|
||||||
|
.GetCountingSnapshotsAsync(SbsProbeElectionType, districts, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (snapshots.Count == 0)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: false,
|
||||||
|
DataSourceConnectionState.Warning,
|
||||||
|
$"광역단체장 선거구 {districts.Count}개 확인, 개표 데이터 없음");
|
||||||
|
_logService.Warning($"SBS 데이터 상태 확인: 광역단체장 선거구 {districts.Count}개 확인, 개표 데이터가 없습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivedAt = snapshots
|
||||||
|
.Select(snapshot => snapshot.ReceivedAt == default ? DateTimeOffset.Now : snapshot.ReceivedAt)
|
||||||
|
.DefaultIfEmpty(DateTimeOffset.Now)
|
||||||
|
.Max();
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: false,
|
||||||
|
DataSourceConnectionState.Connected,
|
||||||
|
$"광역단체장 개표 {snapshots.Count}/{districts.Count}개 수신 / {receivedAt:HH:mm:ss}",
|
||||||
|
receivedAt);
|
||||||
|
_logService.Info($"SBS 데이터 상태 갱신 완료. 광역단체장 개표 {snapshots.Count}/{districts.Count}개 수신");
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: false,
|
||||||
|
DataSourceConnectionState.Disconnected,
|
||||||
|
CreateDataSourceFailureDetail(ex.Message, SbsDataSourceLastRefreshAt));
|
||||||
|
_logService.Warning($"SBS 데이터 상태 확인 실패: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshMbcCniDataSourceAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: true,
|
||||||
|
DataSourceConnectionState.Receiving,
|
||||||
|
"광역의원 개표·의석표 데이터 수신 중");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var scopedRegionFilters = ResolveMbcCniProbeRegionScope();
|
||||||
|
var districts = await _apiClient
|
||||||
|
.GetDistrictOptionsAsync(MbcCniProbeElectionType, scopedRegionFilters, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
var seatRegions = await _apiClient
|
||||||
|
.GetCouncilSeatRegionOptionsAsync(MbcCniProbeElectionType, scopedRegionFilters, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (districts.Count == 0 && seatRegions.Count == 0)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: true,
|
||||||
|
DataSourceConnectionState.Warning,
|
||||||
|
"광역의원 개표·의석표 데이터가 비어 있습니다.");
|
||||||
|
_logService.Warning("MBC CNI 데이터 상태 확인: 광역의원 개표·의석표 데이터가 비어 있습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshots = districts.Count == 0
|
||||||
|
? Array.Empty<SbsElectionApiClient.SbsElectionRefreshResult>()
|
||||||
|
: await _apiClient
|
||||||
|
.GetCountingSnapshotsAsync(MbcCniProbeElectionType, districts, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
var receivedAt = snapshots
|
||||||
|
.Select(snapshot => snapshot.ReceivedAt == default ? DateTimeOffset.Now : snapshot.ReceivedAt)
|
||||||
|
.DefaultIfEmpty(DateTimeOffset.Now)
|
||||||
|
.Max();
|
||||||
|
|
||||||
|
if (snapshots.Count == 0 || seatRegions.Count == 0)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: true,
|
||||||
|
DataSourceConnectionState.Warning,
|
||||||
|
$"광역의원 개표 {snapshots.Count}/{districts.Count}개, 의석표 {seatRegions.Count}개 확인",
|
||||||
|
receivedAt);
|
||||||
|
_logService.Warning($"MBC CNI 데이터 상태 확인: 광역의원 개표 {snapshots.Count}/{districts.Count}개, 의석표 {seatRegions.Count}개 확인");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: true,
|
||||||
|
DataSourceConnectionState.Connected,
|
||||||
|
$"광역의원 개표 {snapshots.Count}/{districts.Count}개 / 의석표 {seatRegions.Count}개 수신 / {receivedAt:HH:mm:ss}",
|
||||||
|
receivedAt);
|
||||||
|
_logService.Info($"MBC CNI 데이터 상태 갱신 완료. 광역의원 개표 {snapshots.Count}/{districts.Count}개, 의석표 {seatRegions.Count}개 수신");
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
SetDataSourceStatus(
|
||||||
|
isMbcCni: true,
|
||||||
|
DataSourceConnectionState.Disconnected,
|
||||||
|
CreateDataSourceFailureDetail(ex.Message, MbcCniDataSourceLastRefreshAt));
|
||||||
|
_logService.Warning($"MBC CNI 데이터 상태 확인 실패: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] ResolveMbcCniProbeRegionScope()
|
||||||
|
{
|
||||||
|
var scopedRegionFilters = ResolveApiDistrictRegionScope(MbcCniProbeElectionType)
|
||||||
|
.Where(region => !string.IsNullOrWhiteSpace(region))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
return scopedRegionFilters.Length > 0
|
||||||
|
? scopedRegionFilters
|
||||||
|
: DefaultDistrictOptions.Select(option => option.RegionName).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetDataSourceStatus(
|
||||||
|
bool isMbcCni,
|
||||||
|
DataSourceConnectionState state,
|
||||||
|
string detail,
|
||||||
|
DateTimeOffset? receivedAt = null)
|
||||||
|
{
|
||||||
|
if (isMbcCni)
|
||||||
|
{
|
||||||
|
_mbcCniDataSourceState = state;
|
||||||
|
_mbcCniDataSourceDetail = string.IsNullOrWhiteSpace(detail) ? ResolveDataSourceSummary(state) : detail;
|
||||||
|
if (receivedAt.HasValue)
|
||||||
|
{
|
||||||
|
_mbcCniDataSourceLastRefreshAt = receivedAt.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(
|
||||||
|
nameof(MbcCniDataSourceState),
|
||||||
|
nameof(MbcCniDataSourceSummary),
|
||||||
|
nameof(MbcCniDataSourceDetail),
|
||||||
|
nameof(MbcCniDataSourceLastRefreshAt),
|
||||||
|
nameof(HasAnyLiveDataSignal));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sbsDataSourceState = state;
|
||||||
|
_sbsDataSourceDetail = string.IsNullOrWhiteSpace(detail) ? ResolveDataSourceSummary(state) : detail;
|
||||||
|
if (receivedAt.HasValue)
|
||||||
|
{
|
||||||
|
_sbsDataSourceLastRefreshAt = receivedAt.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(
|
||||||
|
nameof(SbsDataSourceState),
|
||||||
|
nameof(SbsDataSourceSummary),
|
||||||
|
nameof(SbsDataSourceDetail),
|
||||||
|
nameof(SbsDataSourceLastRefreshAt),
|
||||||
|
nameof(HasAnyLiveDataSignal));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveDataSourceSummary(DataSourceConnectionState state)
|
||||||
|
{
|
||||||
|
return state switch
|
||||||
|
{
|
||||||
|
DataSourceConnectionState.Receiving => "수신 중",
|
||||||
|
DataSourceConnectionState.Connected => "연결됨",
|
||||||
|
DataSourceConnectionState.Warning => "확인 필요",
|
||||||
|
DataSourceConnectionState.Disconnected => "끊김",
|
||||||
|
_ => "대기"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateDataSourceFailureDetail(string message, DateTimeOffset lastRefreshAt)
|
||||||
|
{
|
||||||
|
var detail = string.IsNullOrWhiteSpace(message)
|
||||||
|
? "수신 실패"
|
||||||
|
: $"수신 실패: {message}";
|
||||||
|
return lastRefreshAt == DateTimeOffset.MinValue
|
||||||
|
? detail
|
||||||
|
: $"{detail} / 마지막 성공 {lastRefreshAt:HH:mm:ss}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -2526,6 +2773,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
PollingCountdownSeconds = remainingSeconds;
|
PollingCountdownSeconds = remainingSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await RefreshAllDataSourcesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await RefreshAsync(isManualRequest: false);
|
await RefreshAsync(isManualRequest: false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
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 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 ReceivingStatusBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 56, 189, 248));
|
||||||
|
private static readonly Brush WarningStatusBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 250, 204, 21));
|
||||||
private static readonly Brush ConnectedCardBackgroundBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 8, 52, 34));
|
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 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 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 ReceivingCardBackgroundBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 12, 42, 66));
|
||||||
|
private static readonly Brush WarningCardBackgroundBrush = new SolidColorBrush(ColorHelper.FromArgb(255, 58, 48, 16));
|
||||||
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);
|
||||||
@@ -428,7 +430,7 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
|
|
||||||
public string CgIntegrationOperatorMessage => IsCgConnected ? "송출 가능" : "연결 확인 필요";
|
public string CgIntegrationOperatorMessage => IsCgConnected ? "송출 가능" : "연결 확인 필요";
|
||||||
|
|
||||||
public Brush DataNavigationIconBrush => Data.HasLiveDataSignal
|
public Brush DataNavigationIconBrush => Data.HasAnyLiveDataSignal
|
||||||
? DataReceivingNavigationBrush
|
? DataReceivingNavigationBrush
|
||||||
: DataWaitingNavigationBrush;
|
: DataWaitingNavigationBrush;
|
||||||
|
|
||||||
@@ -546,89 +548,47 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
|
|
||||||
private Brush ResolveDataConnectionBrush(bool isMbcCni)
|
private Brush ResolveDataConnectionBrush(bool isMbcCni)
|
||||||
{
|
{
|
||||||
if (!IsCurrentDataSource(isMbcCni))
|
return ResolveDataConnectionState(isMbcCni) switch
|
||||||
{
|
{
|
||||||
return WaitingStatusBrush;
|
DataSourceConnectionState.Receiving => ReceivingStatusBrush,
|
||||||
}
|
DataSourceConnectionState.Connected => ConnectedStatusBrush,
|
||||||
|
DataSourceConnectionState.Warning => WarningStatusBrush,
|
||||||
if (Data.IsRefreshing)
|
DataSourceConnectionState.Disconnected => DisconnectedStatusBrush,
|
||||||
{
|
_ => WaitingStatusBrush
|
||||||
return ReceivingStatusBrush;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (Data.HasLiveDataSignal)
|
|
||||||
{
|
|
||||||
return ConnectedStatusBrush;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DisconnectedStatusBrush;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Brush ResolveDataConnectionCardBackgroundBrush(bool isMbcCni)
|
private Brush ResolveDataConnectionCardBackgroundBrush(bool isMbcCni)
|
||||||
{
|
{
|
||||||
if (!IsCurrentDataSource(isMbcCni))
|
return ResolveDataConnectionState(isMbcCni) switch
|
||||||
{
|
{
|
||||||
return WaitingCardBackgroundBrush;
|
DataSourceConnectionState.Receiving => ReceivingCardBackgroundBrush,
|
||||||
}
|
DataSourceConnectionState.Connected => ConnectedCardBackgroundBrush,
|
||||||
|
DataSourceConnectionState.Warning => WarningCardBackgroundBrush,
|
||||||
if (Data.IsRefreshing)
|
DataSourceConnectionState.Disconnected => DisconnectedCardBackgroundBrush,
|
||||||
{
|
_ => WaitingCardBackgroundBrush
|
||||||
return ReceivingCardBackgroundBrush;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return Data.HasLiveDataSignal
|
|
||||||
? ConnectedCardBackgroundBrush
|
|
||||||
: DisconnectedCardBackgroundBrush;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveDataConnectionSummary(bool isMbcCni)
|
private string ResolveDataConnectionSummary(bool isMbcCni)
|
||||||
{
|
{
|
||||||
if (!IsCurrentDataSource(isMbcCni))
|
return isMbcCni
|
||||||
{
|
? Data.MbcCniDataSourceSummary
|
||||||
return "대기";
|
: Data.SbsDataSourceSummary;
|
||||||
}
|
|
||||||
|
|
||||||
if (Data.IsRefreshing)
|
|
||||||
{
|
|
||||||
return "수신 중";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Data.HasLiveDataSignal)
|
|
||||||
{
|
|
||||||
return "연결됨";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Data.IsCurrentApiSelectionSupported)
|
|
||||||
{
|
|
||||||
return "미지원";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Data.LastRefreshAt == DateTimeOffset.MinValue ? "미수신" : "확인 필요";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveDataConnectionDetail(bool isMbcCni)
|
private string ResolveDataConnectionDetail(bool isMbcCni)
|
||||||
{
|
{
|
||||||
if (!IsCurrentDataSource(isMbcCni))
|
return isMbcCni
|
||||||
{
|
? Data.MbcCniDataSourceDetail
|
||||||
return isMbcCni
|
: Data.SbsDataSourceDetail;
|
||||||
? "광역의원 포함 MBC CNI 데이터"
|
|
||||||
: "광역단체장 포함 SBS 데이터";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{Data.ElectionType} / {Data.BroadcastPhaseLabel} / {Data.StatusText}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsCurrentDataSource(bool isMbcCni)
|
private DataSourceConnectionState ResolveDataConnectionState(bool isMbcCni)
|
||||||
{
|
{
|
||||||
return IsMbcCniDataSource(Data.ElectionType) == isMbcCni;
|
return isMbcCni
|
||||||
}
|
? Data.MbcCniDataSourceState
|
||||||
|
: Data.SbsDataSourceState;
|
||||||
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)
|
||||||
@@ -919,18 +879,25 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
OnPropertyChanged(nameof(HeaderStatus));
|
OnPropertyChanged(nameof(HeaderStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.PropertyName is nameof(DataViewModel.HasLiveDataSignal))
|
if (args.PropertyName is nameof(DataViewModel.HasAnyLiveDataSignal))
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(DataNavigationIconBrush));
|
OnPropertyChanged(nameof(DataNavigationIconBrush));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.PropertyName is nameof(DataViewModel.HasLiveDataSignal)
|
if (args.PropertyName is nameof(DataViewModel.HasAnyLiveDataSignal)
|
||||||
|
or nameof(DataViewModel.HasLiveDataSignal)
|
||||||
or nameof(DataViewModel.IsRefreshing)
|
or nameof(DataViewModel.IsRefreshing)
|
||||||
or nameof(DataViewModel.LastRefreshAt)
|
or nameof(DataViewModel.LastRefreshAt)
|
||||||
or nameof(DataViewModel.StatusText)
|
or nameof(DataViewModel.StatusText)
|
||||||
or nameof(DataViewModel.ElectionType)
|
or nameof(DataViewModel.ElectionType)
|
||||||
or nameof(DataViewModel.BroadcastPhase)
|
or nameof(DataViewModel.BroadcastPhase)
|
||||||
or nameof(DataViewModel.IsPollingEnabled))
|
or nameof(DataViewModel.IsPollingEnabled)
|
||||||
|
or nameof(DataViewModel.SbsDataSourceState)
|
||||||
|
or nameof(DataViewModel.SbsDataSourceDetail)
|
||||||
|
or nameof(DataViewModel.SbsDataSourceSummary)
|
||||||
|
or nameof(DataViewModel.MbcCniDataSourceState)
|
||||||
|
or nameof(DataViewModel.MbcCniDataSourceDetail)
|
||||||
|
or nameof(DataViewModel.MbcCniDataSourceSummary))
|
||||||
{
|
{
|
||||||
NotifyDataConnectionCardsChanged();
|
NotifyDataConnectionCardsChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user