Compare commits

...

4 Commits

Author SHA1 Message Date
9cc577adbe Programs, members
🐛 Fix web assets redirecting issue
2025-03-23 22:34:58 +08:00
dd196b7754 Golden points 2025-03-23 18:23:18 +08:00
16c07c2133 🐛 Fix deps 2025-03-23 17:01:21 +08:00
6bcb658d44 🐛 Fix platform specific captcha solution cause build failed. 2025-03-23 16:47:06 +08:00
22 changed files with 1412 additions and 210 deletions

View File

@ -897,5 +897,23 @@
"albumDescription": "View albums and manage attachments.", "albumDescription": "View albums and manage attachments.",
"stickers": "Stickers", "stickers": "Stickers",
"stickersDescription": "View sticker packs and manage stickers.", "stickersDescription": "View sticker packs and manage stickers.",
"navBottomUnauthorizedCaption": "Or create an account" "navBottomUnauthorizedCaption": "Or create an account",
"walletCurrencyGoldenShort": "GDP",
"walletCurrencyGolden": {
"one": "{} Golden Point",
"other": "{} Golden Points"
},
"walletTransactionTypeNormal": "Source Point",
"walletTransactionTypeGolden": "Golden Point",
"accountProgram": "Programs",
"accountProgramDescription": "Explore the available member programs.",
"accountProgramJoin": "Join Program",
"accountProgramJoinRequirements": "Requirements",
"accountProgramJoinPricing": "Pricing",
"accountProgramJoinPricingHint": "Billed every (30 days) month.",
"accountProgramLeaveHint": "After leaving the program, the source points will not be refunded.",
"accountProgramJoined": "Joined Program.",
"accountProgramAlreadyJoined": "Joined",
"accountProgramLeft": "Left Program.",
"leave": "Leave"
} }

View File

@ -895,5 +895,23 @@
"albumDescription": "查看相册与管理上传附件。", "albumDescription": "查看相册与管理上传附件。",
"stickers": "贴图", "stickers": "贴图",
"stickersDescription": "查看贴图包与管理贴图。", "stickersDescription": "查看贴图包与管理贴图。",
"navBottomUnauthorizedCaption": "或者注册一个账号" "navBottomUnauthorizedCaption": "或者注册一个账号",
"walletCurrencyGoldenShort": "金点",
"walletCurrencyGolden": {
"one": "{} 金点",
"other": "{} 金点"
},
"walletTransactionTypeNormal": "源点",
"walletTransactionTypeGolden": "金点",
"accountProgram": "计划",
"accountProgramDescription": "了解可用的成员计划。",
"accountProgramJoin": "加入计划",
"accountProgramJoinRequirements": "要求",
"accountProgramJoinPricing": "价格",
"accountProgramJoinPricingHint": "按月30 天)收费",
"accountProgramLeaveHint": "离开计划后,之前花费的源点不会退款。",
"accountProgramJoined": "已加入计划。",
"accountProgramLeft": "已离开计划。",
"accountProgramAlreadyJoined": "已加入",
"leave": "离开"
} }

View File

@ -13,6 +13,7 @@ import 'package:surface/screens/account/prefs/notify.dart';
import 'package:surface/screens/account/prefs/security.dart'; import 'package:surface/screens/account/prefs/security.dart';
import 'package:surface/screens/account/profile_page.dart'; import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/screens/account/profile_edit.dart'; import 'package:surface/screens/account/profile_edit.dart';
import 'package:surface/screens/account/programs.dart';
import 'package:surface/screens/account/publishers/publisher_edit.dart'; import 'package:surface/screens/account/publishers/publisher_edit.dart';
import 'package:surface/screens/account/publishers/publisher_new.dart'; import 'package:surface/screens/account/publishers/publisher_new.dart';
import 'package:surface/screens/account/publishers/publishers.dart'; import 'package:surface/screens/account/publishers/publishers.dart';
@ -130,6 +131,11 @@ final _appRoutes = [
name: 'account', name: 'account',
builder: (context, state) => const AccountScreen(), builder: (context, state) => const AccountScreen(),
routes: [ routes: [
GoRoute(
path: '/programs',
name: 'accountProgram',
builder: (context, state) => const AccountProgramScreen(),
),
GoRoute( GoRoute(
path: '/contacts', path: '/contacts',
name: 'accountContactMethods', name: 'accountContactMethods',

View File

@ -145,6 +145,16 @@ class _AuthorizedAccountScreen extends StatelessWidget {
GoRouter.of(context).pushNamed('accountPublishers'); GoRouter.of(context).pushNamed('accountPublishers');
}, },
), ),
ListTile(
title: Text('accountProgram').tr(),
subtitle: Text('accountProgramDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.communities),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
GoRouter.of(context).pushNamed('accountProgram');
},
),
ListTile( ListTile(
title: Text('friends').tr(), title: Text('friends').tr(),
subtitle: Text('friendsDescription').tr(), subtitle: Text('friendsDescription').tr(),

View File

@ -0,0 +1,284 @@
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/experience.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/account.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class AccountProgramScreen extends StatefulWidget {
const AccountProgramScreen({super.key});
@override
State<AccountProgramScreen> createState() => _AccountProgramScreenState();
}
class _AccountProgramScreenState extends State<AccountProgramScreen> {
bool _isBusy = false;
final List<SnProgram> _programs = List.empty(growable: true);
final List<SnProgramMember> _programMembers = List.empty(growable: true);
Future<void> _fetchPrograms() async {
_programs.clear();
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/programs');
_programs.addAll(
resp.data.map((ele) => SnProgram.fromJson(ele)).cast<SnProgram>(),
);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _fetchProgramMembers() async {
_programMembers.clear();
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/programs/members');
_programMembers.addAll(
resp.data
.map((ele) => SnProgramMember.fromJson(ele))
.cast<SnProgramMember>(),
);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchPrograms();
_fetchProgramMembers();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
title: Text('accountProgram').tr(),
),
body: Column(
children: [
LoadingIndicator(isActive: _isBusy),
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: _programs.length,
itemBuilder: (context, idx) {
final ele = _programs[idx];
return Card(
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(8)),
onTap: () {
showModalBottomSheet(
context: context,
builder: (context) => _ProgramJoinPopup(
program: ele,
isJoined: _programMembers
.any((ele) => ele.programId == ele.id),
),
).then((value) {
_fetchProgramMembers();
});
},
child: Column(
children: [
if (ele.appearance['banner'] != null)
AspectRatio(
aspectRatio: 16 / 5,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
color: Theme.of(context)
.colorScheme
.surfaceVariant,
child: Image.network(
ele.appearance['banner'],
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
ele.name,
style: Theme.of(context)
.textTheme
.titleMedium,
).bold(),
Text(
ele.description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
if (_programMembers
.any((ele) => ele.programId == ele.id))
Text('accountProgramAlreadyJoined'.tr())
.opacity(0.75),
],
),
),
],
),
),
],
),
),
).padding(horizontal: 8);
},
),
),
],
),
);
}
}
class _ProgramJoinPopup extends StatefulWidget {
final SnProgram program;
final bool isJoined;
const _ProgramJoinPopup({required this.program, required this.isJoined});
@override
State<_ProgramJoinPopup> createState() => _ProgramJoinPopupState();
}
class _ProgramJoinPopupState extends State<_ProgramJoinPopup> {
bool _isBusy = false;
Future<void> _joinProgram() async {
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
await sn.client.post('/cgi/id/programs/${widget.program.id}');
if (!mounted) return;
Navigator.pop(context, true);
context.showSnackbar('accountProgramJoined'.tr());
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _leaveProgram() async {
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/id/programs/${widget.program.id}');
if (!mounted) return;
Navigator.pop(context, true);
context.showSnackbar('accountProgramLeft'.tr());
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.add, size: 24),
const Gap(16),
Text(
'accountProgramJoin',
style: Theme.of(context).textTheme.titleLarge,
).tr(),
],
).padding(horizontal: 20, top: 16, bottom: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.program.appearance['banner'] != null)
AspectRatio(
aspectRatio: 16 / 5,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
color: Theme.of(context).colorScheme.surfaceVariant,
child: Image.network(
widget.program.appearance['banner'],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
).padding(bottom: 12),
Text(
widget.program.name,
style: Theme.of(context).textTheme.titleMedium,
).bold(),
Text(
widget.program.description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
const Gap(8),
Text(
'accountProgramJoinRequirements',
style: Theme.of(context).textTheme.titleMedium,
).tr().bold(),
Text('≥EXP ${widget.program.expRequirement}'),
Text('≥Lv${getLevelFromExp(widget.program.expRequirement)}'),
const Gap(8),
Text(
'accountProgramJoinPricing',
style: Theme.of(context).textTheme.titleMedium,
).tr().bold(),
Text('walletCurrency${widget.program.price['currency'].toString().capitalize().replaceFirst('Normal', '')}')
.plural(widget.program.price['amount'].toDouble()),
Text('accountProgramJoinPricingHint').tr().opacity(0.75),
const Gap(8),
if (widget.isJoined)
Text('accountProgramLeaveHint')
.tr()
.opacity(0.75)
.padding(bottom: 8),
if (!widget.isJoined)
ElevatedButton(
onPressed: _isBusy ? null : _joinProgram,
child: Text('join').tr(),
)
else
ElevatedButton(
onPressed: _isBusy ? null : _leaveProgram,
child: Text('leave').tr(),
),
],
).padding(horizontal: 24),
],
);
}
}

View File

@ -7,7 +7,7 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/captcha.dart'; import 'package:surface/screens/captcha/captcha.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';

View File

@ -0,0 +1,3 @@
import 'package:flutter/foundation.dart' show kIsWeb;
export 'captcha_native.dart' if (kIsWeb) 'captcha_web.dart';

View File

@ -0,0 +1,37 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class CaptchaScreen extends StatefulWidget {
const CaptchaScreen({super.key});
@override
State<CaptchaScreen> createState() => _CaptchaScreenState();
}
class _CaptchaScreenState extends State<CaptchaScreen> {
@override
Widget build(BuildContext context) {
final cfg = context.read<ConfigProvider>();
return AppScaffold(
appBar: AppBar(title: Text("reCaptcha").tr()),
body: InAppWebView(
initialUrlRequest: URLRequest(
url: WebUri('${cfg.serverUrl}/captcha?redirect_uri=solink://captcha'),
),
shouldOverrideUrlLoading: (controller, navigationAction) async {
Uri? url = navigationAction.request.url;
if (url != null && url.queryParameters.containsKey('captcha_tk')) {
Navigator.pop(context, url.queryParameters['captcha_tk']!);
return NavigationActionPolicy.CANCEL;
}
return NavigationActionPolicy.ALLOW;
},
),
);
}
}

View File

@ -1,17 +1,13 @@
import 'dart:html' as html; import 'dart:html' as html;
import 'dart:ui_web' as ui; import 'dart:ui_web' as ui;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
class CaptchaScreen extends StatefulWidget { class CaptchaScreen extends StatefulWidget {
const CaptchaScreen({ const CaptchaScreen({super.key});
super.key,
});
@override @override
State<CaptchaScreen> createState() => _CaptchaScreenState(); State<CaptchaScreen> createState() => _CaptchaScreenState();
@ -21,9 +17,7 @@ class _CaptchaScreenState extends State<CaptchaScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (kIsWeb) { _setupWebListener();
_setupWebListener();
}
} }
void _setupWebListener() { void _setupWebListener() {
@ -37,9 +31,8 @@ class _CaptchaScreenState extends State<CaptchaScreen> {
} }
}); });
// Create an iframe for the captcha page
final iframe = html.IFrameElement() final iframe = html.IFrameElement()
..src = '${context.read<ConfigProvider>().serverUrl}/captcha?redirect_uri=solink://captcha' ..src = '${context.read<ConfigProvider>().serverUrl}/captcha?redirect_uri=web'
..style.border = 'none' ..style.border = 'none'
..width = '100%' ..width = '100%'
..height = '100%'; ..height = '100%';
@ -47,36 +40,15 @@ class _CaptchaScreenState extends State<CaptchaScreen> {
html.document.body!.append(iframe); html.document.body!.append(iframe);
ui.platformViewRegistry.registerViewFactory( ui.platformViewRegistry.registerViewFactory(
'captcha-iframe', 'captcha-iframe',
(int viewId) => iframe, (int viewId) => iframe,
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cfg = context.read<ConfigProvider>();
if (kIsWeb) {
return AppScaffold(
appBar: AppBar(title: Text("reCaptcha").tr()),
body: HtmlElementView(viewType: 'captcha-iframe'),
);
}
return AppScaffold( return AppScaffold(
appBar: AppBar(title: Text("reCaptcha").tr()), appBar: AppBar(title: Text("reCaptcha").tr()),
body: InAppWebView( body: HtmlElementView(viewType: 'captcha-iframe'),
initialUrlRequest: URLRequest(
url: WebUri('${cfg.serverUrl}/captcha?redirect_uri=solink://captcha'),
),
shouldOverrideUrlLoading: (controller, navigationAction) async {
Uri? url = navigationAction.request.url;
if (url != null && url.queryParameters.containsKey('captcha_tk')) {
Navigator.pop(context, url.queryParameters['captcha_tk']!);
return NavigationActionPolicy.CANCEL;
}
return NavigationActionPolicy.ALLOW;
},
),
); );
} }
} }

View File

@ -46,9 +46,7 @@ class _FriendScreenState extends State<FriendScreen> {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/users/me/relations?status=1'); final resp = await sn.client.get('/cgi/id/users/me/relations?status=1');
_relations = List<SnRelationship>.from( _relations = List<SnRelationship>.from(resp.data?.map((e) => SnRelationship.fromJson(e)) ?? []);
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
);
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -66,9 +64,7 @@ class _FriendScreenState extends State<FriendScreen> {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/users/me/relations?status=0,3'); final resp = await sn.client.get('/cgi/id/users/me/relations?status=0,3');
_requests = List<SnRelationship>.from( _requests = List<SnRelationship>.from(resp.data?.map((e) => SnRelationship.fromJson(e)) ?? []);
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
);
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -86,9 +82,7 @@ class _FriendScreenState extends State<FriendScreen> {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/users/me/relations?status=2'); final resp = await sn.client.get('/cgi/id/users/me/relations?status=2');
_blocks = List<SnRelationship>.from( _blocks = List<SnRelationship>.from(resp.data?.map((e) => SnRelationship.fromJson(e)) ?? []);
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
);
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -104,11 +98,7 @@ class _FriendScreenState extends State<FriendScreen> {
try { try {
final rel = context.read<SnRelationshipProvider>(); final rel = context.read<SnRelationshipProvider>();
await rel.updateRelationship( await rel.updateRelationship(relation.relatedId, dstStatus, relation.permNodes);
relation.relatedId,
dstStatus,
relation.permNodes,
);
if (!mounted) return; if (!mounted) return;
_fetchRelations(); _fetchRelations();
} catch (err) { } catch (err) {
@ -122,9 +112,7 @@ class _FriendScreenState extends State<FriendScreen> {
Future<void> _deleteRelation(SnRelationship relation) async { Future<void> _deleteRelation(SnRelationship relation) async {
final confirm = await context.showConfirmDialog( final confirm = await context.showConfirmDialog(
'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]), 'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
'friendDeleteDescription'.tr(args: [ 'friendDeleteDescription'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
relation.related?.nick ?? 'unknown'.tr(),
]),
); );
if (!confirm) return; if (!confirm) return;
if (!mounted) return; if (!mounted) return;
@ -145,10 +133,9 @@ class _FriendScreenState extends State<FriendScreen> {
} }
void _showRequests() { void _showRequests() {
showModalBottomSheet( showModalBottomSheet(context: context, builder: (context) => _FriendshipListWidget(relations: _requests)).then((
context: context, value,
builder: (context) => _FriendshipListWidget(relations: _requests), ) {
).then((value) {
if (value != null) { if (value != null) {
_fetchRequests(); _fetchRequests();
_fetchRelations(); _fetchRelations();
@ -157,10 +144,9 @@ class _FriendScreenState extends State<FriendScreen> {
} }
void _showBlocks() { void _showBlocks() {
showModalBottomSheet( showModalBottomSheet(context: context, builder: (context) => _FriendshipListWidget(relations: _blocks)).then((
context: context, value,
builder: (context) => _FriendshipListWidget(relations: _blocks), ) {
).then((value) {
if (value != null) { if (value != null) {
_fetchBlocks(); _fetchBlocks();
_fetchRelations(); _fetchRelations();
@ -173,9 +159,7 @@ class _FriendScreenState extends State<FriendScreen> {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
await sn.client.post('/cgi/id/users/me/relations', data: { await sn.client.post('/cgi/id/users/me/relations', data: {'related': user.name});
'related': user.name,
});
if (!mounted) return; if (!mounted) return;
context.showSnackbar('friendRequestSent'.tr()); context.showSnackbar('friendRequestSent'.tr());
} catch (err) { } catch (err) {
@ -200,29 +184,19 @@ class _FriendScreenState extends State<FriendScreen> {
if (!ua.isAuthorized) { if (!ua.isAuthorized) {
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(leading: PageBackButton(), title: Text('screenFriend').tr()),
leading: PageBackButton(), body: Center(child: UnauthorizedHint()),
title: Text('screenFriend').tr(),
),
body: Center(
child: UnauthorizedHint(),
),
); );
} }
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(leading: AutoAppBarLeading(), title: Text('screenFriend').tr()),
leading: AutoAppBarLeading(),
title: Text('screenFriend').tr(),
),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Symbols.add), child: const Icon(Symbols.add),
onPressed: () async { onPressed: () async {
final user = await showModalBottomSheet<SnAccount?>( final user = await showModalBottomSheet<SnAccount?>(
context: context, context: context,
builder: (context) => AccountSelect( builder: (context) => AccountSelect(title: 'friendNew'.tr()),
title: 'friendNew'.tr(),
),
); );
if (!mounted) return; if (!mounted) return;
if (user == null) return; if (user == null) return;
@ -235,9 +209,7 @@ class _FriendScreenState extends State<FriendScreen> {
if (_requests.isNotEmpty) if (_requests.isNotEmpty)
ListTile( ListTile(
title: Text('friendRequests').tr(), title: Text('friendRequests').tr(),
subtitle: Text( subtitle: Text('friendRequestsDescription').plural(_requests.length),
'friendRequestsDescription',
).plural(_requests.length),
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.group_add), leading: const Icon(Symbols.group_add),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
@ -246,25 +218,19 @@ class _FriendScreenState extends State<FriendScreen> {
if (_blocks.isNotEmpty) if (_blocks.isNotEmpty)
ListTile( ListTile(
title: Text('friendBlocklist').tr(), title: Text('friendBlocklist').tr(),
subtitle: Text( subtitle: Text('friendBlocklistDescription').plural(_blocks.length),
'friendBlocklistDescription',
).plural(_blocks.length),
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.block), leading: const Icon(Symbols.block),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
onTap: _showBlocks, onTap: _showBlocks,
), ),
if (_requests.isNotEmpty || _blocks.isNotEmpty) if (_requests.isNotEmpty || _blocks.isNotEmpty) const Divider(height: 1),
const Divider(height: 1),
Expanded( Expanded(
child: MediaQuery.removePadding( child: MediaQuery.removePadding(
context: context, context: context,
removeTop: true, removeTop: true,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () => Future.wait([ onRefresh: () => Future.wait([_fetchRelations(), _fetchRequests()]),
_fetchRelations(),
_fetchRequests(),
]),
child: ListView.builder( child: ListView.builder(
itemCount: _relations.length, itemCount: _relations.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -288,16 +254,12 @@ class _FriendScreenState extends State<FriendScreen> {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
InkWell( InkWell(
onTap: _isUpdating onTap: _isUpdating ? null : () => _changeRelation(relation, 2),
? null
: () => _changeRelation(relation, 2),
child: Text('friendBlock').tr(), child: Text('friendBlock').tr(),
), ),
const Gap(8), const Gap(8),
InkWell( InkWell(
onTap: _isUpdating onTap: _isUpdating ? null : () => _deleteRelation(relation),
? null
: () => _deleteRelation(relation),
child: Text('friendDeleteAction').tr(), child: Text('friendDeleteAction').tr(),
), ),
], ],
@ -366,11 +328,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
try { try {
final rel = context.read<SnRelationshipProvider>(); final rel = context.read<SnRelationshipProvider>();
await rel.updateRelationship( await rel.updateRelationship(relation.relatedId, dstStatus, relation.permNodes);
relation.relatedId,
dstStatus,
relation.permNodes,
);
if (!mounted) return; if (!mounted) return;
Navigator.pop(context, true); Navigator.pop(context, true);
} catch (err) { } catch (err) {
@ -384,9 +342,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
Future<void> _deleteRelation(SnRelationship relation) async { Future<void> _deleteRelation(SnRelationship relation) async {
final confirm = await context.showConfirmDialog( final confirm = await context.showConfirmDialog(
'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]), 'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
'friendDeleteDescription'.tr(args: [ 'friendDeleteDescription'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
relation.related?.nick ?? 'unknown'.tr(),
]),
); );
if (!confirm) return; if (!confirm) return;
if (!mounted) return; if (!mounted) return;
@ -426,9 +382,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text(kFriendStatus[relation.status] ?? 'unknown') Text(kFriendStatus[relation.status] ?? 'unknown').tr().opacity(0.75),
.tr()
.opacity(0.75),
if (relation.status == 0) if (relation.status == 0)
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
@ -449,8 +403,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
InkWell( InkWell(
onTap: onTap: _isBusy ? null : () => _changeRelation(relation, 1),
_isBusy ? null : () => _changeRelation(relation, 1),
child: Text('friendUnblock').tr(), child: Text('friendUnblock').tr(),
), ),
const Gap(8), const Gap(8),

View File

@ -18,7 +18,7 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/special_day.dart'; import 'package:surface/providers/special_day.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/providers/widget.dart'; import 'package:surface/providers/widget.dart';
import 'package:surface/screens/captcha.dart'; import 'package:surface/screens/captcha/captcha.dart';
import 'package:surface/types/check_in.dart'; import 'package:surface/types/check_in.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/app_bar_leading.dart';

View File

@ -28,11 +28,8 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
try { try {
final pt = context.read<SnPostContentProvider>(); final pt = context.read<SnPostContentProvider>();
final result = await pt.listPosts( final result =
take: 10, await pt.listPosts(take: 10, offset: _posts.length, isShuffle: true);
offset: _posts.length,
isShuffle: true,
);
_posts.addAll(result.$1); _posts.addAll(result.$1);
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
@ -57,19 +54,14 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(title: Text('postShuffle').tr()),
title: Text('postShuffle').tr(),
),
body: Stack( body: Stack(
children: [ children: [
Column( Column(
children: [ children: [
if (_isBusy || _posts.isEmpty) if (_isBusy || _posts.isEmpty)
const Expanded( const Expanded(
child: Center( child: Center(child: CircularProgressIndicator()))
child: CircularProgressIndicator(),
),
)
else else
Expanded( Expanded(
child: CardSwiper( child: CardSwiper(
@ -81,17 +73,20 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
final ele = _posts[idx]; final ele = _posts[idx];
return SingleChildScrollView( return SingleChildScrollView(
child: Center( child: Center(
child: OpenablePostItem( child: Card(
key: ValueKey(ele), color: Theme.of(context).colorScheme.surface,
data: ele, child: OpenablePostItem(
maxWidth: 640, key: ValueKey(ele),
onChanged: (ele) { data: ele,
_posts[idx] = ele; maxWidth: 640,
setState(() {}); onChanged: (ele) {
}, _posts[idx] = ele;
onDeleted: () { setState(() {});
_fetchPosts(); },
}, onDeleted: () {
_fetchPosts();
},
).padding(all: 8),
).padding( ).padding(
all: 24, all: 24,
bottom: bottom:

View File

@ -45,10 +45,7 @@ class _WalletScreenState extends State<WalletScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(leading: PageBackButton(), title: Text('screenAccountWallet').tr()),
leading: PageBackButton(),
title: Text('screenAccountWallet').tr(),
),
body: Column( body: Column(
children: [ children: [
LoadingIndicator(isActive: _isBusy), LoadingIndicator(isActive: _isBusy),
@ -66,11 +63,6 @@ class _WalletScreenState extends State<WalletScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CircleAvatar(
radius: 28,
child: Icon(Symbols.wallet, size: 28),
),
const Gap(12),
SizedBox(width: double.infinity), SizedBox(width: double.infinity),
Text( Text(
NumberFormat.compactCurrency( NumberFormat.compactCurrency(
@ -81,6 +73,16 @@ class _WalletScreenState extends State<WalletScreen> {
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
Text('walletCurrency'.plural(double.parse(_wallet!.balance))), Text('walletCurrency'.plural(double.parse(_wallet!.balance))),
const Gap(16),
Text(
NumberFormat.compactCurrency(
locale: EasyLocalization.of(context)!.currentLocale.toString(),
symbol: '${'walletCurrencyGoldenShort'.tr()} ',
decimalDigits: 2,
).format(double.parse(_wallet!.goldenBalance)),
style: Theme.of(context).textTheme.titleLarge,
),
Text('walletCurrencyGolden'.plural(double.parse(_wallet!.goldenBalance))),
], ],
).padding(horizontal: 20, vertical: 24), ).padding(horizontal: 20, vertical: 24),
).padding(horizontal: 8, top: 16, bottom: 4), ).padding(horizontal: 8, top: 16, bottom: 4),
@ -109,14 +111,12 @@ class _WalletTransactionListState extends State<_WalletTransactionList> {
try { try {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/wa/transactions/me', queryParameters: { final resp = await sn.client.get(
'take': 10, '/cgi/wa/transactions/me',
'offset': _transactions.length, queryParameters: {'take': 10, 'offset': _transactions.length},
});
_totalCount = resp.data['count'];
_transactions.addAll(
resp.data['data']?.map((e) => SnTransaction.fromJson(e)).cast<SnTransaction>() ?? [],
); );
_totalCount = resp.data['count'];
_transactions.addAll(resp.data['data']?.map((e) => SnTransaction.fromJson(e)).cast<SnTransaction>() ?? []);
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -159,12 +159,18 @@ class _WalletTransactionListState extends State<_WalletTransactionList> {
children: [ children: [
Text(ele.remark), Text(ele.remark),
const Gap(2), const Gap(2),
Text( Row(
DateFormat( children: [
null, Text(
EasyLocalization.of(context)!.currentLocale.toString(), 'walletTransactionType${ele.currency.capitalize()}'.tr(),
).format(ele.createdAt), style: Theme.of(context).textTheme.labelSmall,
style: Theme.of(context).textTheme.labelSmall, ),
Text(' · ').textStyle(Theme.of(context).textTheme.labelSmall!).padding(right: 4),
Text(
DateFormat(null, EasyLocalization.of(context)!.currentLocale.toString()).format(ele.createdAt),
style: Theme.of(context).textTheme.labelSmall,
),
],
), ),
], ],
), ),
@ -193,37 +199,33 @@ class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
final password = await showDialog<String?>( final password = await showDialog<String?>(
context: context, context: context,
builder: (ctx) => AlertDialog( builder:
title: Text('walletCreate').tr(), (ctx) => AlertDialog(
content: Column( title: Text('walletCreate').tr(),
crossAxisAlignment: CrossAxisAlignment.start, content: Column(
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start,
children: [ mainAxisSize: MainAxisSize.min,
Text('walletCreatePassword').tr(), children: [
const Gap(8), Text('walletCreatePassword').tr(),
TextField( const Gap(8),
autofocus: true, TextField(
obscureText: true, autofocus: true,
controller: passwordController, obscureText: true,
decoration: InputDecoration( controller: passwordController,
labelText: 'fieldPassword'.tr(), decoration: InputDecoration(labelText: 'fieldPassword'.tr()),
), ),
],
), ),
], actions: [
), TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text('cancel').tr()),
actions: [ TextButton(
TextButton( onPressed: () {
onPressed: () => Navigator.of(ctx).pop(), Navigator.of(ctx).pop(passwordController.text);
child: Text('cancel').tr(), },
child: Text('next').tr(),
),
],
), ),
TextButton(
onPressed: () {
Navigator.of(ctx).pop(passwordController.text);
},
child: Text('next').tr(),
),
],
),
); );
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
passwordController.dispose(); passwordController.dispose();
@ -234,9 +236,7 @@ class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
try { try {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
await sn.client.post('/cgi/wa/wallets/me', data: { await sn.client.post('/cgi/wa/wallets/me', data: {'password': password});
'password': password,
});
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -255,20 +255,14 @@ class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CircleAvatar( CircleAvatar(radius: 28, child: Icon(Symbols.add, size: 28)),
radius: 28,
child: Icon(Symbols.add, size: 28),
),
const Gap(12), const Gap(12),
Text('walletCreate', style: Theme.of(context).textTheme.titleLarge).tr(), Text('walletCreate', style: Theme.of(context).textTheme.titleLarge).tr(),
Text('walletCreateSubtitle', style: Theme.of(context).textTheme.bodyMedium).tr(), Text('walletCreateSubtitle', style: Theme.of(context).textTheme.bodyMedium).tr(),
const Gap(8), const Gap(8),
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: TextButton( child: TextButton(onPressed: _isBusy ? null : () => _createWallet(), child: Text('next').tr()),
onPressed: _isBusy ? null : () => _createWallet(),
child: Text('next').tr(),
),
), ),
], ],
).padding(horizontal: 20, vertical: 24), ).padding(horizontal: 20, vertical: 24),

View File

@ -184,3 +184,42 @@ abstract class SnActionEvent with _$SnActionEvent {
factory SnActionEvent.fromJson(Map<String, Object?> json) => factory SnActionEvent.fromJson(Map<String, Object?> json) =>
_$SnActionEventFromJson(json); _$SnActionEventFromJson(json);
} }
@freezed
abstract class SnProgram with _$SnProgram {
const factory SnProgram({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String name,
required String description,
required String alias,
required int expRequirement,
required Map<String, dynamic> price,
required Map<String, dynamic> badge,
required Map<String, dynamic> group,
required Map<String, dynamic> appearance,
}) = _SnProgram;
factory SnProgram.fromJson(Map<String, Object?> json) =>
_$SnProgramFromJson(json);
}
@freezed
abstract class SnProgramMember with _$SnProgramMember {
const factory SnProgramMember({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required DateTime lastPaid,
required SnAccount account,
required int accountId,
required SnProgram program,
required int programId,
}) = _SnProgramMember;
factory SnProgramMember.fromJson(Map<String, Object?> json) =>
_$SnProgramMemberFromJson(json);
}

View File

@ -3470,4 +3470,763 @@ class __$SnActionEventCopyWithImpl<$Res>
} }
} }
/// @nodoc
mixin _$SnProgram {
int get id;
DateTime get createdAt;
DateTime get updatedAt;
DateTime? get deletedAt;
String get name;
String get description;
String get alias;
int get expRequirement;
Map<String, dynamic> get price;
Map<String, dynamic> get badge;
Map<String, dynamic> get group;
Map<String, dynamic> get appearance;
/// Create a copy of SnProgram
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnProgramCopyWith<SnProgram> get copyWith =>
_$SnProgramCopyWithImpl<SnProgram>(this as SnProgram, _$identity);
/// Serializes this SnProgram to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is SnProgram &&
(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.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.alias, alias) || other.alias == alias) &&
(identical(other.expRequirement, expRequirement) ||
other.expRequirement == expRequirement) &&
const DeepCollectionEquality().equals(other.price, price) &&
const DeepCollectionEquality().equals(other.badge, badge) &&
const DeepCollectionEquality().equals(other.group, group) &&
const DeepCollectionEquality()
.equals(other.appearance, appearance));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
name,
description,
alias,
expRequirement,
const DeepCollectionEquality().hash(price),
const DeepCollectionEquality().hash(badge),
const DeepCollectionEquality().hash(group),
const DeepCollectionEquality().hash(appearance));
@override
String toString() {
return 'SnProgram(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, name: $name, description: $description, alias: $alias, expRequirement: $expRequirement, price: $price, badge: $badge, group: $group, appearance: $appearance)';
}
}
/// @nodoc
abstract mixin class $SnProgramCopyWith<$Res> {
factory $SnProgramCopyWith(SnProgram value, $Res Function(SnProgram) _then) =
_$SnProgramCopyWithImpl;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String name,
String description,
String alias,
int expRequirement,
Map<String, dynamic> price,
Map<String, dynamic> badge,
Map<String, dynamic> group,
Map<String, dynamic> appearance});
}
/// @nodoc
class _$SnProgramCopyWithImpl<$Res> implements $SnProgramCopyWith<$Res> {
_$SnProgramCopyWithImpl(this._self, this._then);
final SnProgram _self;
final $Res Function(SnProgram) _then;
/// Create a copy of SnProgram
/// 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? name = null,
Object? description = null,
Object? alias = null,
Object? expRequirement = null,
Object? price = null,
Object? badge = null,
Object? group = null,
Object? appearance = null,
}) {
return _then(_self.copyWith(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String,
alias: null == alias
? _self.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
expRequirement: null == expRequirement
? _self.expRequirement
: expRequirement // ignore: cast_nullable_to_non_nullable
as int,
price: null == price
? _self.price
: price // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
badge: null == badge
? _self.badge
: badge // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
group: null == group
? _self.group
: group // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
appearance: null == appearance
? _self.appearance
: appearance // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
));
}
}
/// @nodoc
@JsonSerializable()
class _SnProgram implements SnProgram {
const _SnProgram(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.name,
required this.description,
required this.alias,
required this.expRequirement,
required final Map<String, dynamic> price,
required final Map<String, dynamic> badge,
required final Map<String, dynamic> group,
required final Map<String, dynamic> appearance})
: _price = price,
_badge = badge,
_group = group,
_appearance = appearance;
factory _SnProgram.fromJson(Map<String, dynamic> json) =>
_$SnProgramFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final String name;
@override
final String description;
@override
final String alias;
@override
final int expRequirement;
final Map<String, dynamic> _price;
@override
Map<String, dynamic> get price {
if (_price is EqualUnmodifiableMapView) return _price;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_price);
}
final Map<String, dynamic> _badge;
@override
Map<String, dynamic> get badge {
if (_badge is EqualUnmodifiableMapView) return _badge;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_badge);
}
final Map<String, dynamic> _group;
@override
Map<String, dynamic> get group {
if (_group is EqualUnmodifiableMapView) return _group;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_group);
}
final Map<String, dynamic> _appearance;
@override
Map<String, dynamic> get appearance {
if (_appearance is EqualUnmodifiableMapView) return _appearance;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_appearance);
}
/// Create a copy of SnProgram
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnProgramCopyWith<_SnProgram> get copyWith =>
__$SnProgramCopyWithImpl<_SnProgram>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnProgramToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _SnProgram &&
(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.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.alias, alias) || other.alias == alias) &&
(identical(other.expRequirement, expRequirement) ||
other.expRequirement == expRequirement) &&
const DeepCollectionEquality().equals(other._price, _price) &&
const DeepCollectionEquality().equals(other._badge, _badge) &&
const DeepCollectionEquality().equals(other._group, _group) &&
const DeepCollectionEquality()
.equals(other._appearance, _appearance));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
name,
description,
alias,
expRequirement,
const DeepCollectionEquality().hash(_price),
const DeepCollectionEquality().hash(_badge),
const DeepCollectionEquality().hash(_group),
const DeepCollectionEquality().hash(_appearance));
@override
String toString() {
return 'SnProgram(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, name: $name, description: $description, alias: $alias, expRequirement: $expRequirement, price: $price, badge: $badge, group: $group, appearance: $appearance)';
}
}
/// @nodoc
abstract mixin class _$SnProgramCopyWith<$Res>
implements $SnProgramCopyWith<$Res> {
factory _$SnProgramCopyWith(
_SnProgram value, $Res Function(_SnProgram) _then) =
__$SnProgramCopyWithImpl;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String name,
String description,
String alias,
int expRequirement,
Map<String, dynamic> price,
Map<String, dynamic> badge,
Map<String, dynamic> group,
Map<String, dynamic> appearance});
}
/// @nodoc
class __$SnProgramCopyWithImpl<$Res> implements _$SnProgramCopyWith<$Res> {
__$SnProgramCopyWithImpl(this._self, this._then);
final _SnProgram _self;
final $Res Function(_SnProgram) _then;
/// Create a copy of SnProgram
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? name = null,
Object? description = null,
Object? alias = null,
Object? expRequirement = null,
Object? price = null,
Object? badge = null,
Object? group = null,
Object? appearance = null,
}) {
return _then(_SnProgram(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String,
alias: null == alias
? _self.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
expRequirement: null == expRequirement
? _self.expRequirement
: expRequirement // ignore: cast_nullable_to_non_nullable
as int,
price: null == price
? _self._price
: price // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
badge: null == badge
? _self._badge
: badge // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
group: null == group
? _self._group
: group // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
appearance: null == appearance
? _self._appearance
: appearance // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
));
}
}
/// @nodoc
mixin _$SnProgramMember {
int get id;
DateTime get createdAt;
DateTime get updatedAt;
DateTime? get deletedAt;
DateTime get lastPaid;
SnAccount get account;
int get accountId;
SnProgram get program;
int get programId;
/// Create a copy of SnProgramMember
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnProgramMemberCopyWith<SnProgramMember> get copyWith =>
_$SnProgramMemberCopyWithImpl<SnProgramMember>(
this as SnProgramMember, _$identity);
/// Serializes this SnProgramMember to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is SnProgramMember &&
(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.lastPaid, lastPaid) ||
other.lastPaid == lastPaid) &&
(identical(other.account, account) || other.account == account) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId) &&
(identical(other.program, program) || other.program == program) &&
(identical(other.programId, programId) ||
other.programId == programId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, lastPaid, account, accountId, program, programId);
@override
String toString() {
return 'SnProgramMember(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, lastPaid: $lastPaid, account: $account, accountId: $accountId, program: $program, programId: $programId)';
}
}
/// @nodoc
abstract mixin class $SnProgramMemberCopyWith<$Res> {
factory $SnProgramMemberCopyWith(
SnProgramMember value, $Res Function(SnProgramMember) _then) =
_$SnProgramMemberCopyWithImpl;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
DateTime lastPaid,
SnAccount account,
int accountId,
SnProgram program,
int programId});
$SnAccountCopyWith<$Res> get account;
$SnProgramCopyWith<$Res> get program;
}
/// @nodoc
class _$SnProgramMemberCopyWithImpl<$Res>
implements $SnProgramMemberCopyWith<$Res> {
_$SnProgramMemberCopyWithImpl(this._self, this._then);
final SnProgramMember _self;
final $Res Function(SnProgramMember) _then;
/// Create a copy of SnProgramMember
/// 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? lastPaid = null,
Object? account = null,
Object? accountId = null,
Object? program = null,
Object? programId = null,
}) {
return _then(_self.copyWith(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
lastPaid: null == lastPaid
? _self.lastPaid
: lastPaid // ignore: cast_nullable_to_non_nullable
as DateTime,
account: null == account
? _self.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
program: null == program
? _self.program
: program // ignore: cast_nullable_to_non_nullable
as SnProgram,
programId: null == programId
? _self.programId
: programId // ignore: cast_nullable_to_non_nullable
as int,
));
}
/// Create a copy of SnProgramMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {
return _then(_self.copyWith(account: value));
});
}
/// Create a copy of SnProgramMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnProgramCopyWith<$Res> get program {
return $SnProgramCopyWith<$Res>(_self.program, (value) {
return _then(_self.copyWith(program: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _SnProgramMember implements SnProgramMember {
const _SnProgramMember(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.lastPaid,
required this.account,
required this.accountId,
required this.program,
required this.programId});
factory _SnProgramMember.fromJson(Map<String, dynamic> json) =>
_$SnProgramMemberFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final DateTime lastPaid;
@override
final SnAccount account;
@override
final int accountId;
@override
final SnProgram program;
@override
final int programId;
/// Create a copy of SnProgramMember
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnProgramMemberCopyWith<_SnProgramMember> get copyWith =>
__$SnProgramMemberCopyWithImpl<_SnProgramMember>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnProgramMemberToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _SnProgramMember &&
(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.lastPaid, lastPaid) ||
other.lastPaid == lastPaid) &&
(identical(other.account, account) || other.account == account) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId) &&
(identical(other.program, program) || other.program == program) &&
(identical(other.programId, programId) ||
other.programId == programId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, lastPaid, account, accountId, program, programId);
@override
String toString() {
return 'SnProgramMember(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, lastPaid: $lastPaid, account: $account, accountId: $accountId, program: $program, programId: $programId)';
}
}
/// @nodoc
abstract mixin class _$SnProgramMemberCopyWith<$Res>
implements $SnProgramMemberCopyWith<$Res> {
factory _$SnProgramMemberCopyWith(
_SnProgramMember value, $Res Function(_SnProgramMember) _then) =
__$SnProgramMemberCopyWithImpl;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
DateTime lastPaid,
SnAccount account,
int accountId,
SnProgram program,
int programId});
@override
$SnAccountCopyWith<$Res> get account;
@override
$SnProgramCopyWith<$Res> get program;
}
/// @nodoc
class __$SnProgramMemberCopyWithImpl<$Res>
implements _$SnProgramMemberCopyWith<$Res> {
__$SnProgramMemberCopyWithImpl(this._self, this._then);
final _SnProgramMember _self;
final $Res Function(_SnProgramMember) _then;
/// Create a copy of SnProgramMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? lastPaid = null,
Object? account = null,
Object? accountId = null,
Object? program = null,
Object? programId = null,
}) {
return _then(_SnProgramMember(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
lastPaid: null == lastPaid
? _self.lastPaid
: lastPaid // ignore: cast_nullable_to_non_nullable
as DateTime,
account: null == account
? _self.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
program: null == program
? _self.program
: program // ignore: cast_nullable_to_non_nullable
as SnProgram,
programId: null == programId
? _self.programId
: programId // ignore: cast_nullable_to_non_nullable
as int,
));
}
/// Create a copy of SnProgramMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {
return _then(_self.copyWith(account: value));
});
}
/// Create a copy of SnProgramMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnProgramCopyWith<$Res> get program {
return $SnProgramCopyWith<$Res>(_self.program, (value) {
return _then(_self.copyWith(program: value));
});
}
}
// dart format on // dart format on

View File

@ -319,3 +319,64 @@ Map<String, dynamic> _$SnActionEventToJson(_SnActionEvent instance) =>
'account': instance.account.toJson(), 'account': instance.account.toJson(),
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };
_SnProgram _$SnProgramFromJson(Map<String, dynamic> json) => _SnProgram(
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),
name: json['name'] as String,
description: json['description'] as String,
alias: json['alias'] as String,
expRequirement: (json['exp_requirement'] as num).toInt(),
price: json['price'] as Map<String, dynamic>,
badge: json['badge'] as Map<String, dynamic>,
group: json['group'] as Map<String, dynamic>,
appearance: json['appearance'] as Map<String, dynamic>,
);
Map<String, dynamic> _$SnProgramToJson(_SnProgram instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'name': instance.name,
'description': instance.description,
'alias': instance.alias,
'exp_requirement': instance.expRequirement,
'price': instance.price,
'badge': instance.badge,
'group': instance.group,
'appearance': instance.appearance,
};
_SnProgramMember _$SnProgramMemberFromJson(Map<String, dynamic> json) =>
_SnProgramMember(
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),
lastPaid: DateTime.parse(json['last_paid'] as String),
account: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
program: SnProgram.fromJson(json['program'] as Map<String, dynamic>),
programId: (json['program_id'] as num).toInt(),
);
Map<String, dynamic> _$SnProgramMemberToJson(_SnProgramMember instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'last_paid': instance.lastPaid.toIso8601String(),
'account': instance.account.toJson(),
'account_id': instance.accountId,
'program': instance.program.toJson(),
'program_id': instance.programId,
};

View File

@ -11,6 +11,7 @@ abstract class SnWallet with _$SnWallet {
required DateTime updatedAt, required DateTime updatedAt,
required DateTime? deletedAt, required DateTime? deletedAt,
required String balance, required String balance,
required String goldenBalance,
required String password, required String password,
required int accountId, required int accountId,
}) = _SnWallet; }) = _SnWallet;
@ -27,6 +28,7 @@ abstract class SnTransaction with _$SnTransaction {
required DateTime? deletedAt, required DateTime? deletedAt,
required String remark, required String remark,
required String amount, required String amount,
required String currency,
required SnWallet? payer, required SnWallet? payer,
required SnWallet? payee, required SnWallet? payee,
required int? payerId, required int? payerId,

View File

@ -20,6 +20,7 @@ mixin _$SnWallet {
DateTime get updatedAt; DateTime get updatedAt;
DateTime? get deletedAt; DateTime? get deletedAt;
String get balance; String get balance;
String get goldenBalance;
String get password; String get password;
int get accountId; int get accountId;
@ -46,6 +47,8 @@ mixin _$SnWallet {
(identical(other.deletedAt, deletedAt) || (identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) && other.deletedAt == deletedAt) &&
(identical(other.balance, balance) || other.balance == balance) && (identical(other.balance, balance) || other.balance == balance) &&
(identical(other.goldenBalance, goldenBalance) ||
other.goldenBalance == goldenBalance) &&
(identical(other.password, password) || (identical(other.password, password) ||
other.password == password) && other.password == password) &&
(identical(other.accountId, accountId) || (identical(other.accountId, accountId) ||
@ -55,11 +58,11 @@ mixin _$SnWallet {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt, int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, balance, password, accountId); deletedAt, balance, goldenBalance, password, accountId);
@override @override
String toString() { String toString() {
return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, password: $password, accountId: $accountId)'; return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, goldenBalance: $goldenBalance, password: $password, accountId: $accountId)';
} }
} }
@ -74,6 +77,7 @@ abstract mixin class $SnWalletCopyWith<$Res> {
DateTime updatedAt, DateTime updatedAt,
DateTime? deletedAt, DateTime? deletedAt,
String balance, String balance,
String goldenBalance,
String password, String password,
int accountId}); int accountId});
} }
@ -95,6 +99,7 @@ class _$SnWalletCopyWithImpl<$Res> implements $SnWalletCopyWith<$Res> {
Object? updatedAt = null, Object? updatedAt = null,
Object? deletedAt = freezed, Object? deletedAt = freezed,
Object? balance = null, Object? balance = null,
Object? goldenBalance = null,
Object? password = null, Object? password = null,
Object? accountId = null, Object? accountId = null,
}) { }) {
@ -119,6 +124,10 @@ class _$SnWalletCopyWithImpl<$Res> implements $SnWalletCopyWith<$Res> {
? _self.balance ? _self.balance
: balance // ignore: cast_nullable_to_non_nullable : balance // ignore: cast_nullable_to_non_nullable
as String, as String,
goldenBalance: null == goldenBalance
? _self.goldenBalance
: goldenBalance // ignore: cast_nullable_to_non_nullable
as String,
password: null == password password: null == password
? _self.password ? _self.password
: password // ignore: cast_nullable_to_non_nullable : password // ignore: cast_nullable_to_non_nullable
@ -140,6 +149,7 @@ class _SnWallet implements SnWallet {
required this.updatedAt, required this.updatedAt,
required this.deletedAt, required this.deletedAt,
required this.balance, required this.balance,
required this.goldenBalance,
required this.password, required this.password,
required this.accountId}); required this.accountId});
factory _SnWallet.fromJson(Map<String, dynamic> json) => factory _SnWallet.fromJson(Map<String, dynamic> json) =>
@ -156,6 +166,8 @@ class _SnWallet implements SnWallet {
@override @override
final String balance; final String balance;
@override @override
final String goldenBalance;
@override
final String password; final String password;
@override @override
final int accountId; final int accountId;
@ -188,6 +200,8 @@ class _SnWallet implements SnWallet {
(identical(other.deletedAt, deletedAt) || (identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) && other.deletedAt == deletedAt) &&
(identical(other.balance, balance) || other.balance == balance) && (identical(other.balance, balance) || other.balance == balance) &&
(identical(other.goldenBalance, goldenBalance) ||
other.goldenBalance == goldenBalance) &&
(identical(other.password, password) || (identical(other.password, password) ||
other.password == password) && other.password == password) &&
(identical(other.accountId, accountId) || (identical(other.accountId, accountId) ||
@ -197,11 +211,11 @@ class _SnWallet implements SnWallet {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt, int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, balance, password, accountId); deletedAt, balance, goldenBalance, password, accountId);
@override @override
String toString() { String toString() {
return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, password: $password, accountId: $accountId)'; return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, goldenBalance: $goldenBalance, password: $password, accountId: $accountId)';
} }
} }
@ -218,6 +232,7 @@ abstract mixin class _$SnWalletCopyWith<$Res>
DateTime updatedAt, DateTime updatedAt,
DateTime? deletedAt, DateTime? deletedAt,
String balance, String balance,
String goldenBalance,
String password, String password,
int accountId}); int accountId});
} }
@ -239,6 +254,7 @@ class __$SnWalletCopyWithImpl<$Res> implements _$SnWalletCopyWith<$Res> {
Object? updatedAt = null, Object? updatedAt = null,
Object? deletedAt = freezed, Object? deletedAt = freezed,
Object? balance = null, Object? balance = null,
Object? goldenBalance = null,
Object? password = null, Object? password = null,
Object? accountId = null, Object? accountId = null,
}) { }) {
@ -263,6 +279,10 @@ class __$SnWalletCopyWithImpl<$Res> implements _$SnWalletCopyWith<$Res> {
? _self.balance ? _self.balance
: balance // ignore: cast_nullable_to_non_nullable : balance // ignore: cast_nullable_to_non_nullable
as String, as String,
goldenBalance: null == goldenBalance
? _self.goldenBalance
: goldenBalance // ignore: cast_nullable_to_non_nullable
as String,
password: null == password password: null == password
? _self.password ? _self.password
: password // ignore: cast_nullable_to_non_nullable : password // ignore: cast_nullable_to_non_nullable
@ -283,6 +303,7 @@ mixin _$SnTransaction {
DateTime? get deletedAt; DateTime? get deletedAt;
String get remark; String get remark;
String get amount; String get amount;
String get currency;
SnWallet? get payer; SnWallet? get payer;
SnWallet? get payee; SnWallet? get payee;
int? get payerId; int? get payerId;
@ -313,6 +334,8 @@ mixin _$SnTransaction {
other.deletedAt == deletedAt) && other.deletedAt == deletedAt) &&
(identical(other.remark, remark) || other.remark == remark) && (identical(other.remark, remark) || other.remark == remark) &&
(identical(other.amount, amount) || other.amount == amount) && (identical(other.amount, amount) || other.amount == amount) &&
(identical(other.currency, currency) ||
other.currency == currency) &&
(identical(other.payer, payer) || other.payer == payer) && (identical(other.payer, payer) || other.payer == payer) &&
(identical(other.payee, payee) || other.payee == payee) && (identical(other.payee, payee) || other.payee == payee) &&
(identical(other.payerId, payerId) || other.payerId == payerId) && (identical(other.payerId, payerId) || other.payerId == payerId) &&
@ -322,11 +345,11 @@ mixin _$SnTransaction {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt, int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, remark, amount, payer, payee, payerId, payeeId); deletedAt, remark, amount, currency, payer, payee, payerId, payeeId);
@override @override
String toString() { String toString() {
return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)'; return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, currency: $currency, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)';
} }
} }
@ -343,6 +366,7 @@ abstract mixin class $SnTransactionCopyWith<$Res> {
DateTime? deletedAt, DateTime? deletedAt,
String remark, String remark,
String amount, String amount,
String currency,
SnWallet? payer, SnWallet? payer,
SnWallet? payee, SnWallet? payee,
int? payerId, int? payerId,
@ -371,6 +395,7 @@ class _$SnTransactionCopyWithImpl<$Res>
Object? deletedAt = freezed, Object? deletedAt = freezed,
Object? remark = null, Object? remark = null,
Object? amount = null, Object? amount = null,
Object? currency = null,
Object? payer = freezed, Object? payer = freezed,
Object? payee = freezed, Object? payee = freezed,
Object? payerId = freezed, Object? payerId = freezed,
@ -401,6 +426,10 @@ class _$SnTransactionCopyWithImpl<$Res>
? _self.amount ? _self.amount
: amount // ignore: cast_nullable_to_non_nullable : amount // ignore: cast_nullable_to_non_nullable
as String, as String,
currency: null == currency
? _self.currency
: currency // ignore: cast_nullable_to_non_nullable
as String,
payer: freezed == payer payer: freezed == payer
? _self.payer ? _self.payer
: payer // ignore: cast_nullable_to_non_nullable : payer // ignore: cast_nullable_to_non_nullable
@ -459,6 +488,7 @@ class _SnTransaction implements SnTransaction {
required this.deletedAt, required this.deletedAt,
required this.remark, required this.remark,
required this.amount, required this.amount,
required this.currency,
required this.payer, required this.payer,
required this.payee, required this.payee,
required this.payerId, required this.payerId,
@ -479,6 +509,8 @@ class _SnTransaction implements SnTransaction {
@override @override
final String amount; final String amount;
@override @override
final String currency;
@override
final SnWallet? payer; final SnWallet? payer;
@override @override
final SnWallet? payee; final SnWallet? payee;
@ -516,6 +548,8 @@ class _SnTransaction implements SnTransaction {
other.deletedAt == deletedAt) && other.deletedAt == deletedAt) &&
(identical(other.remark, remark) || other.remark == remark) && (identical(other.remark, remark) || other.remark == remark) &&
(identical(other.amount, amount) || other.amount == amount) && (identical(other.amount, amount) || other.amount == amount) &&
(identical(other.currency, currency) ||
other.currency == currency) &&
(identical(other.payer, payer) || other.payer == payer) && (identical(other.payer, payer) || other.payer == payer) &&
(identical(other.payee, payee) || other.payee == payee) && (identical(other.payee, payee) || other.payee == payee) &&
(identical(other.payerId, payerId) || other.payerId == payerId) && (identical(other.payerId, payerId) || other.payerId == payerId) &&
@ -525,11 +559,11 @@ class _SnTransaction implements SnTransaction {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt, int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, remark, amount, payer, payee, payerId, payeeId); deletedAt, remark, amount, currency, payer, payee, payerId, payeeId);
@override @override
String toString() { String toString() {
return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)'; return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, currency: $currency, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)';
} }
} }
@ -548,6 +582,7 @@ abstract mixin class _$SnTransactionCopyWith<$Res>
DateTime? deletedAt, DateTime? deletedAt,
String remark, String remark,
String amount, String amount,
String currency,
SnWallet? payer, SnWallet? payer,
SnWallet? payee, SnWallet? payee,
int? payerId, int? payerId,
@ -578,6 +613,7 @@ class __$SnTransactionCopyWithImpl<$Res>
Object? deletedAt = freezed, Object? deletedAt = freezed,
Object? remark = null, Object? remark = null,
Object? amount = null, Object? amount = null,
Object? currency = null,
Object? payer = freezed, Object? payer = freezed,
Object? payee = freezed, Object? payee = freezed,
Object? payerId = freezed, Object? payerId = freezed,
@ -608,6 +644,10 @@ class __$SnTransactionCopyWithImpl<$Res>
? _self.amount ? _self.amount
: amount // ignore: cast_nullable_to_non_nullable : amount // ignore: cast_nullable_to_non_nullable
as String, as String,
currency: null == currency
? _self.currency
: currency // ignore: cast_nullable_to_non_nullable
as String,
payer: freezed == payer payer: freezed == payer
? _self.payer ? _self.payer
: payer // ignore: cast_nullable_to_non_nullable : payer // ignore: cast_nullable_to_non_nullable

View File

@ -14,6 +14,7 @@ _SnWallet _$SnWalletFromJson(Map<String, dynamic> json) => _SnWallet(
? null ? null
: DateTime.parse(json['deleted_at'] as String), : DateTime.parse(json['deleted_at'] as String),
balance: json['balance'] as String, balance: json['balance'] as String,
goldenBalance: json['golden_balance'] as String,
password: json['password'] as String, password: json['password'] as String,
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
@ -24,6 +25,7 @@ Map<String, dynamic> _$SnWalletToJson(_SnWallet instance) => <String, dynamic>{
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(),
'balance': instance.balance, 'balance': instance.balance,
'golden_balance': instance.goldenBalance,
'password': instance.password, 'password': instance.password,
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };
@ -38,6 +40,7 @@ _SnTransaction _$SnTransactionFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['deleted_at'] as String), : DateTime.parse(json['deleted_at'] as String),
remark: json['remark'] as String, remark: json['remark'] as String,
amount: json['amount'] as String, amount: json['amount'] as String,
currency: json['currency'] as String,
payer: json['payer'] == null payer: json['payer'] == null
? null ? null
: SnWallet.fromJson(json['payer'] as Map<String, dynamic>), : SnWallet.fromJson(json['payer'] as Map<String, dynamic>),
@ -56,6 +59,7 @@ Map<String, dynamic> _$SnTransactionToJson(_SnTransaction instance) =>
'deleted_at': instance.deletedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(),
'remark': instance.remark, 'remark': instance.remark,
'amount': instance.amount, 'amount': instance.amount,
'currency': instance.currency,
'payer': instance.payer?.toJson(), 'payer': instance.payer?.toJson(),
'payee': instance.payee?.toJson(), 'payee': instance.payee?.toJson(),
'payer_id': instance.payerId, 'payer_id': instance.payerId,

View File

@ -2525,10 +2525,11 @@ packages:
workmanager: workmanager:
dependency: "direct main" dependency: "direct main"
description: description:
name: workmanager path: workmanager
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00 ref: main
url: "https://pub.dev" resolved-ref: "4ce065135dc1b91fee918f81596b42a56850391d"
source: hosted url: "https://github.com/fluttercommunity/flutter_workmanager.git"
source: git
version: "0.5.2" version: "0.5.2"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive

View File

@ -59,7 +59,7 @@ dependencies:
relative_time: ^5.0.0 relative_time: ^5.0.0
image_picker: ^1.1.2 image_picker: ^1.1.2
cross_file: ^0.3.4+2 cross_file: ^0.3.4+2
file_picker: ^9.0.0 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643 file_picker: ^9.2.1
croppy: ^1.3.1 croppy: ^1.3.1
flutter_expandable_fab: ^2.3.0 flutter_expandable_fab: ^2.3.0
dropdown_button2: ^2.3.9 dropdown_button2: ^2.3.9
@ -103,7 +103,11 @@ dependencies:
flutter_svg: ^2.0.16 flutter_svg: ^2.0.16
home_widget: ^0.7.0 home_widget: ^0.7.0
receive_sharing_intent: ^1.8.1 receive_sharing_intent: ^1.8.1
workmanager: ^0.5.2 workmanager: # use git due to: https://github.com/fluttercommunity/flutter_workmanager/issues/588#issuecomment-2660871645
git:
url: https://github.com/fluttercommunity/flutter_workmanager.git
path: workmanager
ref: main
flutter_app_update: ^3.2.2 flutter_app_update: ^3.2.2
in_app_review: ^2.0.10 in_app_review: ^2.0.10
version: ^3.0.2 version: ^3.0.2

2
web/_redirects Normal file
View File

@ -0,0 +1,2 @@
/assets/assets/translations/en.json /assets/assets/translations/en-US.json 301
/assets/assets/translations/zh.json /assets/assets/translations/zh-CN.json 301