diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift index a0fa18a..22375be 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift @@ -173,6 +173,7 @@ public enum SessionEvent: Equatable, Sendable { case reactionChanged(Int64, Int64, [Reaction]) case incomingNotificationCandidate(ChatSummary, Message, String) case networkChanged(NetworkState) + case typingChanged(TypingState) case draftChanged(Draft) case profileLoaded(Profile) case mediaDownloadProgress(fileId: Int32, downloadedSize: Int64, totalSize: Int64) @@ -185,3 +186,8 @@ public enum NetworkState: Equatable, Sendable { case updating case ready } + +public enum TypingState: Equatable, Sendable { + case idle + case typing(chatId: Int64, userId: Int64, text: String) +} diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift index 19cd859..14fbd6a 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift @@ -155,6 +155,15 @@ public actor UniFfiSessionBridge: SessionBridge { } } + private static func mapTypingState(_ state: IosTypingState) -> TypingState { + switch state { + case .idle: + .idle + case let .typing(chatId, userId, text): + .typing(chatId: chatId, userId: userId, text: text) + } + } + private static func mapFolder(_ folder: IosFolder) -> Folder { Folder(id: folder.id, name: folder.name) } @@ -236,6 +245,8 @@ public actor UniFfiSessionBridge: SessionBridge { ) case let .networkChanged(state): .networkChanged(mapNetworkState(state)) + case let .typingChanged(state): + .typingChanged(mapTypingState(state)) case let .draftChanged(draft): .draftChanged(mapDraft(draft)) case let .profileLoaded(profile): diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift index 13aae01..55db870 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/ViewModels.swift @@ -6,6 +6,7 @@ public final class SessionStore: ObservableObject { @Published public private(set) var account: Account @Published public private(set) var authState: AuthState = .waitTdlibParameters @Published public private(set) var networkState: NetworkState = .ready + @Published public private(set) var typingState: TypingState = .idle @Published public private(set) var errorMessage: String? public let bridge: SessionBridge @@ -40,6 +41,8 @@ public final class SessionStore: ObservableObject { authState = state case let .networkChanged(state): networkState = state + case let .typingChanged(state): + typingState = state default: break } diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift index 1fc7a33..371705f 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift @@ -25,6 +25,8 @@ struct TeleTuiIOSSmokeTests { precondition(store.authState == .waitPhoneNumber) await store.refreshNetworkState() precondition(store.networkState == .ready) + store.apply(events: [.typingChanged(.typing(chatId: 1, userId: 10, text: "typing"))]) + precondition(store.typingState == .typing(chatId: 1, userId: 10, text: "typing")) viewModel.phone = "+10000000000" await viewModel.submitCurrentStep() diff --git a/crates/tele-core/src/session.rs b/crates/tele-core/src/session.rs index 775e400..6556028 100644 --- a/crates/tele-core/src/session.rs +++ b/crates/tele-core/src/session.rs @@ -813,6 +813,18 @@ mod tests { CoreNetworkState::from(&NetworkState::WaitingForNetwork), CoreNetworkState::WaitingForNetwork ); + assert_eq!( + CoreTypingState::Typing { + chat_id: ChatId::new(42), + user_id: UserId::new(7), + text: "typing".to_string(), + }, + CoreTypingState::Typing { + chat_id: ChatId::new(42), + user_id: UserId::new(7), + text: "typing".to_string(), + } + ); } #[tokio::test] diff --git a/crates/tele-ios-ffi/src/lib.rs b/crates/tele-ios-ffi/src/lib.rs index 35326cd..073d938 100644 --- a/crates/tele-ios-ffi/src/lib.rs +++ b/crates/tele-ios-ffi/src/lib.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use tele_core::session::{ CoreAuthState, CoreChatSummary, CoreDownloadedFile, CoreDraft, CoreEvent, CoreFolder, CoreMedia, CoreMessage, CoreNetworkState, CoreProfile, CoreReaction, CoreSearchResult, - CoreSession, + CoreSession, CoreTypingState, }; #[cfg(feature = "core-session")] use tele_core::tdlib::{ChatInfo, MessageBuilder, NetworkState, ProfileInfo}; @@ -86,6 +86,30 @@ impl From for IosNetworkState { } } +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum)] +pub enum IosTypingState { + Idle, + Typing { + chat_id: i64, + user_id: i64, + text: String, + }, +} + +#[cfg(feature = "core-session")] +impl From for IosTypingState { + fn from(value: CoreTypingState) -> Self { + match value { + CoreTypingState::Idle => Self::Idle, + CoreTypingState::Typing { chat_id, user_id, text } => Self::Typing { + chat_id: chat_id.as_i64(), + user_id: user_id.as_i64(), + text, + }, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum)] pub enum IosDownloadState { NotDownloaded, @@ -412,6 +436,9 @@ pub enum IosEvent { downloaded_size: i64, total_size: i64, }, + TypingChanged { + state: IosTypingState, + }, } #[cfg(feature = "core-session")] @@ -455,7 +482,7 @@ impl From for IosEvent { CoreEvent::MediaDownloadProgress { file_id, downloaded_size, total_size } => { Self::MediaDownloadProgress { file_id, downloaded_size, total_size } } - CoreEvent::TypingChanged(_) => Self::NetworkChanged { state: IosNetworkState::Ready }, + CoreEvent::TypingChanged(state) => Self::TypingChanged { state: state.into() }, } } } @@ -1363,4 +1390,21 @@ mod tests { assert!(format!("{error}").contains("real TDLib sessions")); } + + #[cfg(feature = "core-session")] + #[test] + fn typing_events_are_preserved_for_ios() { + let event = IosEvent::from(CoreEvent::TypingChanged(CoreTypingState::Typing { + chat_id: ChatId::new(1), + user_id: tele_core::types::UserId::new(2), + text: "typing".to_string(), + })); + + assert!(matches!( + event, + IosEvent::TypingChanged { + state: IosTypingState::Typing { chat_id: 1, user_id: 2, text } + } if text == "typing" + )); + } }