@@ -26,15 +26,15 @@ module App =
2626 type Page =
2727 | Setup
2828 | Conversations
29− | Chat of pubkey: string
30− | AddContact
29+ | Chat of pubkey: string * compose: string
30+ | AddContact of pubkey: string * nickname: string
3131 | Settings
3232
3333 type Session = { Url: string; Token: string; Identity: Identity }
3434
3535 type ConnState =
3636 | Offline
37− | Connecting of Identity
37+ | Connecting
3838 | Online of Session
3939
4040 // ── Model ──
@@ -47,12 +47,8 @@ module App =
4747 Contacts: Contact list
4848 PollStatus: string
4949 Messages: Map<string, ChatMessage list>
50− ComposeText: string
51− NewPubkey: string
52− NewNickname: string
5350 Error: string option
54− PollCursor: uint64
55− LastSendStatus: string }
51+ PollCursor: int64 }
5652
5753 // ── Msg ──
5854
@@ -67,11 +63,11 @@ module App =
6763 | DoDisconnect
6864 | SetCompose of string
6965 | DoSend
70− | Sent of ApiClient.SendResult
66+ | Sent of Result<ApiClient.SendResult, string>
7167 | SetNewPubkey of string
7268 | SetNewNickname of string
7369 | DoSaveContact
74− | PollResult of messages: (string * ChatMessage) list * status: string * cursor: uint64
70+ | PollResult of messages: (string * ChatMessage) list * status: string * cursor: int64
7571 | CopyPubKey
7672 | DismissError
7773 | StateLoaded of Identity * Data option
@@ -82,7 +78,7 @@ module App =
8278 | CmdGenIdentity
8379 | CmdConnect of url: string * identity: Identity
8480 | CmdSend of session: Session * recipientHex: string * text: string * messageId: string
85− | CmdPoll of session: Session * cursor: uint64
81+ | CmdPoll of session: Session * cursor: int64
8682 | CmdCopyToClipboard of string
8783 | CmdLoadState
8884 | CmdSaveIdentity of Identity
@@ -136,12 +132,8 @@ module App =
136132 PollStatus = ""
137133 Contacts = []
138134 Messages = Map.empty
139− ComposeText = ""
140− NewPubkey = ""
141− NewNickname = ""
142135 Error = None
143− PollCursor = 0UL
144− LastSendStatus = "" },
136+ PollCursor = 0L },
145137 [ CmdLoadState ]
146138
147139 // ── Update ──
@@ -161,12 +153,12 @@ module App =
161153 | DoConnect ->
162154 match model.Identity with
163155 | Some id ->
164− { model with Conn = Connecting id }, [ CmdConnect(model.ServerUrl, id) ]
156+ { model with Conn = Connecting }, [ CmdConnect(model.ServerUrl, id) ]
165157 | None -> { model with Error = Some "No identity generated" }, []
166158
167159 | AuthOk token ->
168− match model.Conn with
169− | Connecting id ->
160+ match model.Identity, model.Conn with
161+ | Some id, Connecting ->
170162 let session = { Url = model.ServerUrl; Token = token; Identity = id }
171163 { model with Conn = Online session; Page = Conversations; Error = None },
172164 [ CmdPoll(session, model.PollCursor) ]
@@ -176,28 +168,31 @@ module App =
176168
177169 | DoDisconnect -> { model with Conn = Offline }, []
178170
179− | SetCompose text -> { model with ComposeText = text }, []
171+ | SetCompose text ->
172+ match model.Page with
173+ | Chat(pk, _) -> { model with Page = Chat(pk, text) }, []
174+ | _ -> model, []
180175
181176 | DoSend ->
182177 match model.Page, trySession model with
183− | Chat pk, Some session when model.ComposeText <> "" ->
184− let text = model.ComposeText
178+ | Chat(pk, compose), Some session when compose <> "" ->
185179 let id = Guid.NewGuid().ToString()
186180 let outMsg =
187181 { Id = id
188− Body = text
182+ Body = compose
189183 Timestamp = DateTimeOffset.UtcNow
190184 IsOutgoing = true }
191185
192186 let model' =
193187 { model with
194− ComposeText = ""
188+ Page = Chat(pk, "")
195189 Messages = model.Messages |> appendMessage pk outMsg }
196190
197− model', CmdSend(session, pk, text, id) :: saveCmd model'
191+ model', CmdSend(session, pk, compose, id) :: saveCmd model'
198192 | _ -> model, []
199193
200− | Sent result -> { model with LastSendStatus = $"{result.Status}:{result.MessageId}" }, []
194+ | Sent(Ok _) -> model, []
195+ | Sent(Error err) -> { model with Error = Some $"Send failed: {err}" }, []
201196
202197 | PollResult(incoming, status, newCursor) ->
203198 let model' =
@@ -219,25 +214,28 @@ module App =
219214
220215 model', cmds @ (if not incoming.IsEmpty then saveCmd model' else [])
221216
222− | SetNewPubkey pk -> { model with NewPubkey = pk }, []
223− | SetNewNickname nn -> { model with NewNickname = nn }, []
217+ | SetNewPubkey pk ->
218+ match model.Page with
219+ | AddContact(_, nn) -> { model with Page = AddContact(pk, nn) }, []
220+ | _ -> model, []
221+
222+ | SetNewNickname nn ->
223+ match model.Page with
224+ | AddContact(pk, _) -> { model with Page = AddContact(pk, nn) }, []
225+ | _ -> model, []
224226
225227 | DoSaveContact ->
226− if model.NewPubkey <> "" && model.NewNickname <> "" then
227− let contact: Contact =
228− { Pubkey = model.NewPubkey
229− Nickname = model.NewNickname }
228+ match model.Page with
229+ | AddContact(pk, nn) when pk <> "" && nn <> "" ->
230+ let contact: Contact = { Pubkey = pk; Nickname = nn }
230231
231232 let model' =
232233 { model with
233234 Contacts = model.Contacts |> upsertContact contact
234− NewPubkey = ""
235− NewNickname = ""
236235 Page = Conversations }
237236
238237 model', saveCmd model'
239− else
240− model, []
238+ | _ -> model, []
241239
242240 | CopyPubKey ->
243241 match model.Identity with
@@ -247,7 +245,7 @@ module App =
247245 | DismissError -> { model with Error = None }, []
248246
249247 | StateLoaded(identity, dataOpt) ->
250− let model' = { model with Identity = Some identity; Conn = Connecting identity }
248+ let model' = { model with Identity = Some identity; Conn = Connecting }
251249
252250 let model' =
253251 match dataOpt with
@@ -319,9 +317,9 @@ module App =
319317 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
320318 let blobHex = Crypto.toHex blob
321319 let! result = ApiClient.sendMessage session.Url session.Token recipientHex blobHex ts
322− return Sent result
320+ return Sent(Ok result)
323321 with ex ->
324− return DismissError // TODO: show send errors properly
322+ return Sent(Error ex.Message)
325323 })
326324
327325 | CmdPoll(session, cursor) ->
@@ -373,7 +371,7 @@ module App =
373371 |> Async.Start)
374372
375373 | CmdSaveIdentity identity ->
376− Cmd.ofEffect (fun _ -> Store.saveIdentity identity |> ignore)
374+ Cmd.ofEffect (fun _ -> Store.saveIdentity identity |> Async.Start)
377375
378376 | CmdSaveData data ->
379377 Cmd.ofEffect (fun _ -> Store.saveData data)
@@ -428,7 +426,7 @@ module App =
428426
429427 match model.Conn with
430428 | Offline -> Button("Connect", DoConnect)
431− | Connecting _ -> Label("Connecting...").centerTextHorizontal()
429+ | Connecting -> Label("Connecting...").centerTextHorizontal()
432430 | Online _ ->
433431 Button("Disconnect", DoDisconnect)
434432 Button("Go to Conversations", Nav Conversations)
@@ -441,7 +439,7 @@ module App =
441439 let connStr =
442440 match model.Conn with
443441 | Offline -> "OFFLINE"
444− | Connecting _ -> "CONNECTING"
442+ | Connecting -> "CONNECTING"
445443 | Online _ -> "ONLINE"
446444
447445 let contactNames =
@@ -459,7 +457,7 @@ module App =
459457 Label("Conversations")
460458 .font(size = 24.)
461459
462− Button("+", Nav AddContact)
460+ Button("+", Nav(AddContact("", "")))
463461 Button("Settings", Nav Settings)
464462 }
465463
@@ -492,13 +490,13 @@ module App =
492490 let preview =
493491 if lastMsg.Length > 40 then lastMsg.[..39] + "..." else lastMsg
494492
495− Button($"{c.Nickname}\n{preview}", Nav(Chat c.Pubkey))
493+ Button($"{c.Nickname}\n{preview}", Nav(Chat(c.Pubkey, "")))
496494 })
497495 .padding(20.)
498496 )
499497 )
500498
501− let private viewChat model pk =
499+ let private viewChat model pk compose =
502500 let name = contactName model.Contacts pk
503501 let msgs = messagesFor pk model.Messages
504502
@@ -519,10 +517,6 @@ module App =
519517 .font(size = 11.)
520518 | None -> ()
521519
522− Label($"[{msgs.Length} message(s), compose: \"{model.ComposeText}\", send: \"{model.LastSendStatus}\"]")
523− .textColor(Colors.Gray)
524− .font(size = 10.)
525−
526520 let allMessages =
527521 msgs
528522 |> List.map (fun m ->
@@ -534,7 +528,7 @@ module App =
534528 .font(size = 14.)
535529
536530 HStack(spacing = 8.) {
537− Entry(model.ComposeText, SetCompose)
531+ Entry(compose, SetCompose)
538532 .placeholder("Message...")
539533
540534 Button("Send", DoSend)
@@ -543,7 +537,7 @@ module App =
543537 .padding(12.)
544538 )
545539
546− let private viewAddContact model =
540+ let private viewAddContact pk nn =
547541 ContentPage(
548542 (VStack(spacing = 16.) {
549543 HStack(spacing = 8.) {
@@ -554,10 +548,10 @@ module App =
554548 }
555549
556550 Label("Public Key (hex):")
557− Entry(model.NewPubkey, SetNewPubkey)
551+ Entry(pk, SetNewPubkey)
558552
559553 Label("Nickname:")
560− Entry(model.NewNickname, SetNewNickname)
554+ Entry(nn, SetNewNickname)
561555
562556 Button("Save Contact", DoSaveContact)
563557 .centerHorizontal()
@@ -571,8 +565,8 @@ module App =
571565 | Setup -> viewSetup ()
572566 | Settings -> viewSettings model
573567 | Conversations -> viewConversations model
574− | Chat pk -> viewChat model pk
575− | AddContact -> viewAddContact model
568+ | Chat(pk, compose) -> viewChat model pk compose
569+ | AddContact(pk, nn) -> viewAddContact pk nn
576570
577571 Application() { Window(page) }
578572