mirror of
https://github.com/Stone-Red-Code/Decho.git
synced 2026-09-04 00:46:11 +02:00
Add unread and mention badges with notification sounds for channels
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MessageBox.Avalonia" Version="3.2.0" />
|
<PackageReference Include="MessageBox.Avalonia" Version="3.2.0" />
|
||||||
|
<PackageReference Include="NetCoreAudio" Version="2.0.1" />
|
||||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
|
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
|
||||||
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
||||||
<PackageReference Include="Romzetron.Avalonia" Version="11.3.4" />
|
<PackageReference Include="Romzetron.Avalonia" Version="11.3.4" />
|
||||||
|
|||||||
@@ -36,9 +36,68 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase
|
|||||||
set => this.RaiseAndSetIfChanged(ref field, value);
|
set => this.RaiseAndSetIfChanged(ref field, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int UnreadCount
|
||||||
|
{
|
||||||
|
get => field;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (field == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
field = value;
|
||||||
|
this.RaisePropertyChanged();
|
||||||
|
this.RaisePropertyChanged(nameof(HasUnread));
|
||||||
|
this.RaisePropertyChanged(nameof(BadgeText));
|
||||||
|
this.RaisePropertyChanged(nameof(BadgeColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasUnread => UnreadCount > 0;
|
||||||
|
|
||||||
|
public int MentionCount
|
||||||
|
{
|
||||||
|
get => field;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (field == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
field = value;
|
||||||
|
this.RaisePropertyChanged();
|
||||||
|
this.RaisePropertyChanged(nameof(HasMentions));
|
||||||
|
this.RaisePropertyChanged(nameof(BadgeText));
|
||||||
|
this.RaisePropertyChanged(nameof(BadgeColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasMentions => MentionCount > 0;
|
||||||
|
|
||||||
|
public string BadgeColor => HasMentions ? "#E53935" : "#78909C";
|
||||||
|
|
||||||
|
public string BadgeText => HasMentions ? MentionCount.ToString() : UnreadCount.ToString();
|
||||||
|
|
||||||
public ObservableCollection<MessageViewModel> Messages { get; } = new ObservableCollection<MessageViewModel>(
|
public ObservableCollection<MessageViewModel> Messages { get; } = new ObservableCollection<MessageViewModel>(
|
||||||
model.Messages.Select(message => new MessageViewModel(message)));
|
model.Messages.Select(message => new MessageViewModel(message)));
|
||||||
|
|
||||||
|
public void IncrementUnread(bool isMention = false)
|
||||||
|
{
|
||||||
|
UnreadCount++;
|
||||||
|
if (isMention)
|
||||||
|
{
|
||||||
|
MentionCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearUnread()
|
||||||
|
{
|
||||||
|
UnreadCount = 0;
|
||||||
|
MentionCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
public void ClearMessages()
|
public void ClearMessages()
|
||||||
{
|
{
|
||||||
Model.Messages.Clear();
|
Model.Messages.Clear();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Decho.Views;
|
|||||||
|
|
||||||
using EchoHub.Client.Commands;
|
using EchoHub.Client.Commands;
|
||||||
using EchoHub.Client.Config;
|
using EchoHub.Client.Config;
|
||||||
|
using EchoHub.Client.Services;
|
||||||
using EchoHub.Core.Constants;
|
using EchoHub.Core.Constants;
|
||||||
using EchoHub.Core.DTOs;
|
using EchoHub.Core.DTOs;
|
||||||
using EchoHub.Core.Models;
|
using EchoHub.Core.Models;
|
||||||
@@ -22,6 +23,7 @@ namespace Decho.ViewModels;
|
|||||||
public sealed class MainWindowViewModel : ViewModelBase
|
public sealed class MainWindowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly CommandHandler _commandHandler;
|
private readonly CommandHandler _commandHandler;
|
||||||
|
private readonly NotificationSoundService _notificationService;
|
||||||
private Window? _mainWindow;
|
private Window? _mainWindow;
|
||||||
|
|
||||||
public string Title { get; } = "Decho";
|
public string Title { get; } = "Decho";
|
||||||
@@ -44,6 +46,7 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
ConnectionService = new ConnectionService();
|
ConnectionService = new ConnectionService();
|
||||||
_commandHandler = new CommandHandler();
|
_commandHandler = new CommandHandler();
|
||||||
|
_notificationService = new NotificationSoundService(ConfigManager.Load().Notifications);
|
||||||
|
|
||||||
Sidebar = new SidebarViewModel();
|
Sidebar = new SidebarViewModel();
|
||||||
Chat = new ChatViewModel();
|
Chat = new ChatViewModel();
|
||||||
@@ -360,7 +363,7 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
await ConnectionService.NukeChannelAsync(serverUrl, channel);
|
await ConnectionService.NukeChannelAsync(serverUrl, channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
_commandHandler.OnTestSound += () => Task.CompletedTask;
|
_commandHandler.OnTestSound += _notificationService.PlayTestAsync;
|
||||||
|
|
||||||
_commandHandler.OnQuit += () =>
|
_commandHandler.OnQuit += () =>
|
||||||
{
|
{
|
||||||
@@ -554,11 +557,29 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
ConnectionService.MessageReceived += (serverUrl, message) =>
|
ConnectionService.MessageReceived += (serverUrl, message) =>
|
||||||
{
|
{
|
||||||
|
string? username = ConnectionService.GetCurrentUsername(serverUrl);
|
||||||
|
bool isMention = !string.IsNullOrEmpty(username)
|
||||||
|
&& message.Content.Contains($"@{username}", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
ChannelViewModel? channelVm = FindChannelViewModel(serverUrl, message.ChannelName);
|
ChannelViewModel? channelVm = FindChannelViewModel(serverUrl, message.ChannelName);
|
||||||
channelVm?.AddMessage(message);
|
if (channelVm is not null)
|
||||||
|
{
|
||||||
|
channelVm.AddMessage(message);
|
||||||
|
bool isSelected = string.Equals(Chat.CurrentChannelName, message.ChannelName, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& string.Equals(Chat.CurrentServerUrl, serverUrl, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (!isSelected)
|
||||||
|
{
|
||||||
|
channelVm.IncrementUnread(isMention);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isMention)
|
||||||
|
{
|
||||||
|
_ = _notificationService.PlayAsync();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ConnectionService.ErrorOccurred += (serverUrl, error) =>
|
ConnectionService.ErrorOccurred += (serverUrl, error) =>
|
||||||
@@ -646,6 +667,8 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel.ClearUnread();
|
||||||
|
|
||||||
string serverUrl = FindServerUrlForChannel(channel);
|
string serverUrl = FindServerUrlForChannel(channel);
|
||||||
bool isServerConnected = Sidebar.GetServer(serverUrl)?.IsConnected ?? false;
|
bool isServerConnected = Sidebar.GetServer(serverUrl)?.IsConnected ?? false;
|
||||||
Chat.SetChannel(channel, serverUrl, isServerConnected);
|
Chat.SetChannel(channel, serverUrl, isServerConnected);
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ public sealed class MessageViewModel(MessageModel model) : ViewModelBase
|
|||||||
return Brushes.White;
|
return Brushes.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new SolidColorBrush(Color.Parse(color));
|
return new SolidColorBrush(Color.Parse(color));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,24 @@
|
|||||||
SelectedItem="{Binding SelectedChannel, Mode=TwoWay}">
|
SelectedItem="{Binding SelectedChannel, Mode=TwoWay}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:ChannelViewModel">
|
<DataTemplate x:DataType="vm:ChannelViewModel">
|
||||||
<TextBlock>
|
<Grid ColumnDefinitions="Auto, *, Auto" Margin="2 0">
|
||||||
<InlineUIContainer BaselineAlignment="Bottom">
|
<i:Icon Grid.Column="0" Value="fa-hashtag" FontWeight="Bold" Margin="0 0 4 1" VerticalAlignment="Center" />
|
||||||
<i:Icon Value="fa-hashtag" FontWeight="Bold" Margin="0 0 0 1" />
|
<TextBlock Grid.Column="1" Text="{Binding Name}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||||
</InlineUIContainer>
|
<Border Grid.Column="2"
|
||||||
<Run Text="{Binding Name}" />
|
IsVisible="{Binding HasUnread}"
|
||||||
</TextBlock>
|
Background="{Binding BadgeColor}"
|
||||||
|
CornerRadius="8"
|
||||||
|
MinWidth="16"
|
||||||
|
Height="16"
|
||||||
|
Padding="4 0"
|
||||||
|
Margin="4 0 0 0">
|
||||||
|
<TextBlock Text="{Binding BadgeText}"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="White"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
|
|||||||
Reference in New Issue
Block a user