From f7abd1dba04edb9179ffbcd0e052423915c99223 Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Thu, 21 May 2026 00:45:39 +0300 Subject: [PATCH] Expose leave chat to iOS bridge --- .../Sources/TeleTuiIOSCore/Bridge.swift | 7 ++++++ .../TeleTuiIOSCore/UniFfiSessionBridge.swift | 4 ++++ .../Sources/TeleTuiIOSCore/ViewModels.swift | 9 ++++++++ .../Sources/TeleTuiIOSSmokeTests/main.swift | 4 ++++ crates/tele-core/src/session.rs | 5 +++++ crates/tele-ios-ffi/src/lib.rs | 22 +++++++++++++++++++ scripts/smoke-ios-ffi-swift.sh | 6 +++++ 7 files changed, 57 insertions(+) diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift index b23d194..9028d65 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift @@ -12,6 +12,7 @@ public protocol SessionBridge: Sendable { func loadHistory(chatId: Int64) async throws -> [Message] func searchMessages(chatId: Int64, query: String) async throws -> [Message] func openProfile(chatId: Int64) async throws -> Profile + func leaveChat(chatId: Int64) async throws func sendMessage(chatId: Int64, text: String, replyToMessageId: Int64?) async throws -> Message func editMessage(chatId: Int64, messageId: Int64, text: String) async throws -> Message func deleteMessages(chatId: Int64, messageIds: [Int64]) async throws @@ -132,6 +133,12 @@ public actor FakeSessionBridge: SessionBridge { return profile } + public func leaveChat(chatId: Int64) async throws { + chats.removeAll { $0.id == chatId } + messages.removeValue(forKey: chatId) + events.append(.chatListChanged(chats)) + } + public func sendMessage(chatId: Int64, text: String, replyToMessageId: Int64?) async throws -> Message { let message = Message( id: nextMessageId, diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift index 14fbd6a..83c00d7 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift @@ -73,6 +73,10 @@ public actor UniFfiSessionBridge: SessionBridge { try Self.mapProfile(handle.openProfile(chatId: chatId)) } + public func leaveChat(chatId: Int64) async throws { + try handle.leaveChat(chatId: chatId) + } + public func sendMessage(chatId: Int64, text: String, replyToMessageId: Int64?) async throws -> Message { try Self.mapMessage( handle.sendMessage(chatId: chatId, text: text, replyToMessageId: replyToMessageId), diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift index 55db870..63c8825 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift @@ -284,6 +284,15 @@ public final class ProfileViewModel: ObservableObject { errorMessage = error.localizedDescription } } + + public func leave(chatId: Int64) async { + do { + try await bridge.leaveChat(chatId: chatId) + errorMessage = nil + } catch { + errorMessage = error.localizedDescription + } + } } @MainActor diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift index 371705f..6d36275 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift @@ -208,6 +208,10 @@ struct TeleTuiIOSSmokeTests { await viewModel.load(chatId: 1) precondition(viewModel.profile?.title == "Saved Messages") precondition(viewModel.profile?.username == "saved") + + await viewModel.leave(chatId: 1) + let chats = try await bridge.loadChats(folderId: nil) + precondition(!chats.contains { $0.id == 1 }) } private static func appStorageUsesApplicationSupportStyleAccountPaths() { diff --git a/crates/tele-core/src/session.rs b/crates/tele-core/src/session.rs index 6556028..d34bfdb 100644 --- a/crates/tele-core/src/session.rs +++ b/crates/tele-core/src/session.rs @@ -249,6 +249,10 @@ impl CoreSession { Ok(profile) } + pub async fn leave_chat(&mut self, chat_id: ChatId) -> Result<(), String> { + self.client.leave_chat(chat_id).await + } + pub async fn set_draft(&mut self, chat_id: ChatId, text: String) -> Result<(), String> { self.client.set_draft_message(chat_id, text.clone()).await?; self.enqueue_event(CoreEvent::DraftChanged(CoreDraft { chat_id, text })); @@ -851,6 +855,7 @@ mod tests { session.emit_auth_state(); session.emit_network_state(); let loaded_profile = session.open_profile(ChatId::new(42)).await.unwrap(); + session.leave_chat(ChatId::new(42)).await.unwrap(); session .set_draft(ChatId::new(42), "Later".to_string()) .await diff --git a/crates/tele-ios-ffi/src/lib.rs b/crates/tele-ios-ffi/src/lib.rs index 073d938..3f06a7d 100644 --- a/crates/tele-ios-ffi/src/lib.rs +++ b/crates/tele-ios-ffi/src/lib.rs @@ -638,6 +638,13 @@ impl SessionHandle { .map_err(IosFfiError::from) } + pub fn leave_chat(&self, chat_id: i64) -> Result<(), IosFfiError> { + let mut session = self.session.lock().expect("session mutex poisoned"); + self.runtime + .block_on(session.leave_chat(ChatId::new(chat_id))) + .map_err(IosFfiError::from) + } + pub fn send_message( &self, chat_id: i64, @@ -1072,6 +1079,16 @@ impl SessionHandle { Ok(profile) } + pub fn leave_chat(&self, chat_id: i64) -> Result<(), IosFfiError> { + let mut state = self.state.lock().expect("session mutex poisoned"); + state.chats.retain(|chat| chat.id != chat_id); + state.messages.remove(&chat_id); + state.profiles.remove(&chat_id); + let chats = state.chats.clone(); + state.events.push(IosEvent::ChatListChanged { chats }); + Ok(()) + } + pub fn send_message( &self, chat_id: i64, @@ -1374,6 +1391,11 @@ mod tests { assert!(events .iter() .any(|event| matches!(event, IosEvent::IncomingNotificationCandidate { .. }))); + + session.leave_chat(chats[0].id).unwrap(); + if !cfg!(feature = "core-session") { + assert!(session.load_history(chats[0].id, 20).unwrap().is_empty()); + } } #[test] diff --git a/scripts/smoke-ios-ffi-swift.sh b/scripts/smoke-ios-ffi-swift.sh index 52d3413..a118490 100755 --- a/scripts/smoke-ios-ffi-swift.sh +++ b/scripts/smoke-ios-ffi-swift.sh @@ -103,6 +103,12 @@ struct Smoke { require(events.contains { if case .messageAdded = $0 { return true }; return false }, "expected messageAdded event") require(events.contains { if case .incomingNotificationCandidate = $0 { return true }; return false }, "expected notification candidate") + try session.leaveChat(chatId: chat.id) + let chatsAfterLeave = try session.loadChats(limit: 20) + require(chatsAfterLeave.isEmpty, "expected chat to be removed after leave") + let leaveEvents = session.pollEvents() + require(leaveEvents.contains { if case .chatListChanged = $0 { return true }; return false }, "expected chatListChanged event") + print("tele-ios-ffi Swift smoke passed") } }