on 0297421 help sign in
skrepka/client/AppTypes.fs
149 lines 4.1 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
64 SafeAreaTop: float
65 SafeAreaBottom: float }
67 // ── Msg ──
69 type Msg =
70 | SetPage of Page
71 | GenIdentity
72 | SetServerUrl of string
73 | DoConnect
74 | AuthOk of string
75 | AuthErr of string
76 | DoDisconnect
77 | DoSend
78 | DoSaveContact
79 | PollResult of events: IncomingEvent list * status: string * cursor: int64
80 | CopyPubKey
81 | DismissError
82 | StateLoaded of Identity option * Data option * Profile option
83 | DoPickPhoto
84 | PhotoPicked of byte[] option
85 | DoSaveProfile
86 | StartFlush
87 | FlushResult of sent: bool
88 | Search of string
89 | TokenExpired
90 | KeyboardHeightChanged of float
91 | SafeAreaChanged of top: float * bottom: float
92 | DoScanQR
93 | QRScanned of string option
95 // ── CmdMsg ──
97 type CmdMsg =
98 | CmdConnect of url: string * identity: Identity
99 | CmdEnqueue of recipientHex: string * envelope: Envelope
100 | CmdFlushOutbox of session: Session
101 | CmdPoll of session: Session * cursor: int64 * retries: int
102 | CmdCopyToClipboard of string
103 | CmdLoadState
104 | CmdSaveIdentity of Identity
105 | CmdSaveData of Data
106 | CmdPickPhoto
107 | CmdSaveProfile of Profile
108 | CmdSubscribeKeyboard
109 | CmdSubscribeSafeArea
110 | CmdScanQR
112 // ── Shared Helpers ──
114 let hexToOb (hex: string) = Crypto.fromHex hex |> Phonemic.toOb
116 let truncKey (hex: string) =
117 let ob = hexToOb hex
118 let parts = ob.Split('-')
120 if parts.Length <= 4 then
121 ob
122 else
123 $"{parts.[0]}-{parts.[1]}..{parts.[parts.Length - 2]}-{parts.[parts.Length - 1]}"
125 let contactName (contacts: Map<string, Contact>) (pk: string) =
126 match Map.tryFind pk contacts with
127 | Some c -> c.DisplayName
128 | _ -> pk
130 let messagesFor (pk: string) (messages: Map<string, ChatMessage list>) =
131 messages |> Map.tryFind pk |> Option.defaultValue []
133 let connStatusLabel =
134 function
135 | NoIdentity
136 | Identified(_, Offline) -> "OFFLINE"
137 | Identified(_, Connecting) -> "CONNECTING"
138 | Identified(_, Online _) -> "ONLINE"
140 // ── Shared View ──
142 let viewErrorBanner (error: string option) =
143 VStack(4.) {
144 match error with
145 | Some err ->
146 TextBlock(err).foreground(SolidColorBrush(Colors.Red)).fontSize (12.)
147 Button("Dismiss", DismissError)
148 | None -> ()
149 }