on master help sign in
Extract button logic into reusable variants, add text wrapping to labels, simplify AddContact flow
commitf170fff3469b3cf50fd996e93a94a14bb2ab4dc7 authoriko <mail@iko.soy> dateSat 18 Apr 2026, 13:47 (6 months ago) parente966f38
7 files changed+101−84
client/App.fs+13−12 client/AppTypes.fs+1−1 client/Buttons.fs+10−15 client/Labels.fs+23 client/Skrepka.fsproj+1 client/ViewAddContact.fs+10−15 client/ViewConversations.fs+43−41
client/App.fs+13−12
@@ -108,11 +108,13 @@ module App =
108108 | NoIdentity -> model
109109
110110 let private saveCmdMsg model : CmdMsg =
111− CmdSaveData
112− ({ Contacts = model.Contacts
113− Messages = model.Messages
114− ServerUrl = model.ServerUrl
115− PollCursor = model.PollCursor } : Data)
111+ CmdSaveData(
112+ { Contacts = model.Contacts
113+ Messages = model.Messages
114+ ServerUrl = model.ServerUrl
115+ PollCursor = model.PollCursor }
116+ : Data
117+ )
116118
117119 // ── Init ──
118120
@@ -254,15 +256,15 @@ module App =
254256
255257 | DoSaveContact ->
256258 match model.Page with
257− | AddContact(cpk, cnn) when cpk <> "" && cnn <> "" ->
259+ | AddContact(cpk) when cpk <> "" ->
258260 match tryParsePubkey cpk with
259261 | Some bytes ->
260262 let hex = Crypto.toHex bytes
261263
262264 let contact =
263265 match Map.tryFind hex model.Contacts with
264− | Some e -> { e with Nickname = cnn }
265− | None -> newContact hex cnn
266+ | Some e -> { e with Nickname = cpk }
267+ | None -> newContact hex cpk
266268
267269 let model' =
268270 { model with
@@ -598,8 +600,7 @@ module App =
598600 | CmdSaveProfile profile -> Cmd.ofEffect (fun _ -> Store.saveProfile profile |> Async.Start)
599601
600602 | CmdSubscribeKeyboard ->
601− Cmd.ofEffect (fun dispatch ->
602− Keyboard.heightChanged.Add(fun h -> dispatch (KeyboardHeightChanged h)))
603+ Cmd.ofEffect (fun dispatch -> Keyboard.heightChanged.Add(fun h -> dispatch (KeyboardHeightChanged h)))
603604
604605 // ── View ──
605606
@@ -610,12 +611,12 @@ module App =
610611 | Settings -> AnyView(viewSettings model)
611612 | Conversations -> AnyView(viewConversations model)
612613 | Chat(pk, compose) -> AnyView(viewChat model pk compose)
613− | AddContact(pk, nn) -> AnyView(viewAddContact model pk nn)
614+ | AddContact(pk) -> AnyView(viewAddContact model pk)
614615 | EditProfile(displayName, bio, photo) -> AnyView(viewEditProfile model displayName bio photo)
615616
616617 Border(page)
617618 .background(SolidColorBrush(Colors.White))
618− .padding(Avalonia.Thickness(0., 0., 0., model.KeyboardHeight))
619+ .padding (Avalonia.Thickness(0., 0., 0., model.KeyboardHeight))
619620
620621 let view model =
621622#if MOBILE
client/AppTypes.fs+1−1
@@ -17,7 +17,7 @@ module AppTypes =
1717 | Setup
1818 | Conversations
1919 | Chat of pubkey: string * compose: string
20− | AddContact of pubkey: string * nickname: string
20+ | AddContact of pubkey: string
2121 | Settings
2222 | EditProfile of displayName: string * bio: string * photo: byte[] option
2323
client/Buttons.fs+10−15
@@ -34,10 +34,10 @@ module Buttons =
3434 let private borderRect () =
3535 Rectangle().stroke(Colors.Black).strokeThickness (4.)
3636
37− let button priority (text: string) (msg: 'msg) =
37+ let private textButton fontSize pressOffset restOffset margin priority (text: string) (msg: 'msg) =
3838 Component(text) {
3939 let! pressed = Context.State(false)
40− let offset = if pressed.Current then 4. else 8.
40+ let offset = if pressed.Current then pressOffset else restOffset
4141
4242 (Grid() {
4343 shadowRect offset
@@ -56,7 +56,7 @@ module Buttons =
5656 | Secondary -> SolidColorBrush(Colors.Black)
5757
5858 )
59− .fontSize(24.)
59+ .fontSize(fontSize)
6060 .fontFamily(FontFamily(Constants.fontFamily))
6161 .fontWeight(FontWeight.Bold)
6262 .center()
@@ -68,10 +68,13 @@ module Buttons =
6868 .onPointerPressed(fun _ -> pressed.Set true)
6969 .onPointerReleased(fun _ -> pressed.Set false)
7070 .onPointerExited(fun _ -> pressed.Set false)
71− .margin(16.)
71+ .margin(margin)
7272 .onTapped (fun _ -> msg)
7373 }
7474
75+ let button priority = textButton 24. 4. 8. 16. priority
76+ let smallButton priority = textButton 16. 2. 4. 8. priority
77+
7578 let imageButton (img: byte[] option) (msg: 'msg) =
7679 let key =
7780 match img with
@@ -104,9 +107,7 @@ module Buttons =
104107 )
105108 .margin (24.)
106109
107− | Some i ->
108− Image(Styles.cachedBitmap i, Stretch.UniformToFill)
109− .clipToBounds (true)
110+ | Some i -> Image(Styles.cachedBitmap i, Stretch.UniformToFill).clipToBounds (true)
110111
111112 borderRect ()
112113 })
@@ -145,10 +146,7 @@ module Buttons =
145146 )
146147
147148 | Some i ->
148− Image(Styles.cachedBitmap i, Stretch.UniformToFill)
149− .width(32.)
150− .height(32.)
151− .clipToBounds (true)
149+ Image(Styles.cachedBitmap i, Stretch.UniformToFill).width(32.).height(32.).clipToBounds (true)
152150
153151 borderRect ()
154152 })
@@ -172,10 +170,7 @@ module Buttons =
172170
173171 Rectangle().fill (SolidColorBrush(Colors.White))
174172
175− Image("avares://Skrepka/Assets/Images/left.png", Stretch.Uniform)
176− .width(24.)
177− .height(24.)
178− .center ()
173+ Image("avares://Skrepka/Assets/Images/left.png", Stretch.Uniform).width(24.).height(24.).center ()
179174
180175 borderRect ()
181176 })
client/Labels.fsnew file+23
@@ -0,0 +1,23 @@
1+namespace Skrepka
2+
3+open Avalonia.Media
4+open Avalonia.Media.TextFormatting
5+open Fabulous.Avalonia
6+
7+open type Fabulous.Avalonia.View
8+
9+module Labels =
10+ let private label size text =
11+ TextBlock(text)
12+ .fontFamily(FontFamily(Constants.fontFamily))
13+ .fontWeight(FontWeight.Bold)
14+ .fontSize(size)
15+ .textWrapping(TextWrapping.Wrap)
16+
17+ let h1 text = label 32. text
18+ let h2 text = label 24. text
19+ let h3 text = label 20. text
20+ let h4 text = label 16. text
21+ let h5 text = label 14. text
22+ let body text = label 14. text |> _.fontWeight(FontWeight.Normal)
23+ let caption text = label 12. text |> _.fontWeight(FontWeight.Normal)
client/Skrepka.fsproj+1
@@ -35,6 +35,7 @@
3535 <Compile Include="Phonemic.fs" />
3636 <Compile Include="Store.fs" />
3737 <Compile Include="TextFields.fs" />
38+ <Compile Include="Labels.fs" />
3839 <Compile Include="Buttons.fs" />
3940 <Compile Include="$(iOSPlatformFolder)PhotoPicker.fs" Condition="$(AvaloniaPlatform) == 'iOS'" />
4041 <Compile Include="Keyboard.fs" />
client/ViewAddContact.fs+10−15
@@ -10,6 +10,8 @@ module ViewAddContact =
1010 open AppTypes
1111 open Buttons
1212 open Styles
13+ open Labels
14+ open TextFields
1315
1416 let private qrBytes (text: string) =
1517 use qr = new QRCoder.QRCodeGenerator()
@@ -17,36 +19,29 @@ module ViewAddContact =
1719 use png = new QRCoder.PngByteQRCode(data)
1820 png.GetGraphic(8, [| 0uy; 0uy; 0uy |], [| 255uy; 255uy; 255uy |])
1921
20− let viewAddContact model cpk cnn =
22+ let viewAddContact model cpk =
2123 let content =
2224 ScrollViewer(
2325 (VStack(16.) {
2426
25−
27+ h2 ("Share your address")
2628
2729 match model.Auth with
2830 | Identified(id, _) ->
2931 let ob = hexToOb id.PubKeyHex
3032
31− TextBlock("Your key (share with contact):").foreground (SolidColorBrush(Colors.DimGray))
32−
3333 Image(Styles.cachedBitmap (qrBytes ob), Stretch.Uniform).maxWidth(200.).centerHorizontal ()
3434
35− TextBlock(truncKey id.PubKeyHex)
36− .fontSize(12.)
37− .foreground(SolidColorBrush(Colors.DimGray))
38− .centerText ()
39− | NoIdentity -> ()
35+ body(ob).fontSize(12.).centerText ()
4036
41− viewErrorBanner model.Error
37+ smallButton Primary "Copy Address" CopyPubKey
38+ | NoIdentity -> ()
4239
43− TextBlock("Public Key:")
44− TextBox(cpk, fun text -> SetPage(AddContact(text, cnn))).watermark ("sampel-palnet-...")
40+ h2 "Enter someone else's key"
4541
46− TextBlock("Nickname:")
47− TextBox(cnn, fun text -> SetPage(AddContact(cpk, text)))
42+ textField cpk (fun key -> SetPage(AddContact(key)))
4843
49− Button("Save Contact", DoSaveContact).centerHorizontal ()
44+ smallButton Primary "Add" DoSaveContact
5045 })
5146 .margin (20.)
5247 )
client/ViewConversations.fs+43−41
@@ -21,61 +21,63 @@ module ViewConversations =
2121 AnyView(
2222 TextBlock("No contacts yet. Tap + to add one.")
2323 .centerText()
24− .foreground(SolidColorBrush(Colors.Gray))
24+ .foreground (SolidColorBrush(Colors.Gray))
2525 )
2626 else
27− AnyView(ListBox(
28− contacts,
29− fun c ->
30− let name = contactName model.Contacts c.Pubkey
27+ AnyView(
28+ ListBox(
29+ contacts,
30+ fun c ->
31+ let name = contactName model.Contacts c.Pubkey
3132
32− let preview =
33− messagesFor c.Pubkey model.Messages
34− |> List.tryLast
35− |> Option.map (fun m -> if m.Body.Length > 40 then m.Body.[..39] + "..." else m.Body)
36− |> Option.defaultValue ""
33+ let preview =
34+ messagesFor c.Pubkey model.Messages
35+ |> List.tryLast
36+ |> Option.map (fun m -> if m.Body.Length > 40 then m.Body.[..39] + "..." else m.Body)
37+ |> Option.defaultValue ""
3738
38− Border(
39− (HStack(8.) {
39+ Border(
40+ (HStack(8.) {
4041
41− Grid() {
42− (match c.Photo with
43− | None -> Image("avares://Skrepka/Assets/Images/user.png", Stretch.Uniform)
44− | Some i -> Image(cachedBitmap i, Stretch.UniformToFill))
45− .width(16.)
46− .height(16.)
47− .margin(8.)
48− .clipToBounds (true)
42+ Grid() {
43+ (match c.Photo with
44+ | None -> Image("avares://Skrepka/Assets/Images/user.png", Stretch.Uniform)
45+ | Some i -> Image(cachedBitmap i, Stretch.UniformToFill))
46+ .width(16.)
47+ .height(16.)
48+ .margin(8.)
49+ .clipToBounds (true)
4950
50− Rectangle().stroke(Colors.Black).strokeThickness (4.)
51− }
51+ Rectangle().stroke(Colors.Black).strokeThickness (4.)
52+ }
5253
53− VStack(2.) {
54− TextBlock(name)
55− .fontFamily(Constants.fontFamily)
56− .fontWeight(FontWeight.Bold)
57− .fontSize (16.)
54+ VStack(2.) {
55+ TextBlock(name)
56+ .fontFamily(Constants.fontFamily)
57+ .fontWeight(FontWeight.Bold)
58+ .fontSize (16.)
5859
59− if preview <> "" then
60− TextBlock(preview).fontSize(12.).fontWeight (FontWeight.Bold)
61− }
60+ if preview <> "" then
61+ TextBlock(preview).fontSize(12.).fontWeight (FontWeight.Bold)
62+ }
6263
63− })
64− .margin (8.)
65− )
66− .borderThickness(Avalonia.Thickness(0., 0., 0., 4.))
67− .borderBrush(SolidColorBrush(Colors.Black))
68− .background(Colors.Transparent)
69− .onTapped (fun _ -> SetPage(Chat(c.Pubkey, "")))
64+ })
65+ .margin (8.)
66+ )
67+ .borderThickness(Avalonia.Thickness(0., 0., 0., 4.))
68+ .borderBrush(SolidColorBrush(Colors.Black))
69+ .background(Colors.Transparent)
70+ .onTapped (fun _ -> SetPage(Chat(c.Pubkey, "")))
71+ )
72+ .styles(noListBoxPadding ())
73+ .background (Colors.White)
7074 )
71− .styles(noListBoxPadding ())
72− .background(Colors.White))
7375
7476 let bar =
75− (Grid([ Dimension.Auto; Dimension.Star; Dimension.Auto ], [Dimension.Auto]) {
77+ (Grid([ Dimension.Auto; Dimension.Star; Dimension.Auto ], [ Dimension.Auto ]) {
7678 (smallImageButton None (SetPage Settings)).gridColumn (0)
7779 (textField "" Search).margin(8.).verticalAlignment(VerticalAlignment.Stretch).gridColumn (1)
78− (smallTextButton "+" (SetPage(AddContact("", "")))).gridColumn (2)
80+ (smallTextButton "+" (SetPage(AddContact("")))).gridColumn (2)
7981 })
8082 // .margin(20.)
8183 .horizontalAlignment (HorizontalAlignment.Stretch)