Compare commits
3 Commits
4b5b001739
...
b8dcdb2315
Author | SHA1 | Date | |
---|---|---|---|
b8dcdb2315 | |||
b7b921f1f4 | |||
319d5c7d7f |
26
api/Passport/Developer Notify One User.bru
Normal file
26
api/Passport/Developer Notify One User.bru
Normal file
@ -0,0 +1,26 @@
|
||||
meta {
|
||||
name: Developer Notify One User
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{endpoint}}/cgi/id/dev/notify/1
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"client_id": "{{third_client_id}}",
|
||||
"client_secret":"{{third_client_tk}}",
|
||||
"type": "general",
|
||||
"subject": "测试",
|
||||
"subtitle": "Alphabot です",
|
||||
"content": "全新通知动画",
|
||||
"metadata": {
|
||||
"image": "D2EDbcrsTugs3xk5"
|
||||
},
|
||||
"priority": 10
|
||||
}
|
||||
}
|
@ -71,22 +71,29 @@ class NotificationProvider extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
int showingCount = 0;
|
||||
List<SnNotification> notifications = List.empty(growable: true);
|
||||
|
||||
void listen() {
|
||||
_ws.stream.stream.listen((event) {
|
||||
if (event.method == 'notifications.new') {
|
||||
final notification = SnNotification.fromJson(event.payload!);
|
||||
if (showingCount < 0) showingCount = 0;
|
||||
showingCount++;
|
||||
notifications.add(notification);
|
||||
Future.delayed(const Duration(seconds: 3), () {
|
||||
if (showingCount >= 0) showingCount--;
|
||||
notifyListeners();
|
||||
});
|
||||
notifyListeners();
|
||||
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
||||
if (doHaptic) HapticFeedback.lightImpact();
|
||||
if (doHaptic) HapticFeedback.mediumImpact();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void clear() {
|
||||
notifications.clear();
|
||||
showingCount = 0;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,19 @@ class AccountScreen extends StatelessWidget {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text("screenAccount").tr(),
|
||||
title: Text(
|
||||
"screenAccount",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(1, 1),
|
||||
blurRadius: 5.0,
|
||||
color: Color.fromARGB(255, 0, 0, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
).tr(),
|
||||
flexibleSpace: ua.user != null && ua.user!.banner.isNotEmpty
|
||||
? Stack(
|
||||
fit: StackFit.expand,
|
||||
|
@ -288,6 +288,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
|
@ -21,6 +21,16 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
import '../providers/userinfo.dart';
|
||||
import '../widgets/unauthorized_hint.dart';
|
||||
|
||||
const Map<String, IconData> kNotificationTopicIcons = {
|
||||
'general': Symbols.notifications,
|
||||
'passport.security.alert': Symbols.gpp_maybe,
|
||||
'passport.security.otp': Symbols.password,
|
||||
'interactive.subscription': Symbols.subscriptions,
|
||||
'interactive.feedback': Symbols.add_reaction,
|
||||
'messaging.callStart': Symbols.call_received,
|
||||
'wallet.transaction.new': Symbols.receipt,
|
||||
};
|
||||
|
||||
class NotificationScreen extends StatefulWidget {
|
||||
const NotificationScreen({super.key});
|
||||
|
||||
@ -36,15 +46,6 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
final List<SnNotification> _notifications = List.empty(growable: true);
|
||||
int? _totalCount;
|
||||
|
||||
static const Map<String, IconData> kNotificationTopicIcons = {
|
||||
'passport.security.alert': Symbols.gpp_maybe,
|
||||
'passport.security.otp': Symbols.password,
|
||||
'interactive.subscription': Symbols.subscriptions,
|
||||
'interactive.feedback': Symbols.add_reaction,
|
||||
'messaging.callStart': Symbols.call_received,
|
||||
'wallet.transaction.new': Symbols.receipt,
|
||||
};
|
||||
|
||||
Future<void> _fetchNotifications() async {
|
||||
final ua = context.read<UserProvider>();
|
||||
if (!ua.isAuthorized) return;
|
||||
|
@ -4,6 +4,7 @@ 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/config.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/providers/websocket.dart';
|
||||
|
||||
@ -13,6 +14,9 @@ class ConnectionIndicator extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ws = context.watch<WebSocketProvider>();
|
||||
final cfg = context.watch<ConfigProvider>();
|
||||
|
||||
final marginLeft = cfg.drawerIsCollapsed ? 0.0 : cfg.drawerIsExpanded ? 304.0 : 80.0;
|
||||
|
||||
return ListenableBuilder(
|
||||
listenable: ws,
|
||||
@ -22,45 +26,50 @@ class ConnectionIndicator extends StatelessWidget {
|
||||
|
||||
return IgnorePointer(
|
||||
ignoring: !show,
|
||||
child: GestureDetector(
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: ua.isAuthorized
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (ws.isBusy)
|
||||
Text('serverConnecting').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
||||
else if (!ws.isConnected)
|
||||
Text('serverDisconnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
||||
else
|
||||
Text('serverConnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer),
|
||||
const Gap(8),
|
||||
if (ws.isBusy)
|
||||
const CircularProgressIndicator(strokeWidth: 2.5)
|
||||
.width(12)
|
||||
.height(12)
|
||||
.padding(horizontal: 4, right: 4)
|
||||
else if (!ws.isConnected)
|
||||
const Icon(Symbols.power_off, size: 18)
|
||||
else
|
||||
const Icon(Symbols.power, size: 18),
|
||||
],
|
||||
).padding(horizontal: 8, vertical: 4)
|
||||
: const SizedBox.shrink(),
|
||||
).opacity(show ? 1 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300),
|
||||
Curves.easeInOut,
|
||||
),
|
||||
onTap: () {
|
||||
if (!ws.isConnected && !ws.isBusy) {
|
||||
ws.connect();
|
||||
}
|
||||
},
|
||||
),
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: ua.isAuthorized
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (ws.isBusy)
|
||||
Text('serverConnecting').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
||||
else if (!ws.isConnected)
|
||||
Text('serverDisconnected')
|
||||
.tr()
|
||||
.textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
||||
else
|
||||
Text('serverConnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer),
|
||||
const Gap(8),
|
||||
if (ws.isBusy)
|
||||
const CircularProgressIndicator(strokeWidth: 2.5)
|
||||
.width(12)
|
||||
.height(12)
|
||||
.padding(horizontal: 4, right: 4)
|
||||
else if (!ws.isConnected)
|
||||
const Icon(Symbols.power_off, size: 18)
|
||||
else
|
||||
const Icon(Symbols.power, size: 18),
|
||||
],
|
||||
).padding(horizontal: 8, vertical: 4)
|
||||
: const SizedBox.shrink(),
|
||||
).opacity(show ? 1 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300),
|
||||
Curves.easeInOut,
|
||||
),
|
||||
onTap: () {
|
||||
if (!ws.isConnected && !ws.isBusy) {
|
||||
ws.connect();
|
||||
}
|
||||
},
|
||||
),
|
||||
).padding(left: marginLeft),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -28,7 +28,7 @@ class ContextMenuArea extends StatelessWidget {
|
||||
// Leave padding for side navigation
|
||||
mousePosition = cfg.drawerIsExpanded
|
||||
? mousePosition.copyWith(dx: mousePosition.dx - 304 * 2)
|
||||
: mousePosition.copyWith(dx: mousePosition.dx - 72 * 2);
|
||||
: mousePosition.copyWith(dx: mousePosition.dx - 80 * 2);
|
||||
}
|
||||
},
|
||||
child: GestureDetector(
|
||||
|
@ -20,6 +20,7 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
final bool isAutoWarp;
|
||||
final bool isEnlargeSticker;
|
||||
final TextScaler? textScaler;
|
||||
final Color? textColor;
|
||||
final List<SnAttachment?>? attachments;
|
||||
|
||||
const MarkdownTextContent({
|
||||
@ -28,6 +29,7 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
this.isAutoWarp = false,
|
||||
this.isEnlargeSticker = false,
|
||||
this.textScaler,
|
||||
this.textColor,
|
||||
this.attachments,
|
||||
});
|
||||
|
||||
@ -42,6 +44,7 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
Theme.of(context),
|
||||
).copyWith(
|
||||
textScaler: textScaler,
|
||||
p: textColor != null ? Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor) : null,
|
||||
blockquote: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
|
@ -31,34 +31,37 @@ class _AppRailNavigationState extends State<AppRailNavigation> {
|
||||
builder: (context, _) {
|
||||
final destinations = nav.destinations.where((ele) => ele.isPinned).toList();
|
||||
|
||||
return NavigationRail(
|
||||
selectedIndex:
|
||||
nav.currentIndex != null && nav.currentIndex! < nav.pinnedDestinationCount ? nav.currentIndex : null,
|
||||
destinations: [
|
||||
...destinations.where((ele) => ele.isPinned).map((ele) {
|
||||
return NavigationRailDestination(
|
||||
icon: ele.icon,
|
||||
label: Text(ele.label).tr(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
trailing: Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: StyledWidget(
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.menu),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
),
|
||||
).padding(bottom: 16),
|
||||
return SizedBox(
|
||||
width: 80,
|
||||
child: NavigationRail(
|
||||
selectedIndex:
|
||||
nav.currentIndex != null && nav.currentIndex! < nav.pinnedDestinationCount ? nav.currentIndex : null,
|
||||
destinations: [
|
||||
...destinations.where((ele) => ele.isPinned).map((ele) {
|
||||
return NavigationRailDestination(
|
||||
icon: ele.icon,
|
||||
label: Text(ele.label).tr(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
trailing: Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: StyledWidget(
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.menu),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
),
|
||||
).padding(bottom: 16),
|
||||
),
|
||||
),
|
||||
onDestinationSelected: (idx) {
|
||||
nav.setIndex(idx);
|
||||
GoRouter.of(context).goNamed(destinations[idx].screen);
|
||||
},
|
||||
),
|
||||
onDestinationSelected: (idx) {
|
||||
nav.setIndex(idx);
|
||||
GoRouter.of(context).goNamed(destinations[idx].screen);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -140,6 +140,7 @@ class AppRootScaffold extends StatelessWidget {
|
||||
);
|
||||
|
||||
final safeTop = MediaQuery.of(context).padding.top;
|
||||
final safeBottom = MediaQuery.of(context).padding.bottom;
|
||||
|
||||
return Scaffold(
|
||||
key: globalRootScaffoldKey,
|
||||
@ -191,7 +192,10 @@ class AppRootScaffold extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
Positioned(top: safeTop > 0 ? safeTop : 16, right: 8, child: NotifyIndicator()),
|
||||
Positioned(top: safeTop > 0 ? safeTop : 16, left: 8, child: ConnectionIndicator()),
|
||||
if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE))
|
||||
Positioned(bottom: safeBottom > 0 ? safeBottom : 16, left: 0, right: 0, child: ConnectionIndicator())
|
||||
else
|
||||
Positioned(top: safeTop > 0 ? safeTop : 16, left: 0, right: 0, child: ConnectionIndicator()),
|
||||
],
|
||||
),
|
||||
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
|
||||
|
@ -1,60 +1,184 @@
|
||||
import 'dart:math' show min;
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/notification.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/screens/notification.dart';
|
||||
import 'package:surface/types/notification.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
|
||||
class NotifyIndicator extends StatelessWidget {
|
||||
import 'markdown_content.dart';
|
||||
|
||||
class NotifyIndicator extends StatefulWidget {
|
||||
const NotifyIndicator({super.key});
|
||||
|
||||
@override
|
||||
State<NotifyIndicator> createState() => _NotifyIndicatorState();
|
||||
}
|
||||
|
||||
class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
|
||||
void _markOneAsRead(SnNotification notification) async {
|
||||
final ua = context.read<UserProvider>();
|
||||
if (!ua.isAuthorized) return;
|
||||
|
||||
if (notification.id == 0) return;
|
||||
if (notification.readAt != null) return;
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.put('/cgi/id/notifications/read/${notification.id}');
|
||||
|
||||
if (!mounted) return;
|
||||
context.showSnackbar(
|
||||
'notificationMarkOneReadPrompt'.tr(args: ['#${notification.id}']),
|
||||
);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ua = context.read<UserProvider>();
|
||||
final nty = context.watch<NotificationProvider>();
|
||||
|
||||
final show = nty.notifications.isNotEmpty && ua.isAuthorized;
|
||||
final isMobile = ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE);
|
||||
|
||||
final show = nty.showingCount > 0 && ua.isAuthorized;
|
||||
|
||||
if (show) {
|
||||
_animationController.animateTo(1);
|
||||
} else {
|
||||
_animationController.animateTo(0);
|
||||
}
|
||||
|
||||
return ListenableBuilder(
|
||||
listenable: nty,
|
||||
builder: (context, _) {
|
||||
final current = nty.notifications.lastOrNull;
|
||||
|
||||
return IgnorePointer(
|
||||
ignoring: !show,
|
||||
child: GestureDetector(
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: ua.isAuthorized
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
nty.notifications.lastOrNull?.title ??
|
||||
'notificationUnreadCount'.plural(nty.notifications.length),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (nty.notifications.lastOrNull?.body != null)
|
||||
Text(
|
||||
nty.notifications.lastOrNull!.body,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).padding(left: 4),
|
||||
const Gap(8),
|
||||
const Icon(Symbols.notifications_unread, size: 18),
|
||||
],
|
||||
).padding(horizontal: 8, vertical: 4)
|
||||
: const SizedBox.shrink(),
|
||||
).opacity(show ? 1 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300),
|
||||
Curves.easeInOut,
|
||||
child: Animate(
|
||||
autoPlay: false,
|
||||
controller: _animationController,
|
||||
effects: [
|
||||
SlideEffect(
|
||||
begin: isMobile ? Offset(0, -1) : Offset(1, 0),
|
||||
end: Offset(0, 0),
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.fastEaseInToSlowEaseOut,
|
||||
),
|
||||
FadeEffect(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
width: double.infinity,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: isMobile ? MediaQuery.of(context).size.width - 16 : 360,
|
||||
),
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (current?.metadata['avatar'] != null)
|
||||
CircleAvatar(
|
||||
radius: 14,
|
||||
backgroundImage: UniversalImage.provider(
|
||||
sn.getAttachmentUrl(current!.metadata['avatar']),
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(kNotificationTopicIcons[current?.topic] ?? Symbols.notifications),
|
||||
const Gap(16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
current?.title ?? 'Notification',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (current?.subtitle?.isNotEmpty ?? false)
|
||||
Text(
|
||||
current!.subtitle!,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
MarkdownTextContent(
|
||||
content: current?.body ?? '',
|
||||
isAutoWarp: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(DateFormat('HH:mm').format(current?.createdAt.toLocal() ?? DateTime.now()))
|
||||
.fontSize(12)
|
||||
.padding(right: 2),
|
||||
const Gap(6),
|
||||
if (current?.metadata['image'] != null)
|
||||
SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(current?.metadata['image']),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
nty.clear();
|
||||
if (current != null) {
|
||||
_markOneAsRead(current);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
|
@ -61,7 +61,7 @@ class _PostReactionPopupState extends State<PostReactionPopup> {
|
||||
);
|
||||
}
|
||||
}
|
||||
HapticFeedback.mediumImpact();
|
||||
HapticFeedback.heavyImpact();
|
||||
} catch (err) {
|
||||
// ignore: use_build_context_synchronously
|
||||
if (context.mounted) context.showErrorDialog(err);
|
||||
|
Loading…
x
Reference in New Issue
Block a user