@@ -21,6 +21,14 @@ public sealed class LoginViewController : UIViewController
2121
2222 private UIButton authButton ;
2323
24+ private UIButton refreshTokenButton ;
25+
26+ private UIButton getTimelineButton ;
27+
28+ private UIButton saveSessionButton ;
29+
30+ private UIButton loadSessionButton ;
31+
2432 private UITextField handleField ;
2533
2634 /// <summary>
@@ -31,34 +39,77 @@ public LoginViewController()
3139 this . oauthManager = new OAuthManager ( this , "vip.drasticactions" , this . OnSuccess , this . OnError ) ;
3240 var atProtocolBuilder = new ATProtocolBuilder ( ) ;
3341 this . atProtocol = atProtocolBuilder . Build ( ) ;
42+ this . atProtocol . SessionUpdated += ( sender , args ) =>
43+ {
44+ Console . WriteLine ( $ "Session updated: { args . Session . ToString ( ) } ") ;
45+ } ;
3446
3547 this . View ! . BackgroundColor = UIColor . SystemBackground ;
48+
49+ this . handleField = new UITextField ( ) ;
50+ this . handleField . Placeholder = "Handle" ;
51+ this . handleField . TranslatesAutoresizingMaskIntoConstraints = false ;
52+ this . View ! . AddSubview ( this . handleField ) ;
53+
3654 this . authButton = new UIButton ( UIButtonType . System ) ;
3755 this . authButton . SetTitle ( "Authenticate" , UIControlState . Normal ) ;
56+ this . authButton . TranslatesAutoresizingMaskIntoConstraints = false ;
3857 this . authButton . TouchUpInside += this . AuthButton_TouchUpInside ;
3958 this . View ! . AddSubview ( this . authButton ) ;
4059
41- this . handleField = new UITextField ( ) ;
42- this . handleField . Placeholder = "Handle" ;
60+ this . refreshTokenButton = new UIButton ( UIButtonType . System ) ;
61+ this . refreshTokenButton . SetTitle ( "Refresh Token" , UIControlState . Normal ) ;
62+ this . refreshTokenButton . TranslatesAutoresizingMaskIntoConstraints = false ;
63+ this . refreshTokenButton . Enabled = false ;
64+ this . refreshTokenButton . TouchUpInside += this . RefreshTokenButton_TouchUpInside ;
65+ this . View ! . AddSubview ( this . refreshTokenButton ) ;
4366
44- this . View ! . AddSubview ( this . handleField ) ;
45- this . handleField . TranslatesAutoresizingMaskIntoConstraints = false ;
67+ this . getTimelineButton = new UIButton ( UIButtonType . System ) ;
68+ this . getTimelineButton . SetTitle ( "Get Timeline" , UIControlState . Normal ) ;
69+ this . getTimelineButton . TranslatesAutoresizingMaskIntoConstraints = false ;
70+ this . getTimelineButton . Enabled = false ;
71+ this . getTimelineButton . TouchUpInside += this . GetTimelineButton_TouchUpInside ;
72+ this . View ! . AddSubview ( this . getTimelineButton ) ;
73+
74+ this . saveSessionButton = new UIButton ( UIButtonType . System ) ;
75+ this . saveSessionButton . SetTitle ( "Save Session" , UIControlState . Normal ) ;
76+ this . saveSessionButton . TranslatesAutoresizingMaskIntoConstraints = false ;
77+ this . saveSessionButton . Enabled = false ;
78+ this . saveSessionButton . TouchUpInside += this . SaveSessionButton_TouchUpInside ;
79+ this . View ! . AddSubview ( this . saveSessionButton ) ;
80+
81+ this . loadSessionButton = new UIButton ( UIButtonType . System ) ;
82+ this . loadSessionButton . SetTitle ( "Load Session" , UIControlState . Normal ) ;
83+ this . loadSessionButton . TranslatesAutoresizingMaskIntoConstraints = false ;
84+ this . loadSessionButton . TouchUpInside += this . LoadSessionButton_TouchUpInside ;
85+ this . View ! . AddSubview ( this . loadSessionButton ) ;
4686
4787 this . View ! . AddConstraints ( new [ ]
4888 {
4989 NSLayoutConstraint . Create ( this . handleField , NSLayoutAttribute . Top , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Top , 1 , 100 ) ,
5090 NSLayoutConstraint . Create ( this . handleField , NSLayoutAttribute . Leading , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Leading , 1 , 20 ) ,
5191 NSLayoutConstraint . Create ( this . handleField , NSLayoutAttribute . Trailing , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Trailing , 1 , - 20 ) ,
5292 NSLayoutConstraint . Create ( this . handleField , NSLayoutAttribute . Height , NSLayoutRelation . Equal , 1 , 40 ) ,
53- } ) ;
54-
55- this . authButton . TranslatesAutoresizingMaskIntoConstraints = false ;
56- this . View ! . AddConstraints ( new [ ]
57- {
5893 NSLayoutConstraint . Create ( this . authButton , NSLayoutAttribute . Top , NSLayoutRelation . Equal , this . handleField , NSLayoutAttribute . Bottom , 1 , 20 ) ,
5994 NSLayoutConstraint . Create ( this . authButton , NSLayoutAttribute . Leading , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Leading , 1 , 20 ) ,
6095 NSLayoutConstraint . Create ( this . authButton , NSLayoutAttribute . Trailing , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Trailing , 1 , - 20 ) ,
6196 NSLayoutConstraint . Create ( this . authButton , NSLayoutAttribute . Height , NSLayoutRelation . Equal , 1 , 40 ) ,
97+ NSLayoutConstraint . Create ( this . refreshTokenButton , NSLayoutAttribute . Top , NSLayoutRelation . Equal , this . authButton , NSLayoutAttribute . Bottom , 1 , 20 ) ,
98+ NSLayoutConstraint . Create ( this . refreshTokenButton , NSLayoutAttribute . Leading , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Leading , 1 , 20 ) ,
99+ NSLayoutConstraint . Create ( this . refreshTokenButton , NSLayoutAttribute . Trailing , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Trailing , 1 , - 20 ) ,
100+ NSLayoutConstraint . Create ( this . refreshTokenButton , NSLayoutAttribute . Height , NSLayoutRelation . Equal , 1 , 40 ) ,
101+ NSLayoutConstraint . Create ( this . getTimelineButton , NSLayoutAttribute . Top , NSLayoutRelation . Equal , this . refreshTokenButton , NSLayoutAttribute . Bottom , 1 , 20 ) ,
102+ NSLayoutConstraint . Create ( this . getTimelineButton , NSLayoutAttribute . Leading , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Leading , 1 , 20 ) ,
103+ NSLayoutConstraint . Create ( this . getTimelineButton , NSLayoutAttribute . Trailing , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Trailing , 1 , - 20 ) ,
104+ NSLayoutConstraint . Create ( this . getTimelineButton , NSLayoutAttribute . Height , NSLayoutRelation . Equal , 1 , 40 ) ,
105+ NSLayoutConstraint . Create ( this . saveSessionButton , NSLayoutAttribute . Top , NSLayoutRelation . Equal , this . getTimelineButton , NSLayoutAttribute . Bottom , 1 , 20 ) ,
106+ NSLayoutConstraint . Create ( this . saveSessionButton , NSLayoutAttribute . Leading , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Leading , 1 , 20 ) ,
107+ NSLayoutConstraint . Create ( this . saveSessionButton , NSLayoutAttribute . Trailing , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Trailing , 1 , - 20 ) ,
108+ NSLayoutConstraint . Create ( this . saveSessionButton , NSLayoutAttribute . Height , NSLayoutRelation . Equal , 1 , 40 ) ,
109+ NSLayoutConstraint . Create ( this . loadSessionButton , NSLayoutAttribute . Top , NSLayoutRelation . Equal , this . saveSessionButton , NSLayoutAttribute . Bottom , 1 , 20 ) ,
110+ NSLayoutConstraint . Create ( this . loadSessionButton , NSLayoutAttribute . Leading , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Leading , 1 , 20 ) ,
111+ NSLayoutConstraint . Create ( this . loadSessionButton , NSLayoutAttribute . Trailing , NSLayoutRelation . Equal , this . View ! , NSLayoutAttribute . Trailing , 1 , - 20 ) ,
112+ NSLayoutConstraint . Create ( this . loadSessionButton , NSLayoutAttribute . Height , NSLayoutRelation . Equal , 1 , 40 ) ,
62113 } ) ;
63114 }
64115
@@ -79,7 +130,7 @@ private async void AuthButton_TouchUpInside(object? sender, EventArgs e)
79130 var ( uri , error ) = await this . atProtocol . GenerateOAuth2AuthenticationUrlResultAsync (
80131 ClientMetadataUrl ,
81132 RedirectUri ,
82- new [ ] { "atproto" } ,
133+ new [ ] { "atproto" , "transition:generic" } ,
83134 atIdentifier ! ) ;
84135
85136 if ( error != null )
@@ -114,9 +165,11 @@ private async void OnSuccess(NSUrl? callbackUrl)
114165 var ( session , error ) = await this . atProtocol . AuthenticateWithOAuth2CallbackResultAsync ( callbackUrl . ToString ( ) ) ;
115166 if ( session != null )
116167 {
117- // We have a session!
118168 this . InvokeOnMainThread ( ( ) =>
119169 {
170+ this . refreshTokenButton . Enabled = true ;
171+ this . getTimelineButton . Enabled = true ;
172+ this . saveSessionButton . Enabled = true ;
120173 var alert = UIAlertController . Create ( "Success" , $ "Authenticated as { session . Handle } ", UIAlertControllerStyle . Alert ) ;
121174 alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
122175 this . PresentViewController ( alert , true , null ) ;
@@ -144,6 +197,133 @@ private async void OnSuccess(NSUrl? callbackUrl)
144197 }
145198 }
146199
200+ private async void RefreshTokenButton_TouchUpInside ( object ? sender , EventArgs e )
201+ {
202+ var ( refreshedSession , refreshError ) = await this . atProtocol . RefreshAuthSessionResultAsync ( ) ;
203+ if ( refreshError != null )
204+ {
205+ this . InvokeOnMainThread ( ( ) =>
206+ {
207+ var alert = UIAlertController . Create ( "Error" , $ "Token refresh failed: { refreshError } ", UIAlertControllerStyle . Alert ) ;
208+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
209+ this . PresentViewController ( alert , true , null ) ;
210+ } ) ;
211+
212+ return ;
213+ }
214+
215+ this . InvokeOnMainThread ( ( ) =>
216+ {
217+ var alert = UIAlertController . Create ( "Success" , "OAuth token refreshed." , UIAlertControllerStyle . Alert ) ;
218+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
219+ this . PresentViewController ( alert , true , null ) ;
220+ } ) ;
221+ }
222+
223+ private async void GetTimelineButton_TouchUpInside ( object ? sender , EventArgs e )
224+ {
225+ var ( timeline , timelineError ) = await this . atProtocol . Feed . GetTimelineAsync ( limit : 1 ) ;
226+ if ( timelineError != null )
227+ {
228+ this . InvokeOnMainThread ( ( ) =>
229+ {
230+ var alert = UIAlertController . Create ( "Error" , $ "GetTimeline failed: { timelineError } ", UIAlertControllerStyle . Alert ) ;
231+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
232+ this . PresentViewController ( alert , true , null ) ;
233+ } ) ;
234+
235+ return ;
236+ }
237+
238+ var postCount = timeline ? . Feed ? . Count ?? 0 ;
239+ this . InvokeOnMainThread ( ( ) =>
240+ {
241+ var alert = UIAlertController . Create ( "Success" , $ "Timeline returned { postCount } post(s).", UIAlertControllerStyle . Alert ) ;
242+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
243+ this . PresentViewController ( alert , true , null ) ;
244+ } ) ;
245+ }
246+
247+ private void SaveSessionButton_TouchUpInside ( object ? sender , EventArgs e )
248+ {
249+ var oauthSession = this . atProtocol . OAuthSession ;
250+ if ( oauthSession == null )
251+ {
252+ this . InvokeOnMainThread ( ( ) =>
253+ {
254+ var alert = UIAlertController . Create ( "Error" , "No active OAuth session to save." , UIAlertControllerStyle . Alert ) ;
255+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
256+ this . PresentViewController ( alert , true , null ) ;
257+ } ) ;
258+
259+ return ;
260+ }
261+
262+ var json = oauthSession . ToString ( ) ;
263+ var path = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , "session.json" ) ;
264+ File . WriteAllText ( path , json ) ;
265+
266+ this . InvokeOnMainThread ( ( ) =>
267+ {
268+ var alert = UIAlertController . Create ( "Success" , $ "Session saved to { path } ", UIAlertControllerStyle . Alert ) ;
269+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
270+ this . PresentViewController ( alert , true , null ) ;
271+ } ) ;
272+ }
273+
274+ private async void LoadSessionButton_TouchUpInside ( object ? sender , EventArgs e )
275+ {
276+ var path = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , "session.json" ) ;
277+ if ( ! File . Exists ( path ) )
278+ {
279+ this . InvokeOnMainThread ( ( ) =>
280+ {
281+ var alert = UIAlertController . Create ( "Error" , "No saved session file found." , UIAlertControllerStyle . Alert ) ;
282+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
283+ this . PresentViewController ( alert , true , null ) ;
284+ } ) ;
285+
286+ return ;
287+ }
288+
289+ var json = File . ReadAllText ( path ) ;
290+ var authSession = AuthSession . FromString ( json ) ;
291+ if ( authSession == null )
292+ {
293+ this . InvokeOnMainThread ( ( ) =>
294+ {
295+ var alert = UIAlertController . Create ( "Error" , "Failed to deserialize session." , UIAlertControllerStyle . Alert ) ;
296+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
297+ this . PresentViewController ( alert , true , null ) ;
298+ } ) ;
299+
300+ return ;
301+ }
302+
303+ var ( session , error ) = await this . atProtocol . AuthenticateWithOAuth2SessionResultAsync ( authSession , ClientMetadataUrl ) ;
304+ if ( error != null )
305+ {
306+ this . InvokeOnMainThread ( ( ) =>
307+ {
308+ var alert = UIAlertController . Create ( "Error" , $ "Failed to restore session: { error } ", UIAlertControllerStyle . Alert ) ;
309+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
310+ this . PresentViewController ( alert , true , null ) ;
311+ } ) ;
312+
313+ return ;
314+ }
315+
316+ this . InvokeOnMainThread ( ( ) =>
317+ {
318+ this . refreshTokenButton . Enabled = true ;
319+ this . getTimelineButton . Enabled = true ;
320+ this . saveSessionButton . Enabled = true ;
321+ var alert = UIAlertController . Create ( "Success" , $ "Session loaded. Authenticated as { session ? . Handle } ", UIAlertControllerStyle . Alert ) ;
322+ alert . AddAction ( UIAlertAction . Create ( "OK" , UIAlertActionStyle . Default , null ) ) ;
323+ this . PresentViewController ( alert , true , null ) ;
324+ } ) ;
325+ }
326+
147327 private void OnError ( NSError ? error )
148328 {
149329 this . InvokeOnMainThread ( ( ) =>
0 commit comments