From bef3221e2f4c51eb76862fb4e9e6a29bd0e45228 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 21 Apr 2024 20:55:17 +0800 Subject: [PATCH] :sparkles: New sign in page --- lib/i18n/app_en.arb | 5 +++ lib/i18n/app_zh.arb | 5 +++ lib/providers/auth.dart | 54 +++++++++-------------------- lib/router.dart | 6 ++++ lib/screens/account.dart | 13 +++---- lib/screens/signin.dart | 74 ++++++++++++++++++++++++++++++++++++++++ pubspec.lock | 16 ++++----- 7 files changed, 122 insertions(+), 51 deletions(-) create mode 100644 lib/screens/signin.dart diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index 373421c..974d624 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -8,9 +8,14 @@ "signInRequired": "Sign in required", "signUp": "Sign Up", "signUpCaption": "Create an account on Solarpass and then get the access of entire Solar Networks!", + "poweredBy": "Powered by Project Hydrogen", + "copyright": "Copyright © 2024 Solsynth LLC", "confirmation": "Confirmation", "confirmCancel": "Not sure", "confirmOkay": "OK", + "username": "Username", + "password": "Password", + "next": "Next", "edit": "Edit", "apply": "Apply", "delete": "Delete", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 1c04a78..4139935 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -8,9 +8,14 @@ "signInRequired": "请先登陆", "signUp": "注册", "signUpCaption": "在 Solarpass 注册一个账号以获得整个 Solar Networks 的存取权!", + "poweredBy": "由 Project Hydrogen 强力驱动", + "copyright": "2024 Solsynth LLC © 版权所有", "confirmation": "确认", "confirmCancel": "不太确定", "confirmOkay": "确定", + "username": "用户名", + "password": "密码", + "next": "下一步", "edit": "编辑", "delete": "删除", "action": "操作", diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 6a7164b..bea2ba2 100755 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -2,16 +2,13 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:solian/screens/auth.dart'; import 'package:oauth2/oauth2.dart' as oauth2; import 'package:solian/utils/service_url.dart'; class AuthProvider { AuthProvider(); - final deviceEndpoint = - getRequestUri('passport', '/api/notifications/subscribe'); - final authorizationEndpoint = getRequestUri('passport', '/auth/o/connect'); + final deviceEndpoint = getRequestUri('passport', '/api/notifications/subscribe'); final tokenEndpoint = getRequestUri('passport', '/api/auth/token'); final userinfoEndpoint = getRequestUri('passport', '/api/users/me'); final redirectUrl = Uri.parse('solian://auth'); @@ -32,14 +29,12 @@ class AuthProvider { Future pickClient() async { if (await storage.containsKey(key: storageKey)) { try { - final credentials = - oauth2.Credentials.fromJson((await storage.read(key: storageKey))!); - client = oauth2.Client(credentials, - identifier: clientId, secret: clientSecret); + final credentials = oauth2.Credentials.fromJson((await storage.read(key: storageKey))!); + client = oauth2.Client(credentials, identifier: clientId, secret: clientSecret); await fetchProfiles(); return true; } catch (e) { - signOff(); + signoff(); return false; } } else { @@ -47,31 +42,20 @@ class AuthProvider { } } - Future createClient(BuildContext context) async { - // If logged in + Future createClient(BuildContext context, String username, String password) async { if (await pickClient()) { return client!; } - var grant = oauth2.AuthorizationCodeGrant( - clientId, - authorizationEndpoint, + return await oauth2.resourceOwnerPasswordGrant( tokenEndpoint, + username, + password, + identifier: clientId, secret: clientSecret, + scopes: ["openid"], basicAuth: false, ); - - var authorizationUrl = - grant.getAuthorizationUrl(redirectUrl, scopes: ["openid"]); - - var responseUrl = await Navigator.of(context, rootNavigator: true).push( - MaterialPageRoute( - builder: (context) => AuthorizationScreen(authorizationUrl), - ), - ); - - var responseUri = Uri.parse(responseUrl); - return await grant.handleAuthorizationResponse(responseUri.queryParameters); } Future fetchProfiles() async { @@ -83,22 +67,21 @@ class AuthProvider { Future refreshToken() async { if (client != null) { - final credentials = await client!.credentials.refresh( - identifier: clientId, secret: clientSecret, basicAuth: false); - client = oauth2.Client(credentials, - identifier: clientId, secret: clientSecret); + final credentials = + await client!.credentials.refresh(identifier: clientId, secret: clientSecret, basicAuth: false); + client = oauth2.Client(credentials, identifier: clientId, secret: clientSecret); storage.write(key: storageKey, value: credentials.toJson()); } } - Future signIn(BuildContext context) async { - client = await createClient(context); + Future signin(BuildContext context, String username, String password) async { + client = await createClient(context, username, password); storage.write(key: storageKey, value: client!.credentials.toJson()); await fetchProfiles(); } - void signOff() { + void signoff() { storage.delete(key: profileKey); storage.delete(key: storageKey); } @@ -109,10 +92,7 @@ class AuthProvider { if (client == null) { await pickClient(); } - if (lastRefreshedAt == null || - DateTime.now() - .subtract(const Duration(minutes: 3)) - .isAfter(lastRefreshedAt!)) { + if (lastRefreshedAt == null || DateTime.now().subtract(const Duration(minutes: 3)).isAfter(lastRefreshedAt!)) { await refreshToken(); lastRefreshedAt = DateTime.now(); } diff --git a/lib/router.dart b/lib/router.dart index 673a8d3..1d99e89 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -8,6 +8,7 @@ import 'package:solian/screens/explore.dart'; import 'package:solian/screens/posts/comment_editor.dart'; import 'package:solian/screens/posts/moment_editor.dart'; import 'package:solian/screens/posts/screen.dart'; +import 'package:solian/screens/signin.dart'; import 'package:solian/widgets/chat/channel_editor.dart'; final router = GoRouter( @@ -58,5 +59,10 @@ final router = GoRouter( dataset: state.pathParameters['dataset'] as String, ), ), + GoRoute( + path: '/auth/sign-in', + name: 'auth.sign-in', + builder: (context, state) => SignInScreen(), + ), ], ); diff --git a/lib/screens/account.dart b/lib/screens/account.dart index e717123..e0ad63f 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:solian/providers/auth.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/router.dart'; import 'package:solian/utils/service_url.dart'; import 'package:solian/widgets/common_wrapper.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -48,7 +49,7 @@ class _AccountScreenState extends State { ), ), onTap: () { - auth.signOff(); + auth.signoff(); setState(() { isAuthorized = false; }); @@ -65,19 +66,19 @@ class _AccountScreenState extends State { title: AppLocalizations.of(context)!.signIn, caption: AppLocalizations.of(context)!.signInCaption, onTap: () { - auth.signIn(context).then((_) { - auth.isAuthorized().then((val) { - setState(() => isAuthorized = val); + router.pushNamed('auth.sign-in').then((did) { + auth.isAuthorized().then((value) { + setState(() => isAuthorized = value); }); }); }, ), ActionCard( - icon: const Icon(Icons.plus_one, color: Colors.white), + icon: const Icon(Icons.add, color: Colors.white), title: AppLocalizations.of(context)!.signUp, caption: AppLocalizations.of(context)!.signUpCaption, onTap: () { - launchUrl(getRequestUri('passport', '/auth/sign-up')); + launchUrl(getRequestUri('passport', '/sign-up')); }, ), ], diff --git a/lib/screens/signin.dart b/lib/screens/signin.dart new file mode 100644 index 0000000..20f977c --- /dev/null +++ b/lib/screens/signin.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/router.dart'; +import 'package:solian/widgets/indent_wrapper.dart'; + +class SignInScreen extends StatelessWidget { + final _usernameController = TextEditingController(); + final _passwordController = TextEditingController(); + + SignInScreen({super.key}); + + @override + Widget build(BuildContext context) { + final auth = context.read(); + + return IndentWrapper( + title: AppLocalizations.of(context)!.signIn, + hideDrawer: true, + child: Center( + child: Container( + width: MediaQuery.of(context).size.width * 0.6, + constraints: const BoxConstraints(maxWidth: 360), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + hintText: AppLocalizations.of(context)!.username, + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + const SizedBox(height: 12), + TextField( + obscureText: true, + autocorrect: false, + enableSuggestions: false, + controller: _passwordController, + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + hintText: AppLocalizations.of(context)!.password, + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + const SizedBox(height: 16), + TextButton( + child: Text(AppLocalizations.of(context)!.next), + onPressed: () { + final username = _usernameController.value.text; + final password = _passwordController.value.text; + if (username.isEmpty || password.isEmpty) return; + auth.signin(context, username, password).then((_) { + router.pop(true); + }).catchError((e) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(e.toString()), + )); + }); + }, + ) + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 09a6c39..0517944 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,10 +433,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" js: dependency: transitive description: @@ -457,10 +457,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: @@ -473,10 +473,10 @@ packages: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: d4c8f568c60af6b6daa74c80fc04411765769882600f6bf9cd4b391c96de42ce url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" lints: dependency: transitive description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.14.0" mime: dependency: transitive description: