diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml
index 0b136133624..95ea8cba932 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml
@@ -1,7 +1,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs
index f58a122d3f4..94acdf3c9fe 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs
@@ -9,15 +9,75 @@ namespace Content.Client.ADT.CartridgeLoader.Cartridges;
public sealed partial class NanoChatEntry : BoxContainer
{
public event Action? OnPressed;
+ public event Action? OnJoinPressed;
+ public event Action? OnAcceptPressed;
+ public event Action? OnDeclinePressed;
+
private uint _number;
private Action? _pressHandler;
public NanoChatEntry()
{
RobustXamlLoader.Load(this);
+
+ JoinButton.OnPressed += _ => OnJoinPressed?.Invoke(_number);
+ AcceptButton.OnPressed += _ => OnAcceptPressed?.Invoke(_number);
+ DeclineButton.OnPressed += _ => OnDeclinePressed?.Invoke(_number);
}
public void SetRecipient(NanoChatRecipient recipient, uint number, bool isSelected)
+ {
+ SetupButton(number, isSelected);
+
+ NameLabel.Text = recipient.Name;
+ SubLabel.Text = recipient.JobTitle ?? "";
+ SubLabel.Visible = !string.IsNullOrEmpty(recipient.JobTitle);
+ UnreadIndicator.Visible = recipient.HasUnread;
+ ActionButtons.Visible = false;
+ }
+
+ public void SetGroup(NanoChatGroup group, uint number, bool isSelected)
+ {
+ SetupButton(number, isSelected);
+
+ NameLabel.Text = group.Name;
+ SubLabel.Text = Loc.GetString("nano-chat-group-members-count", ("count", group.Members.Count));
+ SubLabel.Visible = true;
+ UnreadIndicator.Visible = group.HasUnread;
+ ActionButtons.Visible = false;
+ }
+
+ public void SetPublicGroup(NanoChatGroupInfo group, uint number)
+ {
+ _number = number;
+ ChatButton.Visible = false;
+ InfoPanel.Visible = true;
+
+ InfoNameLabel.Text = group.Name;
+ InfoSubLabel.Text = Loc.GetString("nano-chat-public-group-info",
+ ("count", group.MemberCount),
+ ("owner", group.OwnerName ?? Loc.GetString("nano-chat-group-unknown-owner")));
+ ActionButtons.Visible = true;
+ JoinButton.Visible = true;
+ AcceptButton.Visible = false;
+ DeclineButton.Visible = false;
+ }
+
+ public void SetInvite(NanoChatGroupInvite invite, uint number)
+ {
+ _number = number;
+ ChatButton.Visible = false;
+ InfoPanel.Visible = true;
+
+ InfoNameLabel.Text = invite.GroupName;
+ InfoSubLabel.Text = Loc.GetString("nano-chat-invite-row", ("from", invite.FromName));
+ ActionButtons.Visible = true;
+ JoinButton.Visible = false;
+ AcceptButton.Visible = true;
+ DeclineButton.Visible = true;
+ }
+
+ private void SetupButton(uint number, bool isSelected)
{
// Remove old handler if it exists
if (_pressHandler != null)
@@ -29,11 +89,7 @@ public void SetRecipient(NanoChatRecipient recipient, uint number, bool isSelect
_pressHandler = _ => OnPressed?.Invoke(_number);
ChatButton.OnPressed += _pressHandler;
- NameLabel.Text = recipient.Name;
- JobLabel.Text = recipient.JobTitle ?? "";
- JobLabel.Visible = !string.IsNullOrEmpty(recipient.JobTitle);
- UnreadIndicator.Visible = recipient.HasUnread;
-
ChatButton.ModulateSelfOverride = isSelected ? NanoChatMessageBubble.OwnMessageColor : null;
+ ChatButton.Disabled = false;
}
}
diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml
index 1a15b7966f0..a1d35966e43 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml
@@ -4,12 +4,70 @@
HorizontalExpand="True"
VerticalExpand="True"
StyleClasses="AngleRect">
-
-
+
+
+
+
+
+
+
-
+ Visible="False"
+ Margin="0 6 0 0">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml.cs b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml.cs
index 03d83207e61..db32f3fee83 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml.cs
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatLookupView.xaml.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Numerics;
using Content.Shared.ADT.CartridgeLoader.Cartridges;
using Robust.Client.AutoGenerated;
@@ -9,59 +10,150 @@ namespace Content.Client.ADT.CartridgeLoader.Cartridges;
[GenerateTypedNameReferences]
public sealed partial class NanoChatLookupView : PanelContainer
{
+ private const int MaxGroupNameLength = 32;
+
+ private List? _contacts;
+ private readonly HashSet _selectedMembers = new();
+ private HashSet _recipientNumbers = new();
+ private uint _ownNumber;
+ private bool _atRecipientLimit;
+ private bool _createMode;
+
public NanoChatLookupView()
{
RobustXamlLoader.Load(this);
+
+ CreateGroupButton.OnPressed += _ => EnterCreateMode();
+ CancelCreateButton.OnPressed += _ => ExitCreateMode();
+ ConfirmCreateButton.OnPressed += _ => CreateGroup();
+ GroupNameInput.OnTextChanged += args =>
+ {
+ if (args.Text.Length > MaxGroupNameLength)
+ GroupNameInput.Text = args.Text[..MaxGroupNameLength];
+ UpdateConfirmButton();
+ };
+ SearchInput.OnTextChanged += _ => UpdateContactList(_contacts, _ownNumber);
}
+
+ public event Action>? OnCreateGroupRequest;
public event Action? OnStartChat;
public void UpdateContactList(NanoChatUiState state)
+ {
+ _contacts = state.Contacts;
+ _ownNumber = state.OwnNumber;
+ _recipientNumbers = new HashSet(state.Recipients.Keys);
+ _atRecipientLimit = state.MaxRecipients <= state.Recipients.Count + state.Groups.Count;
+ UpdateContactList(_contacts, _ownNumber);
+ }
+
+ /// Перерисовывает список контактов с учётом поиска и режима создания группы.
+ private void UpdateContactList(List? contacts, uint ownNumber)
{
ContactsList.RemoveAllChildren();
- if (state.Contacts is not { } contacts)
+
+ if (contacts == null)
{
ContactsList.AddChild(new Label() { Text = Loc.GetString("nano-chat-look-up-no-server") });
return;
}
- for (var idx = 0; idx < contacts.Count; idx++)
+ var query = SearchInput.Text.Trim();
+ var digits = string.Concat(query.Where(char.IsDigit));
+ var filtered = contacts
+ .Where(c => c.Number != ownNumber)
+ .Where(c => MatchesQuery(c, query, digits))
+ .OrderBy(c => c.Name)
+ .ToList();
+
+ if (filtered.Count == 0)
+ {
+ // Номер вне телефонной книги: подсказываем вместо молчаливого пустого списка.
+ var hint = query.Length > 0 && digits.Length > 0
+ ? Loc.GetString("nano-chat-search-number-not-found", ("number", digits))
+ : Loc.GetString("nano-chat-search-no-results");
+ ContactsList.AddChild(new Label()
+ {
+ Text = hint,
+ StyleClasses = { "LabelSubText" },
+ HorizontalAlignment = HAlignment.Center,
+ Margin = new Thickness(0, 8),
+ });
+ return;
+ }
+
+ for (var idx = 0; idx < filtered.Count; idx++)
{
- var contact = contacts[idx];
+ var contact = filtered[idx];
+ var row = new BoxContainer()
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ HorizontalExpand = true,
+ VerticalAlignment = VAlignment.Center,
+ };
+
+ // Чекбокс выбора участника появляется только в режиме создания группы.
+ if (_createMode)
+ {
+ var memberBox = new CheckBox()
+ {
+ HorizontalExpand = false,
+ Margin = new Thickness(4, 0, 4, 0),
+ // Выбор переживает перерисовку (например, при вводе в поиск).
+ Pressed = _selectedMembers.Contains(contact.Number),
+ };
+ memberBox.OnToggled += _ =>
+ {
+ if (memberBox.Pressed)
+ _selectedMembers.Add(contact.Number);
+ else
+ _selectedMembers.Remove(contact.Number);
+ };
+ row.AddChild(memberBox);
+ }
+
var nameLabel = new Label()
{
Text = contact.Name,
- HorizontalAlignment = HAlignment.Left,
- HorizontalExpand = true
+ HorizontalExpand = true,
+ ClipText = true,
};
var numberLabel = new Label()
{
- Text = $"#{contacts[idx].Number:D4}",
+ Text = $"#{contact.Number:D4}",
HorizontalAlignment = HAlignment.Right,
Margin = new Thickness(0, 0, 36, 0),
};
- var startChatButton = new Button()
- {
- Text = "+",
- HorizontalAlignment = HAlignment.Right,
- MinSize = new Vector2(32, 32),
- MaxSize = new Vector2(32, 32),
- ToolTip = Loc.GetString("nano-chat-new-chat"),
- };
- startChatButton.AddStyleClass("OpenBoth");
- if (contact.Number == state.OwnNumber || state.Recipients.ContainsKey(contact.Number) || state.MaxRecipients <= state.Recipients.Count)
+ row.AddChild(nameLabel);
+ row.AddChild(numberLabel);
+
+ // В режиме создания кнопка «+» не нужна: строка выбирается чекбоксом.
+ if (!_createMode)
{
- startChatButton.Disabled = true;
+ var startChatButton = new Button()
+ {
+ Text = "+",
+ HorizontalAlignment = HAlignment.Right,
+ MinSize = new Vector2(32, 32),
+ MaxSize = new Vector2(32, 32),
+ ToolTip = Loc.GetString("nano-chat-new-chat"),
+ };
+ startChatButton.AddStyleClass("OpenBoth");
+ if (contact.Number == ownNumber ||
+ _recipientNumbers.Contains(contact.Number) ||
+ _atRecipientLimit)
+ {
+ startChatButton.Disabled = true;
+ }
+ startChatButton.OnPressed += _ => OnStartChat?.Invoke(contact);
+ row.AddChild(startChatButton);
}
- startChatButton.OnPressed += _ => OnStartChat?.Invoke(contact);
var panel = new PanelContainer()
{
HorizontalExpand = true,
};
-
- panel.AddChild(nameLabel);
- panel.AddChild(numberLabel);
- panel.AddChild(startChatButton);
+ panel.AddChild(row);
var styleClass = idx % 2 == 0 ? "PanelBackgroundBaseDark" : "PanelBackgroundLight";
panel.StyleClasses.Add(styleClass);
@@ -69,4 +161,55 @@ public void UpdateContactList(NanoChatUiState state)
ContactsList.AddChild(panel);
}
}
+
+ private static bool MatchesQuery(NanoChatRecipient contact, string query, string digits)
+ {
+ if (query.Length == 0)
+ return true;
+
+ // Совпадение по имени (без учёта регистра) или по номеру.
+ if (contact.Name.Contains(query, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ return digits.Length > 0 && contact.Number.ToString().Contains(digits);
+ }
+
+ private void EnterCreateMode()
+ {
+ _createMode = true;
+ CreateGroupPanel.Visible = true;
+ CreateGroupButtons.Visible = false;
+ ConfirmGroupButtons.Visible = true;
+ GroupNameInput.Text = string.Empty;
+ PublicCheck.Pressed = false;
+ UpdateConfirmButton();
+ UpdateContactList(_contacts, _ownNumber);
+ }
+
+ private void ExitCreateMode()
+ {
+ _createMode = false;
+ _selectedMembers.Clear();
+ CreateGroupPanel.Visible = false;
+ CreateGroupButtons.Visible = true;
+ ConfirmGroupButtons.Visible = false;
+ UpdateContactList(_contacts, _ownNumber);
+ }
+
+ private void UpdateConfirmButton()
+ {
+ ConfirmCreateButton.Disabled = string.IsNullOrWhiteSpace(GroupNameInput.Text);
+ }
+
+ private void CreateGroup()
+ {
+ var name = GroupNameInput.Text.Trim();
+ if (string.IsNullOrWhiteSpace(name))
+ return;
+
+ var members = _selectedMembers.ToList();
+
+ OnCreateGroupRequest?.Invoke(name, PublicCheck.Pressed, members);
+ ExitCreateMode();
+ }
}
diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml
index 6137b2ca69b..94bb67e107c 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml
@@ -2,8 +2,16 @@
xmlns="https://spacestation14.io"
xmlns:cartridges="clr-namespace:Content.Client.ADT.CartridgeLoader.Cartridges"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+ Orientation="Vertical"
HorizontalExpand="True">
+
+
+
diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs
index ffb20239e21..3484d50bdf2 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs
@@ -21,11 +21,19 @@ public NanoChatMessageBubble()
RobustXamlLoader.Load(this);
}
- public void SetMessage(NanoChatMessage message, bool isOwnMessage)
+ public void SetMessage(NanoChatMessage message, bool isOwnMessage, string? senderName = null)
{
if (MessagePanel.PanelOverride is not StyleBoxFlat)
return;
+ // In group chats show the sender's name above other players' messages.
+ SenderNameLabel.Visible = !isOwnMessage && senderName != null;
+ if (SenderNameLabel.Visible)
+ {
+ SenderNameLabel.Text = senderName;
+ SenderNameLabel.Modulate = new Color(0.72f, 0.9f, 0.72f);
+ }
+
// Configure message appearance
var style = (StyleBoxFlat)MessagePanel.PanelOverride;
style.BackgroundColor = isOwnMessage ? OwnMessageColor : OtherMessageColor;
diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUi.cs b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUi.cs
index 316d5b2b04d..2332b8e2b17 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUi.cs
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUi.cs
@@ -18,9 +18,9 @@ public override void Setup(BoundUserInterface userInterface, EntityUid? fragment
{
_fragment = new NanoChatUiFragment();
- _fragment.OnMessageSent += (type, number, content, job) =>
+ _fragment.OnMessageSent += nanoChatMessage =>
{
- SendNanoChatUiMessage(type, number, content, job, userInterface);
+ SendNanoChatUiMessage(nanoChatMessage, userInterface);
};
}
@@ -30,14 +30,10 @@ public override void UpdateState(BoundUserInterfaceState state)
_fragment?.UpdateState(cast);
}
- private static void SendNanoChatUiMessage(NanoChatUiMessageType type,
- uint? number,
- string? content,
- string? job,
+ private static void SendNanoChatUiMessage(NanoChatUiMessageEvent nanoChatMessage,
BoundUserInterface userInterface)
{
- var nanoChatMessage = new NanoChatUiMessageEvent(type, number, content, job);
var message = new CartridgeUiMessage(nanoChatMessage);
userInterface.SendMessage(message);
}
-}
\ No newline at end of file
+}
diff --git a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml
index 20b0556dc94..5b5e5135add 100644
--- a/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml
+++ b/Content.Client/ADT/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml
@@ -6,7 +6,7 @@
Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
- Margin="5">
+ Margin="3">
-
-
@@ -100,7 +82,7 @@
Orientation="Horizontal"
VerticalExpand="True"
HorizontalExpand="True"
- Margin="0 5 0 0">
+ Margin="0 3 0 0">
+ Margin="2">
-
+
+
+
@@ -41,18 +43,62 @@
HScrollEnabled="False"
HorizontalExpand="True"
MinSize="0 0"
- SizeFlagsStretchRatio="2"
VerticalExpand="True">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+