Skip to content

Commit f4023ee

Browse files
feat: サンプルアプリのカスタムスコープ入力機能追加
- OAuthおよびMiAuthのカスタムスコープを入力するためのTextFieldを追加 - 入力されたスコープを確定して反映するメソッドを実装 - スナックバーの表示方法を改善し、ユーザー体験を向上 - デモ用のサムネイル画像を更新
1 parent b83c3a2 commit f4023ee

2 files changed

Lines changed: 162 additions & 69 deletions

File tree

assets/demo_thumb.gif

-156 KB
Loading

example/lib/main.dart

Lines changed: 162 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
33
import 'package:flutter/material.dart';
44
import 'package:misskey_auth/misskey_auth.dart';
55
import 'package:loader_overlay/loader_overlay.dart';
6+
import 'package:flutter/services.dart';
67

78
void 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

Comments
 (0)