@@ -25,7 +25,7 @@ module App =
2525 | Chat of pubkey: string * compose: string
2626 | AddContact of pubkey: string * nickname: string
2727 | Settings
28− | EditProfile of displayName: string * bio: string * photo: string
28+ | EditProfile of displayName: string * bio: string * photo: byte[]
2929
3030 type Session =
3131 { Url: string
@@ -83,7 +83,7 @@ module App =
8383 | DismissError
8484 | StateLoaded of Identity option * Data option * Profile option
8585 | DoPickPhoto
86− | PhotoPicked of string
86+ | PhotoPicked of byte[]
8787 | DoSaveProfile
8888
8989 // ── CmdMsg ──
@@ -156,13 +156,13 @@ module App =
156156 Nickname = nickname
157157 DisplayName = ""
158158 Bio = ""
159− PhotoBase64 = "" }
159+ Photo = [||] }
160160
161161 let private withProfile (p: Profile) (c: Contact) =
162162 { c with
163163 DisplayName = p.DisplayName
164164 Bio = p.Bio
165− PhotoBase64 = p.PhotoBase64 }
165+ Photo = p.Photo }
166166
167167 let private applyEvent (m: Model) (evt: IncomingEvent) =
168168 match evt.Envelope with
@@ -242,7 +242,7 @@ module App =
242242
243243 { model with
244244 Auth = Identified(identity, Offline)
245− Page = EditProfile("", "", "") },
245+ Page = EditProfile("", "", [||]) },
246246 [ CmdSaveIdentity identity ]
247247
248248 | SetServerUrl url -> { model with ServerUrl = url }, []
@@ -372,7 +372,7 @@ module App =
372372 let profile: Profile =
373373 { DisplayName = name
374374 Bio = bio
375− PhotoBase64 = photo }
375+ Photo = photo }
376376
377377 { model with
378378 Profile = Some profile
@@ -431,7 +431,7 @@ module App =
431431 {| ``type`` = "profile"
432432 display_name = profile.DisplayName
433433 bio = profile.Bio
434− photo = profile.PhotoBase64 |}
434+ photo = Convert.ToBase64String profile.Photo |}
435435 )
436436
437437 let private sendEnvelope (session: Session) (recipientHex: string) (envelope: Envelope) =
@@ -472,7 +472,7 @@ module App =
472472 Envelope.ProfileMessage
473473 { DisplayName = displayName
474474 Bio = bio
475− PhotoBase64 = photo }
475+ Photo = if photo = "" then [||] else Convert.FromBase64String photo }
476476 )
477477 | _ -> None
478478
@@ -510,9 +510,7 @@ module App =
510510 | app ->
511511 match app.ApplicationLifetime with
512512 | :? Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime as desktop ->
513− desktop.MainWindow
514− |> Option.ofObj
515− |> Option.map (fun w -> w.StorageProvider)
513+ desktop.MainWindow |> Option.ofObj |> Option.map (fun w -> w.StorageProvider)
516514 | _ -> None
517515
518516 let mapCmd cmdMsg =
@@ -638,25 +636,31 @@ module App =
638636 try
639637 match getStorageProvider () with
640638 | Some sp ->
641− let opts = Avalonia.Platform.Storage.FilePickerOpenOptions(
642− AllowMultiple = false,
643− Title = "Pick Photo",
644− FileTypeFilter = [
645− Avalonia.Platform.Storage.FilePickerFileType("Images",
646− Patterns = [ "*.png"; "*.jpg"; "*.jpeg"; "*.gif"; "*.bmp" ])
647− ])
639+ let opts =
640+ Avalonia.Platform.Storage.FilePickerOpenOptions(
641+ AllowMultiple = false,
642+ Title = "Pick Photo",
643+ FileTypeFilter =
644+ [ Avalonia.Platform.Storage.FilePickerFileType(
645+ "Images",
646+ Patterns = [ "*.png"; "*.jpg"; "*.jpeg"; "*.gif"; "*.bmp" ]
647+ ) ]
648+ )
649+
648650 let! files = sp.OpenFilePickerAsync(opts)
651+
649652 match files |> Seq.tryHead with
650− | None -> dispatch (PhotoPicked "")
653+ | None -> dispatch (PhotoPicked [||])
651654 | Some file ->
652655 use! stream = file.OpenReadAsync()
653656 use ms = new MemoryStream()
654657 do! stream.CopyToAsync(ms)
655− dispatch (PhotoPicked(Convert.ToBase64String(ms.ToArray())))
656− | None -> dispatch (PhotoPicked "")
658+ dispatch (PhotoPicked(ms.ToArray()))
659+ | None -> dispatch (PhotoPicked [||])
657660 with _ ->
658− dispatch (PhotoPicked "")
659− } :> System.Threading.Tasks.Task)
661+ dispatch (PhotoPicked [||])
662+ }
663+ :> System.Threading.Tasks.Task)
660664 |> ignore)
661665
662666 | CmdSaveProfile profile -> Cmd.ofEffect (fun _ -> Store.saveProfile profile)
@@ -667,7 +671,7 @@ module App =
667671 VStack(4.) {
668672 match error with
669673 | Some err ->
670− TextBlock(err).foreground(SolidColorBrush(Colors.Red)).fontSize(12.)
674+ TextBlock(err).foreground(SolidColorBrush(Colors.Red)).fontSize (12.)
671675 Button("Dismiss", DismissError)
672676 | None -> ()
673677 }
@@ -676,77 +680,73 @@ module App =
676680 (VStack(24.) {
677681 Image("avares://Skrepka/Assets/Images/logo.png", Avalonia.Media.Stretch.Uniform)
678682 .margin(8., 0., 8., 0.)
679− .maxWidth(300.)
683+ .maxWidth (300.)
680684
681685 TextBlock("Skrepka")
682686 .fontSize(32.)
683687 .fontFamily(FontFamily("avares://Skrepka/Assets/Fonts#Unbounded"))
684688 .fontWeight(FontWeight.Bold)
685− .centerText()
689+ .centerText ()
686690
687− TextBlock("").height(50.)
691+ TextBlock("").height (50.)
688692
689693 button "Next" GenIdentity
690694 })
691695 .margin(30.)
692− .centerVertical()
696+ .centerVertical ()
693697
694698 let private viewSettings model =
695699 ScrollViewer(
696700 (VStack(16.) {
697− TextBlock("Settings")
698− .fontSize(24.)
699− .centerText()
701+ TextBlock("Settings").fontSize(24.).centerText ()
700702
701703 match model.Auth with
702704 | Identified(id, conn) ->
703705 TextBlock("Your Public Key:")
704706
705− TextBlock(truncKey id.PubKeyHex).fontSize(14.)
707+ TextBlock(truncKey id.PubKeyHex).fontSize (14.)
706708
707709 Button("Copy Public Key", CopyPubKey)
708710
709− TextBlock("Profile:").fontSize(18.)
711+ TextBlock("Profile:").fontSize (18.)
710712
711713 let displayName, bio, photo =
712714 match model.Profile with
713− | Some p -> p.DisplayName, p.Bio, p.PhotoBase64
714− | None -> "", "", ""
715+ | Some p -> p.DisplayName, p.Bio, p.Photo
716+ | None -> "", "", [||]
715717
716718 if displayName <> "" then
717− TextBlock($"Name: {displayName}").fontSize(14.)
719+ TextBlock($"Name: {displayName}").fontSize (14.)
718720
719721 if bio <> "" then
720− TextBlock($"Bio: {bio}")
721− .fontSize(14.)
722− .foreground(SolidColorBrush(Colors.DimGray))
722+ TextBlock($"Bio: {bio}").fontSize(14.).foreground (SolidColorBrush(Colors.DimGray))
723723
724724 Button("Edit Profile", SetPage(EditProfile(displayName, bio, photo)))
725725
726726 TextBlock("Server:")
727727
728728 match conn with
729− | Online _ -> TextBlock(model.ServerUrl).fontSize(14.)
729+ | Online _ -> TextBlock(model.ServerUrl).fontSize (14.)
730730 | _ -> TextBox(model.ServerUrl, SetServerUrl)
731731
732732 viewErrorBanner model.Error
733733
734734 match conn with
735735 | Offline -> Button("Connect", DoConnect)
736− | Connecting -> TextBlock("Connecting...").centerText()
736+ | Connecting -> TextBlock("Connecting...").centerText ()
737737 | Online _ ->
738738 Button("Disconnect", DoDisconnect)
739739 Button("Go to Conversations", SetPage Conversations)
740740 | NoIdentity -> ()
741741 })
742− .margin(20.)
742+ .margin (20.)
743743 )
744744
745745 let private viewConversations model =
746746 ScrollViewer(
747747 (VStack(8.) {
748748 HStack(8.) {
749− TextBlock("Conversations").fontSize(24.)
749+ TextBlock("Conversations").fontSize (24.)
750750
751751 Button("+", SetPage(AddContact("", "")))
752752 Button("Settings", SetPage Settings)
@@ -765,14 +765,14 @@ module App =
765765
766766 TextBlock($"{connStatusLabel model.Auth} [{pubPrefix}]{pollInfo}")
767767 .fontSize(10.)
768− .foreground(SolidColorBrush(Colors.DimGray))
768+ .foreground (SolidColorBrush(Colors.DimGray))
769769
770770 viewErrorBanner model.Error
771771
772772 if model.Contacts.IsEmpty then
773773 TextBlock("No contacts yet. Tap + to add one.")
774774 .centerText()
775− .foreground(SolidColorBrush(Colors.Gray))
775+ .foreground (SolidColorBrush(Colors.Gray))
776776
777777 for c in model.Contacts.Values do
778778 let preview =
@@ -783,7 +783,7 @@ module App =
783783
784784 Button($"{contactName model.Contacts c.Pubkey}\n{preview}", SetPage(Chat(c.Pubkey, "")))
785785 })
786− .margin(20.)
786+ .margin (20.)
787787 )
788788
789789 let private viewChat model pk compose =
@@ -794,7 +794,7 @@ module App =
794794 HStack(8.) {
795795 Button("< Back", SetPage Conversations)
796796
797− TextBlock(name).fontSize(20.).centerVertical()
797+ TextBlock(name).fontSize(20.).centerVertical ()
798798 }
799799
800800 viewErrorBanner model.Error
@@ -802,7 +802,7 @@ module App =
802802 ScrollViewer(
803803 VStack(4.) {
804804 if msgs.IsEmpty then
805− TextBlock("No messages yet").padding(8.).fontSize(14.)
805+ TextBlock("No messages yet").padding(8.).fontSize (14.)
806806 else
807807 for m in msgs do
808808 let prefix, tick =
@@ -811,76 +811,74 @@ module App =
811811 | Outgoing DeliveryStatus.Sent -> "You: ", ""
812812 | Incoming -> "", ""
813813
814− TextBlock($"{prefix}{m.Body}{tick}").padding(8.).fontSize(14.)
814+ TextBlock($"{prefix}{m.Body}{tick}").padding(8.).fontSize (14.)
815815 }
816816 )
817817
818818 HStack(8.) {
819819 TextBox(compose, fun text -> SetPage(Chat(pk, text)))
820820 .watermark("Message...")
821− .horizontalAlignment(Avalonia.Layout.HorizontalAlignment.Stretch)
821+ .horizontalAlignment (Avalonia.Layout.HorizontalAlignment.Stretch)
822822
823823 Button("Send", DoSend)
824824 }
825825 })
826− .margin(12.)
826+ .margin (12.)
827827
828828 let private viewAddContact model cpk cnn =
829829 (VStack(16.) {
830830 HStack(8.) {
831831 Button("< Back", SetPage Conversations)
832832
833− TextBlock("Add Contact").fontSize(20.)
833+ TextBlock("Add Contact").fontSize (20.)
834834 }
835835
836836 viewErrorBanner model.Error
837837
838838 TextBlock("Public Key:")
839− TextBox(cpk, fun text -> SetPage(AddContact(text, cnn))).watermark("sampel-palnet-...")
839+ TextBox(cpk, fun text -> SetPage(AddContact(text, cnn))).watermark ("sampel-palnet-...")
840840
841841 TextBlock("Nickname:")
842842 TextBox(cnn, fun text -> SetPage(AddContact(cpk, text)))
843843
844− Button("Save Contact", DoSaveContact).centerHorizontal()
844+ Button("Save Contact", DoSaveContact).centerHorizontal ()
845845 })
846− .margin(20.)
846+ .margin (20.)
847847
848− let private viewEditProfile model displayName bio photo =
848+ let private viewEditProfile model displayName bio (photo: byte[]) =
849849 ScrollViewer(
850850 (VStack(16.) {
851851 HStack(8.) {
852852 Button("< Back", SetPage Settings)
853− TextBlock("Edit Profile").fontSize(20.)
853+ TextBlock("Edit Profile").fontSize (20.)
854854 }
855855
856856 viewErrorBanner model.Error
857857
858− if photo <> "" then
858+ if photo.Length > 0 then
859859 Image(
860− new Avalonia.Media.Imaging.Bitmap(new MemoryStream(Convert.FromBase64String(photo))),
861− Avalonia.Media.Stretch.Uniform)
860+ new Avalonia.Media.Imaging.Bitmap(new MemoryStream(photo: byte[])),
861+ Avalonia.Media.Stretch.Uniform
862+ )
862863 .width(120.)
863864 .height(120.)
864− .centerHorizontal()
865+ .centerHorizontal ()
865866 else
866− TextBlock("No Photo")
867− .fontSize(16.)
868− .centerText()
869− .foreground(SolidColorBrush(Colors.Gray))
867+ TextBlock("No Photo").fontSize(16.).centerText().foreground (SolidColorBrush(Colors.Gray))
870868
871− Button("Change Photo", DoPickPhoto).centerHorizontal()
869+ Button("Change Photo", DoPickPhoto).centerHorizontal ()
872870
873871 TextBlock("Display Name:")
874− TextBox(displayName, fun text -> SetPage(EditProfile(text, bio, photo))).watermark("Your name")
872+ TextBox(displayName, fun text -> SetPage(EditProfile(text, bio, photo))).watermark ("Your name")
875873
876874 TextBlock("Bio:")
877875
878876 TextBox(bio, fun text -> SetPage(EditProfile(displayName, text, photo)))
879− .watermark("Tell something about yourself")
877+ .watermark ("Tell something about yourself")
880878
881− Button("Save Profile", DoSaveProfile).centerHorizontal()
879+ Button("Save Profile", DoSaveProfile).centerHorizontal ()
882880 })
883− .margin(20.)
881+ .margin (20.)
884882 )
885883
886884 let private pageContent model =
@@ -893,7 +891,7 @@ module App =
893891 | AddContact(pk, nn) -> AnyView(viewAddContact model pk nn)
894892 | EditProfile(displayName, bio, photo) -> AnyView(viewEditProfile model displayName bio photo)
895893
896− Border(page).background(SolidColorBrush(Colors.White))
894+ Border(page).background (SolidColorBrush(Colors.White))
897895
898896 let view model =
899897#if MOBILE
@@ -902,11 +900,13 @@ module App =
902900 DesktopApplication() {
903901 Window(pageContent model)
904902 .title("Skrepka")
903+ .icon("avares://Skrepka/Assets/Images/logo.png")
905904 .width(450.)
906− .height(750.)
905+ .height (750.)
907906 }
908907#endif
909908
910909 let program = Program.statefulWithCmdMsg init update mapCmd |> Program.withView view
911910
912− let create () = FabulousAppBuilder.Configure(FluentTheme, program)
911+ let create () =
912+ FabulousAppBuilder.Configure(FluentTheme, program)