on ebc16eb help sign in
skrepka/client/AppTypes.fs
143 lines 4.0 kB history raw
1namespace Skrepka
3open System
4open Avalonia.Media
5open Fabulous.Avalonia
7open type Fabulous.Avalonia.View
9module AppTypes =
11 open Store
12 open Crypto
14 // ── Domain Types ──
16 type Page =
17 | Setup
18 | Conversations
19 | Chat of pubkey: string * compose: string
20 | AddContact of pubkey: string
21 | Settings
22 | EditProfile of displayName: string * bio: string * photo: byte[] option
24 type Session =
25 { Url: string
26 Token: string
27 Identity: Identity }
29 type ConnStatus =
30 | Offline
31 | Connecting
32 | Online of token: string
34 type AuthState =
35 | NoIdentity
36 | Identified of identity: Identity * conn: ConnStatus
38 [<RequireQualifiedAccess>]
39 type Envelope =
40 | TextMessage of id: string * body: string
41 | DeliveryAck of ackIds: string list
42 | ProfileMessage of profile: Profile
44 type IncomingEvent =
45 { Sender: string
46 Timestamp: DateTimeOffset
47 Envelope: Envelope }
49 // ── Model ──
51 type Model =
52 { Page: Page
53 Auth: AuthState
54 ServerUrl: string
55 Contacts: Map<string, Contact>
56 PollStatus: string
57 Messages: Map<string, ChatMessage list>
58 Error: string option
59 PollCursor: int64
60 Profile: Profile option
61 FlushingOutbox: bool
62 PollRetries: int
63 KeyboardHeight: float }
65 // ── Msg ──
67 type Msg =
68 | SetPage of Page
69 | GenIdentity
70 | SetServerUrl of string
71 | DoConnect
72 | AuthOk of string
73 | AuthErr of string
74 | DoDisconnect
75 | DoSend
76 | DoSaveContact
77 | PollResult of events: IncomingEvent list * status: string * cursor: int64
78 | CopyPubKey
79 | DismissError
80 | StateLoaded of Identity option * Data option * Profile option
81 | DoPickPhoto
82 | PhotoPicked of byte[] option
83 | DoSaveProfile
84 | StartFlush
85 | FlushResult of sent: bool
86 | Search of string
87 | TokenExpired
88 | KeyboardHeightChanged of float
90 // ── CmdMsg ──
92 type CmdMsg =
93 | CmdConnect of url: string * identity: Identity
94 | CmdEnqueue of recipientHex: string * envelope: Envelope
95 | CmdFlushOutbox of session: Session
96 | CmdPoll of session: Session * cursor: int64 * retries: int
97 | CmdCopyToClipboard of string
98 | CmdLoadState
99 | CmdSaveIdentity of Identity
100 | CmdSaveData of Data
101 | CmdPickPhoto
102 | CmdSaveProfile of Profile
103 | CmdSubscribeKeyboard
105 // ── Shared Helpers ──
107 let hexToOb (hex: string) = Crypto.fromHex hex |> Phonemic.toOb
109 let truncKey (hex: string) =
110 let ob = hexToOb hex
111 let parts = ob.Split('-')
113 if parts.Length <= 4 then
114 ob
115 else
116 $"{parts.[0]}-{parts.[1]}..{parts.[parts.Length - 2]}-{parts.[parts.Length - 1]}"
118 let contactName (contacts: Map<string, Contact>) (pk: string) =
119 match Map.tryFind pk contacts with
120 | Some c when c.Nickname <> "" -> c.Nickname
121 | Some c when c.DisplayName <> "" -> c.DisplayName
122 | _ -> truncKey pk
124 let messagesFor (pk: string) (messages: Map<string, ChatMessage list>) =
125 messages |> Map.tryFind pk |> Option.defaultValue []
127 let connStatusLabel =
128 function
129 | NoIdentity
130 | Identified(_, Offline) -> "OFFLINE"
131 | Identified(_, Connecting) -> "CONNECTING"
132 | Identified(_, Online _) -> "ONLINE"
134 // ── Shared View ──
136 let viewErrorBanner (error: string option) =
137 VStack(4.) {
138 match error with
139 | Some err ->
140 TextBlock(err).foreground(SolidColorBrush(Colors.Red)).fontSize (12.)
141 Button("Dismiss", DismissError)
142 | None -> ()
143 }