Compare commits
	
		
			6 Commits
		
	
	
		
			2.2.2+59
			...
			b8dcdb2315
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b8dcdb2315 | |||
| b7b921f1f4 | |||
| 319d5c7d7f | |||
| 4b5b001739 | |||
| db8871a455 | |||
| 38dcaa6066 | 
| @@ -26,7 +26,7 @@ | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:exported="true" | ||||
|             android:launchMode="singleTask" | ||||
|             android:launchMode="singleInstance" | ||||
|             android:taskAffinity="" | ||||
|             android:theme="@style/LaunchTheme" | ||||
|             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" | ||||
|   | ||||
							
								
								
									
										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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										11
									
								
								api/WatchTower/Run Database Maintenance.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								api/WatchTower/Run Database Maintenance.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| meta { | ||||
|   name: Run Database Maintenance | ||||
|   type: http | ||||
|   seq: 1 | ||||
| } | ||||
|  | ||||
| post { | ||||
|   url: {{endpoint}}/wt/maintenance/database | ||||
|   body: none | ||||
|   auth: inherit | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| vars { | ||||
|   endpoint: https://api.sn.solsynth.dev | ||||
|   third_client_id: alphabot | ||||
|   atk: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3NvbHN5bnRoLmRldiIsInN1YiI6IjEiLCJhdWQiOlsic29sYXItbmV0d29yayJdLCJleHAiOjE3MzgwODE2OTYsIm5iZiI6MTczODA3ODY5NiwiaWF0IjoxNzM4MDc4Njk2LCJqdGkiOiI1Yzg2MTYxZC00MTZjLTQwNDYtOWFlNS04YWZhNGIyZjdlMTkiLCJzZWQiOiIyMTciLCJ0eXAiOiJhY2Nlc3MifQ.LdLZ6FLb8IqPI__U8sT6VyxK5S_ZgwVGGL-tk01tK9C19wnbFFThPDgga1pJu_VVVpGVxzvMyd-3tBotzLMB5LjhYbtPOJakN2oug1HAgJ8zfc1clORlpHlUVisaiQtl3ZkWtzxni8etZDhJpqHU65IGQG01TO6PZGLgxKkMel4gGeeKhHpg9Q4Eewr3Pbl_wJkHVChJ9IJPmgioc_CACE10nEHZgwptCbndUz3AbIDOG9qW-7ZoprtKoRwAcuUXANK277VvdBRhwQjqKBREqVeMXP_Rv37jDPYhWpfS6HtKoHElZOTJG-69S2Zc3HyHlPJAPlzLAjGPoxScky79Gg | ||||
| } | ||||
| vars:secret [ | ||||
|   atk, | ||||
|   third_client_tk | ||||
| ] | ||||
|   | ||||
| @@ -554,6 +554,9 @@ | ||||
|   "postImageShareAds": "Explore posts on the Solar Network", | ||||
|   "postShare": "Share", | ||||
|   "postShareImage": "Share via Image", | ||||
|   "postGetInsight": "Get Insight", | ||||
|   "postGetInsightTitle": "AI Insight", | ||||
|   "postGetInsightDescription": "AI may make mistakes, check important information.", | ||||
|   "appInitializing": "Initializing", | ||||
|   "poweredBy": "Powered by {}", | ||||
|   "shareIntent": "Share", | ||||
| @@ -600,5 +603,6 @@ | ||||
|   "walletCurrency": { | ||||
|     "one": "{} Source Point", | ||||
|     "other": "{} Source Points" | ||||
|   } | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI Thinking Process" | ||||
| } | ||||
|   | ||||
| @@ -552,6 +552,9 @@ | ||||
|   "postImageShareAds": "来 Solar Network 探索更多有趣帖子", | ||||
|   "postShare": "分享", | ||||
|   "postShareImage": "分享帖图", | ||||
|   "postGetInsight": "获取见解", | ||||
|   "postGetInsightTitle": "AI 见解", | ||||
|   "postGetInsightDescription": "AI 可能会出错,检查信息真实性。", | ||||
|   "appInitializing": "正在初始化", | ||||
|   "poweredBy": "由 {} 提供支持", | ||||
|   "shareIntent": "分享", | ||||
| @@ -598,5 +601,6 @@ | ||||
|   "walletCurrency": { | ||||
|     "one": "{} 源点", | ||||
|     "other": "{} 源点" | ||||
|   } | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI 思考过程" | ||||
| } | ||||
|   | ||||
| @@ -552,6 +552,9 @@ | ||||
|   "postImageShareAds": "來 Solar Network 探索更多有趣帖子", | ||||
|   "postShare": "分享", | ||||
|   "postShareImage": "分享帖圖", | ||||
|   "postGetInsight": "獲取見解", | ||||
|   "postGetInsightTitle": "AI 見解", | ||||
|   "postGetInsightDescription": "AI 可能會出錯,檢查信息真實性。", | ||||
|   "appInitializing": "正在初始化", | ||||
|   "poweredBy": "由 {} 提供支持", | ||||
|   "shareIntent": "分享", | ||||
| @@ -598,5 +601,6 @@ | ||||
|   "walletCurrency": { | ||||
|     "one": "{} 源點", | ||||
|     "other": "{} 源點" | ||||
|   } | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI 思考過程" | ||||
| } | ||||
|   | ||||
| @@ -552,6 +552,9 @@ | ||||
|   "postImageShareAds": "來 Solar Network 探索更多有趣帖子", | ||||
|   "postShare": "分享", | ||||
|   "postShareImage": "分享帖圖", | ||||
|   "postGetInsight": "獲取見解", | ||||
|   "postGetInsightTitle": "AI 見解", | ||||
|   "postGetInsightDescription": "AI 可能會出錯,檢查信息真實性。", | ||||
|   "appInitializing": "正在初始化", | ||||
|   "poweredBy": "由 {} 提供支持", | ||||
|   "shareIntent": "分享", | ||||
| @@ -598,5 +601,6 @@ | ||||
|   "walletCurrency": { | ||||
|     "one": "{} 源點", | ||||
|     "other": "{} 源點" | ||||
|   } | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI 思考過程" | ||||
| } | ||||
|   | ||||
| @@ -379,7 +379,7 @@ SPEC CHECKSUMS: | ||||
|   device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 | ||||
|   DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c | ||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||
|   file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 | ||||
|   file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 | ||||
|   file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 | ||||
|   Firebase: 374a441a91ead896215703a674d58cdb3e9d772b | ||||
|   firebase_analytics: 13ea4ad8a42c5060bad7e6694304dabb8b02fe7e | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
|   | ||||
| @@ -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,6 +1,7 @@ | ||||
| import 'dart:io'; | ||||
| import 'dart:math' as math; | ||||
|  | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:file_saver/file_saver.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| @@ -34,6 +35,7 @@ import 'package:surface/widgets/post/post_meta_editor.dart'; | ||||
| import 'package:surface/widgets/post/post_reaction.dart'; | ||||
| import 'package:surface/widgets/post/publisher_popover.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
| import 'package:xml/xml.dart'; | ||||
|  | ||||
| class PostItem extends StatelessWidget { | ||||
|   final SnPost data; | ||||
| @@ -817,6 +819,22 @@ class _PostContentHeader extends StatelessWidget { | ||||
|                 }, | ||||
|               ), | ||||
|               const PopupMenuDivider(), | ||||
|               PopupMenuItem( | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     const Icon(Symbols.book_4_spark), | ||||
|                     const Gap(16), | ||||
|                     Text('postGetInsight').tr(), | ||||
|                   ], | ||||
|                 ), | ||||
|                 onTap: () { | ||||
|                   showModalBottomSheet( | ||||
|                     context: context, | ||||
|                     builder: (context) => _PostGetInsightSheet(postId: data.id), | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|               const PopupMenuDivider(), | ||||
|               PopupMenuItem( | ||||
|                 onTap: onShare, | ||||
|                 child: Row( | ||||
| @@ -1181,3 +1199,96 @@ class _PostAbuseReportDialogState extends State<_PostAbuseReportDialog> { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _PostGetInsightSheet extends StatefulWidget { | ||||
|   final int postId; | ||||
|  | ||||
|   const _PostGetInsightSheet({required this.postId}); | ||||
|  | ||||
|   @override | ||||
|   State<_PostGetInsightSheet> createState() => _PostGetInsightSheetState(); | ||||
| } | ||||
|  | ||||
| class _PostGetInsightSheetState extends State<_PostGetInsightSheet> { | ||||
|   String? _response; | ||||
|   String? _thinkingProcess; | ||||
|  | ||||
|   Future<void> _fetchResponse() async { | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       final resp = await sn.client.get('/cgi/co/posts/${widget.postId}/insight', | ||||
|           options: Options( | ||||
|             sendTimeout: const Duration(minutes: 10), | ||||
|             receiveTimeout: const Duration(minutes: 10), | ||||
|           )); | ||||
|       final out = resp.data['response'] as String; | ||||
|       final document = XmlDocument.parse(out); | ||||
|       _thinkingProcess = document.getElement('think')?.innerText.trim(); | ||||
|       RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>'); | ||||
|       setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim()); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _fetchResponse(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Row( | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: [ | ||||
|             const Icon(Symbols.book_4_spark, size: 24), | ||||
|             const Gap(16), | ||||
|             Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(), | ||||
|           ], | ||||
|         ).padding(horizontal: 20, top: 16, bottom: 12), | ||||
|         const Gap(4), | ||||
|         Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20), | ||||
|         const Gap(4), | ||||
|         if (_response == null) | ||||
|           Expanded( | ||||
|             child: Center( | ||||
|               child: CircularProgressIndicator(), | ||||
|             ), | ||||
|           ) | ||||
|         else | ||||
|           Expanded( | ||||
|             child: SingleChildScrollView( | ||||
|               child: Column( | ||||
|                 children: [ | ||||
|                   if (_thinkingProcess != null && _thinkingProcess!.isNotEmpty) | ||||
|                     ExpansionTile( | ||||
|                       leading: const Icon(Symbols.info), | ||||
|                       title: Text('aiThinkingProcess'.tr()), | ||||
|                       tilePadding: const EdgeInsets.symmetric(horizontal: 20), | ||||
|                       collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|                       minTileHeight: 32, | ||||
|                       children: [ | ||||
|                         SelectableText( | ||||
|                           _thinkingProcess!, | ||||
|                           style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic), | ||||
|                         ).padding(horizontal: 20, vertical: 8), | ||||
|                       ], | ||||
|                     ).padding(vertical: 8), | ||||
|                   SelectionArea( | ||||
|                     child: MarkdownTextContent( | ||||
|                       content: _response!, | ||||
|                     ), | ||||
|                   ).padding(horizontal: 20, top: 8), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import Foundation | ||||
| import bitsdojo_window_macos | ||||
| import connectivity_plus | ||||
| import device_info_plus | ||||
| import file_picker | ||||
| import file_saver | ||||
| import file_selector_macos | ||||
| import firebase_analytics | ||||
| @@ -36,6 +37,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | ||||
|   BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin")) | ||||
|   ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) | ||||
|   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) | ||||
|   FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) | ||||
|   FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) | ||||
|   FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) | ||||
|   FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin")) | ||||
|   | ||||
| @@ -8,6 +8,8 @@ PODS: | ||||
|     - FlutterMacOS | ||||
|   - device_info_plus (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - file_picker (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - file_saver (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - file_selector_macos (0.0.1): | ||||
| @@ -185,6 +187,7 @@ DEPENDENCIES: | ||||
|   - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`) | ||||
|   - croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`) | ||||
|   - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) | ||||
|   - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) | ||||
|   - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) | ||||
|   - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) | ||||
|   - firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`) | ||||
| @@ -237,6 +240,8 @@ EXTERNAL SOURCES: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/croppy/macos | ||||
|   device_info_plus: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos | ||||
|   file_picker: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos | ||||
|   file_saver: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos | ||||
|   file_selector_macos: | ||||
| @@ -293,6 +298,7 @@ SPEC CHECKSUMS: | ||||
|   connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695 | ||||
|   croppy: 25a638bd7d05411d8c697f481568f261037694fc | ||||
|   device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215 | ||||
|   file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af | ||||
|   file_saver: 44e6fbf666677faf097302460e214e977fdd977b | ||||
|   file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d | ||||
|   Firebase: 374a441a91ead896215703a674d58cdb3e9d772b | ||||
|   | ||||
							
								
								
									
										34
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -362,10 +362,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: device_info_plus | ||||
|       sha256: b37d37c2f912ad4e8ec694187de87d05de2a3cb82b465ff1f65f65a2d05de544 | ||||
|       sha256: e3fc9a65820fef83035af8ee8c09004a719d5d1d54e6de978fcb0d84bbeb241a | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "11.2.1" | ||||
|     version: "11.2.2" | ||||
|   device_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -378,10 +378,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: dio | ||||
|       sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" | ||||
|       sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.7.0" | ||||
|     version: "5.8.0+1" | ||||
|   dio_smart_retry: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -394,10 +394,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: dio_web_adapter | ||||
|       sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" | ||||
|       sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|     version: "2.1.0" | ||||
|   dismissible_page: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -490,10 +490,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: file_picker | ||||
|       sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204 | ||||
|       sha256: c9943dd7d702ab4199d199bc151a2d79c86db031a02ad84566dab58c494d2adc | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.1.7" | ||||
|     version: "8.3.1" | ||||
|   file_saver: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -886,10 +886,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: go_router | ||||
|       sha256: daf3ff5570f55396b2d2c9bf8136d7db3a8acf208ac0cef92a3ae2beb9a81550 | ||||
|       sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "14.7.1" | ||||
|     version: "14.7.2" | ||||
|   google_fonts: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1334,10 +1334,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: package_info_plus | ||||
|       sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" | ||||
|       sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.1.3" | ||||
|     version: "8.1.4" | ||||
|   package_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1710,10 +1710,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: shared_preferences | ||||
|       sha256: c59819dacc6669a1165d54d2735a9543f136f9b3cec94ca65cea6ab8dffc422e | ||||
|       sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.0" | ||||
|     version: "2.5.1" | ||||
|   shared_preferences_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2059,10 +2059,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vector_graphics | ||||
|       sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" | ||||
|       sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.15" | ||||
|     version: "1.1.16" | ||||
|   vector_graphics_codec: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2216,7 +2216,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "1.1.0" | ||||
|   xml: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: xml | ||||
|       sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 | ||||
|   | ||||
| @@ -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+59 | ||||
| version: 2.2.2+60 | ||||
|  | ||||
| environment: | ||||
|   sdk: ^3.5.4 | ||||
| @@ -117,6 +117,7 @@ dependencies: | ||||
|   cached_network_image: ^3.4.1 | ||||
|   flutter_inappwebview: ^6.1.5 | ||||
|   html: ^0.15.5 | ||||
|   xml: ^6.5.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user