Compare commits
	
		
			2 Commits
		
	
	
		
			9fa666d0b8
			...
			3a2894b533
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3a2894b533 | |||
| 0d96a6f9ac | 
| @@ -1,6 +1,9 @@ | |||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <uses-permission android:name="android.permission.INTERNET" /> | ||||||
|  |     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||||
|  |  | ||||||
|     <application |     <application | ||||||
|         android:label="solian" |         android:label="Solian" | ||||||
|         android:name="${applicationName}" |         android:name="${applicationName}" | ||||||
|         android:icon="@mipmap/launcher_icon"> |         android:icon="@mipmap/launcher_icon"> | ||||||
|         <activity |         <activity | ||||||
| @@ -24,6 +27,7 @@ | |||||||
|                 <category android:name="android.intent.category.LAUNCHER"/> |                 <category android:name="android.intent.category.LAUNCHER"/> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
|  |  | ||||||
|         <!-- Don't delete the meta-data below. |         <!-- Don't delete the meta-data below. | ||||||
|              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> |              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> | ||||||
|         <meta-data |         <meta-data | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								assets/icon-macos.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 90 KiB | 
| @@ -49,6 +49,8 @@ | |||||||
| 		<string>UIInterfaceOrientationLandscapeLeft</string> | 		<string>UIInterfaceOrientationLandscapeLeft</string> | ||||||
| 		<string>UIInterfaceOrientationLandscapeRight</string> | 		<string>UIInterfaceOrientationLandscapeRight</string> | ||||||
| 	</array> | 	</array> | ||||||
|  | 	<key>ITSAppUsesNonExemptEncryption</key> | ||||||
|  | 	<false/> | ||||||
| 	<key>UISupportedInterfaceOrientations~ipad</key> | 	<key>UISupportedInterfaceOrientations~ipad</key> | ||||||
| 	<array> | 	<array> | ||||||
| 		<string>UIInterfaceOrientationPortrait</string> | 		<string>UIInterfaceOrientationPortrait</string> | ||||||
|   | |||||||
| @@ -26,6 +26,10 @@ | |||||||
|   "report": "Report", |   "report": "Report", | ||||||
|   "reply": "Reply", |   "reply": "Reply", | ||||||
|   "settings": "Settings", |   "settings": "Settings", | ||||||
|  |   "notification": "Notification", | ||||||
|  |   "notifyDone": "You're done!", | ||||||
|  |   "notifyDoneCaption": "There are no notifications unread for you.", | ||||||
|  |   "notifyListHint": "Pull to refresh, swipe to dismiss", | ||||||
|   "reaction": "Reaction", |   "reaction": "Reaction", | ||||||
|   "reactVerb": "React", |   "reactVerb": "React", | ||||||
|   "post": "Post", |   "post": "Post", | ||||||
|   | |||||||
| @@ -26,6 +26,10 @@ | |||||||
|   "report": "举报", |   "report": "举报", | ||||||
|   "reply": "回复", |   "reply": "回复", | ||||||
|   "settings": "设置", |   "settings": "设置", | ||||||
|  |   "notification": "通知", | ||||||
|  |   "notifyDone": "所有通知已读!", | ||||||
|  |   "notifyDoneCaption": "这里没有什么东西可以给你看的了~", | ||||||
|  |   "notifyListHint": "下拉以刷新,左滑来已读", | ||||||
|   "reaction": "反应", |   "reaction": "反应", | ||||||
|   "reactVerb": "作出反应", |   "reactVerb": "作出反应", | ||||||
|   "post": "帖子", |   "post": "帖子", | ||||||
| @@ -49,6 +53,13 @@ | |||||||
|   "chatNew": "新聊天", |   "chatNew": "新聊天", | ||||||
|   "chatNewCreate": "新建频道", |   "chatNewCreate": "新建频道", | ||||||
|   "chatNewJoin": "加入已有频道", |   "chatNewJoin": "加入已有频道", | ||||||
|  |   "chatChannelUsage": "频道", | ||||||
|  |   "chatChannelUsageCaption": "频道是一个地方供你聊天,跟一个人,或者一堆人", | ||||||
|  |   "chatChannelOrganize": "组织频道", | ||||||
|  |   "chatChannelEditNotify": "你正在编辑一个已经存在的频道……", | ||||||
|  |   "chatChannelAliasLabel": "频道别名", | ||||||
|  |   "chatChannelNameLabel": "频道名称", | ||||||
|  |   "chatChannelDescriptionLabel": "频道简介", | ||||||
|   "chatMessagePlaceholder": "发条消息……", |   "chatMessagePlaceholder": "发条消息……", | ||||||
|   "chatMessageEditNotify": "你正在编辑信息中……", |   "chatMessageEditNotify": "你正在编辑信息中……", | ||||||
|   "chatMessageReplyNotify": "你正在回复消息中……", |   "chatMessageReplyNotify": "你正在回复消息中……", | ||||||
|   | |||||||
| @@ -3,10 +3,12 @@ import 'package:provider/provider.dart'; | |||||||
| import 'package:solian/providers/auth.dart'; | import 'package:solian/providers/auth.dart'; | ||||||
| import 'package:solian/providers/chat.dart'; | import 'package:solian/providers/chat.dart'; | ||||||
| import 'package:solian/providers/navigation.dart'; | import 'package:solian/providers/navigation.dart'; | ||||||
|  | import 'package:solian/providers/notify.dart'; | ||||||
| import 'package:solian/router.dart'; | import 'package:solian/router.dart'; | ||||||
| import 'package:solian/utils/timeago.dart'; | import 'package:solian/utils/timeago.dart'; | ||||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||||
| import 'package:solian/utils/video_player.dart'; | import 'package:solian/utils/video_player.dart'; | ||||||
|  | import 'package:solian/widgets/notification_notifier.dart'; | ||||||
|  |  | ||||||
| void main() { | void main() { | ||||||
|   initVideo(); |   initVideo(); | ||||||
| @@ -39,8 +41,9 @@ class SolianApp extends StatelessWidget { | |||||||
|                   Provider(create: (_) => NavigationProvider()), |                   Provider(create: (_) => NavigationProvider()), | ||||||
|                   Provider(create: (_) => AuthProvider()), |                   Provider(create: (_) => AuthProvider()), | ||||||
|                   Provider(create: (_) => ChatProvider()), |                   Provider(create: (_) => ChatProvider()), | ||||||
|  |                   ChangeNotifierProvider(create: (_) => NotifyProvider()), | ||||||
|                 ], |                 ], | ||||||
|                 child: child, |                 child: NotificationNotifier(child: child ?? Container()), | ||||||
|               ); |               ); | ||||||
|             }) |             }) | ||||||
|           ], |           ], | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ class Notification { | |||||||
|   String content; |   String content; | ||||||
|   List<Link>? links; |   List<Link>? links; | ||||||
|   bool isImportant; |   bool isImportant; | ||||||
|  |   bool isRealtime; | ||||||
|   DateTime? readAt; |   DateTime? readAt; | ||||||
|   int senderId; |   int senderId; | ||||||
|   int recipientId; |   int recipientId; | ||||||
| @@ -20,6 +21,7 @@ class Notification { | |||||||
|     required this.content, |     required this.content, | ||||||
|     this.links, |     this.links, | ||||||
|     required this.isImportant, |     required this.isImportant, | ||||||
|  |     required this.isRealtime, | ||||||
|     this.readAt, |     this.readAt, | ||||||
|     required this.senderId, |     required this.senderId, | ||||||
|     required this.recipientId, |     required this.recipientId, | ||||||
| @@ -32,10 +34,9 @@ class Notification { | |||||||
|         deletedAt: json["deleted_at"], |         deletedAt: json["deleted_at"], | ||||||
|         subject: json["subject"], |         subject: json["subject"], | ||||||
|         content: json["content"], |         content: json["content"], | ||||||
|         links: json["links"] != null |         links: json["links"] != null ? List<Link>.from(json["links"].map((x) => Link.fromJson(x))) : List.empty(), | ||||||
|             ? List<Link>.from(json["links"].map((x) => Link.fromJson(x))) |  | ||||||
|             : List.empty(), |  | ||||||
|         isImportant: json["is_important"], |         isImportant: json["is_important"], | ||||||
|  |         isRealtime: json["is_realtime"], | ||||||
|         readAt: json["read_at"], |         readAt: json["read_at"], | ||||||
|         senderId: json["sender_id"], |         senderId: json["sender_id"], | ||||||
|         recipientId: json["recipient_id"], |         recipientId: json["recipient_id"], | ||||||
| @@ -48,10 +49,9 @@ class Notification { | |||||||
|         "deleted_at": deletedAt, |         "deleted_at": deletedAt, | ||||||
|         "subject": subject, |         "subject": subject, | ||||||
|         "content": content, |         "content": content, | ||||||
|         "links": links != null |         "links": links != null ? List<dynamic>.from(links!.map((x) => x.toJson())) : List.empty(), | ||||||
|             ? List<dynamic>.from(links!.map((x) => x.toJson())) |  | ||||||
|             : List.empty(), |  | ||||||
|         "is_important": isImportant, |         "is_important": isImportant, | ||||||
|  |         "is_realtime": isRealtime, | ||||||
|         "read_at": readAt, |         "read_at": readAt, | ||||||
|         "sender_id": senderId, |         "sender_id": senderId, | ||||||
|         "recipient_id": recipientId, |         "recipient_id": recipientId, | ||||||
|   | |||||||
							
								
								
									
										56
									
								
								lib/providers/notify.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,56 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:solian/models/pagination.dart'; | ||||||
|  | import 'package:solian/providers/auth.dart'; | ||||||
|  | import 'package:solian/utils/service_url.dart'; | ||||||
|  | import 'package:solian/models/notification.dart' as model; | ||||||
|  | import 'package:web_socket_channel/web_socket_channel.dart'; | ||||||
|  |  | ||||||
|  | class NotifyProvider extends ChangeNotifier { | ||||||
|  |   bool isOpened = false; | ||||||
|  |  | ||||||
|  |   List<model.Notification> notifications = List.empty(growable: true); | ||||||
|  |  | ||||||
|  |   Future<void> fetch(AuthProvider auth) async { | ||||||
|  |     if (!await auth.isAuthorized()) return; | ||||||
|  |  | ||||||
|  |     var uri = getRequestUri('passport', '/api/notifications?skip=0&take=25'); | ||||||
|  |     var res = await auth.client!.get(uri); | ||||||
|  |     if (res.statusCode == 200) { | ||||||
|  |       final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes))); | ||||||
|  |       notifications = result.data?.map((x) => model.Notification.fromJson(x)).toList() ?? List.empty(growable: true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     notifyListeners(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<WebSocketChannel?> connect(AuthProvider auth) async { | ||||||
|  |     if (auth.client == null) await auth.pickClient(); | ||||||
|  |     if (!await auth.isAuthorized()) return null; | ||||||
|  |  | ||||||
|  |     await auth.refreshToken(); | ||||||
|  |  | ||||||
|  |     var ori = getRequestUri('passport', '/api/notifications/listen'); | ||||||
|  |     var uri = Uri( | ||||||
|  |       scheme: ori.scheme.replaceFirst('http', 'ws'), | ||||||
|  |       host: ori.host, | ||||||
|  |       path: ori.path, | ||||||
|  |       queryParameters: {'tk': Uri.encodeComponent(auth.client!.credentials.accessToken)}, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final channel = WebSocketChannel.connect(uri); | ||||||
|  |     await channel.ready; | ||||||
|  |  | ||||||
|  |     return channel; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void onRemoteMessage(model.Notification item) { | ||||||
|  |     notifications.add(item); | ||||||
|  |     notifyListeners(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void clearNonRealtime() { | ||||||
|  |     notifications = notifications.where((x) => !x.isRealtime).toList(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -5,6 +5,7 @@ import 'package:solian/screens/account.dart'; | |||||||
| import 'package:solian/screens/chat/chat.dart'; | import 'package:solian/screens/chat/chat.dart'; | ||||||
| import 'package:solian/screens/chat/index.dart'; | import 'package:solian/screens/chat/index.dart'; | ||||||
| import 'package:solian/screens/explore.dart'; | import 'package:solian/screens/explore.dart'; | ||||||
|  | import 'package:solian/screens/notification.dart'; | ||||||
| import 'package:solian/screens/posts/comment_editor.dart'; | import 'package:solian/screens/posts/comment_editor.dart'; | ||||||
| import 'package:solian/screens/posts/moment_editor.dart'; | import 'package:solian/screens/posts/moment_editor.dart'; | ||||||
| import 'package:solian/screens/posts/screen.dart'; | import 'package:solian/screens/posts/screen.dart'; | ||||||
| @@ -59,6 +60,11 @@ final router = GoRouter( | |||||||
|         dataset: state.pathParameters['dataset'] as String, |         dataset: state.pathParameters['dataset'] as String, | ||||||
|       ), |       ), | ||||||
|     ), |     ), | ||||||
|  |     GoRoute( | ||||||
|  |       path: '/notification', | ||||||
|  |       name: 'notification', | ||||||
|  |       builder: (context, state) => const NotificationScreen(), | ||||||
|  |     ), | ||||||
|     GoRoute( |     GoRoute( | ||||||
|       path: '/auth/sign-in', |       path: '/auth/sign-in', | ||||||
|       name: 'auth.sign-in', |       name: 'auth.sign-in', | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | |||||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||||
| import 'package:http/http.dart' as http; | import 'package:http/http.dart' as http; | ||||||
| import 'package:solian/widgets/indent_wrapper.dart'; | import 'package:solian/widgets/indent_wrapper.dart'; | ||||||
|  | import 'package:solian/widgets/notification_notifier.dart'; | ||||||
| import 'package:solian/widgets/posts/item.dart'; | import 'package:solian/widgets/posts/item.dart'; | ||||||
|  |  | ||||||
| class ExploreScreen extends StatefulWidget { | class ExploreScreen extends StatefulWidget { | ||||||
| @@ -76,6 +77,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|       ), |       ), | ||||||
|  |       appBarActions: const [NotificationButton()], | ||||||
|       title: AppLocalizations.of(context)!.explore, |       title: AppLocalizations.of(context)!.explore, | ||||||
|       child: RefreshIndicator( |       child: RefreshIndicator( | ||||||
|         onRefresh: () => Future.sync( |         onRefresh: () => Future.sync( | ||||||
|   | |||||||
							
								
								
									
										177
									
								
								lib/screens/notification.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,177 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:solian/providers/auth.dart'; | ||||||
|  | import 'package:solian/providers/notify.dart'; | ||||||
|  | import 'package:solian/utils/service_url.dart'; | ||||||
|  | import 'package:solian/widgets/indent_wrapper.dart'; | ||||||
|  | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  | import 'package:solian/models/notification.dart' as model; | ||||||
|  |  | ||||||
|  | class NotificationScreen extends StatefulWidget { | ||||||
|  |   const NotificationScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<NotificationScreen> createState() => _NotificationScreenState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _NotificationScreenState extends State<NotificationScreen> { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final auth = context.read<AuthProvider>(); | ||||||
|  |     final nty = context.watch<NotifyProvider>(); | ||||||
|  |  | ||||||
|  |     return IndentWrapper( | ||||||
|  |       noSafeArea: true, | ||||||
|  |       title: AppLocalizations.of(context)!.notification, | ||||||
|  |       child: RefreshIndicator( | ||||||
|  |         onRefresh: () => nty.fetch(auth), | ||||||
|  |         child: CustomScrollView( | ||||||
|  |           slivers: [ | ||||||
|  |             nty.notifications.isEmpty | ||||||
|  |                 ? SliverToBoxAdapter( | ||||||
|  |                     child: Container( | ||||||
|  |                       padding: const EdgeInsets.symmetric(horizontal: 10), | ||||||
|  |                       color: Theme.of(context).colorScheme.surfaceVariant, | ||||||
|  |                       child: ListTile( | ||||||
|  |                         leading: const Icon(Icons.check), | ||||||
|  |                         title: Text(AppLocalizations.of(context)!.notifyDone), | ||||||
|  |                         subtitle: Text(AppLocalizations.of(context)!.notifyDoneCaption), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ) | ||||||
|  |                 : SliverList.builder( | ||||||
|  |                     itemCount: nty.notifications.length, | ||||||
|  |                     itemBuilder: (BuildContext context, int index) { | ||||||
|  |                       var element = nty.notifications[index]; | ||||||
|  |                       return NotificationItem( | ||||||
|  |                         index: index, | ||||||
|  |                         item: element, | ||||||
|  |                         onDismiss: () => setState(() { | ||||||
|  |                           nty.notifications.removeAt(index); | ||||||
|  |                         }), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Container( | ||||||
|  |                 padding: const EdgeInsets.only(top: 12), | ||||||
|  |                 child: Text( | ||||||
|  |                   AppLocalizations.of(context)!.notifyListHint, | ||||||
|  |                   textAlign: TextAlign.center, | ||||||
|  |                   style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class NotificationItem extends StatelessWidget { | ||||||
|  |   final int index; | ||||||
|  |   final model.Notification item; | ||||||
|  |   final void Function()? onDismiss; | ||||||
|  |  | ||||||
|  |   const NotificationItem({super.key, required this.index, required this.item, this.onDismiss}); | ||||||
|  |  | ||||||
|  |   bool hasLinks() => item.links != null && item.links!.isNotEmpty; | ||||||
|  |  | ||||||
|  |   void showLinks(BuildContext context) { | ||||||
|  |     if (!hasLinks()) return; | ||||||
|  |  | ||||||
|  |     showModalBottomSheet<void>( | ||||||
|  |       context: context, | ||||||
|  |       builder: (BuildContext context) { | ||||||
|  |         return Column( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |           children: [ | ||||||
|  |             Padding( | ||||||
|  |               padding: const EdgeInsets.only(left: 16, right: 16, top: 34, bottom: 12), | ||||||
|  |               child: Text( | ||||||
|  |                 "Links", | ||||||
|  |                 style: Theme.of(context).textTheme.headlineSmall, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             Expanded( | ||||||
|  |               child: ListView.builder( | ||||||
|  |                 itemCount: item.links!.length, | ||||||
|  |                 itemBuilder: (BuildContext context, int index) { | ||||||
|  |                   var element = item.links![index]; | ||||||
|  |                   return ListTile( | ||||||
|  |                     title: Text(element.label), | ||||||
|  |                     onTap: () async { | ||||||
|  |                       await launchUrlString(element.url); | ||||||
|  |                       if (Navigator.canPop(context)) { | ||||||
|  |                         Navigator.pop(context); | ||||||
|  |                       } | ||||||
|  |                     }, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> markAsRead(model.Notification element, BuildContext context) async { | ||||||
|  |     if (element.isRealtime) return; | ||||||
|  |  | ||||||
|  |     final auth = context.read<AuthProvider>(); | ||||||
|  |     if (!await auth.isAuthorized()) return; | ||||||
|  |  | ||||||
|  |     var id = element.id; | ||||||
|  |     var uri = getRequestUri('passport', '/api/notifications/$id/read'); | ||||||
|  |     await auth.client!.put(uri); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Dismissible( | ||||||
|  |       key: Key('n$index'), | ||||||
|  |       onDismissed: (direction) { | ||||||
|  |         markAsRead(item, context).then((value) { | ||||||
|  |           ScaffoldMessenger.of(context).showSnackBar( | ||||||
|  |             SnackBar( | ||||||
|  |               content: RichText( | ||||||
|  |                 text: TextSpan( | ||||||
|  |                   children: [ | ||||||
|  |                     TextSpan( | ||||||
|  |                       text: item.subject, | ||||||
|  |                       style: const TextStyle(fontWeight: FontWeight.bold), | ||||||
|  |                     ), | ||||||
|  |                     const TextSpan(text: " is marked as read") | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |         if (onDismiss != null) { | ||||||
|  |           onDismiss!(); | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       background: Container( | ||||||
|  |         color: Colors.lightBlue, | ||||||
|  |       ), | ||||||
|  |       child: Container( | ||||||
|  |         padding: const EdgeInsets.only(left: 10), | ||||||
|  |         child: ListTile( | ||||||
|  |           title: Text(item.subject), | ||||||
|  |           subtitle: Text(item.content), | ||||||
|  |           trailing: hasLinks() | ||||||
|  |               ? TextButton( | ||||||
|  |                   onPressed: () => showLinks(context), | ||||||
|  |                   style: TextButton.styleFrom(shape: const CircleBorder()), | ||||||
|  |                   child: const Icon(Icons.more_vert), | ||||||
|  |                 ) | ||||||
|  |               : null, | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -34,7 +34,7 @@ class SignInScreen extends StatelessWidget { | |||||||
|                 decoration: InputDecoration( |                 decoration: InputDecoration( | ||||||
|                   isDense: true, |                   isDense: true, | ||||||
|                   border: const UnderlineInputBorder(), |                   border: const UnderlineInputBorder(), | ||||||
|                   hintText: AppLocalizations.of(context)!.username, |                   labelText: AppLocalizations.of(context)!.username, | ||||||
|                 ), |                 ), | ||||||
|                 onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), |                 onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|               ), |               ), | ||||||
| @@ -47,7 +47,7 @@ class SignInScreen extends StatelessWidget { | |||||||
|                 decoration: InputDecoration( |                 decoration: InputDecoration( | ||||||
|                   isDense: true, |                   isDense: true, | ||||||
|                   border: const UnderlineInputBorder(), |                   border: const UnderlineInputBorder(), | ||||||
|                   hintText: AppLocalizations.of(context)!.password, |                   labelText: AppLocalizations.of(context)!.password, | ||||||
|                 ), |                 ), | ||||||
|                 onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), |                 onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|               ), |               ), | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ class _ChatMaintainerState extends State<ChatMaintainer> { | |||||||
|     final notify = ScaffoldMessenger.of(context).showSnackBar( |     final notify = ScaffoldMessenger.of(context).showSnackBar( | ||||||
|       SnackBar( |       SnackBar( | ||||||
|         content: Text(AppLocalizations.of(context)!.connectingServer), |         content: Text(AppLocalizations.of(context)!.connectingServer), | ||||||
|         duration: const Duration(days: 1), |         duration: const Duration(minutes: 1), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| @@ -55,6 +55,7 @@ class _ChatMaintainerState extends State<ChatMaintainer> { | |||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         onError: (_, __) => connect(), |         onError: (_, __) => connect(), | ||||||
|  |         onDone: () => connect(), | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       notify.close(); |       notify.close(); | ||||||
| @@ -72,6 +73,8 @@ class _ChatMaintainerState extends State<ChatMaintainer> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |     ScaffoldMessenger.of(context).clearSnackBars(); | ||||||
|  |  | ||||||
|     return widget.child; |     return widget.child; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> { | |||||||
|       builder: (context) => AttachmentEditor( |       builder: (context) => AttachmentEditor( | ||||||
|         provider: 'messaging', |         provider: 'messaging', | ||||||
|         current: _attachments, |         current: _attachments, | ||||||
|         onUpdate: (value) => _attachments = value, |         onUpdate: (value) => setState(() => _attachments = value), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										90
									
								
								lib/widgets/notification_notifier.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,90 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:solian/providers/auth.dart'; | ||||||
|  | import 'package:solian/providers/notify.dart'; | ||||||
|  | import 'package:solian/router.dart'; | ||||||
|  | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||||
|  | import 'package:solian/models/notification.dart' as model; | ||||||
|  | import 'package:badges/badges.dart' as badge; | ||||||
|  |  | ||||||
|  | class NotificationNotifier extends StatefulWidget { | ||||||
|  |   final Widget child; | ||||||
|  |  | ||||||
|  |   const NotificationNotifier({super.key, required this.child}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<NotificationNotifier> createState() => _NotificationNotifierState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _NotificationNotifierState extends State<NotificationNotifier> { | ||||||
|  |   void connect() { | ||||||
|  |     final notify = ScaffoldMessenger.of(context).showSnackBar( | ||||||
|  |       SnackBar( | ||||||
|  |         content: Text(AppLocalizations.of(context)!.connectingServer), | ||||||
|  |         duration: const Duration(minutes: 1), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final auth = context.read<AuthProvider>(); | ||||||
|  |     final nty = context.read<NotifyProvider>(); | ||||||
|  |  | ||||||
|  |     nty.fetch(auth); | ||||||
|  |     nty.connect(auth).then((snapshot) { | ||||||
|  |       snapshot!.stream.listen( | ||||||
|  |         (event) { | ||||||
|  |           final result = model.Notification.fromJson(jsonDecode(event)); | ||||||
|  |           nty.onRemoteMessage(result); | ||||||
|  |         }, | ||||||
|  |         onError: (_, __) => connect(), | ||||||
|  |         onDone: () => connect(), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       notify.close(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     Future.delayed(Duration.zero, () { | ||||||
|  |       connect(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     super.initState(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return widget.child; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class NotificationButton extends StatefulWidget { | ||||||
|  |   const NotificationButton({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<NotificationButton> createState() => _NotificationButtonState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _NotificationButtonState extends State<NotificationButton> { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final nty = context.watch<NotifyProvider>(); | ||||||
|  |  | ||||||
|  |     return badge.Badge( | ||||||
|  |       showBadge: nty.notifications.isNotEmpty, | ||||||
|  |       position: badge.BadgePosition.custom(top: -2, end: 8), | ||||||
|  |       badgeContent: Text( | ||||||
|  |         nty.notifications.length.toString(), | ||||||
|  |         style: const TextStyle(color: Colors.white), | ||||||
|  |       ), | ||||||
|  |       child: IconButton( | ||||||
|  |         icon: const Icon(Icons.notifications), | ||||||
|  |         onPressed: () { | ||||||
|  |           router.pushNamed("notification"); | ||||||
|  |         }, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -69,7 +69,7 @@ | |||||||
| 		331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; | 		331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; | ||||||
| 		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; | 		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; | ||||||
| 		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; | 		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; | ||||||
| 		33CC10ED2044A3C60003C045 /* solian.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = solian.app; sourceTree = BUILT_PRODUCTS_DIR; }; | 		33CC10ED2044A3C60003C045 /* Solian.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Solian.app; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||||
| 		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | 		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | ||||||
| 		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; | 		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; | ||||||
| 		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; | 		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; | ||||||
| @@ -144,7 +144,7 @@ | |||||||
| 		33CC10EE2044A3C60003C045 /* Products */ = { | 		33CC10EE2044A3C60003C045 /* Products */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				33CC10ED2044A3C60003C045 /* solian.app */, | 				33CC10ED2044A3C60003C045 /* Solian.app */, | ||||||
| 				331C80D5294CF71000263BE5 /* RunnerTests.xctest */, | 				331C80D5294CF71000263BE5 /* RunnerTests.xctest */, | ||||||
| 			); | 			); | ||||||
| 			name = Products; | 			name = Products; | ||||||
| @@ -195,7 +195,6 @@ | |||||||
| 				1B09C6EB2D1B711F9DE8626F /* Pods-RunnerTests.release.xcconfig */, | 				1B09C6EB2D1B711F9DE8626F /* Pods-RunnerTests.release.xcconfig */, | ||||||
| 				8CBB406EC5E794824CF74930 /* Pods-RunnerTests.profile.xcconfig */, | 				8CBB406EC5E794824CF74930 /* Pods-RunnerTests.profile.xcconfig */, | ||||||
| 			); | 			); | ||||||
| 			name = Pods; |  | ||||||
| 			path = Pods; | 			path = Pods; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
| @@ -249,7 +248,7 @@ | |||||||
| 			); | 			); | ||||||
| 			name = Runner; | 			name = Runner; | ||||||
| 			productName = Runner; | 			productName = Runner; | ||||||
| 			productReference = 33CC10ED2044A3C60003C045 /* solian.app */; | 			productReference = 33CC10ED2044A3C60003C045 /* Solian.app */; | ||||||
| 			productType = "com.apple.product-type.application"; | 			productType = "com.apple.product-type.application"; | ||||||
| 		}; | 		}; | ||||||
| /* End PBXNativeTarget section */ | /* End PBXNativeTarget section */ | ||||||
| @@ -270,7 +269,6 @@ | |||||||
| 					33CC10EC2044A3C60003C045 = { | 					33CC10EC2044A3C60003C045 = { | ||||||
| 						CreatedOnToolsVersion = 9.2; | 						CreatedOnToolsVersion = 9.2; | ||||||
| 						LastSwiftMigration = 1100; | 						LastSwiftMigration = 1100; | ||||||
| 						ProvisioningStyle = Automatic; |  | ||||||
| 						SystemCapabilities = { | 						SystemCapabilities = { | ||||||
| 							com.apple.Sandbox = { | 							com.apple.Sandbox = { | ||||||
| 								enabled = 1; | 								enabled = 1; | ||||||
| @@ -572,10 +570,14 @@ | |||||||
| 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||||
| 				CLANG_ENABLE_MODULES = YES; | 				CLANG_ENABLE_MODULES = YES; | ||||||
| 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; | 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; | ||||||
|  | 				CODE_SIGN_IDENTITY = "Apple Development"; | ||||||
|  | 				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; | ||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				COMBINE_HIDPI_IMAGES = YES; | 				COMBINE_HIDPI_IMAGES = YES; | ||||||
|  | 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||||
| 				INFOPLIST_FILE = Runner/Info.plist; | 				INFOPLIST_FILE = Runner/Info.plist; | ||||||
| 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | ||||||
|  | 				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; | ||||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/../Frameworks", | 					"@executable_path/../Frameworks", | ||||||
| @@ -706,10 +708,14 @@ | |||||||
| 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||||
| 				CLANG_ENABLE_MODULES = YES; | 				CLANG_ENABLE_MODULES = YES; | ||||||
| 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; | 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; | ||||||
|  | 				CODE_SIGN_IDENTITY = "Apple Development"; | ||||||
|  | 				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; | ||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				COMBINE_HIDPI_IMAGES = YES; | 				COMBINE_HIDPI_IMAGES = YES; | ||||||
|  | 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||||
| 				INFOPLIST_FILE = Runner/Info.plist; | 				INFOPLIST_FILE = Runner/Info.plist; | ||||||
| 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | ||||||
|  | 				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; | ||||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/../Frameworks", | 					"@executable_path/../Frameworks", | ||||||
| @@ -728,10 +734,14 @@ | |||||||
| 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||||
| 				CLANG_ENABLE_MODULES = YES; | 				CLANG_ENABLE_MODULES = YES; | ||||||
| 				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; | 				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; | ||||||
|  | 				CODE_SIGN_IDENTITY = "Apple Development"; | ||||||
|  | 				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; | ||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				COMBINE_HIDPI_IMAGES = YES; | 				COMBINE_HIDPI_IMAGES = YES; | ||||||
|  | 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||||
| 				INFOPLIST_FILE = Runner/Info.plist; | 				INFOPLIST_FILE = Runner/Info.plist; | ||||||
| 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | ||||||
|  | 				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; | ||||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/../Frameworks", | 					"@executable_path/../Frameworks", | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ | |||||||
|             <BuildableReference |             <BuildableReference | ||||||
|                BuildableIdentifier = "primary" |                BuildableIdentifier = "primary" | ||||||
|                BlueprintIdentifier = "33CC10EC2044A3C60003C045" |                BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||||
|                BuildableName = "solian.app" |                BuildableName = "Solian.app" | ||||||
|                BlueprintName = "Runner" |                BlueprintName = "Runner" | ||||||
|                ReferencedContainer = "container:Runner.xcodeproj"> |                ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|             </BuildableReference> |             </BuildableReference> | ||||||
| @@ -31,7 +31,7 @@ | |||||||
|          <BuildableReference |          <BuildableReference | ||||||
|             BuildableIdentifier = "primary" |             BuildableIdentifier = "primary" | ||||||
|             BlueprintIdentifier = "33CC10EC2044A3C60003C045" |             BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||||
|             BuildableName = "solian.app" |             BuildableName = "Solian.app" | ||||||
|             BlueprintName = "Runner" |             BlueprintName = "Runner" | ||||||
|             ReferencedContainer = "container:Runner.xcodeproj"> |             ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|          </BuildableReference> |          </BuildableReference> | ||||||
| @@ -65,7 +65,7 @@ | |||||||
|          <BuildableReference |          <BuildableReference | ||||||
|             BuildableIdentifier = "primary" |             BuildableIdentifier = "primary" | ||||||
|             BlueprintIdentifier = "33CC10EC2044A3C60003C045" |             BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||||
|             BuildableName = "solian.app" |             BuildableName = "Solian.app" | ||||||
|             BlueprintName = "Runner" |             BlueprintName = "Runner" | ||||||
|             ReferencedContainer = "container:Runner.xcodeproj"> |             ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|          </BuildableReference> |          </BuildableReference> | ||||||
| @@ -82,7 +82,7 @@ | |||||||
|          <BuildableReference |          <BuildableReference | ||||||
|             BuildableIdentifier = "primary" |             BuildableIdentifier = "primary" | ||||||
|             BlueprintIdentifier = "33CC10EC2044A3C60003C045" |             BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||||
|             BuildableName = "solian.app" |             BuildableName = "Solian.app" | ||||||
|             BlueprintName = "Runner" |             BlueprintName = "Runner" | ||||||
|             ReferencedContainer = "container:Runner.xcodeproj"> |             ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|          </BuildableReference> |          </BuildableReference> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 73 KiB | 
| Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB | 
| Before Width: | Height: | Size: 480 B After Width: | Height: | Size: 527 B | 
| Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.6 KiB | 
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|         </customObject> |         </customObject> | ||||||
|         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||||||
|         <customObject id="-3" userLabel="Application" customClass="NSObject"/> |         <customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||||||
|         <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target"> |         <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Solian" customModuleProvider="target"> | ||||||
|             <connections> |             <connections> | ||||||
|                 <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/> |                 <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/> | ||||||
|                 <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/> |                 <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/> | ||||||
| @@ -330,14 +330,15 @@ | |||||||
|             </items> |             </items> | ||||||
|             <point key="canvasLocation" x="142" y="-258"/> |             <point key="canvasLocation" x="142" y="-258"/> | ||||||
|         </menu> |         </menu> | ||||||
|         <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target"> |         <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" titlebarAppearsTransparent="YES" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Solian" customModuleProvider="target"> | ||||||
|             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> | ||||||
|             <rect key="contentRect" x="335" y="390" width="800" height="600"/> |             <rect key="contentRect" x="335" y="390" width="380" height="640"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1512" height="944"/> | ||||||
|             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> |             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="800" height="600"/> |                 <rect key="frame" x="0.0" y="0.0" width="380" height="640"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|             </view> |             </view> | ||||||
|  |             <point key="canvasLocation" x="142" y="156"/> | ||||||
|         </window> |         </window> | ||||||
|     </objects> |     </objects> | ||||||
| </document> | </document> | ||||||
|   | |||||||
| @@ -10,5 +10,7 @@ | |||||||
| 	<true/> | 	<true/> | ||||||
| 	<key>com.apple.security.network.client</key> | 	<key>com.apple.security.network.client</key> | ||||||
|     <true/> |     <true/> | ||||||
|  |     <key>com.apple.security.files.user-selected.read-only</key> | ||||||
|  |     <true/> | ||||||
| </dict> | </dict> | ||||||
| </plist> | </plist> | ||||||
|   | |||||||
| @@ -26,6 +26,8 @@ | |||||||
| 	<string>$(PRODUCT_COPYRIGHT)</string> | 	<string>$(PRODUCT_COPYRIGHT)</string> | ||||||
| 	<key>NSMainNibFile</key> | 	<key>NSMainNibFile</key> | ||||||
| 	<string>MainMenu</string> | 	<string>MainMenu</string> | ||||||
|  | 	<key>LSApplicationCategoryType</key> | ||||||
|  | 	<string>public.app-category.social-networking</string> | ||||||
| 	<key>NSPrincipalClass</key> | 	<key>NSPrincipalClass</key> | ||||||
| 	<string>NSApplication</string> | 	<string>NSApplication</string> | ||||||
| </dict> | </dict> | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ class MainFlutterWindow: NSWindow { | |||||||
|     let windowFrame = self.frame |     let windowFrame = self.frame | ||||||
|     self.contentViewController = flutterViewController |     self.contentViewController = flutterViewController | ||||||
|     self.setFrame(windowFrame, display: true) |     self.setFrame(windowFrame, display: true) | ||||||
|  |     self.minSize = NSSize(width: 380, height: 600) | ||||||
|  |  | ||||||
|     RegisterGeneratedPlugins(registry: flutterViewController) |     RegisterGeneratedPlugins(registry: flutterViewController) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,5 +6,7 @@ | |||||||
| 	<true/> | 	<true/> | ||||||
| 	<key>com.apple.security.network.client</key> | 	<key>com.apple.security.network.client</key> | ||||||
|     <true/> |     <true/> | ||||||
|  |     <key>com.apple.security.files.user-selected.read-only</key> | ||||||
|  |     <true/> | ||||||
| </dict> | </dict> | ||||||
| </plist> | </plist> | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ name: solian | |||||||
| description: "A new Flutter project." | description: "A new Flutter project." | ||||||
| # The following line prevents the package from being accidentally published to | # The following line prevents the package from being accidentally published to | ||||||
| # pub.dev using `flutter pub publish`. This is preferred for private packages. | # pub.dev using `flutter pub publish`. This is preferred for private packages. | ||||||
| publish_to: 'none' # Remove this line if you wish to publish to pub.dev | publish_to: "none" # Remove this line if you wish to publish to pub.dev | ||||||
|  |  | ||||||
| # The following defines the version and build number for your application. | # The following defines the version and build number for your application. | ||||||
| # A version number is three numbers separated by dots, like 1.2.43 | # A version number is three numbers separated by dots, like 1.2.43 | ||||||
| @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | |||||||
| version: 1.0.0+1 | version: 1.0.0+1 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: '>=3.3.3 <4.0.0' |   sdk: ">=3.3.3 <4.0.0" | ||||||
|  |  | ||||||
| # Dependencies specify other packages that your package needs in order to work. | # Dependencies specify other packages that your package needs in order to work. | ||||||
| # To automatically upgrade your package dependencies to the latest versions | # To automatically upgrade your package dependencies to the latest versions | ||||||
| @@ -31,7 +31,6 @@ dependencies: | |||||||
|   flutter: |   flutter: | ||||||
|     sdk: flutter |     sdk: flutter | ||||||
|  |  | ||||||
|  |  | ||||||
|   # The following adds the Cupertino Icons font to your application. |   # The following adds the Cupertino Icons font to your application. | ||||||
|   # Use with the CupertinoIcons class for iOS style icons. |   # Use with the CupertinoIcons class for iOS style icons. | ||||||
|   cupertino_icons: ^1.0.6 |   cupertino_icons: ^1.0.6 | ||||||
| @@ -132,4 +131,4 @@ flutter_launcher_icons: | |||||||
|     icon_size: 256 |     icon_size: 256 | ||||||
|   macos: |   macos: | ||||||
|     generate: true |     generate: true | ||||||
|     image_path: "assets/icon.png" |     image_path: "assets/icon-macos.png" | ||||||
|   | |||||||