✨ Support captcha
This commit is contained in:
parent
b4990308e9
commit
dc38b46b2c
@ -889,5 +889,6 @@
|
|||||||
"other": "Total {} posts"
|
"other": "Total {} posts"
|
||||||
},
|
},
|
||||||
"settingsHideBottomNav": "Hide Bottom Navigation",
|
"settingsHideBottomNav": "Hide Bottom Navigation",
|
||||||
"settingsHideBottomNavDescription": "Hide the bottom navigation bar, and show the navigation buttons in the drawer."
|
"settingsHideBottomNavDescription": "Hide the bottom navigation bar, and show the navigation buttons in the drawer.",
|
||||||
|
"reCaptcha": "reCaptcha"
|
||||||
}
|
}
|
||||||
|
@ -887,5 +887,6 @@
|
|||||||
"one": "共 {} 条帖子"
|
"one": "共 {} 条帖子"
|
||||||
},
|
},
|
||||||
"settingsHideBottomNav": "隐藏底部导航栏",
|
"settingsHideBottomNav": "隐藏底部导航栏",
|
||||||
"settingsHideBottomNavDescription": "隐藏底部导航栏,在侧边栏抽屉显示导航按钮。"
|
"settingsHideBottomNavDescription": "隐藏底部导航栏,在侧边栏抽屉显示导航按钮。",
|
||||||
|
"reCaptcha": "人机验证"
|
||||||
}
|
}
|
||||||
|
@ -887,5 +887,6 @@
|
|||||||
"one": "共 {} 條帖子"
|
"one": "共 {} 條帖子"
|
||||||
},
|
},
|
||||||
"settingsHideBottomNav": "隱藏底部導航欄",
|
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||||
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。"
|
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||||
|
"reCaptcha": "人機驗證"
|
||||||
}
|
}
|
||||||
|
@ -887,5 +887,6 @@
|
|||||||
"one": "共 {} 條帖子"
|
"one": "共 {} 條帖子"
|
||||||
},
|
},
|
||||||
"settingsHideBottomNav": "隱藏底部導航欄",
|
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||||
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。"
|
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||||
|
"reCaptcha": "人機驗證"
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/screens/captcha.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
@ -33,10 +34,20 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
final username = _usernameController.value.text;
|
final username = _usernameController.value.text;
|
||||||
final nickname = _nicknameController.value.text;
|
final nickname = _nicknameController.value.text;
|
||||||
final password = _passwordController.value.text;
|
final password = _passwordController.value.text;
|
||||||
if (email.isEmpty || username.isEmpty || nickname.isEmpty || password.isEmpty) {
|
if (email.isEmpty ||
|
||||||
|
username.isEmpty ||
|
||||||
|
nickname.isEmpty ||
|
||||||
|
password.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TurnstileScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (captchaTk == null) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.post('/cgi/id/users', data: {
|
await sn.client.post('/cgi/id/users', data: {
|
||||||
@ -45,6 +56,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
'email': email,
|
'email': email,
|
||||||
'password': password,
|
'password': password,
|
||||||
'language': EasyLocalization.of(context)!.currentLocale.toString(),
|
'language': EasyLocalization.of(context)!.currentLocale.toString(),
|
||||||
|
'captcha_token': captchaTk,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@ -91,8 +103,11 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.length < 4 || value.length > 32) {
|
if (value == null ||
|
||||||
return 'fieldUsernameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
|
value.length < 4 ||
|
||||||
|
value.length > 32) {
|
||||||
|
return 'fieldUsernameLengthLimit'
|
||||||
|
.tr(args: [4.toString(), 32.toString()]);
|
||||||
}
|
}
|
||||||
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
|
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
|
||||||
return 'fieldUsernameAlphanumOnly'.tr();
|
return 'fieldUsernameAlphanumOnly'.tr();
|
||||||
@ -108,13 +123,17 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldUsername'.tr(),
|
labelText: 'fieldUsername'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.length < 4 || value.length > 32) {
|
if (value == null ||
|
||||||
return 'fieldNicknameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
|
value.length < 4 ||
|
||||||
|
value.length > 32) {
|
||||||
|
return 'fieldNicknameLengthLimit'
|
||||||
|
.tr(args: [4.toString(), 32.toString()]);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@ -127,7 +146,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldNickname'.tr(),
|
labelText: 'fieldNickname'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
@ -149,7 +169,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldEmail'.tr(),
|
labelText: 'fieldEmail'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
@ -169,7 +190,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldPassword'.tr(),
|
labelText: 'fieldPassword'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 7),
|
).padding(horizontal: 7),
|
||||||
@ -186,9 +208,13 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
Text(
|
Text(
|
||||||
'termAcceptNextWithAgree'.tr(),
|
'termAcceptNextWithAgree'.tr(),
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
style:
|
||||||
color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()),
|
Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface
|
||||||
|
.withAlpha((255 * 0.75).round()),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Material(
|
Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
38
lib/screens/captcha.dart
Normal file
38
lib/screens/captcha.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class TurnstileScreen extends StatefulWidget {
|
||||||
|
const TurnstileScreen({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TurnstileScreen> createState() => _TurnstileScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TurnstileScreenState extends State<TurnstileScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text("reCaptcha").tr()),
|
||||||
|
body: InAppWebView(
|
||||||
|
initialUrlRequest: URLRequest(
|
||||||
|
url: WebUri('${cfg.serverUrl}/captcha?redirect_uri=solink://captcha'),
|
||||||
|
),
|
||||||
|
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||||
|
Uri? url = navigationAction.request.url;
|
||||||
|
if (url != null && url.queryParameters.containsKey('captcha_tk')) {
|
||||||
|
Navigator.pop(context, url.queryParameters['captcha_tk']!);
|
||||||
|
return NavigationActionPolicy.CANCEL;
|
||||||
|
}
|
||||||
|
return NavigationActionPolicy.ALLOW;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/special_day.dart';
|
import 'package:surface/providers/special_day.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/providers/widget.dart';
|
import 'package:surface/providers/widget.dart';
|
||||||
|
import 'package:surface/screens/captcha.dart';
|
||||||
import 'package:surface/types/check_in.dart';
|
import 'package:surface/types/check_in.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
@ -508,11 +509,20 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _doCheckIn() async {
|
Future<void> _doCheckIn() async {
|
||||||
|
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TurnstileScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (captchaTk == null) return;
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final home = context.read<HomeWidgetProvider>();
|
final home = context.read<HomeWidgetProvider>();
|
||||||
final resp = await sn.client.post('/cgi/id/check-in');
|
final resp = await sn.client.post('/cgi/id/check-in', data: {
|
||||||
|
'captcha_token': captchaTk,
|
||||||
|
});
|
||||||
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
||||||
await home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
await home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -185,6 +185,16 @@ class _DrawerContentList extends StatelessWidget {
|
|||||||
horizontal: 32,
|
horizontal: 32,
|
||||||
vertical: 12,
|
vertical: 12,
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
contentPadding: EdgeInsets.only(left: 28, right: 16),
|
||||||
|
leading: const Icon(Symbols.home),
|
||||||
|
title: Text('screenHome').tr(),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).goNamed('home');
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
...rel.availableRealms.map((ele) {
|
...rel.availableRealms.map((ele) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user