Skip to main content

iris_chat_core/
state.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(uniffi::Enum, Clone, Debug, PartialEq, Eq)]
4pub enum Screen {
5    Welcome,
6    CreateAccount,
7    RestoreAccount,
8    AddDevice,
9    ChatList,
10    NewChat,
11    NewGroup,
12    CreateInvite,
13    JoinInvite,
14    Settings,
15    Chat { chat_id: String },
16    GroupDetails { group_id: String },
17    DeviceRoster,
18    AwaitingDeviceApproval,
19    DeviceRevoked,
20}
21
22#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
23pub struct Router {
24    pub default_screen: Screen,
25    pub screen_stack: Vec<Screen>,
26}
27
28#[derive(uniffi::Record, Clone, Debug, Default, PartialEq, Eq)]
29pub struct BusyState {
30    pub creating_account: bool,
31    pub restoring_session: bool,
32    pub linking_device: bool,
33    pub creating_chat: bool,
34    pub creating_group: bool,
35    pub sending_message: bool,
36    pub updating_roster: bool,
37    pub updating_group: bool,
38    pub creating_invite: bool,
39    pub accepting_invite: bool,
40    pub syncing_network: bool,
41    pub uploading_attachment: bool,
42}
43
44#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
45pub struct PreferencesSnapshot {
46    pub send_typing_indicators: bool,
47    pub send_read_receipts: bool,
48    pub desktop_notifications_enabled: bool,
49    pub invite_acceptance_notifications_enabled: bool,
50    pub startup_at_login_enabled: bool,
51    pub nearby_bluetooth_enabled: bool,
52    pub nearby_lan_enabled: bool,
53    pub nostr_relay_urls: Vec<String>,
54    pub image_proxy_enabled: bool,
55    pub image_proxy_url: String,
56    pub image_proxy_key_hex: String,
57    pub image_proxy_salt_hex: String,
58    pub muted_chat_ids: Vec<String>,
59    /// User-configurable notification server URL. Empty string means
60    /// "use the platform default" (notifications.iris.to in release,
61    /// notifications-sandbox.iris.to in debug). When non-empty, the
62    /// shells should pass this as the override to
63    /// `build_mobile_push_*_subscription_request`.
64    pub mobile_push_server_url: String,
65}
66
67impl Default for PreferencesSnapshot {
68    fn default() -> Self {
69        Self {
70            send_typing_indicators: false,
71            send_read_receipts: true,
72            desktop_notifications_enabled: true,
73            invite_acceptance_notifications_enabled: true,
74            startup_at_login_enabled: true,
75            nearby_bluetooth_enabled: false,
76            nearby_lan_enabled: false,
77            nostr_relay_urls: crate::core::configured_relays(),
78            image_proxy_enabled: true,
79            image_proxy_url: crate::image_proxy::DEFAULT_IMAGE_PROXY_URL.to_string(),
80            image_proxy_key_hex: crate::image_proxy::DEFAULT_IMAGE_PROXY_KEY_HEX.to_string(),
81            image_proxy_salt_hex: crate::image_proxy::DEFAULT_IMAGE_PROXY_SALT_HEX.to_string(),
82            muted_chat_ids: Vec::new(),
83            mobile_push_server_url: String::new(),
84        }
85    }
86}
87
88#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
89pub struct OutgoingAttachment {
90    pub file_path: String,
91    pub filename: String,
92}
93
94#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
95pub struct AttachmentDownloadResult {
96    pub data_base64: Option<String>,
97    pub error: Option<String>,
98}
99
100#[derive(uniffi::Enum, Clone, Debug, PartialEq, Eq)]
101pub enum DeviceAuthorizationState {
102    Authorized,
103    AwaitingApproval,
104    Revoked,
105}
106
107#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
108pub struct AccountSnapshot {
109    pub public_key_hex: String,
110    pub npub: String,
111    pub display_name: String,
112    pub picture_url: Option<String>,
113    pub device_public_key_hex: String,
114    pub device_npub: String,
115    pub has_owner_signing_authority: bool,
116    pub authorization_state: DeviceAuthorizationState,
117}
118
119#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
120pub struct DeviceEntrySnapshot {
121    pub device_pubkey_hex: String,
122    pub device_npub: String,
123    pub is_current_device: bool,
124    pub is_authorized: bool,
125    pub is_stale: bool,
126    pub last_activity_secs: Option<u64>,
127}
128
129#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
130pub struct DeviceRosterSnapshot {
131    pub owner_public_key_hex: String,
132    pub owner_npub: String,
133    pub current_device_public_key_hex: String,
134    pub current_device_npub: String,
135    pub can_manage_devices: bool,
136    pub authorization_state: DeviceAuthorizationState,
137    pub devices: Vec<DeviceEntrySnapshot>,
138}
139
140#[derive(uniffi::Enum, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
141pub enum DeliveryState {
142    Queued,
143    Pending,
144    Sent,
145    Received,
146    Seen,
147    Failed,
148}
149
150#[derive(uniffi::Enum, Clone, Debug, PartialEq, Eq)]
151pub enum ChatKind {
152    Direct,
153    Group,
154}
155
156#[derive(uniffi::Enum, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
157pub enum ChatMessageKind {
158    User,
159    System,
160}
161
162#[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
163pub struct MessageAttachmentSnapshot {
164    pub nhash: String,
165    pub filename: String,
166    pub filename_encoded: String,
167    pub htree_url: String,
168    pub is_image: bool,
169    pub is_video: bool,
170    pub is_audio: bool,
171}
172
173#[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
174pub struct MessageReactionSnapshot {
175    pub emoji: String,
176    pub count: u64,
177    pub reacted_by_me: bool,
178}
179
180#[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
181pub struct MessageReactor {
182    /// Hex-encoded pubkey of the user who reacted.
183    pub author: String,
184    /// Emoji content of their current (latest) reaction. Empty means unreacted.
185    pub emoji: String,
186}
187
188#[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
189pub struct MessageRecipientDeliverySnapshot {
190    /// Hex-encoded owner/user pubkey.
191    pub owner_pubkey_hex: String,
192    pub delivery: DeliveryState,
193    pub updated_at_secs: u64,
194}
195
196#[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
197#[serde(default)]
198pub struct MessageDeliveryTraceSnapshot {
199    pub outer_event_ids: Vec<String>,
200    pub pending_relay_event_ids: Vec<String>,
201    pub queued_protocol_targets: Vec<String>,
202    pub target_device_ids: Vec<String>,
203    pub transport_channels: Vec<String>,
204    pub last_transport_error: Option<String>,
205}
206
207#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
208pub struct ChatMessageSnapshot {
209    pub id: String,
210    pub chat_id: String,
211    pub kind: ChatMessageKind,
212    pub author: String,
213    pub body: String,
214    pub attachments: Vec<MessageAttachmentSnapshot>,
215    pub reactions: Vec<MessageReactionSnapshot>,
216    pub reactors: Vec<MessageReactor>,
217    pub is_outgoing: bool,
218    pub created_at_secs: u64,
219    pub expires_at_secs: Option<u64>,
220    pub delivery: DeliveryState,
221    pub recipient_deliveries: Vec<MessageRecipientDeliverySnapshot>,
222    pub delivery_trace: MessageDeliveryTraceSnapshot,
223    /// Hex ID of the outer relay event that carried this rumor. The
224    /// notification extension joins on this to find a body the
225    /// foreground app already decrypted, so it can render a real
226    /// preview instead of "New activity". `None` for messages that
227    /// didn't come over the wire (system notices, locally-composed
228    /// outgoing rumors).
229    pub source_event_id: Option<String>,
230}
231
232#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
233pub struct TypingIndicatorSnapshot {
234    pub chat_id: String,
235    pub display_name: String,
236    pub expires_at_secs: u64,
237}
238
239#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
240pub struct ChatThreadSnapshot {
241    pub chat_id: String,
242    pub kind: ChatKind,
243    pub display_name: String,
244    pub subtitle: Option<String>,
245    pub picture_url: Option<String>,
246    pub member_count: u64,
247    pub last_message_preview: Option<String>,
248    pub last_message_at_secs: Option<u64>,
249    pub last_message_is_outgoing: Option<bool>,
250    pub last_message_delivery: Option<DeliveryState>,
251    pub unread_count: u64,
252    pub is_typing: bool,
253    pub is_muted: bool,
254}
255
256#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
257pub struct CurrentChatSnapshot {
258    pub chat_id: String,
259    pub kind: ChatKind,
260    pub display_name: String,
261    pub subtitle: Option<String>,
262    pub picture_url: Option<String>,
263    pub group_id: Option<String>,
264    pub member_count: u64,
265    pub message_ttl_seconds: Option<u64>,
266    pub is_muted: bool,
267    pub messages: Vec<ChatMessageSnapshot>,
268    pub typing_indicators: Vec<TypingIndicatorSnapshot>,
269}
270
271#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
272pub struct GroupMemberSnapshot {
273    pub owner_pubkey_hex: String,
274    pub display_name: String,
275    pub npub: String,
276    pub is_admin: bool,
277    pub is_creator: bool,
278    pub is_local_owner: bool,
279}
280
281#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
282pub struct GroupDetailsSnapshot {
283    pub group_id: String,
284    pub name: String,
285    pub picture_url: Option<String>,
286    pub created_by_display_name: String,
287    pub created_by_npub: String,
288    pub can_manage: bool,
289    pub is_muted: bool,
290    pub revision: u64,
291    pub members: Vec<GroupMemberSnapshot>,
292}
293
294#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
295pub struct RelayConnectionSnapshot {
296    pub url: String,
297    pub status: String,
298}
299
300#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
301pub struct NetworkStatusSnapshot {
302    pub relay_set_id: String,
303    pub relay_urls: Vec<String>,
304    pub relay_connections: Vec<RelayConnectionSnapshot>,
305    pub connected_relay_count: u64,
306    pub all_relays_offline_since_secs: Option<u64>,
307    pub syncing: bool,
308    pub pending_outbound_count: u64,
309    pub pending_group_control_count: u64,
310    pub recent_event_count: u64,
311    pub recent_log_count: u64,
312    pub last_debug_category: Option<String>,
313    pub last_debug_detail: Option<String>,
314}
315
316#[derive(uniffi::Record, Clone, Debug, Default, PartialEq, Eq)]
317pub struct MobilePushSessionSnapshot {
318    pub recipient_pubkey_hex: String,
319    pub display_name: String,
320    pub state_json: String,
321    pub tracked_sender_pubkeys: Vec<String>,
322    pub has_receiving_capability: bool,
323}
324
325#[derive(uniffi::Record, Clone, Debug, Default, PartialEq, Eq)]
326pub struct MobilePushSyncSnapshot {
327    pub owner_pubkey_hex: Option<String>,
328    pub message_author_pubkeys: Vec<String>,
329    pub invite_response_pubkeys: Vec<String>,
330    pub sessions: Vec<MobilePushSessionSnapshot>,
331}
332
333#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
334pub struct MobilePushNotificationResolution {
335    pub should_show: bool,
336    pub title: String,
337    pub body: String,
338    pub payload_json: String,
339}
340
341#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
342pub struct MobilePushSubscriptionRequest {
343    pub method: String,
344    pub url: String,
345    pub authorization_header: String,
346    pub body_json: Option<String>,
347}
348
349#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
350pub struct PublicInviteSnapshot {
351    pub url: String,
352}
353
354#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
355pub struct LinkDeviceSnapshot {
356    pub url: String,
357    pub device_input: String,
358}
359
360#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
361pub struct AppState {
362    pub rev: u64,
363    pub router: Router,
364    pub account: Option<AccountSnapshot>,
365    pub device_roster: Option<DeviceRosterSnapshot>,
366    pub busy: BusyState,
367    pub chat_list: Vec<ChatThreadSnapshot>,
368    pub current_chat: Option<CurrentChatSnapshot>,
369    pub group_details: Option<GroupDetailsSnapshot>,
370    pub public_invite: Option<PublicInviteSnapshot>,
371    pub link_device: Option<LinkDeviceSnapshot>,
372    pub network_status: Option<NetworkStatusSnapshot>,
373    pub mobile_push: MobilePushSyncSnapshot,
374    pub preferences: PreferencesSnapshot,
375    pub toast: Option<String>,
376}
377
378impl AppState {
379    pub fn empty() -> Self {
380        Self {
381            rev: 0,
382            router: Router {
383                default_screen: Screen::Welcome,
384                screen_stack: Vec::new(),
385            },
386            account: None,
387            device_roster: None,
388            busy: BusyState::default(),
389            chat_list: Vec::new(),
390            current_chat: None,
391            group_details: None,
392            public_invite: None,
393            link_device: None,
394            network_status: None,
395            mobile_push: MobilePushSyncSnapshot::default(),
396            preferences: PreferencesSnapshot::default(),
397            toast: None,
398        }
399    }
400}