11using  System ; 
22using  System . Linq ; 
33using  Windows . System ; 
4+ using  Coder . Desktop . App . Models ; 
5+ using  Coder . Desktop . App . Services ; 
46using  Coder . Desktop . App . Utils ; 
57using  Coder . Desktop . Vpn . Proto ; 
68using  CommunityToolkit . Mvvm . ComponentModel ; 
79using  CommunityToolkit . Mvvm . Input ; 
810using  Microsoft . Extensions . Logging ; 
911using  Microsoft . UI . Xaml ; 
12+ using  Microsoft . UI . Xaml . Controls ; 
13+ using  Microsoft . UI . Xaml . Controls . Primitives ; 
1014using  Microsoft . UI . Xaml . Media ; 
1115using  Microsoft . UI . Xaml . Media . Imaging ; 
1216
1317namespace  Coder . Desktop . App . ViewModels ; 
1418
1519public  interface  IAgentAppViewModelFactory 
1620{ 
17-     public  AgentAppViewModel  Create ( Uuid  id ,  string  name ,  Uri  appUri ,  Uri ?  iconUrl ) ; 
21+     public  AgentAppViewModel  Create ( Uuid  id ,  string  name ,  string  appUri ,  Uri ?  iconUrl ) ; 
1822} 
1923
20- public  class  AgentAppViewModelFactory  :  IAgentAppViewModelFactory 
24+ public  class  AgentAppViewModelFactory ( ILogger < AgentAppViewModel >  childLogger ,  ICredentialManager  credentialManager ) 
25+     :  IAgentAppViewModelFactory 
2126{ 
22-     private  readonly  ILogger < AgentAppViewModel >  _childLogger ; 
23- 
24-     public  AgentAppViewModelFactory ( ILogger < AgentAppViewModel >  childLogger ) 
25-     { 
26-         _childLogger  =  childLogger ; 
27-     } 
28- 
29-     public  AgentAppViewModel  Create ( Uuid  id ,  string  name ,  Uri  appUri ,  Uri ?  iconUrl ) 
27+     public  AgentAppViewModel  Create ( Uuid  id ,  string  name ,  string  appUri ,  Uri ?  iconUrl ) 
3028    { 
31-         return  new  AgentAppViewModel ( _childLogger ) 
29+         return  new  AgentAppViewModel ( childLogger ,   credentialManager ) 
3230        { 
3331            Id  =  id , 
3432            Name  =  name , 
@@ -40,61 +38,40 @@ public AgentAppViewModel Create(Uuid id, string name, Uri appUri, Uri? iconUrl)
4038
4139public  partial  class  AgentAppViewModel  :  ObservableObject ,  IModelUpdateable < AgentAppViewModel > 
4240{ 
41+     private  const  string  SessionTokenUriVar  =  "$SESSION_TOKEN" ; 
42+ 
4343    private  readonly  ILogger < AgentAppViewModel >  _logger ; 
44+     private  readonly  ICredentialManager  _credentialManager ; 
4445
4546    public  required  Uuid  Id  {  get ;  init ;  } 
4647
4748    [ ObservableProperty ]  public  required  partial  string  Name  {  get ;  set ;  } 
4849
4950    [ ObservableProperty ] 
5051    [ NotifyPropertyChangedFor ( nameof ( Details ) ) ] 
51-     public  required  partial  Uri  AppUri  {  get ;  set ;  } 
52+     public  required  partial  string  AppUri  {  get ;  set ;  } 
5253
53-     [ ObservableProperty ] 
54-      [ NotifyPropertyChangedFor ( nameof ( ImageSource ) ) ] 
55-     public  partial  Uri ?   IconUrl  {  get ;  set ;  } 
54+     [ ObservableProperty ]   public   partial   Uri ?   IconUrl   {   get ;   set ;   } 
55+ 
56+     [ ObservableProperty ]   public  partial  ImageSource   IconImageSource  {  get ;  set ;  } 
5657
5758    [ ObservableProperty ]  public  partial  bool  UseFallbackIcon  {  get ;  set ;  }  =  true ; 
5859
5960    public  string  Details  => 
6061        ( string . IsNullOrWhiteSpace ( Name )  ?  "(no name)"  :  Name )  +  ":\n \n "  +  AppUri ; 
6162
62-     public  ImageSource  ImageSource 
63-     { 
64-         get 
65-         { 
66-             if  ( IconUrl  is  null  ||  ( IconUrl . Scheme  !=  "http"  &&  IconUrl . Scheme  !=  "https" ) ) 
67-             { 
68-                 UseFallbackIcon  =  true ; 
69-                 return  new  BitmapImage ( ) ; 
70-             } 
71- 
72-             // Determine what image source to use based on extension, use a 
73-             // BitmapImage as last resort. 
74-             var  ext  =  IconUrl . AbsolutePath . Split ( '/' ) . LastOrDefault ( ) ? . Split ( '.' ) . LastOrDefault ( ) ; 
75-             // TODO: this is definitely a hack, URLs shouldn't need to end in .svg 
76-             if  ( ext  is  "svg" ) 
77-             { 
78-                 // TODO: Some SVGs like `/icon/cursor.svg` contain PNG data and 
79-                 //       don't render at all. 
80-                 var  svg  =  new  SvgImageSource ( IconUrl ) ; 
81-                 svg . Opened  +=  ( _ ,  _ )  =>  _logger . LogDebug ( "app icon opened (svg): {uri}" ,  IconUrl ) ; 
82-                 svg . OpenFailed  +=  ( _ ,  args )  => 
83-                     _logger . LogDebug ( "app icon failed to open (svg): {uri}: {Status}" ,  IconUrl ,  args . Status ) ; 
84-                 return  svg ; 
85-             } 
86- 
87-             var  bitmap  =  new  BitmapImage ( IconUrl ) ; 
88-             bitmap . ImageOpened  +=  ( _ ,  _ )  =>  _logger . LogDebug ( "app icon opened (bitmap): {uri}" ,  IconUrl ) ; 
89-             bitmap . ImageFailed  +=  ( _ ,  args )  => 
90-                 _logger . LogDebug ( "app icon failed to open (bitmap): {uri}: {ErrorMessage}" ,  IconUrl ,  args . ErrorMessage ) ; 
91-             return  bitmap ; 
92-         } 
93-     } 
94- 
95-     public  AgentAppViewModel ( ILogger < AgentAppViewModel >  logger ) 
63+     public  AgentAppViewModel ( ILogger < AgentAppViewModel >  logger ,  ICredentialManager  credentialManager ) 
9664    { 
9765        _logger  =  logger ; 
66+         _credentialManager  =  credentialManager ; 
67+ 
68+         // Apply the icon URL to the icon image source when it is updated. 
69+         IconImageSource  =  UpdateIcon ( ) ; 
70+         PropertyChanged  +=  ( _ ,  args )  => 
71+         { 
72+             if  ( args . PropertyName  ==  nameof ( IconUrl ) ) 
73+                 IconImageSource  =  UpdateIcon ( ) ; 
74+         } ; 
9875    } 
9976
10077    public  bool  TryApplyChanges ( AgentAppViewModel  obj ) 
@@ -116,6 +93,36 @@ public bool TryApplyChanges(AgentAppViewModel obj)
11693        return  true ; 
11794    } 
11895
96+     private  ImageSource  UpdateIcon ( ) 
97+     { 
98+         if  ( IconUrl  is  null  ||  ( IconUrl . Scheme  !=  "http"  &&  IconUrl . Scheme  !=  "https" ) ) 
99+         { 
100+             UseFallbackIcon  =  true ; 
101+             return  new  BitmapImage ( ) ; 
102+         } 
103+ 
104+         // Determine what image source to use based on extension, use a 
105+         // BitmapImage as last resort. 
106+         var  ext  =  IconUrl . AbsolutePath . Split ( '/' ) . LastOrDefault ( ) ? . Split ( '.' ) . LastOrDefault ( ) ; 
107+         // TODO: this is definitely a hack, URLs shouldn't need to end in .svg 
108+         if  ( ext  is  "svg" ) 
109+         { 
110+             // TODO: Some SVGs like `/icon/cursor.svg` contain PNG data and 
111+             //       don't render at all. 
112+             var  svg  =  new  SvgImageSource ( IconUrl ) ; 
113+             svg . Opened  +=  ( _ ,  _ )  =>  _logger . LogDebug ( "app icon opened (svg): {uri}" ,  IconUrl ) ; 
114+             svg . OpenFailed  +=  ( _ ,  args )  => 
115+                 _logger . LogDebug ( "app icon failed to open (svg): {uri}: {Status}" ,  IconUrl ,  args . Status ) ; 
116+             return  svg ; 
117+         } 
118+ 
119+         var  bitmap  =  new  BitmapImage ( IconUrl ) ; 
120+         bitmap . ImageOpened  +=  ( _ ,  _ )  =>  _logger . LogDebug ( "app icon opened (bitmap): {uri}" ,  IconUrl ) ; 
121+         bitmap . ImageFailed  +=  ( _ ,  args )  => 
122+             _logger . LogDebug ( "app icon failed to open (bitmap): {uri}: {ErrorMessage}" ,  IconUrl ,  args . ErrorMessage ) ; 
123+         return  bitmap ; 
124+     } 
125+ 
119126    public  void  OnImageOpened ( object ?  sender ,  RoutedEventArgs  e ) 
120127    { 
121128        UseFallbackIcon  =  false ; 
@@ -127,15 +134,45 @@ public void OnImageFailed(object? sender, RoutedEventArgs e)
127134    } 
128135
129136    [ RelayCommand ] 
130-     private  void  OpenApp ( ) 
137+     private  void  OpenApp ( object   parameter ) 
131138    { 
132139        try 
133140        { 
134-             _  =  Launcher . LaunchUriAsync ( AppUri ) ; 
141+             var  uriString  =  AppUri ; 
142+             var  cred  =  _credentialManager . GetCachedCredentials ( ) ; 
143+             if  ( cred . State  is  CredentialState . Valid  &&  cred . ApiToken  is  not null ) 
144+                 uriString  =  uriString . Replace ( SessionTokenUriVar ,  cred . ApiToken ) ; 
145+             uriString  +=  SessionTokenUriVar ; 
146+             if  ( uriString . Contains ( SessionTokenUriVar ) ) 
147+                 throw  new  Exception ( $ "URI contains { SessionTokenUriVar }  variable but could not be replaced") ; 
148+ 
149+             var  uri  =  new  Uri ( uriString ) ; 
150+             _  =  Launcher . LaunchUriAsync ( uri ) ; 
135151        } 
136-         catch 
152+         catch   ( Exception   e ) 
137153        { 
138-             // TODO: log/notify 
154+             _logger . LogWarning ( e ,  "could not parse or launch app" ) ; 
155+ 
156+             if  ( parameter  is  not FrameworkElement  frameworkElement )  return ; 
157+             var  flyout  =  new  Flyout 
158+             { 
159+                 Content  =  new  TextBlock 
160+                 { 
161+                     Text  =  $ "Could not open app: { e . Message } ", 
162+                     Margin  =  new  Thickness ( 4 ) , 
163+                     TextWrapping  =  TextWrapping . Wrap , 
164+                 } , 
165+                 FlyoutPresenterStyle  =  new  Style ( typeof ( FlyoutPresenter ) ) 
166+                 { 
167+                     Setters  = 
168+                     { 
169+                         new  Setter ( ScrollViewer . HorizontalScrollModeProperty ,  ScrollMode . Disabled ) , 
170+                         new  Setter ( ScrollViewer . HorizontalScrollBarVisibilityProperty ,  ScrollBarVisibility . Disabled ) , 
171+                     } , 
172+                 } , 
173+             } ; 
174+             FlyoutBase . SetAttachedFlyout ( frameworkElement ,  flyout ) ; 
175+             FlyoutBase . ShowAttachedFlyout ( frameworkElement ) ; 
139176        } 
140177    } 
141178} 
0 commit comments