From 4d12d243b3ba6a4bedfe4cce4304ab176f874a05 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 9 Nov 2024 18:28:45 +0800 Subject: [PATCH] :sparkles: Login & register --- assets/translations/en-US.json | 42 +- assets/translations/zh-CN.json | 42 +- ios/Podfile.lock | 6 + lib/main.dart | 6 +- lib/providers/sn_network.dart | 88 ++ lib/providers/userinfo.dart | 43 +- lib/router.dart | 22 +- lib/screens/account.dart | 25 +- lib/screens/auth/login.dart | 556 ++++++++ lib/screens/auth/register.dart | 161 +++ lib/types/account.dart | 69 + lib/types/account.freezed.dart | 1266 ++++++++++++++++++ lib/types/account.g.dart | 131 ++ lib/types/auth.dart | 57 + lib/types/auth.freezed.dart | 1002 ++++++++++++++ lib/types/auth.g.dart | 98 ++ lib/widgets/dialog.dart | 151 +++ lib/widgets/navigation/app_scaffold.dart | 34 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + pubspec.lock | 12 +- pubspec.yaml | 2 + 22 files changed, 3798 insertions(+), 20 deletions(-) create mode 100644 lib/screens/auth/login.dart create mode 100644 lib/screens/auth/register.dart create mode 100644 lib/types/account.dart create mode 100644 lib/types/account.freezed.dart create mode 100644 lib/types/account.g.dart create mode 100644 lib/types/auth.dart create mode 100644 lib/types/auth.freezed.dart create mode 100644 lib/types/auth.g.dart create mode 100644 lib/widgets/dialog.dart diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index eff1db6..8381f11 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -1,5 +1,39 @@ { - "screenHome": "Home", - "screenExplore": "Explore", - "screenAccount": "Account" -} \ No newline at end of file + "screen": "Screen", + "screenHome": "Home", + "screenExplore": "Explore", + "screenAccount": "Account", + "screenAuthLogin": "Login", + "screenAuthLoginSubtitle": "Login to Solar Network using Solarpass", + "screenAuthLoginGreeting": "Welcome back", + "screenAuthRegister": "Create an account", + "screenAuthRegisterSubtitle": "Create a Solarpass account", + "dialogOkay": "Okay", + "dialogCancel": "Cancel", + "dialogConfirm": "Confirm", + "dialogDismiss": "Dismiss", + "dialogError": "Something went wrong", + "errorRequestBad": "Bad request, please check your input.", + "errorRequestUnauthorized": "Unauthorized request, please login or try re-login.", + "errorRequestForbidden": "Forbidden request, you have not enough permission to do that.", + "errorRequestNotFound": "The resource that you looking for is not found.", + "errorRequestConnection": "Network connection error, please check your network or the service status.", + "errorRequestUnknown": "Unknown request error, maybe you want to take screenshot and report it to us.", + "prev": "Next", + "next": "Previous", + "fieldUsername": "Username", + "fieldNickname": "Nickname", + "fieldEmail": "Email address", + "fieldPassword": "Password", + "fieldUsernameLookupHint": "You can use username, phone number or email to login", + "forgotPassword": "Forgot password", + "loginPickFactor": "Pick a factor", + "loginMultiFactor": { + "one": "{} step left", + "other": "{} steps left" + }, + "loginEnterPassword": "Enter the code", + "loginSuccess": "Logged in as {}", + "authFactorPassword": "Password", + "authFactorEmail": "Email verification code" +} diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json index b6d3e0a..d2da117 100644 --- a/assets/translations/zh-CN.json +++ b/assets/translations/zh-CN.json @@ -1,5 +1,39 @@ { - "screenHome": "首页", - "screenExplore": "探索" , - "screenAccount": "您" -} \ No newline at end of file + "screen": "页面", + "screenHome": "首页", + "screenExplore": "探索", + "screenAccount": "您", + "screenAuthLogin": "登陆", + "screenAuthLoginSubtitle": "使用 Solarpass 登陆 Solar Network", + "screenAuthLoginGreeting": "欢迎回来", + "screenAuthRegister": "创建账号", + "screenAuthRegisterSubtitle": "创建一个 Solarpass 账号", + "dialogOkay": "好的", + "dialogCancel": "取消", + "dialogConfirm": "确认", + "dialogDismiss": "忽略", + "dialogError": "出了点问题", + "errorRequestBad": "服务器拒绝了您的请求,请检查您的输入。", + "errorRequestUnauthorized": "未授权的请求,请登录或者尝试重新登陆。", + "errorRequestForbidden": "被禁止的请求,您没有足够的权限去做那件事。", + "errorRequestNotFound": "您正查找的资源无法被找到。", + "errorRequestConnection": "网络连接错误,请检查您的网络状态或者检查我们的服务状态。", + "errorRequestUnknown": "位置请求错误,您可能想将此对话框截图并发送给我们。", + "prev": "上一步", + "next": "下一步", + "fieldUsername": "用户名", + "fieldNickname": "显示名", + "fieldEmail": "电子邮箱地址", + "fieldPassword": "密码", + "fieldUsernameLookupHint": "支持用户名、电话号码或邮箱地址", + "forgotPassword": "忘记密码", + "loginPickFactor": "选择方式验证", + "loginMultiFactor": { + "one": "{} 步验证", + "other": "{} 步验证" + }, + "loginEnterPassword": "验证代码", + "loginSuccess": "登录为 {}", + "authFactorPassword": "密码", + "authFactorEmail": "电邮一次性验证码" +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 252a418..0e88c5a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -7,6 +7,8 @@ PODS: - Flutter (1.0.0) - flutter_native_splash (0.0.1): - Flutter + - flutter_secure_storage (3.3.1): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -24,6 +26,7 @@ DEPENDENCIES: - cupertino_http (from `.symlinks/plugins/cupertino_http/ios`) - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) @@ -38,6 +41,8 @@ EXTERNAL SOURCES: :path: Flutter flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" shared_preferences_foundation: @@ -52,6 +57,7 @@ SPEC CHECKSUMS: cupertino_http: 1a3a0f163c1b26e7f1a293b33d476e0fde7a64ec Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 + flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d diff --git a/lib/main.dart b/lib/main.dart index 02cf71d..637b18a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -33,11 +33,15 @@ class SolianApp extends StatelessWidget { providers: [ Provider(create: (_) => SnNetworkProvider()), Provider(create: (ctx) => SnAttachmentProvider(ctx)), - ChangeNotifierProvider(create: (_) => UserProvider()), + ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), ChangeNotifierProvider(create: (_) => ThemeProvider()), ], child: Builder(builder: (context) { + // Initialize some providers + context.read(); + final th = context.watch(); + return MaterialApp.router( theme: th.theme.light, darkTheme: th.theme.dark, diff --git a/lib/providers/sn_network.dart b/lib/providers/sn_network.dart index b7f042a..8eed0e1 100644 --- a/lib/providers/sn_network.dart +++ b/lib/providers/sn_network.dart @@ -1,15 +1,23 @@ +import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio_smart_retry/dio_smart_retry.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:native_dio_adapter/native_dio_adapter.dart'; const kUseLocalNetwork = true; +const kAtkStoreKey = 'nex_user_atk'; +const kRtkStoreKey = 'nex_user_rtk'; + class SnNetworkProvider { late final Dio client; + late final FlutterSecureStorage _storage = FlutterSecureStorage(); + SnNetworkProvider() { client = Dio(); @@ -27,6 +35,56 @@ class SnNetworkProvider { ], )); + client.interceptors.add( + InterceptorsWrapper( + onRequest: ( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + try { + var atk = await _storage.read(key: kAtkStoreKey); + if (atk != null) { + final atkParts = atk.split('.'); + if (atkParts.length != 3) { + throw Exception('invalid format of access token'); + } + + var rawPayload = + atkParts[1].replaceAll('-', '+').replaceAll('_', '/'); + switch (rawPayload.length % 4) { + case 0: + break; + case 2: + rawPayload += '=='; + break; + case 3: + rawPayload += '='; + break; + default: + throw Exception('illegal format of access token payload'); + } + + final b64 = utf8.fuse(base64Url); + final payload = b64.decode(rawPayload); + final exp = jsonDecode(payload)['exp']; + if (exp >= DateTime.now().millisecondsSinceEpoch) { + log('Access token need refresh, doing it at ${DateTime.now()}'); + atk = await refreshToken(); + } + + if (atk != null) { + options.headers['Authorization'] = 'Bearer $atk'; + } else { + log('Access token refresh failed...'); + } + } + } finally { + handler.next(options); + } + }, + ), + ); + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) { // Switch to native implementation if possible client.httpClientAdapter = NativeAdapter(); @@ -37,4 +95,34 @@ class SnNetworkProvider { if (ky.startsWith("http://")) return ky; return '${client.options.baseUrl}/cgi/uc/attachments/$ky'; } + + Future setTokenPair(String atk, String rtk) async { + await Future.wait([ + _storage.write(key: kAtkStoreKey, value: atk), + _storage.write(key: kRtkStoreKey, value: rtk), + ]); + } + + Future clearTokenPair() async { + await Future.wait([ + _storage.delete(key: kAtkStoreKey), + _storage.delete(key: kRtkStoreKey), + ]); + } + + Future refreshToken() async { + final rtk = await _storage.read(key: kRtkStoreKey); + if (rtk == null) return null; + + final resp = await client.post('/cgi/id/auth/token', data: { + 'grant_type': 'refresh_token', + 'refresh_token': rtk, + }); + + final atk = resp.data['access_token']; + final nRtk = resp.data['refresh_token']; + await setTokenPair(atk, nRtk); + + return atk; + } } diff --git a/lib/providers/userinfo.dart b/lib/providers/userinfo.dart index 1fbc24b..4b37797 100644 --- a/lib/providers/userinfo.dart +++ b/lib/providers/userinfo.dart @@ -1,3 +1,42 @@ -import 'package:flutter/foundation.dart'; +import 'dart:developer'; -class UserProvider extends ChangeNotifier {} +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:provider/provider.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/types/account.dart'; + +class UserProvider extends ChangeNotifier { + bool isAuthorized = false; + SnAccount? user; + + late final SnNetworkProvider sn; + + late final FlutterSecureStorage _storage = FlutterSecureStorage(); + + UserProvider(BuildContext context) { + sn = context.read(); + + _storage.read(key: kAtkStoreKey).then((value) { + isAuthorized = value != null; + notifyListeners(); + refreshUser().then((value) { + if (value != null) { + log('Logged in as @${value.name}'); + } + }); + }); + } + + Future refreshUser() async { + if (!isAuthorized) return null; + + final resp = await sn.client.get('/cgi/id/users/me'); + final out = SnAccount.fromJson(resp.data); + + user = out; + notifyListeners(); + + return out; + } +} diff --git a/lib/router.dart b/lib/router.dart index 8fa1645..fc956ae 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,5 +1,7 @@ import 'package:go_router/go_router.dart'; import 'package:surface/screens/account.dart'; +import 'package:surface/screens/auth/login.dart'; +import 'package:surface/screens/auth/register.dart'; import 'package:surface/screens/explore.dart'; import 'package:surface/screens/home.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; @@ -28,6 +30,24 @@ final appRouter = GoRouter( builder: (context, state) => const AccountScreen(), ), ], - ) + ), + ShellRoute( + builder: (context, state, child) => AppScaffold( + body: child, + autoImplyAppBar: true, + ), + routes: [ + GoRoute( + path: '/auth/login', + name: 'authLogin', + builder: (context, state) => const LoginScreen(), + ), + GoRoute( + path: '/auth.register', + name: 'authRegister', + builder: (context, state) => const RegisterScreen(), + ), + ], + ), ], ); diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 8f970a0..b0ae87a 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; class AccountScreen extends StatefulWidget { @@ -14,7 +15,29 @@ class _AccountScreenState extends State { Widget build(BuildContext context) { return AppScaffold( appBar: AppBar( - title: Text("screenHome").tr(), + title: Text("screenAccount").tr(), + ), + body: ListView( + children: [ + ListTile( + title: Text('screenAuthLogin').tr(), + subtitle: Text('screenAuthLoginSubtitle').tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + trailing: const Icon(Icons.chevron_right), + onTap: () { + GoRouter.of(context).pushNamed('authLogin'); + }, + ), + ListTile( + title: Text('screenAuthRegister').tr(), + subtitle: Text('screenAuthRegisterSubtitle').tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + trailing: const Icon(Icons.chevron_right), + onTap: () { + GoRouter.of(context).pushNamed('authRegister'); + }, + ) + ], ), ); } diff --git a/lib/screens/auth/login.dart b/lib/screens/auth/login.dart new file mode 100644 index 0000000..b34f73a --- /dev/null +++ b/lib/screens/auth/login.dart @@ -0,0 +1,556 @@ +import 'package:animations/animations.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/userinfo.dart'; +import 'package:surface/types/auth.dart'; +import 'package:surface/widgets/dialog.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +final Map _factorLabelMap = { + 0: ('authFactorPassword'.tr(), Icons.password, false), + 1: ('authFactorEmail'.tr(), Icons.email, true), +}; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + SnAuthTicket? _currentTicket; + + List? _factors; + SnAuthFactor? _factorPicked; + + int _period = 0; + + @override + Widget build(BuildContext context) { + return Container( + constraints: const BoxConstraints(maxWidth: 280), + child: Theme( + data: Theme.of(context).copyWith(canvasColor: Colors.transparent), + child: SingleChildScrollView( + child: PageTransitionSwitcher( + transitionBuilder: ( + Widget child, + Animation primaryAnimation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.horizontal, + child: child, + ); + }, + child: switch (_period % 3) { + 1 => _LoginPickerScreen( + key: const ValueKey(1), + ticket: _currentTicket, + factors: _factors, + onTicket: (p0) => setState(() { + _currentTicket = p0; + }), + onPickFactor: (p0) => setState(() { + _factorPicked = p0; + }), + onNext: () => setState(() { + _period++; + }), + ), + 2 => _LoginCheckScreen( + key: const ValueKey(2), + ticket: _currentTicket, + factor: _factorPicked, + onTicket: (p0) => setState(() { + _currentTicket = p0; + }), + onNext: (p0) => setState(() { + _period = 1; + }), + ), + _ => _LoginLookupScreen( + key: const ValueKey(0), + ticket: _currentTicket, + onTicket: (p0) => setState(() { + _currentTicket = p0; + }), + onFactor: (p0) => setState(() { + _factors = p0; + }), + onNext: () => setState(() { + _period++; + }), + ), + }, + ).padding(all: 24), + ).center(), + ), + ); + } +} + +class _LoginCheckScreen extends StatefulWidget { + final SnAuthTicket? ticket; + final SnAuthFactor? factor; + final Function(SnAuthTicket?) onTicket; + final Function onNext; + const _LoginCheckScreen({ + super.key, + required this.ticket, + required this.factor, + required this.onTicket, + required this.onNext, + }); + + @override + State<_LoginCheckScreen> createState() => _LoginCheckScreenState(); +} + +class _LoginCheckScreenState extends State<_LoginCheckScreen> { + bool _isBusy = false; + + final _passwordController = TextEditingController(); + + void _performCheckTicket() async { + final password = _passwordController.value.text; + if (password.isEmpty) return; + + final sn = context.read(); + + setState(() => _isBusy = true); + + try { + // Check ticket + final resp = await sn.client.patch('/cgi/id/auth', data: { + 'ticket_id': widget.ticket!.id, + 'factor_id': widget.factor!.id, + 'code': password, + }); + + final result = SnAuthResult.fromJson(resp.data); + widget.onTicket(result.ticket); + + if (!result.isFinished) { + widget.onNext(); + return; + } + + // Finish sign in if possible + final tokenResp = await sn.client.post('/cgi/id/auth/token', data: { + 'grant_type': 'grant_token', + 'code': result.ticket!.grantToken, + }); + final atk = tokenResp.data['access_token']; + final rtk = tokenResp.data['refresh_token']; + await sn.setTokenPair(atk, rtk); + if (!mounted) return; + final user = context.read(); + final userinfo = await user.refreshUser(); + context.showSnackbar('loginSuccess'.tr(args: [ + '@${userinfo!.name} (${userinfo.nick})', + ])); + + Navigator.pop(context); + } catch (err) { + context.showErrorDialog(err); + return; + } finally { + setState(() => _isBusy = false); + } + } + + @override + void dispose() { + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CircleAvatar( + radius: 26, + child: const Icon( + Symbols.password, + size: 28, + ), + ).padding(bottom: 8), + ), + Text( + 'loginEnterPassword'.tr(), + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, + ), + ).padding(left: 4, bottom: 16), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _passwordController, + obscureText: true, + autofillHints: [ + (_factorLabelMap[widget.factor!.type]?.$3 ?? true) + ? AutofillHints.password + : AutofillHints.oneTimeCode + ], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldPassword'.tr(), + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: _isBusy ? null : (_) => _performCheckTicket(), + ).padding(horizontal: 7), + const Gap(12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: _isBusy ? null : () => _performCheckTicket(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next').tr(), + const Icon(Icons.chevron_right), + ], + ), + ), + ], + ), + ], + ); + } +} + +class _LoginPickerScreen extends StatefulWidget { + final SnAuthTicket? ticket; + final List? factors; + final Function(SnAuthTicket?) onTicket; + final Function(SnAuthFactor) onPickFactor; + final Function onNext; + const _LoginPickerScreen({ + super.key, + required this.ticket, + required this.factors, + required this.onTicket, + required this.onPickFactor, + required this.onNext, + }); + + @override + State<_LoginPickerScreen> createState() => _LoginPickerScreenState(); +} + +class _LoginPickerScreenState extends State<_LoginPickerScreen> { + bool _isBusy = false; + int? _factorPicked; + + Color get _unFocusColor => + Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()); + + void _performGetFactorCode() async { + if (_factorPicked == null) return; + + final sn = context.read(); + + setState(() => _isBusy = true); + + try { + // Request one-time-password code + sn.client.post('/cgi/id/auth/factors/$_factorPicked'); + widget.onPickFactor( + widget.factors!.where((x) => x.id == _factorPicked).first, + ); + widget.onNext(); + } catch (err) { + context.showErrorDialog(err); + return; + } finally { + setState(() => _isBusy = false); + } + } + + @override + Widget build(BuildContext context) { + return Column( + key: const ValueKey(1), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CircleAvatar( + radius: 26, + child: const Icon( + Symbols.security, + size: 28, + ), + ).padding(bottom: 8), + ), + Text( + 'loginPickFactor', + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, + ), + ).tr().padding(left: 4), + const Gap(8), + Card( + margin: const EdgeInsets.symmetric(vertical: 4), + child: Column( + children: widget.factors + ?.map( + (x) => CheckboxListTile( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(8), + ), + ), + secondary: Icon( + _factorLabelMap[x.type]?.$2 ?? Icons.question_mark, + ), + title: Text( + _factorLabelMap[x.type]?.$1 ?? 'unknown'.tr(), + ), + enabled: !widget.ticket!.factorTrail.contains(x.id), + value: _factorPicked == x.id, + onChanged: (value) { + if (value == true) { + setState(() => _factorPicked = x.id); + } + }, + ), + ) + .toList() ?? + List.empty(), + ), + ), + const Gap(8), + Text( + 'loginMultiFactor'.plural( + widget.ticket!.stepRemain, + ), + style: TextStyle(color: _unFocusColor, fontSize: 13), + ).padding(horizontal: 16), + const Gap(12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: _isBusy ? null : () => _performGetFactorCode(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next'.tr()), + const Icon(Icons.chevron_right), + ], + ), + ), + ], + ), + ], + ); + } +} + +class _LoginLookupScreen extends StatefulWidget { + final SnAuthTicket? ticket; + final Function(SnAuthTicket?) onTicket; + final Function(List?) onFactor; + final Function onNext; + const _LoginLookupScreen({ + super.key, + required this.ticket, + required this.onTicket, + required this.onFactor, + required this.onNext, + }); + + @override + State<_LoginLookupScreen> createState() => _LoginLookupScreenState(); +} + +class _LoginLookupScreenState extends State<_LoginLookupScreen> { + final _usernameController = TextEditingController(); + + bool _isBusy = false; + + void _requestResetPassword() async { + final username = _usernameController.value.text; + if (username.isEmpty) { + context.showErrorDialog('signinResetPasswordHint'.tr()); + return; + } + + setState(() => _isBusy = true); + + try { + final sn = context.read(); + final lookupResp = + await sn.client.get('/cgi/id/users/lookup?probe=$username'); + await sn.client.post('/cgi/id/users/me/password-reset', data: { + 'user_id': lookupResp.data['id'], + }); + context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr()); + } catch (err) { + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + void _performNewTicket() async { + final username = _usernameController.value.text; + if (username.isEmpty) return; + + final sn = context.read(); + + setState(() => _isBusy = true); + + try { + // Create ticket + final resp = await sn.client.post('/cgi/id/auth', data: { + 'username': username, + }); + final result = SnAuthResult.fromJson(resp.data); + widget.onTicket(result.ticket); + + // Pull factors + final factorResp = + await sn.client.get('/cgi/id/auth/factors', queryParameters: { + 'ticketId': result.ticket!.id.toString(), + }); + widget.onFactor( + List.from( + factorResp.data.map((ele) => SnAuthFactor.fromJson(ele)), + ), + ); + + widget.onNext(); + } catch (err) { + context.showErrorDialog(err); + return; + } finally { + setState(() => _isBusy = false); + } + } + + @override + void dispose() { + _usernameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CircleAvatar( + radius: 26, + child: const Icon( + Symbols.login, + size: 28, + ), + ).padding(bottom: 8), + ), + Text( + 'screenAuthLoginGreeting', + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, + ), + ).tr().padding(left: 4, bottom: 16), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldUsername'.tr(), + helperText: 'fieldUsernameLookupHint'.tr(), + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: _isBusy ? null : (_) => _performNewTicket(), + ).padding(horizontal: 7), + const Gap(12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: _isBusy ? null : () => _requestResetPassword(), + style: TextButton.styleFrom(foregroundColor: Colors.grey), + child: Text('forgotPassword'.tr()), + ), + TextButton( + onPressed: _isBusy ? null : () => _performNewTicket(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next').tr(), + const Icon(Icons.chevron_right), + ], + ), + ), + ], + ), + const Gap(12), + Align( + alignment: Alignment.centerRight, + child: StyledWidget( + Container( + constraints: const BoxConstraints(maxWidth: 290), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'termAcceptNextWithAgree'.tr(), + textAlign: TextAlign.end, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha((255 * 0.75).round()), + ), + ), + Material( + color: Colors.transparent, + child: InkWell( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('termAcceptLink'.tr()), + const Gap(4), + const Icon(Icons.launch, size: 14), + ], + ), + onTap: () { + launchUrlString('https://solsynth.dev/terms'); + }, + ), + ), + ], + ), + ), + ).padding(horizontal: 16), + ), + ], + ); + } +} diff --git a/lib/screens/auth/register.dart b/lib/screens/auth/register.dart new file mode 100644 index 0000000..bc70375 --- /dev/null +++ b/lib/screens/auth/register.dart @@ -0,0 +1,161 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/widgets/dialog.dart'; + +class RegisterScreen extends StatefulWidget { + const RegisterScreen({super.key}); + + @override + State createState() => _RegisterScreenState(); +} + +class _RegisterScreenState extends State { + final _emailController = TextEditingController(); + final _usernameController = TextEditingController(); + final _nicknameController = TextEditingController(); + final _passwordController = TextEditingController(); + + void _performAction(BuildContext context) async { + final email = _emailController.value.text; + final username = _usernameController.value.text; + final nickname = _nicknameController.value.text; + final password = _passwordController.value.text; + if (email.isEmpty || + username.isEmpty || + nickname.isEmpty || + password.isEmpty) { + return; + } + + try { + final sn = context.read(); + await sn.client.post('/cgi/id/users', data: { + 'name': username, + 'nick': nickname, + 'email': email, + 'password': password, + }); + + if (!mounted) return; + + // TODO make celebration here + // ignore: use_build_context_synchronously + Navigator.pop(context); + } catch (err) { + context.showErrorDialog(err); + } + } + + @override + Widget build(BuildContext context) { + return Container( + constraints: const BoxConstraints(maxWidth: 280), + child: StyledWidget( + SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CircleAvatar( + radius: 26, + child: const Icon( + Symbols.person_add, + size: 28, + ), + ).padding(bottom: 8), + ), + Text( + 'screenAuthRegister', + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, + ), + ).tr().padding(left: 4, bottom: 16), + Column( + children: [ + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldUsername'.tr(), + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _nicknameController, + autofillHints: const [AutofillHints.nickname], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldNickname'.tr(), + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _emailController, + autofillHints: const [AutofillHints.email], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldEmail'.tr(), + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + obscureText: true, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.password], + controller: _passwordController, + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldPassword'.tr(), + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: (_) => _performAction(context), + ), + ], + ).padding(horizontal: 7), + const Gap(16), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () => _performAction(context), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next').tr(), + const Icon(Icons.chevron_right), + ], + ), + ), + ) + ], + ), + ), + ).padding(all: 24).center(), + ); + } +} diff --git a/lib/types/account.dart b/lib/types/account.dart new file mode 100644 index 0000000..345c397 --- /dev/null +++ b/lib/types/account.dart @@ -0,0 +1,69 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'account.freezed.dart'; +part 'account.g.dart'; + +@freezed +class SnAccount with _$SnAccount { + const factory SnAccount({ + required int id, + required int? affiliatedId, + required int? affiliatedTo, + required int? automatedBy, + required int? automatedId, + required String avatar, + required String banner, + required DateTime? confirmedAt, + required List contacts, + required DateTime createdAt, + required DateTime? deletedAt, + required String description, + required String name, + required String nick, + required Map permNodes, + required SnAccountProfile? profile, + required DateTime? suspendedAt, + required DateTime updatedAt, + }) = _SnAccount; + + factory SnAccount.fromJson(Map json) => + _$SnAccountFromJson(json); +} + +@freezed +class SnAccountContact with _$SnAccountContact { + const factory SnAccountContact({ + required int accountId, + required String content, + required DateTime createdAt, + required DateTime? deletedAt, + required int id, + required bool isPrimary, + required bool isPublic, + required int type, + required DateTime updatedAt, + required DateTime? verifiedAt, + }) = _SnAccountContact; + + factory SnAccountContact.fromJson(Map json) => + _$SnAccountContactFromJson(json); +} + +@freezed +class SnAccountProfile with _$SnAccountProfile { + const factory SnAccountProfile({ + required int id, + required int accountId, + required DateTime? birthday, + required DateTime createdAt, + required DateTime? deletedAt, + required int experience, + required String firstName, + required String lastName, + required DateTime? lastSeenAt, + required DateTime updatedAt, + }) = _SnAccountProfile; + + factory SnAccountProfile.fromJson(Map json) => + _$SnAccountProfileFromJson(json); +} diff --git a/lib/types/account.freezed.dart b/lib/types/account.freezed.dart new file mode 100644 index 0000000..43f6bde --- /dev/null +++ b/lib/types/account.freezed.dart @@ -0,0 +1,1266 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'account.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +SnAccount _$SnAccountFromJson(Map json) { + return _SnAccount.fromJson(json); +} + +/// @nodoc +mixin _$SnAccount { + int get id => throw _privateConstructorUsedError; + int? get affiliatedId => throw _privateConstructorUsedError; + int? get affiliatedTo => throw _privateConstructorUsedError; + int? get automatedBy => throw _privateConstructorUsedError; + int? get automatedId => throw _privateConstructorUsedError; + String get avatar => throw _privateConstructorUsedError; + String get banner => throw _privateConstructorUsedError; + DateTime? get confirmedAt => throw _privateConstructorUsedError; + List get contacts => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime? get deletedAt => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get nick => throw _privateConstructorUsedError; + Map get permNodes => throw _privateConstructorUsedError; + SnAccountProfile? get profile => throw _privateConstructorUsedError; + DateTime? get suspendedAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this SnAccount to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnAccount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnAccountCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnAccountCopyWith<$Res> { + factory $SnAccountCopyWith(SnAccount value, $Res Function(SnAccount) then) = + _$SnAccountCopyWithImpl<$Res, SnAccount>; + @useResult + $Res call( + {int id, + int? affiliatedId, + int? affiliatedTo, + int? automatedBy, + int? automatedId, + String avatar, + String banner, + DateTime? confirmedAt, + List contacts, + DateTime createdAt, + DateTime? deletedAt, + String description, + String name, + String nick, + Map permNodes, + SnAccountProfile? profile, + DateTime? suspendedAt, + DateTime updatedAt}); + + $SnAccountProfileCopyWith<$Res>? get profile; +} + +/// @nodoc +class _$SnAccountCopyWithImpl<$Res, $Val extends SnAccount> + implements $SnAccountCopyWith<$Res> { + _$SnAccountCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnAccount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? affiliatedId = freezed, + Object? affiliatedTo = freezed, + Object? automatedBy = freezed, + Object? automatedId = freezed, + Object? avatar = null, + Object? banner = null, + Object? confirmedAt = freezed, + Object? contacts = null, + Object? createdAt = null, + Object? deletedAt = freezed, + Object? description = null, + Object? name = null, + Object? nick = null, + Object? permNodes = null, + Object? profile = freezed, + Object? suspendedAt = freezed, + Object? updatedAt = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + affiliatedId: freezed == affiliatedId + ? _value.affiliatedId + : affiliatedId // ignore: cast_nullable_to_non_nullable + as int?, + affiliatedTo: freezed == affiliatedTo + ? _value.affiliatedTo + : affiliatedTo // ignore: cast_nullable_to_non_nullable + as int?, + automatedBy: freezed == automatedBy + ? _value.automatedBy + : automatedBy // ignore: cast_nullable_to_non_nullable + as int?, + automatedId: freezed == automatedId + ? _value.automatedId + : automatedId // ignore: cast_nullable_to_non_nullable + as int?, + avatar: null == avatar + ? _value.avatar + : avatar // ignore: cast_nullable_to_non_nullable + as String, + banner: null == banner + ? _value.banner + : banner // ignore: cast_nullable_to_non_nullable + as String, + confirmedAt: freezed == confirmedAt + ? _value.confirmedAt + : confirmedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + contacts: null == contacts + ? _value.contacts + : contacts // ignore: cast_nullable_to_non_nullable + as List, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + nick: null == nick + ? _value.nick + : nick // ignore: cast_nullable_to_non_nullable + as String, + permNodes: null == permNodes + ? _value.permNodes + : permNodes // ignore: cast_nullable_to_non_nullable + as Map, + profile: freezed == profile + ? _value.profile + : profile // ignore: cast_nullable_to_non_nullable + as SnAccountProfile?, + suspendedAt: freezed == suspendedAt + ? _value.suspendedAt + : suspendedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) as $Val); + } + + /// Create a copy of SnAccount + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SnAccountProfileCopyWith<$Res>? get profile { + if (_value.profile == null) { + return null; + } + + return $SnAccountProfileCopyWith<$Res>(_value.profile!, (value) { + return _then(_value.copyWith(profile: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SnAccountImplCopyWith<$Res> + implements $SnAccountCopyWith<$Res> { + factory _$$SnAccountImplCopyWith( + _$SnAccountImpl value, $Res Function(_$SnAccountImpl) then) = + __$$SnAccountImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + int? affiliatedId, + int? affiliatedTo, + int? automatedBy, + int? automatedId, + String avatar, + String banner, + DateTime? confirmedAt, + List contacts, + DateTime createdAt, + DateTime? deletedAt, + String description, + String name, + String nick, + Map permNodes, + SnAccountProfile? profile, + DateTime? suspendedAt, + DateTime updatedAt}); + + @override + $SnAccountProfileCopyWith<$Res>? get profile; +} + +/// @nodoc +class __$$SnAccountImplCopyWithImpl<$Res> + extends _$SnAccountCopyWithImpl<$Res, _$SnAccountImpl> + implements _$$SnAccountImplCopyWith<$Res> { + __$$SnAccountImplCopyWithImpl( + _$SnAccountImpl _value, $Res Function(_$SnAccountImpl) _then) + : super(_value, _then); + + /// Create a copy of SnAccount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? affiliatedId = freezed, + Object? affiliatedTo = freezed, + Object? automatedBy = freezed, + Object? automatedId = freezed, + Object? avatar = null, + Object? banner = null, + Object? confirmedAt = freezed, + Object? contacts = null, + Object? createdAt = null, + Object? deletedAt = freezed, + Object? description = null, + Object? name = null, + Object? nick = null, + Object? permNodes = null, + Object? profile = freezed, + Object? suspendedAt = freezed, + Object? updatedAt = null, + }) { + return _then(_$SnAccountImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + affiliatedId: freezed == affiliatedId + ? _value.affiliatedId + : affiliatedId // ignore: cast_nullable_to_non_nullable + as int?, + affiliatedTo: freezed == affiliatedTo + ? _value.affiliatedTo + : affiliatedTo // ignore: cast_nullable_to_non_nullable + as int?, + automatedBy: freezed == automatedBy + ? _value.automatedBy + : automatedBy // ignore: cast_nullable_to_non_nullable + as int?, + automatedId: freezed == automatedId + ? _value.automatedId + : automatedId // ignore: cast_nullable_to_non_nullable + as int?, + avatar: null == avatar + ? _value.avatar + : avatar // ignore: cast_nullable_to_non_nullable + as String, + banner: null == banner + ? _value.banner + : banner // ignore: cast_nullable_to_non_nullable + as String, + confirmedAt: freezed == confirmedAt + ? _value.confirmedAt + : confirmedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + contacts: null == contacts + ? _value._contacts + : contacts // ignore: cast_nullable_to_non_nullable + as List, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + nick: null == nick + ? _value.nick + : nick // ignore: cast_nullable_to_non_nullable + as String, + permNodes: null == permNodes + ? _value._permNodes + : permNodes // ignore: cast_nullable_to_non_nullable + as Map, + profile: freezed == profile + ? _value.profile + : profile // ignore: cast_nullable_to_non_nullable + as SnAccountProfile?, + suspendedAt: freezed == suspendedAt + ? _value.suspendedAt + : suspendedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnAccountImpl implements _SnAccount { + const _$SnAccountImpl( + {required this.id, + required this.affiliatedId, + required this.affiliatedTo, + required this.automatedBy, + required this.automatedId, + required this.avatar, + required this.banner, + required this.confirmedAt, + required final List contacts, + required this.createdAt, + required this.deletedAt, + required this.description, + required this.name, + required this.nick, + required final Map permNodes, + required this.profile, + required this.suspendedAt, + required this.updatedAt}) + : _contacts = contacts, + _permNodes = permNodes; + + factory _$SnAccountImpl.fromJson(Map json) => + _$$SnAccountImplFromJson(json); + + @override + final int id; + @override + final int? affiliatedId; + @override + final int? affiliatedTo; + @override + final int? automatedBy; + @override + final int? automatedId; + @override + final String avatar; + @override + final String banner; + @override + final DateTime? confirmedAt; + final List _contacts; + @override + List get contacts { + if (_contacts is EqualUnmodifiableListView) return _contacts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_contacts); + } + + @override + final DateTime createdAt; + @override + final DateTime? deletedAt; + @override + final String description; + @override + final String name; + @override + final String nick; + final Map _permNodes; + @override + Map get permNodes { + if (_permNodes is EqualUnmodifiableMapView) return _permNodes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_permNodes); + } + + @override + final SnAccountProfile? profile; + @override + final DateTime? suspendedAt; + @override + final DateTime updatedAt; + + @override + String toString() { + return 'SnAccount(id: $id, affiliatedId: $affiliatedId, affiliatedTo: $affiliatedTo, automatedBy: $automatedBy, automatedId: $automatedId, avatar: $avatar, banner: $banner, confirmedAt: $confirmedAt, contacts: $contacts, createdAt: $createdAt, deletedAt: $deletedAt, description: $description, name: $name, nick: $nick, permNodes: $permNodes, profile: $profile, suspendedAt: $suspendedAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnAccountImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.affiliatedId, affiliatedId) || + other.affiliatedId == affiliatedId) && + (identical(other.affiliatedTo, affiliatedTo) || + other.affiliatedTo == affiliatedTo) && + (identical(other.automatedBy, automatedBy) || + other.automatedBy == automatedBy) && + (identical(other.automatedId, automatedId) || + other.automatedId == automatedId) && + (identical(other.avatar, avatar) || other.avatar == avatar) && + (identical(other.banner, banner) || other.banner == banner) && + (identical(other.confirmedAt, confirmedAt) || + other.confirmedAt == confirmedAt) && + const DeepCollectionEquality().equals(other._contacts, _contacts) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && + (identical(other.description, description) || + other.description == description) && + (identical(other.name, name) || other.name == name) && + (identical(other.nick, nick) || other.nick == nick) && + const DeepCollectionEquality() + .equals(other._permNodes, _permNodes) && + (identical(other.profile, profile) || other.profile == profile) && + (identical(other.suspendedAt, suspendedAt) || + other.suspendedAt == suspendedAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + affiliatedId, + affiliatedTo, + automatedBy, + automatedId, + avatar, + banner, + confirmedAt, + const DeepCollectionEquality().hash(_contacts), + createdAt, + deletedAt, + description, + name, + nick, + const DeepCollectionEquality().hash(_permNodes), + profile, + suspendedAt, + updatedAt); + + /// Create a copy of SnAccount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnAccountImplCopyWith<_$SnAccountImpl> get copyWith => + __$$SnAccountImplCopyWithImpl<_$SnAccountImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SnAccountImplToJson( + this, + ); + } +} + +abstract class _SnAccount implements SnAccount { + const factory _SnAccount( + {required final int id, + required final int? affiliatedId, + required final int? affiliatedTo, + required final int? automatedBy, + required final int? automatedId, + required final String avatar, + required final String banner, + required final DateTime? confirmedAt, + required final List contacts, + required final DateTime createdAt, + required final DateTime? deletedAt, + required final String description, + required final String name, + required final String nick, + required final Map permNodes, + required final SnAccountProfile? profile, + required final DateTime? suspendedAt, + required final DateTime updatedAt}) = _$SnAccountImpl; + + factory _SnAccount.fromJson(Map json) = + _$SnAccountImpl.fromJson; + + @override + int get id; + @override + int? get affiliatedId; + @override + int? get affiliatedTo; + @override + int? get automatedBy; + @override + int? get automatedId; + @override + String get avatar; + @override + String get banner; + @override + DateTime? get confirmedAt; + @override + List get contacts; + @override + DateTime get createdAt; + @override + DateTime? get deletedAt; + @override + String get description; + @override + String get name; + @override + String get nick; + @override + Map get permNodes; + @override + SnAccountProfile? get profile; + @override + DateTime? get suspendedAt; + @override + DateTime get updatedAt; + + /// Create a copy of SnAccount + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnAccountImplCopyWith<_$SnAccountImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SnAccountContact _$SnAccountContactFromJson(Map json) { + return _SnAccountContact.fromJson(json); +} + +/// @nodoc +mixin _$SnAccountContact { + int get accountId => throw _privateConstructorUsedError; + String get content => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime? get deletedAt => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + bool get isPrimary => throw _privateConstructorUsedError; + bool get isPublic => throw _privateConstructorUsedError; + int get type => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + DateTime? get verifiedAt => throw _privateConstructorUsedError; + + /// Serializes this SnAccountContact to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnAccountContact + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnAccountContactCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnAccountContactCopyWith<$Res> { + factory $SnAccountContactCopyWith( + SnAccountContact value, $Res Function(SnAccountContact) then) = + _$SnAccountContactCopyWithImpl<$Res, SnAccountContact>; + @useResult + $Res call( + {int accountId, + String content, + DateTime createdAt, + DateTime? deletedAt, + int id, + bool isPrimary, + bool isPublic, + int type, + DateTime updatedAt, + DateTime? verifiedAt}); +} + +/// @nodoc +class _$SnAccountContactCopyWithImpl<$Res, $Val extends SnAccountContact> + implements $SnAccountContactCopyWith<$Res> { + _$SnAccountContactCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnAccountContact + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountId = null, + Object? content = null, + Object? createdAt = null, + Object? deletedAt = freezed, + Object? id = null, + Object? isPrimary = null, + Object? isPublic = null, + Object? type = null, + Object? updatedAt = null, + Object? verifiedAt = freezed, + }) { + return _then(_value.copyWith( + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + content: null == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + isPrimary: null == isPrimary + ? _value.isPrimary + : isPrimary // ignore: cast_nullable_to_non_nullable + as bool, + isPublic: null == isPublic + ? _value.isPublic + : isPublic // ignore: cast_nullable_to_non_nullable + as bool, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as int, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + verifiedAt: freezed == verifiedAt + ? _value.verifiedAt + : verifiedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SnAccountContactImplCopyWith<$Res> + implements $SnAccountContactCopyWith<$Res> { + factory _$$SnAccountContactImplCopyWith(_$SnAccountContactImpl value, + $Res Function(_$SnAccountContactImpl) then) = + __$$SnAccountContactImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int accountId, + String content, + DateTime createdAt, + DateTime? deletedAt, + int id, + bool isPrimary, + bool isPublic, + int type, + DateTime updatedAt, + DateTime? verifiedAt}); +} + +/// @nodoc +class __$$SnAccountContactImplCopyWithImpl<$Res> + extends _$SnAccountContactCopyWithImpl<$Res, _$SnAccountContactImpl> + implements _$$SnAccountContactImplCopyWith<$Res> { + __$$SnAccountContactImplCopyWithImpl(_$SnAccountContactImpl _value, + $Res Function(_$SnAccountContactImpl) _then) + : super(_value, _then); + + /// Create a copy of SnAccountContact + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountId = null, + Object? content = null, + Object? createdAt = null, + Object? deletedAt = freezed, + Object? id = null, + Object? isPrimary = null, + Object? isPublic = null, + Object? type = null, + Object? updatedAt = null, + Object? verifiedAt = freezed, + }) { + return _then(_$SnAccountContactImpl( + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + content: null == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + isPrimary: null == isPrimary + ? _value.isPrimary + : isPrimary // ignore: cast_nullable_to_non_nullable + as bool, + isPublic: null == isPublic + ? _value.isPublic + : isPublic // ignore: cast_nullable_to_non_nullable + as bool, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as int, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + verifiedAt: freezed == verifiedAt + ? _value.verifiedAt + : verifiedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnAccountContactImpl implements _SnAccountContact { + const _$SnAccountContactImpl( + {required this.accountId, + required this.content, + required this.createdAt, + required this.deletedAt, + required this.id, + required this.isPrimary, + required this.isPublic, + required this.type, + required this.updatedAt, + required this.verifiedAt}); + + factory _$SnAccountContactImpl.fromJson(Map json) => + _$$SnAccountContactImplFromJson(json); + + @override + final int accountId; + @override + final String content; + @override + final DateTime createdAt; + @override + final DateTime? deletedAt; + @override + final int id; + @override + final bool isPrimary; + @override + final bool isPublic; + @override + final int type; + @override + final DateTime updatedAt; + @override + final DateTime? verifiedAt; + + @override + String toString() { + return 'SnAccountContact(accountId: $accountId, content: $content, createdAt: $createdAt, deletedAt: $deletedAt, id: $id, isPrimary: $isPrimary, isPublic: $isPublic, type: $type, updatedAt: $updatedAt, verifiedAt: $verifiedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnAccountContactImpl && + (identical(other.accountId, accountId) || + other.accountId == accountId) && + (identical(other.content, content) || other.content == content) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && + (identical(other.id, id) || other.id == id) && + (identical(other.isPrimary, isPrimary) || + other.isPrimary == isPrimary) && + (identical(other.isPublic, isPublic) || + other.isPublic == isPublic) && + (identical(other.type, type) || other.type == type) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + (identical(other.verifiedAt, verifiedAt) || + other.verifiedAt == verifiedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, accountId, content, createdAt, + deletedAt, id, isPrimary, isPublic, type, updatedAt, verifiedAt); + + /// Create a copy of SnAccountContact + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnAccountContactImplCopyWith<_$SnAccountContactImpl> get copyWith => + __$$SnAccountContactImplCopyWithImpl<_$SnAccountContactImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SnAccountContactImplToJson( + this, + ); + } +} + +abstract class _SnAccountContact implements SnAccountContact { + const factory _SnAccountContact( + {required final int accountId, + required final String content, + required final DateTime createdAt, + required final DateTime? deletedAt, + required final int id, + required final bool isPrimary, + required final bool isPublic, + required final int type, + required final DateTime updatedAt, + required final DateTime? verifiedAt}) = _$SnAccountContactImpl; + + factory _SnAccountContact.fromJson(Map json) = + _$SnAccountContactImpl.fromJson; + + @override + int get accountId; + @override + String get content; + @override + DateTime get createdAt; + @override + DateTime? get deletedAt; + @override + int get id; + @override + bool get isPrimary; + @override + bool get isPublic; + @override + int get type; + @override + DateTime get updatedAt; + @override + DateTime? get verifiedAt; + + /// Create a copy of SnAccountContact + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnAccountContactImplCopyWith<_$SnAccountContactImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SnAccountProfile _$SnAccountProfileFromJson(Map json) { + return _SnAccountProfile.fromJson(json); +} + +/// @nodoc +mixin _$SnAccountProfile { + int get id => throw _privateConstructorUsedError; + int get accountId => throw _privateConstructorUsedError; + DateTime? get birthday => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime? get deletedAt => throw _privateConstructorUsedError; + int get experience => throw _privateConstructorUsedError; + String get firstName => throw _privateConstructorUsedError; + String get lastName => throw _privateConstructorUsedError; + DateTime? get lastSeenAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this SnAccountProfile to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnAccountProfile + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnAccountProfileCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnAccountProfileCopyWith<$Res> { + factory $SnAccountProfileCopyWith( + SnAccountProfile value, $Res Function(SnAccountProfile) then) = + _$SnAccountProfileCopyWithImpl<$Res, SnAccountProfile>; + @useResult + $Res call( + {int id, + int accountId, + DateTime? birthday, + DateTime createdAt, + DateTime? deletedAt, + int experience, + String firstName, + String lastName, + DateTime? lastSeenAt, + DateTime updatedAt}); +} + +/// @nodoc +class _$SnAccountProfileCopyWithImpl<$Res, $Val extends SnAccountProfile> + implements $SnAccountProfileCopyWith<$Res> { + _$SnAccountProfileCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnAccountProfile + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? accountId = null, + Object? birthday = freezed, + Object? createdAt = null, + Object? deletedAt = freezed, + Object? experience = null, + Object? firstName = null, + Object? lastName = null, + Object? lastSeenAt = freezed, + Object? updatedAt = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + birthday: freezed == birthday + ? _value.birthday + : birthday // ignore: cast_nullable_to_non_nullable + as DateTime?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + experience: null == experience + ? _value.experience + : experience // ignore: cast_nullable_to_non_nullable + as int, + firstName: null == firstName + ? _value.firstName + : firstName // ignore: cast_nullable_to_non_nullable + as String, + lastName: null == lastName + ? _value.lastName + : lastName // ignore: cast_nullable_to_non_nullable + as String, + lastSeenAt: freezed == lastSeenAt + ? _value.lastSeenAt + : lastSeenAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SnAccountProfileImplCopyWith<$Res> + implements $SnAccountProfileCopyWith<$Res> { + factory _$$SnAccountProfileImplCopyWith(_$SnAccountProfileImpl value, + $Res Function(_$SnAccountProfileImpl) then) = + __$$SnAccountProfileImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + int accountId, + DateTime? birthday, + DateTime createdAt, + DateTime? deletedAt, + int experience, + String firstName, + String lastName, + DateTime? lastSeenAt, + DateTime updatedAt}); +} + +/// @nodoc +class __$$SnAccountProfileImplCopyWithImpl<$Res> + extends _$SnAccountProfileCopyWithImpl<$Res, _$SnAccountProfileImpl> + implements _$$SnAccountProfileImplCopyWith<$Res> { + __$$SnAccountProfileImplCopyWithImpl(_$SnAccountProfileImpl _value, + $Res Function(_$SnAccountProfileImpl) _then) + : super(_value, _then); + + /// Create a copy of SnAccountProfile + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? accountId = null, + Object? birthday = freezed, + Object? createdAt = null, + Object? deletedAt = freezed, + Object? experience = null, + Object? firstName = null, + Object? lastName = null, + Object? lastSeenAt = freezed, + Object? updatedAt = null, + }) { + return _then(_$SnAccountProfileImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + birthday: freezed == birthday + ? _value.birthday + : birthday // ignore: cast_nullable_to_non_nullable + as DateTime?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + experience: null == experience + ? _value.experience + : experience // ignore: cast_nullable_to_non_nullable + as int, + firstName: null == firstName + ? _value.firstName + : firstName // ignore: cast_nullable_to_non_nullable + as String, + lastName: null == lastName + ? _value.lastName + : lastName // ignore: cast_nullable_to_non_nullable + as String, + lastSeenAt: freezed == lastSeenAt + ? _value.lastSeenAt + : lastSeenAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnAccountProfileImpl implements _SnAccountProfile { + const _$SnAccountProfileImpl( + {required this.id, + required this.accountId, + required this.birthday, + required this.createdAt, + required this.deletedAt, + required this.experience, + required this.firstName, + required this.lastName, + required this.lastSeenAt, + required this.updatedAt}); + + factory _$SnAccountProfileImpl.fromJson(Map json) => + _$$SnAccountProfileImplFromJson(json); + + @override + final int id; + @override + final int accountId; + @override + final DateTime? birthday; + @override + final DateTime createdAt; + @override + final DateTime? deletedAt; + @override + final int experience; + @override + final String firstName; + @override + final String lastName; + @override + final DateTime? lastSeenAt; + @override + final DateTime updatedAt; + + @override + String toString() { + return 'SnAccountProfile(id: $id, accountId: $accountId, birthday: $birthday, createdAt: $createdAt, deletedAt: $deletedAt, experience: $experience, firstName: $firstName, lastName: $lastName, lastSeenAt: $lastSeenAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnAccountProfileImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.accountId, accountId) || + other.accountId == accountId) && + (identical(other.birthday, birthday) || + other.birthday == birthday) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && + (identical(other.experience, experience) || + other.experience == experience) && + (identical(other.firstName, firstName) || + other.firstName == firstName) && + (identical(other.lastName, lastName) || + other.lastName == lastName) && + (identical(other.lastSeenAt, lastSeenAt) || + other.lastSeenAt == lastSeenAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + accountId, + birthday, + createdAt, + deletedAt, + experience, + firstName, + lastName, + lastSeenAt, + updatedAt); + + /// Create a copy of SnAccountProfile + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnAccountProfileImplCopyWith<_$SnAccountProfileImpl> get copyWith => + __$$SnAccountProfileImplCopyWithImpl<_$SnAccountProfileImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SnAccountProfileImplToJson( + this, + ); + } +} + +abstract class _SnAccountProfile implements SnAccountProfile { + const factory _SnAccountProfile( + {required final int id, + required final int accountId, + required final DateTime? birthday, + required final DateTime createdAt, + required final DateTime? deletedAt, + required final int experience, + required final String firstName, + required final String lastName, + required final DateTime? lastSeenAt, + required final DateTime updatedAt}) = _$SnAccountProfileImpl; + + factory _SnAccountProfile.fromJson(Map json) = + _$SnAccountProfileImpl.fromJson; + + @override + int get id; + @override + int get accountId; + @override + DateTime? get birthday; + @override + DateTime get createdAt; + @override + DateTime? get deletedAt; + @override + int get experience; + @override + String get firstName; + @override + String get lastName; + @override + DateTime? get lastSeenAt; + @override + DateTime get updatedAt; + + /// Create a copy of SnAccountProfile + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnAccountProfileImplCopyWith<_$SnAccountProfileImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/types/account.g.dart b/lib/types/account.g.dart new file mode 100644 index 0000000..bbf2f82 --- /dev/null +++ b/lib/types/account.g.dart @@ -0,0 +1,131 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'account.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SnAccountImpl _$$SnAccountImplFromJson(Map json) => + _$SnAccountImpl( + id: (json['id'] as num).toInt(), + affiliatedId: (json['affiliated_id'] as num?)?.toInt(), + affiliatedTo: (json['affiliated_to'] as num?)?.toInt(), + automatedBy: (json['automated_by'] as num?)?.toInt(), + automatedId: (json['automated_id'] as num?)?.toInt(), + avatar: json['avatar'] as String, + banner: json['banner'] as String, + confirmedAt: json['confirmed_at'] == null + ? null + : DateTime.parse(json['confirmed_at'] as String), + contacts: (json['contacts'] as List) + .map((e) => SnAccountContact.fromJson(e as Map)) + .toList(), + createdAt: DateTime.parse(json['created_at'] as String), + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + description: json['description'] as String, + name: json['name'] as String, + nick: json['nick'] as String, + permNodes: json['perm_nodes'] as Map, + profile: json['profile'] == null + ? null + : SnAccountProfile.fromJson(json['profile'] as Map), + suspendedAt: json['suspended_at'] == null + ? null + : DateTime.parse(json['suspended_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + ); + +Map _$$SnAccountImplToJson(_$SnAccountImpl instance) => + { + 'id': instance.id, + 'affiliated_id': instance.affiliatedId, + 'affiliated_to': instance.affiliatedTo, + 'automated_by': instance.automatedBy, + 'automated_id': instance.automatedId, + 'avatar': instance.avatar, + 'banner': instance.banner, + 'confirmed_at': instance.confirmedAt?.toIso8601String(), + 'contacts': instance.contacts.map((e) => e.toJson()).toList(), + 'created_at': instance.createdAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'description': instance.description, + 'name': instance.name, + 'nick': instance.nick, + 'perm_nodes': instance.permNodes, + 'profile': instance.profile?.toJson(), + 'suspended_at': instance.suspendedAt?.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + }; + +_$SnAccountContactImpl _$$SnAccountContactImplFromJson( + Map json) => + _$SnAccountContactImpl( + accountId: (json['account_id'] as num).toInt(), + content: json['content'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + id: (json['id'] as num).toInt(), + isPrimary: json['is_primary'] as bool, + isPublic: json['is_public'] as bool, + type: (json['type'] as num).toInt(), + updatedAt: DateTime.parse(json['updated_at'] as String), + verifiedAt: json['verified_at'] == null + ? null + : DateTime.parse(json['verified_at'] as String), + ); + +Map _$$SnAccountContactImplToJson( + _$SnAccountContactImpl instance) => + { + 'account_id': instance.accountId, + 'content': instance.content, + 'created_at': instance.createdAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'id': instance.id, + 'is_primary': instance.isPrimary, + 'is_public': instance.isPublic, + 'type': instance.type, + 'updated_at': instance.updatedAt.toIso8601String(), + 'verified_at': instance.verifiedAt?.toIso8601String(), + }; + +_$SnAccountProfileImpl _$$SnAccountProfileImplFromJson( + Map json) => + _$SnAccountProfileImpl( + id: (json['id'] as num).toInt(), + accountId: (json['account_id'] as num).toInt(), + birthday: json['birthday'] == null + ? null + : DateTime.parse(json['birthday'] as String), + createdAt: DateTime.parse(json['created_at'] as String), + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + experience: (json['experience'] as num).toInt(), + firstName: json['first_name'] as String, + lastName: json['last_name'] as String, + lastSeenAt: json['last_seen_at'] == null + ? null + : DateTime.parse(json['last_seen_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + ); + +Map _$$SnAccountProfileImplToJson( + _$SnAccountProfileImpl instance) => + { + 'id': instance.id, + 'account_id': instance.accountId, + 'birthday': instance.birthday?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'experience': instance.experience, + 'first_name': instance.firstName, + 'last_name': instance.lastName, + 'last_seen_at': instance.lastSeenAt?.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + }; diff --git a/lib/types/auth.dart b/lib/types/auth.dart new file mode 100644 index 0000000..5bcdead --- /dev/null +++ b/lib/types/auth.dart @@ -0,0 +1,57 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'auth.freezed.dart'; +part 'auth.g.dart'; + +@freezed +class SnAuthResult with _$SnAuthResult { + const factory SnAuthResult({ + required bool isFinished, + required SnAuthTicket? ticket, + }) = _SnAuthResult; + + factory SnAuthResult.fromJson(Map json) => + _$SnAuthResultFromJson(json); +} + +@freezed +class SnAuthTicket with _$SnAuthTicket { + const factory SnAuthTicket({ + required int id, + required DateTime createdAt, + required DateTime updatedAt, + required DateTime? deletedAt, + required int stepRemain, + required String? grantToken, + required String? accessToken, + required String? refreshToken, + required String ipAddress, + required String location, + required String userAgent, + required DateTime? expiredAt, + required DateTime? lastGrantAt, + required DateTime? availableAt, + required String? nonce, + required int? accountId, + @Default([]) List factorTrail, + }) = _SnAuthTicket; + + factory SnAuthTicket.fromJson(Map json) => + _$SnAuthTicketFromJson(json); +} + +@freezed +class SnAuthFactor with _$SnAuthFactor { + const factory SnAuthFactor({ + required int id, + required DateTime createdAt, + required DateTime updatedAt, + required DateTime? deletedAt, + required int type, + required Map? config, + required int? accountId, + }) = _SnAuthFactor; + + factory SnAuthFactor.fromJson(Map json) => + _$SnAuthFactorFromJson(json); +} diff --git a/lib/types/auth.freezed.dart b/lib/types/auth.freezed.dart new file mode 100644 index 0000000..8fda77e --- /dev/null +++ b/lib/types/auth.freezed.dart @@ -0,0 +1,1002 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +SnAuthResult _$SnAuthResultFromJson(Map json) { + return _SnAuthResult.fromJson(json); +} + +/// @nodoc +mixin _$SnAuthResult { + bool get isFinished => throw _privateConstructorUsedError; + SnAuthTicket? get ticket => throw _privateConstructorUsedError; + + /// Serializes this SnAuthResult to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnAuthResult + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnAuthResultCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnAuthResultCopyWith<$Res> { + factory $SnAuthResultCopyWith( + SnAuthResult value, $Res Function(SnAuthResult) then) = + _$SnAuthResultCopyWithImpl<$Res, SnAuthResult>; + @useResult + $Res call({bool isFinished, SnAuthTicket? ticket}); + + $SnAuthTicketCopyWith<$Res>? get ticket; +} + +/// @nodoc +class _$SnAuthResultCopyWithImpl<$Res, $Val extends SnAuthResult> + implements $SnAuthResultCopyWith<$Res> { + _$SnAuthResultCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnAuthResult + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isFinished = null, + Object? ticket = freezed, + }) { + return _then(_value.copyWith( + isFinished: null == isFinished + ? _value.isFinished + : isFinished // ignore: cast_nullable_to_non_nullable + as bool, + ticket: freezed == ticket + ? _value.ticket + : ticket // ignore: cast_nullable_to_non_nullable + as SnAuthTicket?, + ) as $Val); + } + + /// Create a copy of SnAuthResult + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SnAuthTicketCopyWith<$Res>? get ticket { + if (_value.ticket == null) { + return null; + } + + return $SnAuthTicketCopyWith<$Res>(_value.ticket!, (value) { + return _then(_value.copyWith(ticket: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SnAuthResultImplCopyWith<$Res> + implements $SnAuthResultCopyWith<$Res> { + factory _$$SnAuthResultImplCopyWith( + _$SnAuthResultImpl value, $Res Function(_$SnAuthResultImpl) then) = + __$$SnAuthResultImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({bool isFinished, SnAuthTicket? ticket}); + + @override + $SnAuthTicketCopyWith<$Res>? get ticket; +} + +/// @nodoc +class __$$SnAuthResultImplCopyWithImpl<$Res> + extends _$SnAuthResultCopyWithImpl<$Res, _$SnAuthResultImpl> + implements _$$SnAuthResultImplCopyWith<$Res> { + __$$SnAuthResultImplCopyWithImpl( + _$SnAuthResultImpl _value, $Res Function(_$SnAuthResultImpl) _then) + : super(_value, _then); + + /// Create a copy of SnAuthResult + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isFinished = null, + Object? ticket = freezed, + }) { + return _then(_$SnAuthResultImpl( + isFinished: null == isFinished + ? _value.isFinished + : isFinished // ignore: cast_nullable_to_non_nullable + as bool, + ticket: freezed == ticket + ? _value.ticket + : ticket // ignore: cast_nullable_to_non_nullable + as SnAuthTicket?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnAuthResultImpl implements _SnAuthResult { + const _$SnAuthResultImpl({required this.isFinished, required this.ticket}); + + factory _$SnAuthResultImpl.fromJson(Map json) => + _$$SnAuthResultImplFromJson(json); + + @override + final bool isFinished; + @override + final SnAuthTicket? ticket; + + @override + String toString() { + return 'SnAuthResult(isFinished: $isFinished, ticket: $ticket)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnAuthResultImpl && + (identical(other.isFinished, isFinished) || + other.isFinished == isFinished) && + (identical(other.ticket, ticket) || other.ticket == ticket)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, isFinished, ticket); + + /// Create a copy of SnAuthResult + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnAuthResultImplCopyWith<_$SnAuthResultImpl> get copyWith => + __$$SnAuthResultImplCopyWithImpl<_$SnAuthResultImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SnAuthResultImplToJson( + this, + ); + } +} + +abstract class _SnAuthResult implements SnAuthResult { + const factory _SnAuthResult( + {required final bool isFinished, + required final SnAuthTicket? ticket}) = _$SnAuthResultImpl; + + factory _SnAuthResult.fromJson(Map json) = + _$SnAuthResultImpl.fromJson; + + @override + bool get isFinished; + @override + SnAuthTicket? get ticket; + + /// Create a copy of SnAuthResult + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnAuthResultImplCopyWith<_$SnAuthResultImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SnAuthTicket _$SnAuthTicketFromJson(Map json) { + return _SnAuthTicket.fromJson(json); +} + +/// @nodoc +mixin _$SnAuthTicket { + int get id => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + DateTime? get deletedAt => throw _privateConstructorUsedError; + int get stepRemain => throw _privateConstructorUsedError; + String? get grantToken => throw _privateConstructorUsedError; + String? get accessToken => throw _privateConstructorUsedError; + String? get refreshToken => throw _privateConstructorUsedError; + String get ipAddress => throw _privateConstructorUsedError; + String get location => throw _privateConstructorUsedError; + String get userAgent => throw _privateConstructorUsedError; + DateTime? get expiredAt => throw _privateConstructorUsedError; + DateTime? get lastGrantAt => throw _privateConstructorUsedError; + DateTime? get availableAt => throw _privateConstructorUsedError; + String? get nonce => throw _privateConstructorUsedError; + int? get accountId => throw _privateConstructorUsedError; + List get factorTrail => throw _privateConstructorUsedError; + + /// Serializes this SnAuthTicket to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnAuthTicket + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnAuthTicketCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnAuthTicketCopyWith<$Res> { + factory $SnAuthTicketCopyWith( + SnAuthTicket value, $Res Function(SnAuthTicket) then) = + _$SnAuthTicketCopyWithImpl<$Res, SnAuthTicket>; + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + int stepRemain, + String? grantToken, + String? accessToken, + String? refreshToken, + String ipAddress, + String location, + String userAgent, + DateTime? expiredAt, + DateTime? lastGrantAt, + DateTime? availableAt, + String? nonce, + int? accountId, + List factorTrail}); +} + +/// @nodoc +class _$SnAuthTicketCopyWithImpl<$Res, $Val extends SnAuthTicket> + implements $SnAuthTicketCopyWith<$Res> { + _$SnAuthTicketCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnAuthTicket + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? stepRemain = null, + Object? grantToken = freezed, + Object? accessToken = freezed, + Object? refreshToken = freezed, + Object? ipAddress = null, + Object? location = null, + Object? userAgent = null, + Object? expiredAt = freezed, + Object? lastGrantAt = freezed, + Object? availableAt = freezed, + Object? nonce = freezed, + Object? accountId = freezed, + Object? factorTrail = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + stepRemain: null == stepRemain + ? _value.stepRemain + : stepRemain // ignore: cast_nullable_to_non_nullable + as int, + grantToken: freezed == grantToken + ? _value.grantToken + : grantToken // ignore: cast_nullable_to_non_nullable + as String?, + accessToken: freezed == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String?, + refreshToken: freezed == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String?, + ipAddress: null == ipAddress + ? _value.ipAddress + : ipAddress // ignore: cast_nullable_to_non_nullable + as String, + location: null == location + ? _value.location + : location // ignore: cast_nullable_to_non_nullable + as String, + userAgent: null == userAgent + ? _value.userAgent + : userAgent // ignore: cast_nullable_to_non_nullable + as String, + expiredAt: freezed == expiredAt + ? _value.expiredAt + : expiredAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + lastGrantAt: freezed == lastGrantAt + ? _value.lastGrantAt + : lastGrantAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + availableAt: freezed == availableAt + ? _value.availableAt + : availableAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + nonce: freezed == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String?, + accountId: freezed == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int?, + factorTrail: null == factorTrail + ? _value.factorTrail + : factorTrail // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SnAuthTicketImplCopyWith<$Res> + implements $SnAuthTicketCopyWith<$Res> { + factory _$$SnAuthTicketImplCopyWith( + _$SnAuthTicketImpl value, $Res Function(_$SnAuthTicketImpl) then) = + __$$SnAuthTicketImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + int stepRemain, + String? grantToken, + String? accessToken, + String? refreshToken, + String ipAddress, + String location, + String userAgent, + DateTime? expiredAt, + DateTime? lastGrantAt, + DateTime? availableAt, + String? nonce, + int? accountId, + List factorTrail}); +} + +/// @nodoc +class __$$SnAuthTicketImplCopyWithImpl<$Res> + extends _$SnAuthTicketCopyWithImpl<$Res, _$SnAuthTicketImpl> + implements _$$SnAuthTicketImplCopyWith<$Res> { + __$$SnAuthTicketImplCopyWithImpl( + _$SnAuthTicketImpl _value, $Res Function(_$SnAuthTicketImpl) _then) + : super(_value, _then); + + /// Create a copy of SnAuthTicket + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? stepRemain = null, + Object? grantToken = freezed, + Object? accessToken = freezed, + Object? refreshToken = freezed, + Object? ipAddress = null, + Object? location = null, + Object? userAgent = null, + Object? expiredAt = freezed, + Object? lastGrantAt = freezed, + Object? availableAt = freezed, + Object? nonce = freezed, + Object? accountId = freezed, + Object? factorTrail = null, + }) { + return _then(_$SnAuthTicketImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + stepRemain: null == stepRemain + ? _value.stepRemain + : stepRemain // ignore: cast_nullable_to_non_nullable + as int, + grantToken: freezed == grantToken + ? _value.grantToken + : grantToken // ignore: cast_nullable_to_non_nullable + as String?, + accessToken: freezed == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String?, + refreshToken: freezed == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String?, + ipAddress: null == ipAddress + ? _value.ipAddress + : ipAddress // ignore: cast_nullable_to_non_nullable + as String, + location: null == location + ? _value.location + : location // ignore: cast_nullable_to_non_nullable + as String, + userAgent: null == userAgent + ? _value.userAgent + : userAgent // ignore: cast_nullable_to_non_nullable + as String, + expiredAt: freezed == expiredAt + ? _value.expiredAt + : expiredAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + lastGrantAt: freezed == lastGrantAt + ? _value.lastGrantAt + : lastGrantAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + availableAt: freezed == availableAt + ? _value.availableAt + : availableAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + nonce: freezed == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String?, + accountId: freezed == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int?, + factorTrail: null == factorTrail + ? _value._factorTrail + : factorTrail // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnAuthTicketImpl implements _SnAuthTicket { + const _$SnAuthTicketImpl( + {required this.id, + required this.createdAt, + required this.updatedAt, + required this.deletedAt, + required this.stepRemain, + required this.grantToken, + required this.accessToken, + required this.refreshToken, + required this.ipAddress, + required this.location, + required this.userAgent, + required this.expiredAt, + required this.lastGrantAt, + required this.availableAt, + required this.nonce, + required this.accountId, + final List factorTrail = const []}) + : _factorTrail = factorTrail; + + factory _$SnAuthTicketImpl.fromJson(Map json) => + _$$SnAuthTicketImplFromJson(json); + + @override + final int id; + @override + final DateTime createdAt; + @override + final DateTime updatedAt; + @override + final DateTime? deletedAt; + @override + final int stepRemain; + @override + final String? grantToken; + @override + final String? accessToken; + @override + final String? refreshToken; + @override + final String ipAddress; + @override + final String location; + @override + final String userAgent; + @override + final DateTime? expiredAt; + @override + final DateTime? lastGrantAt; + @override + final DateTime? availableAt; + @override + final String? nonce; + @override + final int? accountId; + final List _factorTrail; + @override + @JsonKey() + List get factorTrail { + if (_factorTrail is EqualUnmodifiableListView) return _factorTrail; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_factorTrail); + } + + @override + String toString() { + return 'SnAuthTicket(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stepRemain: $stepRemain, grantToken: $grantToken, accessToken: $accessToken, refreshToken: $refreshToken, ipAddress: $ipAddress, location: $location, userAgent: $userAgent, expiredAt: $expiredAt, lastGrantAt: $lastGrantAt, availableAt: $availableAt, nonce: $nonce, accountId: $accountId, factorTrail: $factorTrail)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnAuthTicketImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && + (identical(other.stepRemain, stepRemain) || + other.stepRemain == stepRemain) && + (identical(other.grantToken, grantToken) || + other.grantToken == grantToken) && + (identical(other.accessToken, accessToken) || + other.accessToken == accessToken) && + (identical(other.refreshToken, refreshToken) || + other.refreshToken == refreshToken) && + (identical(other.ipAddress, ipAddress) || + other.ipAddress == ipAddress) && + (identical(other.location, location) || + other.location == location) && + (identical(other.userAgent, userAgent) || + other.userAgent == userAgent) && + (identical(other.expiredAt, expiredAt) || + other.expiredAt == expiredAt) && + (identical(other.lastGrantAt, lastGrantAt) || + other.lastGrantAt == lastGrantAt) && + (identical(other.availableAt, availableAt) || + other.availableAt == availableAt) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.accountId, accountId) || + other.accountId == accountId) && + const DeepCollectionEquality() + .equals(other._factorTrail, _factorTrail)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + createdAt, + updatedAt, + deletedAt, + stepRemain, + grantToken, + accessToken, + refreshToken, + ipAddress, + location, + userAgent, + expiredAt, + lastGrantAt, + availableAt, + nonce, + accountId, + const DeepCollectionEquality().hash(_factorTrail)); + + /// Create a copy of SnAuthTicket + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnAuthTicketImplCopyWith<_$SnAuthTicketImpl> get copyWith => + __$$SnAuthTicketImplCopyWithImpl<_$SnAuthTicketImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SnAuthTicketImplToJson( + this, + ); + } +} + +abstract class _SnAuthTicket implements SnAuthTicket { + const factory _SnAuthTicket( + {required final int id, + required final DateTime createdAt, + required final DateTime updatedAt, + required final DateTime? deletedAt, + required final int stepRemain, + required final String? grantToken, + required final String? accessToken, + required final String? refreshToken, + required final String ipAddress, + required final String location, + required final String userAgent, + required final DateTime? expiredAt, + required final DateTime? lastGrantAt, + required final DateTime? availableAt, + required final String? nonce, + required final int? accountId, + final List factorTrail}) = _$SnAuthTicketImpl; + + factory _SnAuthTicket.fromJson(Map json) = + _$SnAuthTicketImpl.fromJson; + + @override + int get id; + @override + DateTime get createdAt; + @override + DateTime get updatedAt; + @override + DateTime? get deletedAt; + @override + int get stepRemain; + @override + String? get grantToken; + @override + String? get accessToken; + @override + String? get refreshToken; + @override + String get ipAddress; + @override + String get location; + @override + String get userAgent; + @override + DateTime? get expiredAt; + @override + DateTime? get lastGrantAt; + @override + DateTime? get availableAt; + @override + String? get nonce; + @override + int? get accountId; + @override + List get factorTrail; + + /// Create a copy of SnAuthTicket + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnAuthTicketImplCopyWith<_$SnAuthTicketImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SnAuthFactor _$SnAuthFactorFromJson(Map json) { + return _SnAuthFactor.fromJson(json); +} + +/// @nodoc +mixin _$SnAuthFactor { + int get id => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + DateTime? get deletedAt => throw _privateConstructorUsedError; + int get type => throw _privateConstructorUsedError; + Map? get config => throw _privateConstructorUsedError; + int? get accountId => throw _privateConstructorUsedError; + + /// Serializes this SnAuthFactor to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnAuthFactor + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnAuthFactorCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnAuthFactorCopyWith<$Res> { + factory $SnAuthFactorCopyWith( + SnAuthFactor value, $Res Function(SnAuthFactor) then) = + _$SnAuthFactorCopyWithImpl<$Res, SnAuthFactor>; + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + int type, + Map? config, + int? accountId}); +} + +/// @nodoc +class _$SnAuthFactorCopyWithImpl<$Res, $Val extends SnAuthFactor> + implements $SnAuthFactorCopyWith<$Res> { + _$SnAuthFactorCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnAuthFactor + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? type = null, + Object? config = freezed, + Object? accountId = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as int, + config: freezed == config + ? _value.config + : config // ignore: cast_nullable_to_non_nullable + as Map?, + accountId: freezed == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SnAuthFactorImplCopyWith<$Res> + implements $SnAuthFactorCopyWith<$Res> { + factory _$$SnAuthFactorImplCopyWith( + _$SnAuthFactorImpl value, $Res Function(_$SnAuthFactorImpl) then) = + __$$SnAuthFactorImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + int type, + Map? config, + int? accountId}); +} + +/// @nodoc +class __$$SnAuthFactorImplCopyWithImpl<$Res> + extends _$SnAuthFactorCopyWithImpl<$Res, _$SnAuthFactorImpl> + implements _$$SnAuthFactorImplCopyWith<$Res> { + __$$SnAuthFactorImplCopyWithImpl( + _$SnAuthFactorImpl _value, $Res Function(_$SnAuthFactorImpl) _then) + : super(_value, _then); + + /// Create a copy of SnAuthFactor + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? type = null, + Object? config = freezed, + Object? accountId = freezed, + }) { + return _then(_$SnAuthFactorImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as int, + config: freezed == config + ? _value._config + : config // ignore: cast_nullable_to_non_nullable + as Map?, + accountId: freezed == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnAuthFactorImpl implements _SnAuthFactor { + const _$SnAuthFactorImpl( + {required this.id, + required this.createdAt, + required this.updatedAt, + required this.deletedAt, + required this.type, + required final Map? config, + required this.accountId}) + : _config = config; + + factory _$SnAuthFactorImpl.fromJson(Map json) => + _$$SnAuthFactorImplFromJson(json); + + @override + final int id; + @override + final DateTime createdAt; + @override + final DateTime updatedAt; + @override + final DateTime? deletedAt; + @override + final int type; + final Map? _config; + @override + Map? get config { + final value = _config; + if (value == null) return null; + if (_config is EqualUnmodifiableMapView) return _config; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + final int? accountId; + + @override + String toString() { + return 'SnAuthFactor(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, config: $config, accountId: $accountId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnAuthFactorImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && + (identical(other.type, type) || other.type == type) && + const DeepCollectionEquality().equals(other._config, _config) && + (identical(other.accountId, accountId) || + other.accountId == accountId)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt, + deletedAt, type, const DeepCollectionEquality().hash(_config), accountId); + + /// Create a copy of SnAuthFactor + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnAuthFactorImplCopyWith<_$SnAuthFactorImpl> get copyWith => + __$$SnAuthFactorImplCopyWithImpl<_$SnAuthFactorImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SnAuthFactorImplToJson( + this, + ); + } +} + +abstract class _SnAuthFactor implements SnAuthFactor { + const factory _SnAuthFactor( + {required final int id, + required final DateTime createdAt, + required final DateTime updatedAt, + required final DateTime? deletedAt, + required final int type, + required final Map? config, + required final int? accountId}) = _$SnAuthFactorImpl; + + factory _SnAuthFactor.fromJson(Map json) = + _$SnAuthFactorImpl.fromJson; + + @override + int get id; + @override + DateTime get createdAt; + @override + DateTime get updatedAt; + @override + DateTime? get deletedAt; + @override + int get type; + @override + Map? get config; + @override + int? get accountId; + + /// Create a copy of SnAuthFactor + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnAuthFactorImplCopyWith<_$SnAuthFactorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/types/auth.g.dart b/lib/types/auth.g.dart new file mode 100644 index 0000000..a8a8a38 --- /dev/null +++ b/lib/types/auth.g.dart @@ -0,0 +1,98 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SnAuthResultImpl _$$SnAuthResultImplFromJson(Map json) => + _$SnAuthResultImpl( + isFinished: json['is_finished'] as bool, + ticket: json['ticket'] == null + ? null + : SnAuthTicket.fromJson(json['ticket'] as Map), + ); + +Map _$$SnAuthResultImplToJson(_$SnAuthResultImpl instance) => + { + 'is_finished': instance.isFinished, + 'ticket': instance.ticket?.toJson(), + }; + +_$SnAuthTicketImpl _$$SnAuthTicketImplFromJson(Map json) => + _$SnAuthTicketImpl( + id: (json['id'] as num).toInt(), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + stepRemain: (json['step_remain'] as num).toInt(), + grantToken: json['grant_token'] as String?, + accessToken: json['access_token'] as String?, + refreshToken: json['refresh_token'] as String?, + ipAddress: json['ip_address'] as String, + location: json['location'] as String, + userAgent: json['user_agent'] as String, + expiredAt: json['expired_at'] == null + ? null + : DateTime.parse(json['expired_at'] as String), + lastGrantAt: json['last_grant_at'] == null + ? null + : DateTime.parse(json['last_grant_at'] as String), + availableAt: json['available_at'] == null + ? null + : DateTime.parse(json['available_at'] as String), + nonce: json['nonce'] as String?, + accountId: (json['account_id'] as num?)?.toInt(), + factorTrail: (json['factor_trail'] as List?) + ?.map((e) => (e as num).toInt()) + .toList() ?? + const [], + ); + +Map _$$SnAuthTicketImplToJson(_$SnAuthTicketImpl instance) => + { + 'id': instance.id, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'step_remain': instance.stepRemain, + 'grant_token': instance.grantToken, + 'access_token': instance.accessToken, + 'refresh_token': instance.refreshToken, + 'ip_address': instance.ipAddress, + 'location': instance.location, + 'user_agent': instance.userAgent, + 'expired_at': instance.expiredAt?.toIso8601String(), + 'last_grant_at': instance.lastGrantAt?.toIso8601String(), + 'available_at': instance.availableAt?.toIso8601String(), + 'nonce': instance.nonce, + 'account_id': instance.accountId, + 'factor_trail': instance.factorTrail, + }; + +_$SnAuthFactorImpl _$$SnAuthFactorImplFromJson(Map json) => + _$SnAuthFactorImpl( + id: (json['id'] as num).toInt(), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + type: (json['type'] as num).toInt(), + config: json['config'] as Map?, + accountId: (json['account_id'] as num?)?.toInt(), + ); + +Map _$$SnAuthFactorImplToJson(_$SnAuthFactorImpl instance) => + { + 'id': instance.id, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'type': instance.type, + 'config': instance.config, + 'account_id': instance.accountId, + }; diff --git a/lib/widgets/dialog.dart b/lib/widgets/dialog.dart new file mode 100644 index 0000000..c5fdb2d --- /dev/null +++ b/lib/widgets/dialog.dart @@ -0,0 +1,151 @@ +import 'dart:math' as math; + +import 'package:dio/dio.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +extension AppPromptExtension on BuildContext { + void showSnackbar(String content, {SnackBarAction? action}) { + ScaffoldMessenger.of(this).showSnackBar(SnackBar( + content: Text(content), + action: action, + )); + } + + void clearSnackbar() { + ScaffoldMessenger.of(this).clearSnackBars(); + } + + Future showModalDialog(String title, desc) { + return showDialog( + useRootNavigator: true, + context: this, + builder: (ctx) => AlertDialog( + title: Text(title), + content: Text(desc), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text('dialogDismiss').tr(), + ) + ], + ), + ); + } + + Future showInfoDialog(String title, body) { + return showDialog( + useRootNavigator: true, + context: this, + builder: (ctx) => AlertDialog( + title: Text(title), + content: Text(body), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text('dialogDismiss').tr(), + ) + ], + ), + ); + } + + Future showConfirmDialog(String title, body) async { + return await showDialog( + useRootNavigator: true, + context: this, + builder: (ctx) => AlertDialog( + title: Text(title), + content: Text(body), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: Text('dialogCancel').tr(), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: Text('dialogConfirm').tr(), + ) + ], + ), + ) ?? + false; + } + + Future showErrorDialog(dynamic exception) { + Widget content = Text(exception.toString().capitalize()); + if (exception is DioException) { + String preview; + switch (exception.response?.statusCode) { + case 400: + preview = 'errorRequestBad'.tr(); + break; + case 401: + preview = 'errorRequestUnauthorized'.tr(); + break; + case 403: + preview = 'errorRequestForbidden'.tr(); + break; + case 404: + preview = 'errorRequestNotFound'.tr(); + break; + case null: + preview = 'errorRequestConnection'.tr(); + break; + default: + preview = 'errorRequestUnknown'.tr(); + break; + } + + if (exception.response != null) { + content = Text( + '$preview\n\n(${exception.response?.statusCode}) ${exception.response?.data}', + ); + } else { + content = Text(preview); + } + } + + return showDialog( + useRootNavigator: true, + context: this, + builder: (ctx) => AlertDialog( + title: Text('dialogError').tr(), + content: content, + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text('dialogDismiss').tr(), + ) + ], + ), + ); + } +} + +extension ByteFormatter on int { + String formatBytes({int decimals = 2}) { + if (this == 0) return '0 Bytes'; + const k = 1024; + final dm = decimals < 0 ? 0 : decimals; + final sizes = [ + 'Bytes', + 'KiB', + 'MiB', + 'GiB', + 'TiB', + 'PiB', + 'EiB', + 'ZiB', + 'YiB' + ]; + final i = (math.log(this) / math.log(k)).floor().toInt(); + return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; + } +} + +extension StringFormatter on String { + String capitalize() { + return "${this[0].toUpperCase()}${substring(1)}"; + } +} diff --git a/lib/widgets/navigation/app_scaffold.dart b/lib/widgets/navigation/app_scaffold.dart index a2dfd32..5b7f34f 100644 --- a/lib/widgets/navigation/app_scaffold.dart +++ b/lib/widgets/navigation/app_scaffold.dart @@ -1,24 +1,48 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:responsive_framework/responsive_framework.dart'; +import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/navigation/app_background.dart'; import 'package:surface/widgets/navigation/app_bottom_navigation.dart'; class AppScaffold extends StatelessWidget { final PreferredSizeWidget? appBar; + final String? title; final Widget? body; - final bool? showBottomNavigation; - const AppScaffold( - {super.key, this.appBar, this.body, this.showBottomNavigation}); + final bool autoImplyAppBar; + final bool showBottomNavigation; + const AppScaffold({ + super.key, + this.appBar, + this.title, + this.body, + this.autoImplyAppBar = false, + this.showBottomNavigation = false, + }); @override Widget build(BuildContext context) { - final isShowBottomNavigation = (showBottomNavigation ?? false) + final isShowBottomNavigation = (showBottomNavigation) ? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE) : false; + final state = GoRouter.maybeOf(context); + return AppBackground( child: Scaffold( - appBar: appBar, + appBar: appBar ?? + (autoImplyAppBar + ? AppBar( + title: title != null + ? Text(title!) + : state != null + ? Text( + ('screen${state.routerDelegate.currentConfiguration.last.route.name?.capitalize()}') + .tr(), + ) + : null) + : null), body: body, bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bf..80f0c37 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStoragePlugin"); + flutter_secure_storage_plugin_register_with_registrar(flutter_secure_storage_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index df8d2f7..3d4708b 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage url_launcher_linux ) diff --git a/pubspec.lock b/pubspec.lock index 9636647..b028ed2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -416,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.2" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9f3dd2ac3b6875b0fde5b04734789c3ef35ba3965c18e99dd564a7a2f8056df6" + url: "https://pub.dev" + source: hosted + version: "4.2.1" flutter_shaders: dependency: transitive description: @@ -886,10 +894,10 @@ packages: dependency: transitive description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" shared_preferences_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 50d011b..9ae35c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: google_fonts: ^6.2.1 path: ^1.9.0 relative_time: ^5.0.0 + flutter_secure_storage: ^4.2.1 dev_dependencies: flutter_test: @@ -87,6 +88,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: + - assets/icon/icon.png - assets/translations/ # An image asset can refer to one or more resolution-specific "variants", see