on master help sign in
Extract button logic into reusable variants, add text wrapping to labels, simplify AddContact flow
f170fffiko
client/App.fs+13−12
@@ -108,11 +108,13 @@ module App =
108 | NoIdentity -> model
108 | NoIdentity -> model
109
109
110 let private saveCmdMsg model : CmdMsg =
110 let private saveCmdMsg model : CmdMsg =
111− CmdSaveData
111+ CmdSaveData(
112− ({ Contacts = model.Contacts
112+ { Contacts = model.Contacts
113− Messages = model.Messages
113+ Messages = model.Messages
114− ServerUrl = model.ServerUrl
114+ ServerUrl = model.ServerUrl
115− PollCursor = model.PollCursor } : Data)
115+ PollCursor = model.PollCursor }
116+ : Data
117+ )
116
118
117 // ── Init ──
119 // ── Init ──
118
120
@@ -254,15 +256,15 @@ module App =
254
256
255 | DoSaveContact ->
257 | DoSaveContact ->
256 match model.Page with
258 match model.Page with
257− | AddContact(cpk, cnn) when cpk <> "" && cnn <> "" ->
259+ | AddContact(cpk) when cpk <> "" ->
258 match tryParsePubkey cpk with
260 match tryParsePubkey cpk with
259 | Some bytes ->
261 | Some bytes ->
260 let hex = Crypto.toHex bytes
262 let hex = Crypto.toHex bytes
261
263
262 let contact =
264 let contact =
263 match Map.tryFind hex model.Contacts with
265 match Map.tryFind hex model.Contacts with
264− | Some e -> { e with Nickname = cnn }
266+ | Some e -> { e with Nickname = cpk }
265− | None -> newContact hex cnn
267+ | None -> newContact hex cpk
266
268
267 let model' =
269 let model' =
268 { model with
270 { model with
@@ -598,8 +600,7 @@ module App =
598 | CmdSaveProfile profile -> Cmd.ofEffect (fun _ -> Store.saveProfile profile |> Async.Start)
600 | CmdSaveProfile profile -> Cmd.ofEffect (fun _ -> Store.saveProfile profile |> Async.Start)
599
601
600 | CmdSubscribeKeyboard ->
602 | 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)))
603
604
604 // ── View ──
605 // ── View ──
605
606
@@ -610,12 +611,12 @@ module App =
610 | Settings -> AnyView(viewSettings model)
611 | Settings -> AnyView(viewSettings model)
611 | Conversations -> AnyView(viewConversations model)
612 | Conversations -> AnyView(viewConversations model)
612 | Chat(pk, compose) -> AnyView(viewChat model pk compose)
613 | Chat(pk, compose) -> AnyView(viewChat model pk compose)
613− | AddContact(pk, nn) -> AnyView(viewAddContact model pk nn)
614+ | AddContact(pk) -> AnyView(viewAddContact model pk)
614 | EditProfile(displayName, bio, photo) -> AnyView(viewEditProfile model displayName bio photo)
615 | EditProfile(displayName, bio, photo) -> AnyView(viewEditProfile model displayName bio photo)
615
616
616 Border(page)
617 Border(page)
617 .background(SolidColorBrush(Colors.White))
618 .background(SolidColorBrush(Colors.White))
618− .padding(Avalonia.Thickness(0., 0., 0., model.KeyboardHeight))
619+ .padding (Avalonia.Thickness(0., 0., 0., model.KeyboardHeight))
619
620
620 let view model =
621 let view model =
621#if MOBILE
622#if MOBILE
client/AppTypes.fs+1−1
@@ -17,7 +17,7 @@ module AppTypes =
17 | Setup
17 | Setup
18 | Conversations
18 | Conversations
19 | Chat of pubkey: string * compose: string
19 | Chat of pubkey: string * compose: string
20− | AddContact of pubkey: string * nickname: string
20+ | AddContact of pubkey: string
21 | Settings
21 | Settings
22 | EditProfile of displayName: string * bio: string * photo: byte[] option
22 | EditProfile of displayName: string * bio: string * photo: byte[] option
23
23
client/Buttons.fs+10−15
@@ -34,10 +34,10 @@ module Buttons =
34 let private borderRect () =
34 let private borderRect () =
35 Rectangle().stroke(Colors.Black).strokeThickness (4.)
35 Rectangle().stroke(Colors.Black).strokeThickness (4.)
36
36
37− let button priority (text: string) (msg: 'msg) =
37+ let private textButton fontSize pressOffset restOffset margin priority (text: string) (msg: 'msg) =
38 Component(text) {
38 Component(text) {
39 let! pressed = Context.State(false)
39 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
41
41
42 (Grid() {
42 (Grid() {
43 shadowRect offset
43 shadowRect offset
@@ -56,7 +56,7 @@ module Buttons =
56 | Secondary -> SolidColorBrush(Colors.Black)
56 | Secondary -> SolidColorBrush(Colors.Black)
57
57
58 )
58 )
59− .fontSize(24.)
59+ .fontSize(fontSize)
60 .fontFamily(FontFamily(Constants.fontFamily))
60 .fontFamily(FontFamily(Constants.fontFamily))
61 .fontWeight(FontWeight.Bold)
61 .fontWeight(FontWeight.Bold)
62 .center()
62 .center()
@@ -68,10 +68,13 @@ module Buttons =
68 .onPointerPressed(fun _ -> pressed.Set true)
68 .onPointerPressed(fun _ -> pressed.Set true)
69 .onPointerReleased(fun _ -> pressed.Set false)
69 .onPointerReleased(fun _ -> pressed.Set false)
70 .onPointerExited(fun _ -> pressed.Set false)
70 .onPointerExited(fun _ -> pressed.Set false)
71− .margin(16.)
71+ .margin(margin)
72 .onTapped (fun _ -> msg)
72 .onTapped (fun _ -> msg)
73 }
73 }
74
74
75+ let button priority = textButton 24. 4. 8. 16. priority
76+ let smallButton priority = textButton 16. 2. 4. 8. priority
77+
75 let imageButton (img: byte[] option) (msg: 'msg) =
78 let imageButton (img: byte[] option) (msg: 'msg) =
76 let key =
79 let key =
77 match img with
80 match img with
@@ -104,9 +107,7 @@ module Buttons =
104 )
107 )
105 .margin (24.)
108 .margin (24.)
106
109
107− | Some i ->
108− Image(Styles.cachedBitmap i, Stretch.UniformToFill)
110+ | Some i -> Image(Styles.cachedBitmap i, Stretch.UniformToFill).clipToBounds (true)
109− .clipToBounds (true)
110
111
111 borderRect ()
112 borderRect ()
112 })
113 })
@@ -145,10 +146,7 @@ module Buttons =
145 )
146 )
146
147
147 | Some i ->
148 | Some i ->
148− Image(Styles.cachedBitmap i, Stretch.UniformToFill)
149+ Image(Styles.cachedBitmap i, Stretch.UniformToFill).width(32.).height(32.).clipToBounds (true)
149− .width(32.)
150− .height(32.)
151− .clipToBounds (true)
152
150
153 borderRect ()
151 borderRect ()
154 })
152 })
@@ -172,10 +170,7 @@ module Buttons =
172
170
173 Rectangle().fill (SolidColorBrush(Colors.White))
171 Rectangle().fill (SolidColorBrush(Colors.White))
174
172
175− Image("avares://Skrepka/Assets/Images/left.png", Stretch.Uniform)
173+ Image("avares://Skrepka/Assets/Images/left.png", Stretch.Uniform).width(24.).height(24.).center ()
176− .width(24.)
177− .height(24.)
178− .center ()
179
174
180 borderRect ()
175 borderRect ()
181 })
176 })
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 @@
35 <Compile Include="Phonemic.fs" />
35 <Compile Include="Phonemic.fs" />
36 <Compile Include="Store.fs" />
36 <Compile Include="Store.fs" />
37 <Compile Include="TextFields.fs" />
37 <Compile Include="TextFields.fs" />
38+ <Compile Include="Labels.fs" />
38 <Compile Include="Buttons.fs" />
39 <Compile Include="Buttons.fs" />
39 <Compile Include="$(iOSPlatformFolder)PhotoPicker.fs" Condition="$(AvaloniaPlatform) == 'iOS'" />
40 <Compile Include="$(iOSPlatformFolder)PhotoPicker.fs" Condition="$(AvaloniaPlatform) == 'iOS'" />
40 <Compile Include="Keyboard.fs" />
41 <Compile Include="Keyboard.fs" />
client/ViewAddContact.fs+10−15
@@ -10,6 +10,8 @@ module ViewAddContact =
10 open AppTypes
10 open AppTypes
11 open Buttons
11 open Buttons
12 open Styles
12 open Styles
13+ open Labels
14+ open TextFields
13
15
14 let private qrBytes (text: string) =
16 let private qrBytes (text: string) =
15 use qr = new QRCoder.QRCodeGenerator()
17 use qr = new QRCoder.QRCodeGenerator()
@@ -17,36 +19,29 @@ module ViewAddContact =
17 use png = new QRCoder.PngByteQRCode(data)
19 use png = new QRCoder.PngByteQRCode(data)
18 png.GetGraphic(8, [| 0uy; 0uy; 0uy |], [| 255uy; 255uy; 255uy |])
20 png.GetGraphic(8, [| 0uy; 0uy; 0uy |], [| 255uy; 255uy; 255uy |])
19
21
20− let viewAddContact model cpk cnn =
22+ let viewAddContact model cpk =
21 let content =
23 let content =
22 ScrollViewer(
24 ScrollViewer(
23 (VStack(16.) {
25 (VStack(16.) {
24
26
25−
27+ h2 ("Share your address")
26
28
27 match model.Auth with
29 match model.Auth with
28 | Identified(id, _) ->
30 | Identified(id, _) ->
29 let ob = hexToOb id.PubKeyHex
31 let ob = hexToOb id.PubKeyHex
30
32
31− TextBlock("Your key (share with contact):").foreground (SolidColorBrush(Colors.DimGray))
32−
33 Image(Styles.cachedBitmap (qrBytes ob), Stretch.Uniform).maxWidth(200.).centerHorizontal ()
33 Image(Styles.cachedBitmap (qrBytes ob), Stretch.Uniform).maxWidth(200.).centerHorizontal ()
34
34
35− TextBlock(truncKey id.PubKeyHex)
36− .fontSize(12.)
35+ body(ob).fontSize(12.).centerText ()
37− .foreground(SolidColorBrush(Colors.DimGray))
38− .centerText ()
39− | NoIdentity -> ()
40
36
41− viewErrorBanner model.Error
37+ smallButton Primary "Copy Address" CopyPubKey
38+ | NoIdentity -> ()
42
39
43− TextBlock("Public Key:")
40+ h2 "Enter someone else's key"
44− TextBox(cpk, fun text -> SetPage(AddContact(text, cnn))).watermark ("sampel-palnet-...")
45
41
46− TextBlock("Nickname:")
47− TextBox(cnn, fun text -> SetPage(AddContact(cpk, text)))
42+ textField cpk (fun key -> SetPage(AddContact(key)))
48
43
49− Button("Save Contact", DoSaveContact).centerHorizontal ()
44+ smallButton Primary "Add" DoSaveContact
50 })
45 })
51 .margin (20.)
46 .margin (20.)
52 )
47 )
client/ViewConversations.fs+43−41
@@ -21,61 +21,63 @@ module ViewConversations =
21 AnyView(
21 AnyView(
22 TextBlock("No contacts yet. Tap + to add one.")
22 TextBlock("No contacts yet. Tap + to add one.")
23 .centerText()
23 .centerText()
24− .foreground(SolidColorBrush(Colors.Gray))
24+ .foreground (SolidColorBrush(Colors.Gray))
25 )
25 )
26 else
26 else
27− AnyView(ListBox(
27+ AnyView(
28+ ListBox(
28− contacts,
29+ contacts,
29− fun c ->
30+ fun c ->
30− let name = contactName model.Contacts c.Pubkey
31+ let name = contactName model.Contacts c.Pubkey
31
32
32− let preview =
33+ let preview =
33− messagesFor c.Pubkey model.Messages
34+ messagesFor c.Pubkey model.Messages
34− |> List.tryLast
35+ |> List.tryLast
35− |> Option.map (fun m -> if m.Body.Length > 40 then m.Body.[..39] + "..." else m.Body)
36+ |> Option.map (fun m -> if m.Body.Length > 40 then m.Body.[..39] + "..." else m.Body)
36− |> Option.defaultValue ""
37+ |> Option.defaultValue ""
37
38
38− Border(
39+ Border(
39− (HStack(8.) {
40+ (HStack(8.) {
40
41
41− Grid() {
42+ Grid() {
42− (match c.Photo with
43+ (match c.Photo with
43− | None -> Image("avares://Skrepka/Assets/Images/user.png", Stretch.Uniform)
44+ | None -> Image("avares://Skrepka/Assets/Images/user.png", Stretch.Uniform)
44− | Some i -> Image(cachedBitmap i, Stretch.UniformToFill))
45+ | Some i -> Image(cachedBitmap i, Stretch.UniformToFill))
45− .width(16.)
46+ .width(16.)
46− .height(16.)
47+ .height(16.)
47− .margin(8.)
48+ .margin(8.)
48− .clipToBounds (true)
49+ .clipToBounds (true)
49
50
50− Rectangle().stroke(Colors.Black).strokeThickness (4.)
51+ Rectangle().stroke(Colors.Black).strokeThickness (4.)
51− }
52+ }
52
53
53− VStack(2.) {
54+ VStack(2.) {
54− TextBlock(name)
55+ TextBlock(name)
55− .fontFamily(Constants.fontFamily)
56+ .fontFamily(Constants.fontFamily)
56− .fontWeight(FontWeight.Bold)
57+ .fontWeight(FontWeight.Bold)
57− .fontSize (16.)
58+ .fontSize (16.)
58
59
59− if preview <> "" then
60+ if preview <> "" then
60− TextBlock(preview).fontSize(12.).fontWeight (FontWeight.Bold)
61+ TextBlock(preview).fontSize(12.).fontWeight (FontWeight.Bold)
61− }
62+ }
62
63
63− })
64+ })
64− .margin (8.)
65+ .margin (8.)
65− )
66+ )
66− .borderThickness(Avalonia.Thickness(0., 0., 0., 4.))
67+ .borderThickness(Avalonia.Thickness(0., 0., 0., 4.))
67− .borderBrush(SolidColorBrush(Colors.Black))
68+ .borderBrush(SolidColorBrush(Colors.Black))
68− .background(Colors.Transparent)
69+ .background(Colors.Transparent)
69− .onTapped (fun _ -> SetPage(Chat(c.Pubkey, "")))
70+ .onTapped (fun _ -> SetPage(Chat(c.Pubkey, "")))
71+ )
72+ .styles(noListBoxPadding ())
73+ .background (Colors.White)
70 )
74 )
71− .styles(noListBoxPadding ())
72− .background(Colors.White))
73
75
74 let bar =
76 let bar =
75− (Grid([ Dimension.Auto; Dimension.Star; Dimension.Auto ], [Dimension.Auto]) {
77+ (Grid([ Dimension.Auto; Dimension.Star; Dimension.Auto ], [ Dimension.Auto ]) {
76 (smallImageButton None (SetPage Settings)).gridColumn (0)
78 (smallImageButton None (SetPage Settings)).gridColumn (0)
77 (textField "" Search).margin(8.).verticalAlignment(VerticalAlignment.Stretch).gridColumn (1)
79 (textField "" Search).margin(8.).verticalAlignment(VerticalAlignment.Stretch).gridColumn (1)
78− (smallTextButton "+" (SetPage(AddContact("", "")))).gridColumn (2)
80+ (smallTextButton "+" (SetPage(AddContact("")))).gridColumn (2)
79 })
81 })
80 // .margin(20.)
82 // .margin(20.)
81 .horizontalAlignment (HorizontalAlignment.Stretch)
83 .horizontalAlignment (HorizontalAlignment.Stretch)