@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
33import 'package:flutter/material.dart' ;
44import 'package:misskey_auth/misskey_auth.dart' ;
55import 'package:loader_overlay/loader_overlay.dart' ;
6+ import 'package:flutter/services.dart' ;
67
78void main () {
89 runApp (const MyApp ());
@@ -53,6 +54,38 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
5354 // 状態
5455 OAuthServerInfo ? _serverInfo;
5556
57+ // スコープ入力(カスタムのみを採用)
58+ final TextEditingController _oauthCustomScopesController =
59+ TextEditingController ();
60+ final TextEditingController _miCustomScopesController =
61+ TextEditingController ();
62+
63+ void _addOAuthCustomScopesFromInput () {
64+ final List <String > items = _oauthCustomScopesController.text
65+ .split (',' )
66+ .map ((e) => e.trim ())
67+ .where ((e) => e.isNotEmpty)
68+ .toList ();
69+ if (items.isEmpty) return ;
70+ _scopeController.text = items.join (' ' );
71+ setState (() {
72+ _oauthCustomScopesController.clear ();
73+ });
74+ }
75+
76+ void _addMiCustomScopesFromInput () {
77+ final List <String > items = _miCustomScopesController.text
78+ .split (',' )
79+ .map ((e) => e.trim ())
80+ .where ((e) => e.isNotEmpty)
81+ .toList ();
82+ if (items.isEmpty) return ;
83+ _miPermissionsController.text = items.join (' ' );
84+ setState (() {
85+ _miCustomScopesController.clear ();
86+ });
87+ }
88+
5689 String _mapErrorToMessage (Object error) {
5790 // MisskeyAuth のカスタム例外をユーザー向け日本語に整形
5891 if (error is MisskeyAuthException ) {
@@ -145,6 +178,8 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
145178 _miAppNameController.text = 'Misskey Auth Example' ;
146179 _miPermissionsController.text = 'read:account write:notes' ;
147180 _miIconUrlController.text = '' ;
181+
182+ // 候補配列は廃止(カスタム欄から確定時にTextControllerへ反映)
148183 }
149184
150185 Future <void > _checkServerInfo () async {
@@ -169,22 +204,24 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
169204 });
170205
171206 if (serverInfo == null && mounted) {
172- ScaffoldMessenger .of (context).showSnackBar (
173- const SnackBar (
174- content: Text ('OAuth認証はサポートされていません(MiAuth認証を使用してください)' )),
175- );
207+ ScaffoldMessenger .of (context)
208+ ..hideCurrentSnackBar ()
209+ ..showSnackBar (
210+ const SnackBar (
211+ content: Text ('OAuth認証はサポートされていません(MiAuth認証を使用してください)' )),
212+ );
176213 }
177214 } on MisskeyAuthException catch (e) {
178215 if (mounted) {
179- ScaffoldMessenger .of (context). showSnackBar (
180- SnackBar (content : Text ( _mapErrorToMessage (e))),
181- );
216+ ScaffoldMessenger .of (context)
217+ .. hideCurrentSnackBar ()
218+ .. showSnackBar ( SnackBar (content : Text ( _mapErrorToMessage (e))) );
182219 }
183220 } catch (e) {
184221 if (mounted) {
185- ScaffoldMessenger .of (context). showSnackBar (
186- SnackBar (content : Text (e. toString ())),
187- );
222+ ScaffoldMessenger .of (context)
223+ .. hideCurrentSnackBar ()
224+ .. showSnackBar ( SnackBar (content : Text (e. toString ())) );
188225 }
189226 } finally {
190227 if (mounted) {
@@ -198,6 +235,8 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
198235 context.loaderOverlay.show ();
199236
200237 try {
238+ // 未確定のカスタムスコープ入力を確定して反映
239+ _addOAuthCustomScopesFromInput ();
201240 final config = MisskeyOAuthConfig (
202241 host: _hostController.text.trim (),
203242 clientId: _clientIdController.text.trim (),
@@ -214,24 +253,24 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
214253 }
215254
216255 if (mounted) {
217- ScaffoldMessenger .of (context). showSnackBar (
218- const SnackBar (content : Text ( '認証に成功しました!' )),
219- );
256+ ScaffoldMessenger .of (context)
257+ .. hideCurrentSnackBar ()
258+ .. showSnackBar ( const SnackBar (content : Text ( '認証に成功しました!' )) );
220259 setState (() {
221260 _currentIndex = 3 ; // アカウント一覧タブへ
222261 });
223262 }
224263 } on MisskeyAuthException catch (e) {
225264 if (mounted) {
226- ScaffoldMessenger .of (context). showSnackBar (
227- SnackBar (content : Text ( _mapErrorToMessage (e))),
228- );
265+ ScaffoldMessenger .of (context)
266+ .. hideCurrentSnackBar ()
267+ .. showSnackBar ( SnackBar (content : Text ( _mapErrorToMessage (e))) );
229268 }
230269 } catch (e) {
231270 if (mounted) {
232- ScaffoldMessenger .of (context). showSnackBar (
233- SnackBar (content : Text ( '認証エラー: $ e ' )),
234- );
271+ ScaffoldMessenger .of (context)
272+ .. hideCurrentSnackBar ()
273+ .. showSnackBar ( SnackBar (content : Text ( '認証エラー: $ e ' )) );
235274 }
236275 } finally {
237276 if (mounted) {
@@ -245,6 +284,8 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
245284 context.loaderOverlay.show ();
246285
247286 try {
287+ // 未確定のカスタムスコープ入力を確定して反映
288+ _addMiCustomScopesFromInput ();
248289 final host = _hostController.text.trim ();
249290 if (host.isEmpty) {
250291 throw Exception ('ホストを入力してください' );
@@ -278,24 +319,24 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
278319 }
279320
280321 if (mounted) {
281- ScaffoldMessenger .of (context). showSnackBar (
282- const SnackBar (content : Text ( 'MiAuth に成功しました!' )),
283- );
322+ ScaffoldMessenger .of (context)
323+ .. hideCurrentSnackBar ()
324+ .. showSnackBar ( const SnackBar (content : Text ( 'MiAuth に成功しました!' )) );
284325 setState (() {
285326 _currentIndex = 3 ; // アカウント一覧タブへ
286327 });
287328 }
288329 } on MisskeyAuthException catch (e) {
289330 if (mounted) {
290- ScaffoldMessenger .of (context). showSnackBar (
291- SnackBar (content : Text ( _mapErrorToMessage (e))),
292- );
331+ ScaffoldMessenger .of (context)
332+ .. hideCurrentSnackBar ()
333+ .. showSnackBar ( SnackBar (content : Text ( _mapErrorToMessage (e))) );
293334 }
294335 } catch (e) {
295336 if (mounted) {
296- ScaffoldMessenger .of (context). showSnackBar (
297- SnackBar (content : Text ( 'MiAuth エラー: $ e ' )),
298- );
337+ ScaffoldMessenger .of (context)
338+ .. hideCurrentSnackBar ()
339+ .. showSnackBar ( SnackBar (content : Text ( 'MiAuth エラー: $ e ' )) );
299340 }
300341 } finally {
301342 if (mounted) {
@@ -364,7 +405,7 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
364405 hintText: '例: misskeyauth' ,
365406 ),
366407 ),
367- const SizedBox (height: 16 ),
408+ const SizedBox (height: 8 ),
368409 TextField (
369410 controller: _hostController,
370411 decoration: const InputDecoration (
@@ -390,25 +431,31 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
390431 ),
391432 ),
392433 const SizedBox (height: 8 ),
434+ const Text (
435+ 'カスタムスコープ(カンマ区切り)' ,
436+ style: TextStyle (fontWeight: FontWeight .w600),
437+ ),
438+ const SizedBox (height: 8 ),
393439 TextField (
394- controller: _scopeController ,
440+ controller: _oauthCustomScopesController ,
395441 decoration: const InputDecoration (
396- labelText: 'スコープ' ,
397- hintText: '例: read:account write:notes' ,
442+ labelText: '例: write:drive, read:favorites' ,
398443 ),
444+ keyboardType: TextInputType .text,
445+ textInputAction: TextInputAction .done,
446+ onSubmitted: (_) => _addOAuthCustomScopesFromInput (),
399447 ),
400448 const SizedBox (height: 16 ),
401- Row (
402- children: [
403- ElevatedButton (
404- onPressed: _startAuth,
405- style: ElevatedButton .styleFrom (
406- backgroundColor: Theme .of (context).colorScheme.primary,
407- foregroundColor: Colors .white,
408- ),
409- child: const Text ('認証を開始' ),
449+ SizedBox (
450+ width: double .infinity,
451+ child: ElevatedButton (
452+ onPressed: _startAuth,
453+ style: ElevatedButton .styleFrom (
454+ backgroundColor: Theme .of (context).colorScheme.primary,
455+ foregroundColor: Colors .white,
410456 ),
411- ],
457+ child: const Text ('OAuthで認証' ),
458+ ),
412459 ),
413460 ],
414461 ),
@@ -452,12 +499,19 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
452499 ),
453500 ),
454501 const SizedBox (height: 8 ),
502+ const Text (
503+ 'カスタムスコープ(カンマ区切り)' ,
504+ style: TextStyle (fontWeight: FontWeight .w600),
505+ ),
506+ const SizedBox (height: 8 ),
455507 TextField (
456- controller: _miPermissionsController ,
508+ controller: _miCustomScopesController ,
457509 decoration: const InputDecoration (
458- labelText: '権限(空白/カンマ区切り)' ,
459- hintText: '例: read:account write:notes' ,
510+ labelText: '例: write:drive, read:favorites' ,
460511 ),
512+ keyboardType: TextInputType .text,
513+ textInputAction: TextInputAction .done,
514+ onSubmitted: (_) => _addMiCustomScopesFromInput (),
461515 ),
462516 const SizedBox (height: 8 ),
463517 TextField (
@@ -467,17 +521,16 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
467521 ),
468522 ),
469523 const SizedBox (height: 16 ),
470- Row (
471- children: [
472- ElevatedButton (
473- onPressed: _startMiAuth,
474- style: ElevatedButton .styleFrom (
475- backgroundColor: Theme .of (context).colorScheme.primary,
476- foregroundColor: Colors .white,
477- ),
478- child: const Text ('MiAuthで認証' ),
524+ SizedBox (
525+ width: double .infinity,
526+ child: ElevatedButton (
527+ onPressed: _startMiAuth,
528+ style: ElevatedButton .styleFrom (
529+ backgroundColor: Theme .of (context).colorScheme.primary,
530+ foregroundColor: Colors .white,
479531 ),
480- ],
532+ child: const Text ('MiAuthで認証' ),
533+ ),
481534 ),
482535 ],
483536 ),
@@ -497,13 +550,45 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
497550 style: TextStyle (fontSize: 18 , fontWeight: FontWeight .bold),
498551 ),
499552 const SizedBox (height: 8 ),
500- Text ('認証エンドポイント: \n ${ _serverInfo !. authorizationEndpoint } ' ),
553+ const Text ('認証エンドポイント' ),
501554 const SizedBox (height: 4 ),
502- Text ('トークンエンドポイント:\n ${_serverInfo !.tokenEndpoint }' ),
503- if (_serverInfo! .scopesSupported != null ) ...[
555+ SelectableText (_serverInfo! .authorizationEndpoint),
556+ const SizedBox (height: 8 ),
557+ const Text ('トークンエンドポイント' ),
558+ const SizedBox (height: 4 ),
559+ SelectableText (_serverInfo! .tokenEndpoint),
560+ if (_serverInfo! .scopesSupported != null &&
561+ _serverInfo! .scopesSupported! .isNotEmpty) ...[
562+ const SizedBox (height: 12 ),
563+ const Text ('サポートされているスコープ(タップでコピー)' ),
504564 const SizedBox (height: 4 ),
505- Text (
506- 'サポートされているスコープ:\n ${_serverInfo !.scopesSupported !.join (', ' )}' ),
565+ ConstrainedBox (
566+ constraints: const BoxConstraints (maxHeight: 200 ),
567+ child: Scrollbar (
568+ child: ListView .separated (
569+ itemCount: _serverInfo! .scopesSupported! .length,
570+ separatorBuilder: (_, __) => const Divider (height: 1 ),
571+ itemBuilder: (context, index) {
572+ final scope = _serverInfo! .scopesSupported! [index];
573+ return InkWell (
574+ onTap: () async {
575+ await Clipboard .setData (ClipboardData (text: scope));
576+ if (! context.mounted) return ;
577+ ScaffoldMessenger .of (context)
578+ ..hideCurrentSnackBar ()
579+ ..showSnackBar (
580+ SnackBar (content: Text ('コピーしました: $scope ' )),
581+ );
582+ },
583+ child: Padding (
584+ padding: const EdgeInsets .symmetric (vertical: 10.0 ),
585+ child: Text (scope),
586+ ),
587+ );
588+ },
589+ ),
590+ ),
591+ ),
507592 ],
508593 ],
509594 ),
@@ -534,9 +619,12 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
534619 ),
535620 ),
536621 const SizedBox (height: 16 ),
537- ElevatedButton (
538- onPressed: _checkServerInfo,
539- child: const Text ('サーバー情報を確認' ),
622+ SizedBox (
623+ width: double .infinity,
624+ child: ElevatedButton (
625+ onPressed: _checkServerInfo,
626+ child: const Text ('サーバー情報を確認' ),
627+ ),
540628 ),
541629 ],
542630 ),
@@ -585,9 +673,11 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
585673 '[Dump] ${key .host }/${key .accountId } token=${t ?.accessToken }' );
586674 }
587675 if (! context.mounted) return ;
588- ScaffoldMessenger .of (context).showSnackBar (
589- const SnackBar (content: Text ('デバッグログにトークンを出力しました' )),
590- );
676+ ScaffoldMessenger .of (context)
677+ ..hideCurrentSnackBar ()
678+ ..showSnackBar (
679+ const SnackBar (content: Text ('デバッグログにトークンを出力しました' )),
680+ );
591681 },
592682 )
593683 ],
@@ -647,9 +737,12 @@ class _AuthExamplePageState extends State<AuthExamplePage> {
647737 await _auth.setActive (key);
648738 if (mounted) setState (() {});
649739 if (! context.mounted) return ;
650- ScaffoldMessenger .of (context).showSnackBar (
651- SnackBar (content: Text ('デフォルトを変更: ${key .accountId }' )),
652- );
740+ ScaffoldMessenger .of (context)
741+ ..hideCurrentSnackBar ()
742+ ..showSnackBar (
743+ SnackBar (
744+ content: Text ('デフォルトを変更: ${key .accountId }' )),
745+ );
653746 },
654747 );
655748 },
0 commit comments