✨ Finish up connections
This commit is contained in:
parent
9b67d58ee4
commit
eb4d2c2e2f
@ -156,22 +156,8 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
getLocalizedProviderName(connection.provider),
|
getLocalizedProviderName(connection.provider),
|
||||||
).tr(),
|
).tr(),
|
||||||
subtitle:
|
subtitle:
|
||||||
connection.meta.isNotEmpty
|
connection.meta['email'] != null
|
||||||
? Column(
|
? Text(connection.meta['email'])
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.stretch,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
for (final meta
|
|
||||||
in connection.meta.entries)
|
|
||||||
Text(
|
|
||||||
'${meta.key.split('_').map((word) => word[0].toUpperCase() + word.substring(1)).join(' ')}: ${meta.value}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Text(connection.providedIdentifier),
|
: Text(connection.providedIdentifier),
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
child: getProviderIcon(
|
child: getProviderIcon(
|
||||||
@ -184,7 +170,6 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
).padding(top: 4),
|
).padding(top: 4),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
isThreeLine: true,
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -8,6 +8,7 @@ import 'package:island/models/auth.dart';
|
|||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/account/me/settings.dart';
|
import 'package:island/screens/account/me/settings.dart';
|
||||||
import 'package:island/screens/auth/oidc.native.dart';
|
import 'package:island/screens/auth/oidc.native.dart';
|
||||||
|
import 'package:island/services/text.dart';
|
||||||
import 'package:island/services/time.dart';
|
import 'package:island/services/time.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
@ -96,6 +97,18 @@ class AccountConnectionSheet extends HookConsumerWidget {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(getLocalizedProviderName(connection.provider)).tr(),
|
Text(getLocalizedProviderName(connection.provider)).tr(),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
|
if (connection.meta.isNotEmpty)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
for (final meta in connection.meta.entries)
|
||||||
|
Text(
|
||||||
|
'${meta.key.replaceAll('_', ' ').capitalizeEachWord()}: ${meta.value}',
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
connection.providedIdentifier,
|
connection.providedIdentifier,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
@ -174,10 +187,12 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
|
|||||||
builder:
|
builder:
|
||||||
(context) => OidcScreen(
|
(context) => OidcScreen(
|
||||||
provider: selectedProvider.value.toLowerCase(),
|
provider: selectedProvider.value.toLowerCase(),
|
||||||
title: 'Connect with ${selectedProvider.value}',
|
title:
|
||||||
|
'Connect with ${selectedProvider.value.capitalizeEachWord()}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
showSnackBar(context, 'accountConnectionAddError'.tr());
|
showSnackBar(context, 'accountConnectionAddError'.tr());
|
||||||
@ -326,7 +341,10 @@ class AccountConnectionsSheet extends HookConsumerWidget {
|
|||||||
connection.provider,
|
connection.provider,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
subtitle: Text(connection.providedIdentifier),
|
subtitle:
|
||||||
|
connection.meta['email'] != null
|
||||||
|
? Text(connection.meta['email'])
|
||||||
|
: Text(connection.providedIdentifier),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
DateFormat.yMd().format(
|
DateFormat.yMd().format(
|
||||||
connection.lastUsedAt.toLocal(),
|
connection.lastUsedAt.toLocal(),
|
||||||
|
@ -2,12 +2,15 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
class OidcScreen extends ConsumerStatefulWidget {
|
class OidcScreen extends ConsumerStatefulWidget {
|
||||||
final String provider;
|
final String provider;
|
||||||
@ -21,6 +24,15 @@ class OidcScreen extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _OidcScreenState extends ConsumerState<OidcScreen> {
|
class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||||
String? authToken;
|
String? authToken;
|
||||||
|
String? currentUrl;
|
||||||
|
final TextEditingController _urlController = TextEditingController();
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_urlController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -31,74 +43,155 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.title != null ? Text(widget.title!) : Text('login').tr(),
|
title: widget.title != null ? Text(widget.title!) : Text('login').tr(),
|
||||||
),
|
),
|
||||||
body: InAppWebView(
|
body: Column(
|
||||||
initialSettings: InAppWebViewSettings(
|
children: [
|
||||||
userAgent:
|
Expanded(
|
||||||
kIsWeb
|
child: InAppWebView(
|
||||||
? null
|
initialSettings: InAppWebViewSettings(
|
||||||
: Platform.isIOS
|
userAgent:
|
||||||
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
|
kIsWeb
|
||||||
: Platform.isAndroid
|
? null
|
||||||
? 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
|
: Platform.isIOS
|
||||||
: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
|
||||||
),
|
: Platform.isAndroid
|
||||||
initialUrlRequest: URLRequest(
|
? 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
|
||||||
url: WebUri(
|
: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
||||||
(token?.token.isNotEmpty ?? false)
|
),
|
||||||
? '$serverUrl/auth/login/${widget.provider}?tk=${token!.token}'
|
initialUrlRequest: URLRequest(
|
||||||
: '$serverUrl/auth/login/${widget.provider}',
|
url: WebUri('$serverUrl/auth/login/${widget.provider}'),
|
||||||
),
|
headers: {
|
||||||
),
|
if (token?.token.isNotEmpty ?? false)
|
||||||
onWebViewCreated: (controller) {
|
'Authorization': 'AtField ${token!.token}',
|
||||||
// Register a handler to receive the token from JavaScript
|
},
|
||||||
controller.addJavaScriptHandler(
|
),
|
||||||
handlerName: 'tokenHandler',
|
onWebViewCreated: (controller) {
|
||||||
callback: (args) {
|
// Register a handler to receive the token from JavaScript
|
||||||
// args[0] will be the token string
|
controller.addJavaScriptHandler(
|
||||||
if (args.isNotEmpty && args[0] is String) {
|
handlerName: 'tokenHandler',
|
||||||
|
callback: (args) {
|
||||||
|
// args[0] will be the token string
|
||||||
|
if (args.isNotEmpty && args[0] is String) {
|
||||||
|
setState(() {
|
||||||
|
authToken = args[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the token and close the webview
|
||||||
|
Navigator.of(context).pop(authToken);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||||
|
final url = navigationAction.request.url;
|
||||||
|
if (url != null) {
|
||||||
|
setState(() {
|
||||||
|
currentUrl = url.toString();
|
||||||
|
_urlController.text = currentUrl ?? '';
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final path = url.path;
|
||||||
|
final queryParams = url.queryParameters;
|
||||||
|
|
||||||
|
// Check if we're on the token page
|
||||||
|
if (path.contains('/auth/callback')) {
|
||||||
|
// Extract token from URL
|
||||||
|
final token = queryParams['token'] ?? true;
|
||||||
|
// Return the token and close the webview
|
||||||
|
Navigator.of(context).pop(token);
|
||||||
|
return NavigationActionPolicy.CANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NavigationActionPolicy.ALLOW;
|
||||||
|
},
|
||||||
|
onUpdateVisitedHistory: (controller, url, androidIsReload) {
|
||||||
|
if (url != null) {
|
||||||
|
setState(() {
|
||||||
|
currentUrl = url.toString();
|
||||||
|
_urlController.text = currentUrl ?? '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoadStop: (controller, url) {
|
||||||
setState(() {
|
setState(() {
|
||||||
authToken = args[0];
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
// Return the token and close the webview
|
onLoadStart: (controller, url) {
|
||||||
Navigator.of(context).pop(authToken);
|
setState(() {
|
||||||
}
|
_isLoading = true;
|
||||||
},
|
});
|
||||||
);
|
},
|
||||||
},
|
onLoadError: (controller, url, code, message) {
|
||||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
setState(() {
|
||||||
final url = navigationAction.request.url;
|
_isLoading = false;
|
||||||
if (url != null) {
|
});
|
||||||
final path = url.path;
|
},
|
||||||
final queryParams = url.queryParameters;
|
),
|
||||||
|
),
|
||||||
// Check if we're on the token page
|
// Loading progress indicator
|
||||||
if (path.contains('/auth/token') &&
|
if (_isLoading)
|
||||||
queryParams.containsKey('token')) {
|
LinearProgressIndicator(
|
||||||
// Extract token from URL
|
color: Theme.of(context).colorScheme.primary,
|
||||||
final token = queryParams['token']!;
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
// Return the token and close the webview
|
stopIndicatorRadius: 0,
|
||||||
Navigator.of(context).pop(token);
|
minHeight: 2,
|
||||||
return NavigationActionPolicy.CANCEL;
|
)
|
||||||
}
|
else
|
||||||
}
|
ColoredBox(
|
||||||
return NavigationActionPolicy.ALLOW;
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
},
|
).height(2),
|
||||||
onLoadStop: (controller, url) async {
|
// Debug location bar (only visible in debug mode)
|
||||||
if (url != null && url.path.contains('/auth/token')) {
|
Container(
|
||||||
// Inject JavaScript to call our handler with the token
|
padding: EdgeInsets.only(
|
||||||
await controller.evaluateJavascript(
|
left: 16,
|
||||||
source: '''
|
right: 0,
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
bottom: MediaQuery.of(context).padding.bottom + 8,
|
||||||
const token = urlParams.get('token');
|
top: 8,
|
||||||
if (token) {
|
),
|
||||||
window.flutter_inappwebview.callHandler('tokenHandler', token);
|
color: Theme.of(context).colorScheme.surface,
|
||||||
}
|
child: Row(
|
||||||
''',
|
children: [
|
||||||
);
|
Expanded(
|
||||||
}
|
child: TextField(
|
||||||
},
|
controller: _urlController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
hintText: 'URL',
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
readOnly: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.copy, size: 20),
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () {
|
||||||
|
if (currentUrl != null) {
|
||||||
|
Clipboard.setData(ClipboardData(text: currentUrl!));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('copyToClipboard').tr(),
|
||||||
|
duration: const Duration(seconds: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
14
lib/services/text.dart
Normal file
14
lib/services/text.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
extension StringExtension on String {
|
||||||
|
String capitalizeEachWord() {
|
||||||
|
if (isEmpty) return this;
|
||||||
|
|
||||||
|
return split(' ')
|
||||||
|
.map(
|
||||||
|
(word) =>
|
||||||
|
word.isNotEmpty
|
||||||
|
? '${word[0].toUpperCase()}${word.substring(1).toLowerCase()}'
|
||||||
|
: '',
|
||||||
|
)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user