add server info screen, connection stats, and emoji picker
Server information (Monal-style): - Add ServerInformation model and ServerInfoView reachable from Settings, showing server software (XEP-0092), disco#info identities and features, conference (MUC) services via disco#items, and STUN/TURN via XEP-0215. - Add a curated XEP capability list with per-XEP success/error status derived from server/account disco features and connection flags. - Detect PEP (0163) from account disco pubsub#* features, HTTP Upload (0363) from the main domain plus its components, and add Roster Versioning (0237), Pre-Authenticated Roster Subscription (0379), and SASL SCRAM Downgrade Protection (0474, neutral because Martin does not expose SSDP). Connection statistics: - Add LumaConnectionStatsModule, an XmppStanzaFilter registered before stream management, to count sent / acknowledged / received stanzas. - Track last login and SMACKS session timestamps, and read SASL mechanisms and Client State Indication from the raw stream features. MAM / OMEMO handling: - Prefer a plaintext <body> fallback when OMEMO decryption fails instead of showing "failed to decrypt". - Keep the optimistic text of our own outgoing message when its echo cannot be decrypted back, instead of replacing it with a placeholder. Emoji picker: - Add EmojiCatalog and a cross-platform EmojiPickerView. - Open the picker for reactions (replacing the static quick list) and from the composer's smiley button, inserting the chosen emoji into the draft.
This commit is contained in:
1 parent
a886917dc8
commit
0317277d46
12 files changed
+1081
-24
No files matched your search
@@ -41,6 +41,7 @@ final class AppModel: ObservableObject {
|
||||
@Published var selectedConversationID: String?
|
||||
@Published private(set) var isOMEMOReady = false
|
||||
@Published private(set) var ownFingerprint: String?
|
||||
@Published private(set) var serverInformation: ServerInformation?
|
||||
@Published private(set) var isArchiveSyncing = false
|
||||
@Published private(set) var isLoadingOlderHistory = false
|
||||
@Published private(set) var hasMoreOlderHistory = true
|
||||
@@ -1621,6 +1622,15 @@ final class AppModel: ObservableObject {
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
func refreshServerInformation() async {
|
||||
do {
|
||||
serverInformation = try await xmpp.fetchServerInformation()
|
||||
} catch {
|
||||
// The screen shows its own error state; keep any previously loaded
|
||||
// snapshot rather than clearing it on a transient refresh failure.
|
||||
}
|
||||
}
|
||||
|
||||
func clearError() {
|
||||
errorMessage = nil
|
||||
}
|
||||
@@ -2521,6 +2531,25 @@ final class AppModel: ObservableObject {
|
||||
merged.security = previous.security
|
||||
merged.encryptionFingerprint = previous.encryptionFingerprint
|
||||
}
|
||||
if message.security == .decryptionFailed,
|
||||
previous.security != .decryptionFailed,
|
||||
!previous.body.isEmpty
|
||||
{
|
||||
// Our own outgoing message was rendered optimistically with the
|
||||
// text the user typed. Its server echo (carbon or MAM) cannot
|
||||
// be decrypted back (no encrypt-to-self key, or a consumed
|
||||
// session), so keep the original content instead of replacing
|
||||
// it with a "failed to decrypt" placeholder.
|
||||
merged.body = previous.body
|
||||
merged.security = previous.security
|
||||
merged.encryptionFingerprint = previous.encryptionFingerprint
|
||||
merged.kind = previous.kind
|
||||
merged.remoteAttachmentURL = previous.remoteAttachmentURL
|
||||
merged.localFilename = previous.localFilename
|
||||
merged.mimeType = previous.mimeType
|
||||
merged.duration = previous.duration
|
||||
merged.byteCount = previous.byteCount
|
||||
}
|
||||
messages[index] = merged
|
||||
if let stanzaID = merged.stanzaID {
|
||||
messageIndexByStanzaKey[
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import Foundation
|
||||
|
||||
/// A category of emojis shown in the emoji picker.
|
||||
struct EmojiCategory: Identifiable, Equatable, Sendable {
|
||||
let title: String
|
||||
let symbol: String
|
||||
let emojis: [String]
|
||||
|
||||
var id: String { title }
|
||||
}
|
||||
|
||||
/// Curated, widely-supported emoji set grouped into categories. Used by both
|
||||
/// the reaction picker and the composer's emoji picker.
|
||||
enum EmojiCatalog {
|
||||
static let categories: [EmojiCategory] = [
|
||||
EmojiCategory(
|
||||
title: "Смайлы",
|
||||
symbol: "face.smiling",
|
||||
emojis: [
|
||||
"😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "🥲", "☺️",
|
||||
"😊", "😇", "🙂", "🙃", "😉", "😌", "😍", "🥰", "😘", "😗",
|
||||
"😙", "😚", "😋", "😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓",
|
||||
"😎", "🥸", "🤩", "🥳", "😏", "😒", "😞", "😔", "😟", "😕",
|
||||
"🙁", "☹️", "😣", "😖", "😫", "😩", "🥺", "😢", "😭", "😤",
|
||||
"😠", "😡", "🤬", "🤯", "😳", "🥵", "🥶", "😱", "😨", "😰",
|
||||
"😥", "😓", "🤗", "🤔", "🫣", "🤭", "🫢", "🤫", "🤥", "😶",
|
||||
"😐", "😑", "😬", "🙄", "😯", "😦", "😧", "😮", "😲", "🥱",
|
||||
"😴", "🤤", "😪", "😵", "🤐", "🥴", "🤢", "🤮", "🤧", "😷",
|
||||
"🤒", "🤕", "🤑", "🤠", "😈", "👿", "👹", "👺", "🤡", "💩",
|
||||
"👻", "💀", "☠️", "👽", "👾", "🤖", "🎃", "😺", "😸", "😹",
|
||||
"😻", "😼", "😽", "🙀", "😿", "😾", "❤️", "🧡", "💛", "💚",
|
||||
"💙", "💜", "🖤", "🤍", "🤎", "💔", "❤️🔥", "❤️🩹", "💕", "💞",
|
||||
"💓", "💗", "💖", "💘", "💝", "💟", "❣️", "💌", "💢", "💥",
|
||||
"💫", "💦", "💨", "🕳️", "💬", "💭", "🗯️",
|
||||
]
|
||||
),
|
||||
EmojiCategory(
|
||||
title: "Жесты",
|
||||
symbol: "hand.raised",
|
||||
emojis: [
|
||||
"👋", "🤚", "🖐️", "✋", "🖖", "👌", "🤌", "🤏", "✌️", "🤞",
|
||||
"🫰", "🤟", "🤘", "🤙", "👈", "👉", "👆", "🖕", "👇", "☝️",
|
||||
"👍", "👎", "✊", "👊", "🤛", "🤜", "👏", "🙌", "🫶", "👐",
|
||||
"🤲", "🤝", "🙏", "✍️", "💅", "🤳", "💪", "🦾", "🦵", "🦶",
|
||||
"👂", "🦻", "👃", "🧠", "🦷", "🦴", "👀", "👁️", "👅", "👄",
|
||||
"👶", "🧒", "👦", "👧", "🧑", "👱", "👨", "🧔", "👩", "🧓",
|
||||
"👴", "👵", "🙍", "🙎", "🙅", "🙆", "💁", "🙋", "🧏", "🙇",
|
||||
"🤦", "🤷", "💆", "💇", "🚶", "🧍", "🧎", "🏃", "💃", "🕺",
|
||||
"🧖", "🧗", "🤺", "🏇", "⛷️", "🏂", "🏌️", "🏄", "🚣", "🏊",
|
||||
"⛹️", "🏋️", "🚴", "🚵", "🤸", "🤼", "🤽", "🤾", "🤹", "🧘",
|
||||
"🛀", "🛌", "🧑🤝🧑", "👭", "👫", "👬", "💏", "💑", "👪",
|
||||
]
|
||||
),
|
||||
EmojiCategory(
|
||||
title: "Животные и природа",
|
||||
symbol: "leaf",
|
||||
emojis: [
|
||||
"🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼", "🐻❄️", "🐨",
|
||||
"🐯", "🦁", "🐮", "🐷", "🐸", "🐵", "🙈", "🙉", "🙊", "🐔",
|
||||
"🐧", "🐦", "🐤", "🐣", "🐥", "🦆", "🦅", "🦉", "🦇", "🐺",
|
||||
"🐗", "🐴", "🦄", "🐝", "🪱", "🐛", "🦋", "🐌", "🐞", "🐜",
|
||||
"🪰", "🪲", "🪳", "🦟", "🦗", "🕷️", "🕸️", "🦂", "🐢", "🐍",
|
||||
"🦎", "🦖", "🦕", "🐙", "🦑", "🦐", "🦞", "🦀", "🐡", "🐠",
|
||||
"🐟", "🐬", "🐳", "🐋", "🦈", "🐊", "🐅", "🐆", "🦓", "🦍",
|
||||
"🦧", "🦣", "🐘", "🦛", "🦏", "🐪", "🐫", "🦒", "🦘", "🦬",
|
||||
"🐃", "🐂", "🐄", "🐎", "🐖", "🐏", "🐑", "🦙", "🐐", "🦌",
|
||||
"🐕", "🐩", "🦮", "🐈", "🐈⬛", "🪶", "🐓", "🦃", "🦤", "🦚",
|
||||
"🦜", "🦢", "🦩", "🕊️", "🐇", "🦝", "🦨", "🦡", "🦫", "🦦",
|
||||
"🦥", "🐁", "🐀", "🐿️", "🦔", "🐾", "🐉", "🐲", "🌵", "🎄",
|
||||
"🌲", "🌳", "🌴", "🌱", "🌿", "☘️", "🍀", "🎍", "🎋", "🍃",
|
||||
"🍂", "🍁", "🍄", "🐚", "🌾", "💐", "🌷", "🌹", "🥀", "🌺",
|
||||
"🌸", "🌼", "🌻", "🌞", "🌝", "🌛", "🌜", "🌚", "🌕", "🌖",
|
||||
"🌗", "🌘", "🌑", "🌒", "🌓", "🌔", "🌙", "🌎", "🌍", "🌏",
|
||||
"💫", "⭐", "🌟", "✨", "⚡", "☄️", "💥", "🔥", "🌪️", "🌈",
|
||||
"☀️", "🌤️", "⛅", "🌥️", "☁️", "🌦️", "🌧️", "⛈️", "🌩️", "🌨️",
|
||||
"❄️", "☃️", "⛄", "🌬️", "💨", "💧", "💦", "☔", "☂️", "🌊", "🌫️",
|
||||
]
|
||||
),
|
||||
EmojiCategory(
|
||||
title: "Еда и напитки",
|
||||
symbol: "fork.knife",
|
||||
emojis: [
|
||||
"🍏", "🍎", "🍐", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🫐",
|
||||
"🍈", "🍒", "🍑", "🥭", "🍍", "🥥", "🥝", "🍅", "🍆", "🥑",
|
||||
"🥦", "🥬", "🥒", "🌶️", "🫑", "🌽", "🥕", "🫒", "🧄", "🧅",
|
||||
"🥔", "🍠", "🥐", "🥯", "🍞", "🥖", "🥨", "🧀", "🥚", "🍳",
|
||||
"🧈", "🥞", "🧇", "🥓", "🥩", "🍗", "🍖", "🦴", "🌭", "🍔",
|
||||
"🍟", "🍕", "🫓", "🥪", "🥙", "🧆", "🌮", "🌯", "🫔", "🥗",
|
||||
"🥘", "🫕", "🥫", "🍝", "🍜", "🍲", "🍛", "🍣", "🍱", "🥟",
|
||||
"🦪", "🍤", "🍙", "🍚", "🍘", "🍥", "🥠", "🥮", "🍢", "🍡",
|
||||
"🍧", "🍨", "🍦", "🥧", "🧁", "🍰", "🎂", "🍮", "🍭", "🍬",
|
||||
"🍫", "🍿", "🍩", "🍪", "🌰", "🥜", "🍯", "🥛", "🍼", "🫖",
|
||||
"☕", "🍵", "🧃", "🥤", "🧋", "🍶", "🍺", "🍻", "🥂", "🍷",
|
||||
"🥃", "🍸", "🍹", "🧉", "🍾", "🧊", "🥄", "🍴", "🍽️", "🥣",
|
||||
"🥡", "🥢",
|
||||
]
|
||||
),
|
||||
EmojiCategory(
|
||||
title: "Активности",
|
||||
symbol: "soccerball",
|
||||
emojis: [
|
||||
"⚽", "🏀", "🏈", "⚾", "🥎", "🎾", "🏐", "🏉", "🥏", "🎱",
|
||||
"🪀", "🏓", "🏸", "🏒", "🏑", "🥍", "🏏", "🪃", "🥅", "⛳",
|
||||
"🪁", "🏹", "🎣", "🤿", "🥊", "🥋", "🎽", "🛹", "🛼", "🛷",
|
||||
"⛸️", "🥌", "🎿", "⛷️", "🏂", "🪂", "🏋️", "🤼", "🤸", "⛹️",
|
||||
"🤺", "🤾", "🏌️", "🏇", "🧘", "🏄", "🏊", "🤽", "🚣", "🧗",
|
||||
"🚵", "🚴", "🏆", "🥇", "🥈", "🥉", "🏅", "🎖️", "🏵️", "🎗️",
|
||||
"🎫", "🎟️", "🎪", "🤹", "🎭", "🩰", "🎨", "🎬", "🎤", "🎧",
|
||||
"🎼", "🎹", "🥁", "🪘", "🎷", "🎺", "🪗", "🎸", "🪕", "🎻",
|
||||
"🎲", "♟️", "🎯", "🎳", "🎮", "🎰", "🧩",
|
||||
]
|
||||
),
|
||||
EmojiCategory(
|
||||
title: "Путешествия",
|
||||
symbol: "airplane",
|
||||
emojis: [
|
||||
"🚗", "🚕", "🚙", "🚌", "🚎", "🏎️", "🚓", "🚑", "🚒", "🚐",
|
||||
"🛻", "🚚", "🚛", "🚜", "🛵", "🏍️", "🛺", "🚲", "🛴", "🛹",
|
||||
"🚨", "🚔", "🚍", "🚘", "🚖", "🚡", "🚠", "🚟", "🚃", "🚋",
|
||||
"🚞", "🚝", "🚄", "🚅", "🚈", "🚂", "🚆", "🚇", "🚊", "🚉",
|
||||
"✈️", "🛫", "🛬", "🛩️", "💺", "🛰️", "🚀", "🛸", "🚁", "🛶",
|
||||
"⛵", "🚤", "🛥️", "🛳️", "⛴️", "🚢", "⚓", "🪝", "🚧", "🚦",
|
||||
"🚥", "🚏", "🗺️", "🗿", "🗽", "🗼", "🏰", "🏯", "🏟️", "🎡",
|
||||
"🎢", "🎠", "⛲", "⛱️", "🏖️", "🏝️", "🏜️", "🌋", "⛰️", "🏔️",
|
||||
"🗻", "🏕️", "⛺", "🛖", "🏠", "🏡", "🏘️", "🏚️", "🏗️", "🏭",
|
||||
"🏢", "🏬", "🏣", "🏤", "🏥", "🏦", "🏨", "🏪", "🏫", "🏩",
|
||||
"💒", "🏛️", "⛪", "🕌", "🕍", "🛕", "🕋", "⛩️", "🛤️", "🛣️",
|
||||
"🗾", "🎑", "🏞️", "🌅", "🌄", "🌠", "🎇", "🎆", "🌇", "🌆",
|
||||
"🏙️", "🌃", "🌌", "🌉", "🌁",
|
||||
]
|
||||
),
|
||||
EmojiCategory(
|
||||
title: "Предметы",
|
||||
symbol: "lightbulb",
|
||||
emojis: [
|
||||
"⌚", "📱", "📲", "💻", "⌨️", "🖥️", "🖨️", "🖱️", "🖲️", "🕹️",
|
||||
"🗜️", "💽", "💾", "💿", "📀", "📼", "📷", "📸", "📹", "🎥",
|
||||
"📽️", "🎞️", "📞", "☎️", "📟", "📠", "📺", "📻", "🎙️", "🎚️",
|
||||
"🎛️", "🧭", "⏱️", "⏲️", "⏰", "🕰️", "⌛", "⏳", "📡", "🔋",
|
||||
"🔌", "💡", "🔦", "🕯️", "🪔", "🧯", "🛢️", "💸", "💵", "💴",
|
||||
"💶", "💷", "🪙", "💰", "💳", "💎", "⚖️", "🪜", "🧰", "🪛",
|
||||
"🔧", "🔨", "⚒️", "🛠️", "⛏️", "🪚", "🔩", "⚙️", "🪤", "🧱",
|
||||
"⛓️", "🧲", "🔫", "💣", "🧨", "🪓", "🔪", "🗡️", "⚔️", "🛡️",
|
||||
"🚬", "⚰️", "🪦", "⚱️", "🏺", "🔮", "📿", "🧿", "💈", "⚗️",
|
||||
"🔭", "🔬", "🕳️", "🩹", "🩺", "💊", "💉", "🩸", "🧬", "🦠",
|
||||
"🧫", "🧪", "🌡️", "🧹", "🪠", "🧺", "🧻", "🚽", "🚰", "🚿",
|
||||
"🛁", "🛀", "🧼", "🪥", "🪒", "🧽", "🪣", "🧴", "🛎️", "🔑",
|
||||
"🗝️", "🚪", "🪑", "🛋️", "🛏️", "🛌", "🧸", "🪆", "🖼️", "🪞",
|
||||
"🪟", "🛍️", "🛒", "🎁", "🎈", "🎏", "🎀", "🪄", "🪅", "🎊",
|
||||
"🎉", "🎎", "🏮", "🎐", "🧧", "✉️", "📩", "📨", "📧", "💌",
|
||||
"📥", "📤", "📦", "🏷️", "🪧", "📪", "📫", "📬", "📭", "📮",
|
||||
"📯", "📜", "📃", "📄", "📑", "🧾", "📊", "📈", "📉", "🗒️",
|
||||
"🗓️", "📆", "📅", "🗑️", "📇", "🗃️", "🗳️", "🗄️", "📋", "📁",
|
||||
"📂", "🗂️", "🗞️", "📰", "📓", "📔", "📒", "📕", "📗", "📘",
|
||||
"📙", "📚", "📖", "🔖", "🧷", "🔗", "📎", "🖇️", "📐", "📏",
|
||||
"🧮", "📌", "📍", "✂️", "🖊️", "🖋️", "✒️", "🖌️", "🖍️", "📝",
|
||||
"✏️", "🔍", "🔎", "🔏", "🔐", "🔒", "🔓",
|
||||
]
|
||||
),
|
||||
EmojiCategory(
|
||||
title: "Символы",
|
||||
symbol: "heart",
|
||||
emojis: [
|
||||
"❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "💔",
|
||||
"❣️", "💕", "💞", "💓", "💗", "💖", "💘", "💝", "💟", "♥️",
|
||||
"♠️", "♣️", "♦️", "🃏", "🀄", "🎴", "🔇", "🔈", "🔉", "🔊",
|
||||
"📢", "📣", "📯", "🔔", "🔕", "🎼", "🎵", "🎶", "🎙️", "🎚️",
|
||||
"🎛️", "🎤", "🎧", "📻", "🎷", "🪗", "🎸", "🎹", "🎺", "🎻",
|
||||
"🥁", "🪘", "📱", "📲", "☎️", "📞", "📟", "📠", "🔋", "🔌",
|
||||
"💻", "🖥️", "🖨️", "⌨️", "🖱️", "🖲️", "💽", "💾", "💿", "📀",
|
||||
"🧮", "🎥", "🎞️", "📽️", "🎬", "📺", "📷", "📸", "📹", "📼",
|
||||
"🔍", "🔎", "🕯️", "💡", "🔦", "🏮", "🪔", "📔", "📕", "📖",
|
||||
"📗", "📘", "📙", "📚", "📓", "📒", "📃", "📜", "📄", "📰",
|
||||
"🗞️", "📑", "🔖", "🏷️", "💰", "🪙", "💴", "💵", "💶", "💷",
|
||||
"💸", "💳", "🧾", "✉️", "📧", "📨", "📩", "📤", "📥", "📦",
|
||||
"📫", "📪", "📬", "📭", "📮", "🗳️", "✏️", "✒️", "🖋️", "🖊️",
|
||||
"🖌️", "🖍️", "📝", "💼", "📁", "📂", "🗂️", "📅", "📆", "🗒️",
|
||||
"🗓️", "📇", "📈", "📉", "📊", "📋", "📌", "📍", "📎", "🖇️",
|
||||
"📏", "📐", "✂️", "🗃️", "🗄️", "🗑️", "🔒", "🔓", "🔏", "🔐",
|
||||
"🔑", "🗝️", "🔨", "🪓", "⛏️", "⚒️", "🛠️", "🗡️", "⚔️", "🔫",
|
||||
"🪃", "🏹", "🛡️", "🪚", "🔧", "🪛", "🔩", "⚙️", "🗜️", "⚖️",
|
||||
"🦯", "🔗", "⛓️", "🪝", "🧰", "🧲", "🪜", "⚗️", "🧪", "🧫",
|
||||
"🧬", "🔬", "🔭", "📡", "💉", "🩸", "💊", "🩹", "🩺", "🚪",
|
||||
"🛏️", "🛋️", "🪑", "🚽", "🪠", "🚿", "🛁", "🪒", "🧴", "🧷",
|
||||
"🧹", "🧺", "🧻", "🧼", "🪥", "🧽", "🧯", "🛒", "🚬", "⚰️",
|
||||
"🪦", "⚱️", "🗿", "🪧", "🪪", "🆗", "🆕", "🆒", "🆓", "🆙",
|
||||
"🆖", "🆚", "🈁", "🈂️", "🈷️", "🈶", "🈯", "🉐", "🈹", "🈚",
|
||||
"🈲", "🉑", "🈸", "🈴", "🈳", "㊗️", "㊙️", "🈺", "🈵", "🔴",
|
||||
"🟠", "🟡", "🟢", "🔵", "🟣", "🟤", "⚫", "⚪", "🟥", "🟧",
|
||||
"🟨", "🟩", "🟦", "🟪", "🟫", "⬛", "⬜", "◼️", "◻️", "◾",
|
||||
"◽", "▪️", "▫️", "🔶", "🔷", "🔸", "🔹", "🔺", "🔻", "💠",
|
||||
"🔘", "🔳", "🔲",
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
static var allEmojis: [String] {
|
||||
categories.flatMap(\.emojis)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import Foundation
|
||||
|
||||
/// Snapshot of everything the "server details" screen shows, modelled after
|
||||
/// Monal's `ServerDetails`: server software (XEP-0092), disco#info identities
|
||||
/// and features (XEP-0030), conference (MUC) services, STUN/TURN services
|
||||
/// (XEP-0215) and a few connection-level capability flags.
|
||||
struct ServerInformation: Equatable, Sendable {
|
||||
enum CapabilityStatus: Equatable, Sendable {
|
||||
case success, normal, warning, error
|
||||
}
|
||||
|
||||
struct Software: Equatable, Sendable {
|
||||
let name: String?
|
||||
let version: String?
|
||||
let os: String?
|
||||
}
|
||||
|
||||
struct Identity: Equatable, Sendable, Identifiable {
|
||||
let category: String
|
||||
let type: String
|
||||
let name: String?
|
||||
|
||||
var id: String { "\(category)/\(type)" }
|
||||
|
||||
var title: String { "\(category) / \(type)" }
|
||||
}
|
||||
|
||||
struct ConferenceServer: Equatable, Sendable, Identifiable {
|
||||
let jid: String
|
||||
let name: String?
|
||||
let type: String
|
||||
let category: String
|
||||
|
||||
var id: String { jid }
|
||||
}
|
||||
|
||||
struct ExternalService: Equatable, Sendable, Identifiable {
|
||||
let type: String
|
||||
let host: String
|
||||
let port: UInt16?
|
||||
let transport: String?
|
||||
|
||||
var id: String { "\(type)|\(host)|\(port.map(String.init) ?? "")" }
|
||||
}
|
||||
|
||||
struct ConnectionStats: Equatable, Sendable {
|
||||
let lastLogin: Date?
|
||||
let smacksSessionEstablished: Date?
|
||||
let sent: UInt32
|
||||
let acknowledged: UInt32
|
||||
let received: UInt32
|
||||
|
||||
var unacknowledged: UInt32 {
|
||||
sent > acknowledged ? sent - acknowledged : 0
|
||||
}
|
||||
}
|
||||
|
||||
struct Capability: Equatable, Sendable, Identifiable {
|
||||
let title: String
|
||||
let detail: String
|
||||
let status: CapabilityStatus
|
||||
|
||||
var id: String { title }
|
||||
}
|
||||
|
||||
let serverDomain: String
|
||||
let software: Software
|
||||
let identities: [Identity]
|
||||
let serverFeatures: [String]
|
||||
let accountFeatures: [String]
|
||||
let conferenceServers: [ConferenceServer]
|
||||
let externalServices: [ExternalService]
|
||||
let connectionStats: ConnectionStats
|
||||
let saslMethods: [String]
|
||||
let supportsStreamManagement: Bool
|
||||
let supportsCarbons: Bool
|
||||
let supportsClientState: Bool
|
||||
let supportsHTTPUpload: Bool
|
||||
let supportsRosterVersioning: Bool
|
||||
let supportsRosterPreApproval: Bool
|
||||
|
||||
/// The curated XEP list Monal shows, with a status derived from the server
|
||||
/// and account disco features plus the connection-level flags.
|
||||
var capabilities: [Capability] {
|
||||
let server = Set(serverFeatures)
|
||||
let account = Set(accountFeatures)
|
||||
let supportsPubSub = account.contains { $0.hasPrefix("http://jabber.org/protocol/pubsub") }
|
||||
return [
|
||||
Capability(
|
||||
title: "XEP-0163 Personal Eventing Protocol",
|
||||
detail: "Публикация событий состояния аккаунта (аватары, статусы) через publish-subscribe.",
|
||||
status: supportsPubSub ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0191 Blocking Command",
|
||||
detail: "Расширение XMPP для блокировки контактов.",
|
||||
status: server.contains("urn:xmpp:blocking") ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0198 Stream Management",
|
||||
detail: "Возобновление потока при обрыве соединения — быстрее переподключение и экономия заряда.",
|
||||
status: supportsStreamManagement ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0199 XMPP Ping",
|
||||
detail: "Пинг уровня приложения поверх XML-потока.",
|
||||
status: server.contains("urn:xmpp:ping") ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0215 External Service Discovery",
|
||||
detail: "Обнаружение внешних сервисов (STUN/TURN) для аудио- и видеозвонков.",
|
||||
status: server.contains("urn:xmpp:extdisco:2") ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0237 Roster Versioning",
|
||||
detail: "Версионирование ростера, чтобы сервер не отправлял его повторно, если он не менялся.",
|
||||
status: supportsRosterVersioning ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0280 Message Carbons",
|
||||
detail: "Синхронизация сообщений между всеми вашими устройствами.",
|
||||
status: supportsCarbons ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0313 Message Archive Management",
|
||||
detail: "Доступ к архиву сообщений на сервере.",
|
||||
status: account.contains("urn:xmpp:mam:2") ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0352 Client State Indication",
|
||||
detail: "Указывает, активно ли устройство. Экономит заряд.",
|
||||
status: supportsClientState ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0357 Push Notifications",
|
||||
detail: "Push-уведомления через Apple даже когда приложение выгружено.",
|
||||
status: account.contains("urn:xmpp:push:0") ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0363 HTTP File Upload",
|
||||
detail: "Загрузка файлов на сервер для обмена с собеседниками.",
|
||||
status: supportsHTTPUpload ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0379 Pre-Authenticated Roster Subscription",
|
||||
detail: "Ссылки для предварительно аутентифицированной подписки на присутствие.",
|
||||
status: supportsRosterPreApproval ? .success : .error
|
||||
),
|
||||
Capability(
|
||||
title: "XEP-0474 SASL SCRAM Downgrade Protection",
|
||||
detail: "Защита SASL/SASL2-рукопожатия от понижения метода и channel-binding. Определяется во время SCRAM-аутентификации, а не рекламируется статически.",
|
||||
status: .normal
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user