Compare commits

...

6 Commits

Author SHA1 Message Date
8bb62b5992 🚀 Launch 2.2.2+58 2025-01-28 21:15:11 +08:00
1e8a6dea5b 💄 Optimize attachment list 2025-01-28 20:39:34 +08:00
5c2804cc4d 💄 Optimize news design 2025-01-28 20:21:51 +08:00
0dbb8f132a Factor settings with TOTP, In app notify authenticate method 2025-01-28 19:55:35 +08:00
3395f3dbd0 Create auth factor 2025-01-28 00:52:44 +08:00
d258ba776e ♻️ Splitting up account page and settings 2025-01-27 20:14:02 +08:00
20 changed files with 783 additions and 339 deletions

View File

@ -17,6 +17,7 @@
"screenAccountProfileEdit": "Edit Profile",
"screenAbuseReport": "Abuse Reports",
"screenSettings": "Settings",
"screenAccountSettings": "Account Settings",
"screenNews": "News",
"screenAlbum": "Album",
"screenChat": "Chat",
@ -28,6 +29,7 @@
"screenNotification": "Notification",
"screenPostSearch": "Search Posts",
"screenFriend": "Friends",
"screenFactorSettings": "Auth Factors",
"dialogOkay": "Okay",
"dialogCancel": "Cancel",
"dialogConfirm": "Confirm",
@ -104,8 +106,18 @@
},
"loginEnterPassword": "Enter the code",
"loginSuccess": "Logged in as {}",
"authFactorDelete": "Delete Auth Factor",
"authFactorDeleteDescription": "Are you sure you want delete auth factor {}?",
"authFactorPassword": "Password",
"authFactorPasswordDescription": "The password you set when you registered.",
"authFactorEmail": "Email verification code",
"authFactorEmailDescription": "An one-time code sent to the email address you set when you registered.",
"authFactorTOTP": "Time-based OTP",
"authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.",
"authFactorInAppNotify": "In-app notification",
"authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.",
"authFactorAdd": "Add a factor",
"authFactorAddSubtitle": "Provide another way to login your account.",
"accountIntroTitle": "Hello there!",
"accountIntroSubtitle": "Pick an option below to get started.",
"accountLogout": "Logout",
@ -114,8 +126,12 @@
"accountLogoutConfirm": "You will need to re-enter your account password, even if you have already done so. This is required to login again.",
"accountPublishers": "Your publishers",
"accountPublishersSubtitle": "Manage your publish identities.",
"accountSettings": "Account Settings",
"accountSettingsSubtitle": "Manage your account and make it yours.",
"accountProfileEdit": "Edit your profile",
"accountProfileEditSubtitle": "Make your Solarpass account more looks like you.",
"factorSettings": "Auth Factors",
"factorSettingsSubtitle": "Manage your authentication factors.",
"accountProfileEditApplied": "Profile modification applied.",
"publishersNew": "New Publisher",
"publisherNewSubtitle": "Create a new publisher identity.",
@ -565,5 +581,8 @@
"newsReadingFromReader": "You're reading from HyperNet.Reader",
"newsReadingFromOriginal": "You're reading the original article",
"newsDisclaimer": "This article is fetched from the Internet, we do not guarantee its authenticity, please judge for yourself. All content in this article belongs to the original author.",
"newsToday": "Today's News"
"newsToday": "Today's News",
"totpPostSetup": "One More Thing",
"totpPostSetupDescription": "Scan the QR Code below with Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden or any of kind of authenticator app which supports TOTP.",
"totpNeverShare": "Never share this QR Code"
}

View File

@ -15,6 +15,7 @@
"screenAccountProfileEdit": "编辑资料",
"screenAbuseReport": "滥用检举",
"screenSettings": "设置",
"screenAccountSettings": "账号设置",
"screenNews": "新闻",
"screenAlbum": "相册",
"screenChat": "聊天",
@ -88,8 +89,18 @@
},
"loginEnterPassword": "验证代码",
"loginSuccess": "登录为 {}",
"authFactorDelete": "删除验证因子",
"authFactorDeleteDescription": "你确定要删除 {} 验证因子吗?",
"authFactorPassword": "密码",
"authFactorPasswordDescription": "注册时选择设置的密码。",
"authFactorEmail": "电邮一次性验证码",
"authFactorEmailDescription": "由我们生成并发送到绑定的的电子邮箱的一次性验证码。",
"authFactorTOTP": "时序验证码",
"authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等验证器生成的一次性验证码。",
"authFactorInAppNotify": "应用内通知验证码",
"authFactorInAppNotifyDescription": "通过站内通知推送的一次性验证码。",
"authFactorAdd": "添加新验证因子",
"authFactorAddSubtitle": "给你的帐户登陆时提供另一个方案。",
"accountIntroTitle": "喜欢您来!",
"accountIntroSubtitle": "登陆以探索更广大的世界。",
"accountLogout": "退出登录",
@ -98,8 +109,12 @@
"accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。",
"accountPublishers": "你的发布者",
"accountPublishersSubtitle": "管理你的公共形象。",
"accountSettings": "帐号设置",
"accountSettingsSubtitle": "管理你的帐号并让它更好的服务你。",
"accountProfileEdit": "编辑资料",
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
"factorSettings": "验证因子",
"factorSettingsSubtitle": "管理你的登陆验证方式。",
"accountProfileEditApplied": "个人资料修改已被应用。",
"publishersNew": "新发布者",
"publisherNewSubtitle": "创建一个新的公共身份。",
@ -563,5 +578,8 @@
"newsReadingFromReader": "你正在从 HyperNet.Reader 阅读文章",
"newsReadingFromOriginal": "你正在阅读原始文章",
"newsDisclaimer": "本文由 HyperNet.Reader 从互联网上获取,我们不担保其内容的真实性,请自行判断。本文章的所有内容版权归原作者所有。",
"newsToday": "快讯"
"newsToday": "快讯",
"totpPostSetup": "还有一件事",
"totpPostSetupDescription": "使用 Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden 或其他支持 TOTP 的验证器扫描本 QR Code 来添加。",
"totpNeverShare": "永远不要分享这个 QR Code"
}

View File

@ -53,14 +53,14 @@ PODS:
- Firebase/Messaging (11.6.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.6.0)
- firebase_analytics (11.4.0):
- firebase_analytics (11.4.1):
- Firebase/Analytics (= 11.6.0)
- firebase_core
- Flutter
- firebase_core (3.10.0):
- firebase_core (3.10.1):
- Firebase/CoreOnly (= 11.6.0)
- Flutter
- firebase_messaging (15.2.0):
- firebase_messaging (15.2.1):
- Firebase/Messaging (= 11.6.0)
- firebase_core
- Flutter
@ -382,9 +382,9 @@ SPEC CHECKSUMS:
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
firebase_analytics: 07bd7cfbac54bfcdccf2bb2530f9a65486f7ef3f
firebase_core: feb37e79f775c2bd08dd35e02d83678291317e10
firebase_messaging: e2f0ba891b1509668c07f5099761518a5af8fe3c
firebase_analytics: 13ea4ad8a42c5060bad7e6694304dabb8b02fe7e
firebase_core: e2aa06dbd854d961f8ce46c2e20933bee1bf2d2b
firebase_messaging: 96cf6d67121b3f39746b2a4f29a26c0eee4af70e
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2

View File

@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:surface/screens/abuse_report.dart';
import 'package:surface/screens/account.dart';
import 'package:surface/screens/account/account_settings.dart';
import 'package:surface/screens/account/factor_settings.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/screens/account/profile_edit.dart';
import 'package:surface/screens/account/publishers/publisher_edit.dart';
@ -96,11 +98,47 @@ final _appRoutes = [
),
],
),
GoRoute(
path: '/account',
name: 'account',
builder: (context, state) => const AccountScreen(),
),
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [
GoRoute(
path: '/settings',
name: 'accountSettings',
builder: (context, state) => AccountSettingsScreen(),
),
GoRoute(
path: '/settings/factors',
name: 'factorSettings',
builder: (context, state) => FactorSettingsScreen(),
),
GoRoute(
path: '/profile/edit',
name: 'accountProfileEdit',
builder: (context, state) => ProfileEditScreen(),
),
GoRoute(
path: '/publishers',
name: 'accountPublishers',
builder: (context, state) => PublisherScreen(),
),
GoRoute(
path: '/publishers/new',
name: 'accountPublisherNew',
builder: (context, state) => AccountPublisherNewScreen(),
),
GoRoute(
path: '/publishers/edit/:name',
name: 'accountPublisherEdit',
builder: (context, state) => AccountPublisherEditScreen(
name: state.pathParameters['name']!,
),
),
GoRoute(
path: '/:name',
name: 'accountProfilePage',
pageBuilder: (context, state) => NoTransitionPage(
child: UserScreen(name: state.pathParameters['name']!),
),
),
]),
GoRoute(
path: '/chat',
name: 'chat',
@ -161,20 +199,15 @@ final _appRoutes = [
),
],
),
GoRoute(
path: '/news',
name: 'news',
builder: (context, state) => const NewsScreen(),
routes: [
GoRoute(
path: '/:hash',
name: 'newsDetail',
builder: (context, state) => NewsDetailScreen(
hash: state.pathParameters['hash']!,
),
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
GoRoute(
path: '/:hash',
name: 'newsDetail',
builder: (context, state) => NewsDetailScreen(
hash: state.pathParameters['hash']!,
),
]
),
),
]),
GoRoute(
path: '/album',
name: 'album',
@ -205,35 +238,6 @@ final _appRoutes = [
name: 'abuseReport',
builder: (context, state) => AbuseReportScreen(),
),
GoRoute(
path: '/account/profile/edit',
name: 'accountProfileEdit',
builder: (context, state) => ProfileEditScreen(),
),
GoRoute(
path: '/account/publishers',
name: 'accountPublishers',
builder: (context, state) => PublisherScreen(),
),
GoRoute(
path: '/account/publishers/new',
name: 'accountPublisherNew',
builder: (context, state) => AccountPublisherNewScreen(),
),
GoRoute(
path: '/account/publishers/edit/:name',
name: 'accountPublisherEdit',
builder: (context, state) => AccountPublisherEditScreen(
name: state.pathParameters['name']!,
),
),
GoRoute(
path: '/account/:name',
name: 'accountProfilePage',
pageBuilder: (context, state) => NoTransitionPage(
child: UserScreen(name: state.pathParameters['name']!),
),
),
GoRoute(
path: '/settings',
name: 'settings',

View File

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
@ -13,6 +15,7 @@ import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/universal_image.dart';
class AccountScreen extends StatelessWidget {
const AccountScreen({super.key});
@ -20,11 +23,39 @@ class AccountScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ua = context.watch<UserProvider>();
final sn = context.read<SnNetworkProvider>();
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text("screenAccount").tr(),
flexibleSpace: ua.user != null && ua.user!.banner.isNotEmpty
? Stack(
fit: StackFit.expand,
children: [
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover),
Positioned(
top: 0,
left: 0,
right: 0,
height: 56 + MediaQuery.of(context).padding.top,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 10,
sigmaY: 10,
),
child: Container(
color: Colors.black.withOpacity(
clampDouble(10 * 0.1, 0, 0.5),
),
),
),
),
),
],
)
: null,
actions: [
IconButton(
icon: const Icon(Symbols.settings, fill: 1),
@ -83,16 +114,6 @@ class _AuthorizedAccountScreen extends StatelessWidget {
);
}).padding(all: 20),
).padding(horizontal: 8, top: 16, bottom: 4),
ListTile(
title: Text('accountProfileEdit').tr(),
subtitle: Text('accountProfileEditSubtitle').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.contact_page),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
GoRouter.of(context).pushNamed('accountProfileEdit');
},
),
ListTile(
title: Text('accountPublishers').tr(),
subtitle: Text('accountPublishersSubtitle').tr(),
@ -113,6 +134,26 @@ class _AuthorizedAccountScreen extends StatelessWidget {
GoRouter.of(context).pushNamed('abuseReport');
},
),
ListTile(
title: Text('factorSettings').tr(),
subtitle: Text('factorSettingsSubtitle').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.lock),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
GoRouter.of(context).pushNamed('factorSettings');
},
),
ListTile(
title: Text('accountSettings').tr(),
subtitle: Text('accountSettingsSubtitle').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.manage_accounts),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
GoRouter.of(context).pushNamed('accountSettings');
},
),
ListTile(
title: Text('accountLogout').tr(),
subtitle: Text('accountLogoutSubtitle').tr(),
@ -134,33 +175,6 @@ class _AuthorizedAccountScreen extends StatelessWidget {
await Hive.initFlutter();
},
),
ListTile(
title: Text('accountDeletion'.tr()),
subtitle: Text('accountDeletionActionDescription'.tr()),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.person_cancel),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
context
.showConfirmDialog(
'accountDeletion'.tr(),
'accountDeletionDescription'.tr(),
)
.then((value) {
if (!value || !context.mounted) return;
final sn = context.read<SnNetworkProvider>();
sn.client.post('/cgi/id/users/me/deletion').then((value) {
if (context.mounted) {
context.showSnackbar('accountDeletionSubmitted'.tr());
}
}).catchError((err) {
if (context.mounted) {
context.showErrorDialog(err);
}
});
});
},
),
],
);
}

View File

@ -0,0 +1,66 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class AccountSettingsScreen extends StatelessWidget {
const AccountSettingsScreen({super.key});
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
leading: PageBackButton(),
title: Text('screenAccountSettings').tr(),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text('accountProfileEdit').tr(),
subtitle: Text('accountProfileEditSubtitle').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.contact_page),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
GoRouter.of(context).pushNamed('accountProfileEdit');
},
),
ListTile(
title: Text('accountDeletion'.tr()),
subtitle: Text('accountDeletionActionDescription'.tr()),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.person_cancel),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
context
.showConfirmDialog(
'accountDeletion'.tr(),
'accountDeletionDescription'.tr(),
)
.then((value) {
if (!value || !context.mounted) return;
final sn = context.read<SnNetworkProvider>();
sn.client.post('/cgi/id/users/me/deletion').then((value) {
if (context.mounted) {
context.showSnackbar('accountDeletionSubmitted'.tr());
}
}).catchError((err) {
if (context.mounted) {
context.showErrorDialog(err);
}
});
});
},
),
],
),
),
);
}
}

View File

@ -0,0 +1,294 @@
import 'package:dropdown_button2/dropdown_button2.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:qr_flutter/qr_flutter.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/auth.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
final Map<int, (String, String, IconData)> kFactorTypes = {
0: ('authFactorPassword', 'authFactorPasswordDescription', Symbols.password),
1: ('authFactorEmail', 'authFactorEmailDescription', Symbols.email),
2: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
3: ('authFactorInAppNotify', 'authFactorInAppNotifyDescription', Symbols.notifications_active),
};
class FactorSettingsScreen extends StatefulWidget {
const FactorSettingsScreen({super.key});
@override
State<FactorSettingsScreen> createState() => _FactorSettingsScreenState();
}
class _FactorSettingsScreenState extends State<FactorSettingsScreen> {
bool _isBusy = false;
List<SnAuthFactor>? _factors;
Future<void> _fetchFactors() async {
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/users/me/factors');
_factors = List<SnAuthFactor>.from(
resp.data?.map((e) => SnAuthFactor.fromJson(e as Map<String, dynamic>)).toList() ?? [],
);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchFactors();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
leading: PageBackButton(),
title: Text('screenFactorSettings').tr(),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LoadingIndicator(
isActive: _isBusy,
),
ListTile(
title: Text('authFactorAdd').tr(),
subtitle: Text('authFactorAddSubtitle').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.add),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
showDialog(
context: context,
builder: (context) => _FactorNewDialog(
currentlyHave: _factors!,
),
).then((val) {
if (val == true) _fetchFactors();
});
},
),
const Divider(height: 1),
Expanded(
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: _fetchFactors,
child: ListView.builder(
itemCount: _factors?.length ?? 0,
itemBuilder: (context, idx) {
final ele = _factors![idx];
return ListTile(
title: Text(kFactorTypes[ele.type]!.$1).tr(),
subtitle: Text(kFactorTypes[ele.type]!.$2).tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 12),
leading: Icon(kFactorTypes[ele.type]!.$3),
trailing: IconButton(
icon: const Icon(Symbols.close),
onPressed: ele.type > 0
? () {
context
.showConfirmDialog(
'authFactorDelete'.tr(),
'authFactorDeleteDescription'.tr(args: [kFactorTypes[ele.type]!.$1.tr()]),
)
.then((val) async {
if (!val) return;
try {
if (!context.mounted) return;
final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/id/users/me/factors/${ele.id}');
_fetchFactors();
} catch (err) {
if (!context.mounted) return;
context.showErrorDialog(err);
}
});
}
: null,
),
);
},
),
),
),
),
],
),
);
}
}
class _FactorNewDialog extends StatefulWidget {
final List<SnAuthFactor> currentlyHave;
const _FactorNewDialog({required this.currentlyHave});
@override
State<_FactorNewDialog> createState() => _FactorNewDialogState();
}
class _FactorNewDialogState extends State<_FactorNewDialog> {
int? _factorType;
bool _isBusy = false;
Future<void> _submit() async {
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.post('/cgi/id/users/me/factors', data: {
'type': _factorType,
});
final factor = SnAuthFactor.fromJson(resp.data);
if (!mounted) return;
if (factor.type == 2) {
await showModalBottomSheet(
context: context,
builder: (context) => _FactorTotpFactorDialog(factor: factor),
);
}
if (!mounted) return;
Navigator.of(context).pop(true);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('authFactorAdd').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButtonHideUnderline(
child: DropdownButton2<int>(
hint: Text(
'Select Item',
style: TextStyle(
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
),
value: _factorType,
items: kFactorTypes.entries.map(
(ele) {
final contains = widget.currentlyHave.map((ele) => ele.type).contains(ele.key);
return DropdownMenuItem<int>(
enabled: !contains,
value: ele.key,
child: Text(
ele.value.$1.tr(),
style: const TextStyle(
fontSize: 14,
),
).opacity(contains ? 0.75 : 1),
);
},
).toList(),
onChanged: (val) => setState(() {
_factorType = val;
}),
buttonStyleData: ButtonStyleData(
height: 50,
padding: const EdgeInsets.only(left: 14, right: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: Theme.of(context).dividerColor,
),
),
),
),
),
],
),
actions: [
TextButton(
onPressed: _isBusy ? null : () => Navigator.of(context).pop(),
child: Text('dialogCancel').tr(),
),
TextButton(
onPressed: _isBusy ? null : () => _submit(),
child: Text('dialogConfirm').tr(),
),
],
);
}
}
class _FactorTotpFactorDialog extends StatelessWidget {
final SnAuthFactor factor;
const _FactorTotpFactorDialog({super.key, required this.factor});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Text(
'totpPostSetup',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge,
).tr().width(280),
),
const Gap(4),
Center(
child: Text(
'totpPostSetupDescription',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall,
).tr().width(280),
),
const Gap(16),
QrImageView(
padding: EdgeInsets.zero,
data: factor.config!['url'],
errorCorrectionLevel: QrErrorCorrectLevel.H,
version: QrVersions.auto,
size: 160,
gapless: true,
eyeStyle: QrEyeStyle(
eyeShape: QrEyeShape.circle,
color: Theme.of(context).colorScheme.onSurface,
),
dataModuleStyle: QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Theme.of(context).colorScheme.onSurface,
),
),
const Gap(16),
Center(
child: Text(
'totpNeverShare',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
).tr().bold().width(280),
),
],
),
);
}
}

View File

@ -7,6 +7,7 @@ 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/screens/account/factor_settings.dart';
import 'package:surface/types/auth.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
@ -14,11 +15,6 @@ import 'package:url_launcher/url_launcher_string.dart';
import '../../providers/websocket.dart';
final Map<int, (String label, IconData icon, bool isOtp)> _factorLabelMap = {
0: ('authFactorPassword'.tr(), Symbols.password, false),
1: ('authFactorEmail'.tr(), Symbols.email, true),
};
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@ -212,7 +208,9 @@ class _LoginCheckScreenState extends State<_LoginCheckScreen> {
controller: _passwordController,
obscureText: true,
autofillHints: [
(_factorLabelMap[widget.factor!.type]?.$3 ?? true) ? AutofillHints.password : AutofillHints.oneTimeCode
widget.factor!.type == 0
? AutofillHints.password
: AutofillHints.oneTimeCode
],
decoration: InputDecoration(
isDense: true,
@ -267,7 +265,8 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> {
bool _isBusy = false;
int? _factorPicked;
Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round());
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round());
void _performGetFactorCode() async {
if (_factorPicked == null) return;
@ -328,11 +327,11 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> {
),
),
secondary: Icon(
_factorLabelMap[x.type]?.$2 ?? Symbols.question_mark,
kFactorTypes[x.type]?.$3 ?? Symbols.question_mark,
),
title: Text(
_factorLabelMap[x.type]?.$1 ?? 'unknown'.tr(),
),
kFactorTypes[x.type]?.$1 ?? 'unknown',
).tr(),
enabled: !widget.ticket!.factorTrail.contains(x.id),
value: _factorPicked == x.id,
onChanged: (value) {
@ -408,11 +407,13 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
try {
final sn = context.read<SnNetworkProvider>();
final lookupResp = await sn.client.get('/cgi/id/users/lookup?probe=$username');
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'],
});
if (mounted) context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr());
if (mounted)
context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr());
} catch (err) {
if (mounted) context.showErrorDialog(err);
} finally {
@ -437,7 +438,8 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
widget.onTicket(result.ticket);
// Pull factors
final factorResp = await sn.client.get('/cgi/id/auth/factors', queryParameters: {
final factorResp =
await sn.client.get('/cgi/id/auth/factors', queryParameters: {
'ticketId': result.ticket!.id.toString(),
});
widget.onFactor(
@ -531,7 +533,10 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
'termAcceptNextWithAgree'.tr(),
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()),
color: Theme.of(context)
.colorScheme
.onSurface
.withAlpha((255 * 0.75).round()),
),
),
Material(

View File

@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/post/post_detail.dart';
@ -96,6 +97,8 @@ class _ExploreScreenState extends State<ExploreScreen> {
@override
Widget build(BuildContext context) {
final cfg = context.read<ConfigProvider>();
return AppScaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
@ -243,8 +246,10 @@ class _ExploreScreenState extends State<ExploreScreen> {
),
openColor: Colors.transparent,
openElevation: 0,
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(0.75),
transitionType: ContainerTransitionType.fade,
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
),
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),

View File

@ -51,7 +51,7 @@ class HomeScreen extends StatefulWidget {
}
class _HomeScreenState extends State<HomeScreen> {
static const List<HomeScreenDashEntry> kCards = [
late final List<HomeScreenDashEntry> kCards = [
HomeScreenDashEntry(
name: 'dashEntryRecommendation',
child: _HomeDashRecommendationPostWidget(),
@ -69,7 +69,7 @@ class _HomeScreenState extends State<HomeScreen> {
HomeScreenDashEntry(
name: 'dashEntryTodayNews',
child: _HomeDashTodayNews(),
cols: 2,
cols: MediaQuery.of(context).size.width >= 640 ? 3 : 2,
),
];
@ -293,7 +293,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
Text(
_article!.title,
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 18),
maxLines: 2,
maxLines: MediaQuery.of(context).size.width >= 640 ? 2 : 1,
overflow: TextOverflow.ellipsis,
),
Text(

View File

@ -175,54 +175,57 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
),
if (_articleFragment != null && _isReadingFromReader)
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
Builder(builder: (context) {
final htmlDescription = parse(_article!.description);
return Text(
htmlDescription.children.map((ele) => ele.text.trim()).join(),
style: Theme.of(context).textTheme.bodyMedium,
);
}),
Builder(builder: (context) {
final date = _article!.publishedAt ?? _article!.createdAt;
return Row(
spacing: 2,
children: [
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
],
).opacity(0.75);
}),
Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
const Divider(),
..._parseHtmlToWidgets(_articleFragment!.children),
const Divider(),
InkWell(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Reference from original website',
style: TextStyle(decoration: TextDecoration.underline),
),
const Gap(4),
Icon(Icons.launch, size: 16),
],
).opacity(0.85),
onTap: () {
launchUrlString(_article!.url);
},
),
Gap(MediaQuery.of(context).padding.bottom),
],
).padding(horizontal: 12, vertical: 16),
),
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
Builder(builder: (context) {
final htmlDescription = parse(_article!.description);
return Text(
htmlDescription.children.map((ele) => ele.text.trim()).join(),
style: Theme.of(context).textTheme.bodyMedium,
);
}),
Builder(builder: (context) {
final date = _article!.publishedAt ?? _article!.createdAt;
return Row(
spacing: 2,
children: [
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
],
).opacity(0.75);
}),
Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
const Divider(),
..._parseHtmlToWidgets(_articleFragment!.children),
const Divider(),
InkWell(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Reference from original website',
style: TextStyle(decoration: TextDecoration.underline),
),
const Gap(4),
Icon(Icons.launch, size: 16),
],
).opacity(0.85),
onTap: () {
launchUrlString(_article!.url);
},
),
Gap(MediaQuery.of(context).padding.bottom),
],
).padding(horizontal: 12, vertical: 16),
),
).center(),
)
else if (_article != null)
Expanded(

View File

@ -70,11 +70,16 @@ class _NewsScreenState extends State<NewsScreen> {
sliver: SliverAppBar(
leading: AutoAppBarLeading(),
title: Text('screenNews').tr(),
floating: true,
snap: true,
bottom: TabBar(
isScrollable: true,
tabs: [
Tab(child: Text('newsAllSources'.tr())),
for (final source in _sources!) Tab(child: Text(source.label)),
Tab(child: Text('newsAllSources'.tr()).textColor(Theme.of(context).appBarTheme.foregroundColor)),
for (final source in _sources!)
Tab(
child: Text(source.label).textColor(Theme.of(context).appBarTheme.foregroundColor),
),
],
),
),
@ -146,80 +151,87 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> {
return MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: _fetchArticles,
child: InfiniteList(
isLoading: _isBusy,
itemCount: _articles.length,
hasReachedMax: _totalCount != null && _articles.length >= _totalCount!,
onFetchData: () {
_fetchArticles();
},
itemBuilder: (context, index) {
final article = _articles[index];
child: Center(
child: Container(
constraints: BoxConstraints(maxWidth: 640),
child: RefreshIndicator(
onRefresh: _fetchArticles,
child: InfiniteList(
isLoading: _isBusy,
itemCount: _articles.length,
hasReachedMax: _totalCount != null && _articles.length >= _totalCount!,
onFetchData: () {
_fetchArticles();
},
itemBuilder: (context, index) {
final article = _articles[index];
final baseUri = Uri.parse(article.url);
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
final baseUri = Uri.parse(article.url);
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
final htmlDescription = parse(article.description);
final date = article.publishedAt ?? article.createdAt;
final htmlDescription = parse(article.description);
final date = article.publishedAt ?? article.createdAt;
return Card(
child: InkWell(
radius: 8,
onTap: () {
GoRouter.of(context).pushNamed(
'newsDetail',
pathParameters: {'hash': article.hash},
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
ClipRRect(
borderRadius: BorderRadius.only(
topRight: Radius.circular(8),
topLeft: Radius.circular(8),
),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child: AutoResizeUniversalImage(
article.thumbnail.startsWith('http') ? article.thumbnail : '$baseUrl/${article.thumbnail}',
return Card(
child: InkWell(
radius: 8,
onTap: () {
GoRouter.of(context).pushNamed(
'newsDetail',
pathParameters: {'hash': article.hash},
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
ClipRRect(
borderRadius: BorderRadius.only(
topRight: Radius.circular(8),
topLeft: Radius.circular(8),
),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child: AutoResizeUniversalImage(
article.thumbnail.startsWith('http')
? article.thumbnail
: '$baseUrl/${article.thumbnail}',
),
),
),
),
),
),
const Gap(16),
Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16),
const Gap(8),
Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
.textStyle(Theme.of(context).textTheme.bodyMedium!)
.padding(horizontal: 16),
const Gap(8),
Row(
spacing: 2,
children: [
Text(widget.allSources.where((x) => x.id == article.source).first.label)
.textStyle(Theme.of(context).textTheme.bodySmall!),
const Gap(16),
Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16),
const Gap(8),
Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
.textStyle(Theme.of(context).textTheme.bodyMedium!)
.padding(horizontal: 16),
const Gap(8),
Row(
spacing: 2,
children: [
Text(widget.allSources.where((x) => x.id == article.source).first.label)
.textStyle(Theme.of(context).textTheme.bodySmall!),
],
).opacity(0.75).padding(horizontal: 16),
Row(
spacing: 2,
children: [
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
],
).opacity(0.75).padding(horizontal: 16),
const Gap(16),
],
).opacity(0.75).padding(horizontal: 16),
Row(
spacing: 2,
children: [
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
],
).opacity(0.75).padding(horizontal: 16),
const Gap(16),
],
),
),
);
},
),
),
);
},
),
),
),
),
);

View File

@ -15,8 +15,8 @@ class SnAccount with _$SnAccount {
required DateTime? deletedAt,
required DateTime? confirmedAt,
required List<SnAccountContact>? contacts,
required String avatar,
required String banner,
@Default("") String avatar,
@Default("") String banner,
required String description,
required String name,
required String nick,

View File

@ -367,8 +367,8 @@ class _$SnAccountImpl extends _SnAccount {
required this.deletedAt,
required this.confirmedAt,
required final List<SnAccountContact>? contacts,
required this.avatar,
required this.banner,
this.avatar = "",
this.banner = "",
required this.description,
required this.name,
required this.nick,
@ -410,8 +410,10 @@ class _$SnAccountImpl extends _SnAccount {
}
@override
@JsonKey()
final String avatar;
@override
@JsonKey()
final String banner;
@override
final String description;
@ -540,8 +542,8 @@ abstract class _SnAccount extends SnAccount {
required final DateTime? deletedAt,
required final DateTime? confirmedAt,
required final List<SnAccountContact>? contacts,
required final String avatar,
required final String banner,
final String avatar,
final String banner,
required final String description,
required final String name,
required final String nick,

View File

@ -20,8 +20,8 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
contacts: (json['contacts'] as List<dynamic>?)
?.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>))
.toList(),
avatar: json['avatar'] as String,
banner: json['banner'] as String,
avatar: json['avatar'] as String? ?? "",
banner: json['banner'] as String? ?? "",
description: json['description'] as String,
name: json['name'] as String,
nick: json['nick'] as String,

View File

@ -196,68 +196,71 @@ class _AttachmentListState extends State<AttachmentList> {
);
}
return Container(
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
child: ScrollConfiguration(
behavior: _AttachmentListScrollBehavior(),
child: ListView.separated(
padding: widget.padding,
shrinkWrap: true,
itemCount: widget.data.length,
itemBuilder: (context, idx) {
return Container(
constraints: constraints.copyWith(maxWidth: widget.maxWidth),
child: AspectRatio(
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
child: GestureDetector(
onTap: () {
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
child: Stack(
fit: StackFit.expand,
children: [
Container(
decoration: BoxDecoration(
color: backgroundColor,
border: Border(
top: borderSide,
bottom: borderSide,
),
borderRadius: AttachmentList.kDefaultRadius,
return AspectRatio(
aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1,
child: Container(
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
child: ScrollConfiguration(
behavior: _AttachmentListScrollBehavior(),
child: ListView.separated(
padding: widget.padding,
shrinkWrap: true,
itemCount: widget.data.length,
itemBuilder: (context, idx) {
return Container(
constraints: constraints.copyWith(maxWidth: widget.maxWidth),
child: AspectRatio(
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
child: GestureDetector(
onTap: () {
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
initialIndex: idx,
heroTags: heroTags,
),
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(
data: widget.data[idx],
heroTag: heroTags[idx],
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
child: Stack(
fit: StackFit.expand,
children: [
Container(
decoration: BoxDecoration(
color: backgroundColor,
border: Border(
top: borderSide,
bottom: borderSide,
),
borderRadius: AttachmentList.kDefaultRadius,
),
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(
data: widget.data[idx],
heroTag: heroTags[idx],
),
),
),
),
Positioned(
right: 8,
bottom: 8,
child: Chip(
label: Text('${idx + 1}/${widget.data.length}'),
Positioned(
right: 8,
bottom: 8,
child: Chip(
label: Text('${idx + 1}/${widget.data.length}'),
),
),
),
],
],
),
),
),
),
);
},
separatorBuilder: (context, index) => const Gap(8),
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
);
},
separatorBuilder: (context, index) => const Gap(8),
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
),
),
),
);

View File

@ -5,10 +5,9 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:surface/providers/config.dart';
// Keep this import to make the web image render work
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:surface/providers/config.dart';
class UniversalImage extends StatelessWidget {
final String url;

View File

@ -22,14 +22,14 @@ PODS:
- Firebase/Messaging (11.6.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.6.0)
- firebase_analytics (11.4.0):
- firebase_analytics (11.4.1):
- Firebase/Analytics (= 11.6.0)
- firebase_core
- FlutterMacOS
- firebase_core (3.10.0):
- firebase_core (3.10.1):
- Firebase/CoreOnly (~> 11.6.0)
- FlutterMacOS
- firebase_messaging (15.2.0):
- firebase_messaging (15.2.1):
- Firebase/CoreOnly (~> 11.6.0)
- Firebase/Messaging (~> 11.6.0)
- firebase_core
@ -296,9 +296,9 @@ SPEC CHECKSUMS:
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
firebase_analytics: 5249f87da6fed852901581aab2602e0280ec2fdb
firebase_core: 6d9bb8b0ea817e8fe0d928177d42275b45fdba6f
firebase_messaging: ae8e88b586e4d50abc7cac5bacf74d21967fd226
firebase_analytics: 91efc58e8e37964469fdd59ad11ba36bc97e75d6
firebase_core: 75e003524565fb5bd80c9960bc5892e8475821cd
firebase_messaging: 082a385eb98b5bb843a566cb30404859c4bd6e25
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2

View File

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "27899c95f9e7ec06c8310e6e0eac967707714b9f1450c4a58fa00ca011a4a8ae"
sha256: e4f2a7ef31b0ab2c89d2bde35ef3e6e6aff1dce5e66069c6540b0e9cfe33ee6b
url: "https://pub.dev"
source: hosted
version: "1.3.49"
version: "1.3.50"
_macros:
dependency: transitive
description: dart
@ -338,10 +338,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev"
source: hosted
version: "2.3.7"
version: "2.3.8"
dart_webrtc:
dependency: "direct main"
description:
@ -418,10 +418,10 @@ packages:
dependency: "direct main"
description:
name: easy_localization
sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201
sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12"
url: "https://pub.dev"
source: hosted
version: "3.0.7"
version: "3.0.7+1"
easy_localization_loader:
dependency: "direct main"
description:
@ -538,34 +538,34 @@ packages:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "498c6cb8468e348a556709c745d92a52173ab3a9b906aa0593393f0787f201ea"
sha256: eac382bbcd5ae78c1d1ce5619d13f5a7424429f4bf55df9e3ad5110da34d1060
url: "https://pub.dev"
source: hosted
version: "11.4.0"
version: "11.4.1"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: ccbb350554e98afdb4b59852689292d194d31232a2647b5012a66622b3711df9
sha256: a34db46c367265c4c961626e4b128bfb7b7e50958e7add4c27ba103f5f81b9b0
url: "https://pub.dev"
source: hosted
version: "4.3.0"
version: "4.3.1"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "68e1f18fc16482c211c658e739c25f015b202a260d9ad8249c6d3d7963b8105f"
sha256: b6b4cef08e45e4c7d48476d9fc49fe9577081809a59026fe95b1a1b1eea165fa
url: "https://pub.dev"
source: hosted
version: "0.5.10+6"
version: "0.5.10+7"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "0307c1fde82e2b8b97e0be2dab93612aff9a72f31ebe9bfac66ed8b37ef7c568"
sha256: d851c1ca98fd5a4c07c747f8c65dacc2edd84a4d9ac055d32a5f0342529069f5
url: "https://pub.dev"
source: hosted
version: "3.10.0"
version: "3.10.1"
firebase_core_platform_interface:
dependency: transitive
description:
@ -586,26 +586,26 @@ packages:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "48a8a59197c1c5174060ba9aa1e0036e9b5a0d28a0cc22d19c1fcabc67fafe3c"
sha256: e20ea2a0ecf9b0971575ab3ab42a6e285a94e50092c555b090c1a588a81b4d54
url: "https://pub.dev"
source: hosted
version: "15.2.0"
version: "15.2.1"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: "9770a8e91f54296829dcaa61ce9b7c2f9ae9abbf99976dd3103a60470d5264dd"
sha256: c57a92b5ae1857ef4fe4ae2e73452b44d32e984e15ab8b53415ea1bb514bdabd
url: "https://pub.dev"
source: hosted
version: "4.6.0"
version: "4.6.1"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "329ca4ef45ec616abe6f1d5e58feed0934a50840a65aa327052354ad3c64ed77"
sha256: "83694a990d8525d6b01039240b97757298369622ca0253ad0ebcfed221bf8ee0"
url: "https://pub.dev"
source: hosted
version: "3.10.0"
version: "3.10.1"
fixnum:
dependency: transitive
description:
@ -830,10 +830,10 @@ packages:
dependency: "direct main"
description:
name: flutter_webrtc
sha256: "188401cc3275bc4f1f965babdff6cac612a4b46572f1e49f49db8af5361d5712"
sha256: "4c76069f724f79dc6e8739c8f16c2ee00ca30a1f8e3aa75c0e830f8183278f03"
url: "https://pub.dev"
source: hosted
version: "0.12.6"
version: "0.12.7"
freezed:
dependency: "direct dev"
description:
@ -878,18 +878,18 @@ packages:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d"
sha256: daf3ff5570f55396b2d2c9bf8136d7db3a8acf208ac0cef92a3ae2beb9a81550
url: "https://pub.dev"
source: hosted
version: "14.6.3"
version: "14.7.1"
google_fonts:
dependency: "direct main"
description:
@ -950,10 +950,10 @@ packages:
dependency: transitive
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.3.0"
http_multi_server:
dependency: transitive
description:
@ -1030,10 +1030,10 @@ packages:
dependency: transitive
description:
name: image_picker_macos
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
url: "https://pub.dev"
source: hosted
version: "0.2.1+1"
version: "0.2.1+2"
image_picker_platform_interface:
dependency: transitive
description:
@ -1710,18 +1710,18 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a
sha256: c59819dacc6669a1165d54d2735a9543f136f9b3cec94ca65cea6ab8dffc422e
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.4.0"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "138b7bbbc7f59c56236e426c37afb8f78cbc57b094ac64c440e0bb90e380a4f5"
sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.4.4"
shared_preferences_foundation:
dependency: transitive
description:
@ -2171,10 +2171,10 @@ packages:
dependency: "direct main"
description:
name: web_socket_channel
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
webrtc_interface:
dependency: transitive
description:
@ -2187,10 +2187,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29"
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
url: "https://pub.dev"
source: hosted
version: "5.10.0"
version: "5.10.1"
win32_registry:
dependency: transitive
description:

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.2.2+57
version: 2.2.2+58
environment:
sdk: ^3.5.4