✨ Dashboard basis
This commit is contained in:
		| @@ -12,6 +12,7 @@ import 'package:solian/screens/channel/channel_chat.dart'; | ||||
| import 'package:solian/screens/channel/channel_detail.dart'; | ||||
| import 'package:solian/screens/channel/channel_organize.dart'; | ||||
| import 'package:solian/screens/chat.dart'; | ||||
| import 'package:solian/screens/dashboard.dart'; | ||||
| import 'package:solian/screens/feed/search.dart'; | ||||
| import 'package:solian/screens/posts/post_detail.dart'; | ||||
| import 'package:solian/screens/feed/draft_box.dart'; | ||||
| @@ -19,7 +20,7 @@ import 'package:solian/screens/realms.dart'; | ||||
| import 'package:solian/screens/realms/realm_detail.dart'; | ||||
| import 'package:solian/screens/realms/realm_organize.dart'; | ||||
| import 'package:solian/screens/realms/realm_view.dart'; | ||||
| import 'package:solian/screens/home.dart'; | ||||
| import 'package:solian/screens/feed.dart'; | ||||
| import 'package:solian/screens/posts/post_editor.dart'; | ||||
| import 'package:solian/screens/settings.dart'; | ||||
| import 'package:solian/shells/root_shell.dart'; | ||||
| @@ -34,6 +35,14 @@ abstract class AppRouter { | ||||
|           child: child, | ||||
|         ), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: '/', | ||||
|             name: 'dashboard', | ||||
|             builder: (context, state) => TitleShell( | ||||
|               state: state, | ||||
|               child: const DashboardScreen(), | ||||
|             ), | ||||
|           ), | ||||
|           _feedRoute, | ||||
|           _chatRoute, | ||||
|           _realmRoute, | ||||
| @@ -63,9 +72,9 @@ abstract class AppRouter { | ||||
|     builder: (context, state, child) => child, | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/', | ||||
|         name: 'home', | ||||
|         builder: (context, state) => const HomeScreen(), | ||||
|         path: '/feed', | ||||
|         name: 'feed', | ||||
|         builder: (context, state) => const FeedScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/feed/search', | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class NotificationScreen extends StatefulWidget { | ||||
| class _NotificationScreenState extends State<NotificationScreen> { | ||||
|   bool _isBusy = false; | ||||
|  | ||||
|   Future<void> markAllRead() async { | ||||
|   Future<void> _markAllRead() async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     if (auth.isAuthorized.isFalse) return; | ||||
|  | ||||
| @@ -40,7 +40,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|     setState(() => _isBusy = false); | ||||
|   } | ||||
|  | ||||
|   Future<void> markOneRead(notify.Notification element, int index) async { | ||||
|   Future<void> _markOneRead(notify.Notification element, int index) async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     if (auth.isAuthorized.isFalse) return; | ||||
|  | ||||
| @@ -64,7 +64,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final WebSocketProvider provider = Get.find(); | ||||
|     final WebSocketProvider ws = Get.find(); | ||||
|  | ||||
|     return SizedBox( | ||||
|       height: MediaQuery.of(context).size.height * 0.85, | ||||
| @@ -83,7 +83,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                     SliverToBoxAdapter( | ||||
|                       child: const LinearProgressIndicator().animate().scaleX(), | ||||
|                     ), | ||||
|                   if (provider.notifications.isEmpty) | ||||
|                   if (ws.notifications.isEmpty) | ||||
|                     SliverToBoxAdapter( | ||||
|                       child: Container( | ||||
|                         padding: const EdgeInsets.symmetric(horizontal: 10), | ||||
| @@ -96,7 +96,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                   if (provider.notifications.isNotEmpty) | ||||
|                   if (ws.notifications.isNotEmpty) | ||||
|                     SliverToBoxAdapter( | ||||
|                       child: Container( | ||||
|                         padding: const EdgeInsets.symmetric(horizontal: 10), | ||||
| @@ -104,14 +104,14 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                         child: ListTile( | ||||
|                           leading: const Icon(Icons.checklist), | ||||
|                           title: Text('notifyAllRead'.tr), | ||||
|                           onTap: _isBusy ? null : () => markAllRead(), | ||||
|                           onTap: _isBusy ? null : () => _markAllRead(), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                   SliverList.separated( | ||||
|                     itemCount: provider.notifications.length, | ||||
|                     itemCount: ws.notifications.length, | ||||
|                     itemBuilder: (BuildContext context, int index) { | ||||
|                       var element = provider.notifications[index]; | ||||
|                       var element = ws.notifications[index]; | ||||
|                       return Dismissible( | ||||
|                         key: Key(const Uuid().v4()), | ||||
|                         background: Container( | ||||
| @@ -135,7 +135,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                             ], | ||||
|                           ), | ||||
|                         ), | ||||
|                         onDismissed: (_) => markOneRead(element, index), | ||||
|                         onDismissed: (_) => _markOneRead(element, index), | ||||
|                       ); | ||||
|                     }, | ||||
|                     separatorBuilder: (_, __) => | ||||
|   | ||||
							
								
								
									
										116
									
								
								lib/screens/dashboard.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								lib/screens/dashboard.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| import 'dart:math'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:intl/intl.dart'; | ||||
| import 'package:solian/providers/websocket.dart'; | ||||
| import 'package:solian/screens/account/notification.dart'; | ||||
|  | ||||
| class DashboardScreen extends StatefulWidget { | ||||
|   const DashboardScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<DashboardScreen> createState() => _DashboardScreenState(); | ||||
| } | ||||
|  | ||||
| class _DashboardScreenState extends State<DashboardScreen> { | ||||
|   late final WebSocketProvider _ws = Get.find(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final width = MediaQuery.of(context).size.width; | ||||
|  | ||||
|     return ListView( | ||||
|       children: [ | ||||
|         Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Text('today'.tr, style: Theme.of(context).textTheme.headlineSmall), | ||||
|             Text(DateFormat('yyyy/MM/dd').format(DateTime.now())), | ||||
|           ], | ||||
|         ).paddingOnly(top: 8, left: 18, right: 18), | ||||
|         const Divider(thickness: 0.3).paddingSymmetric(vertical: 8), | ||||
|         Obx( | ||||
|           () => Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               Row( | ||||
|                 mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                 children: [ | ||||
|                   Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         'notification'.tr, | ||||
|                         style: Theme.of(context) | ||||
|                             .textTheme | ||||
|                             .titleMedium! | ||||
|                             .copyWith(fontSize: 18), | ||||
|                       ), | ||||
|                       Text( | ||||
|                         'notificationUnreadCount'.trParams({ | ||||
|                           'count': _ws.notifications.length.toString(), | ||||
|                         }), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                   IconButton( | ||||
|                     icon: const Icon(Icons.more_horiz), | ||||
|                     onPressed: () { | ||||
|                       showModalBottomSheet( | ||||
|                         useRootNavigator: true, | ||||
|                         isScrollControlled: true, | ||||
|                         context: context, | ||||
|                         builder: (context) => const NotificationScreen(), | ||||
|                       ).then((_) => _ws.notificationUnread.value = 0); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ], | ||||
|               ).paddingOnly(left: 18, right: 18, bottom: 8), | ||||
|               if (_ws.notifications.isNotEmpty) | ||||
|                 SizedBox( | ||||
|                   height: 76, | ||||
|                   width: width, | ||||
|                   child: ListView.builder( | ||||
|                     scrollDirection: Axis.horizontal, | ||||
|                     itemCount: min(_ws.notifications.length, 3), | ||||
|                     itemBuilder: (context, idx) { | ||||
|                       final x = _ws.notifications[idx]; | ||||
|                       return SizedBox( | ||||
|                         width: width, | ||||
|                         child: Card( | ||||
|                           child: ListTile( | ||||
|                             contentPadding: const EdgeInsets.symmetric( | ||||
|                               horizontal: 24, | ||||
|                               vertical: 4, | ||||
|                             ), | ||||
|                             title: Text(x.title), | ||||
|                             subtitle: Column( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                               children: [ | ||||
|                                 if (x.subtitle != null) Text(x.subtitle!), | ||||
|                                 Text(x.body), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ), | ||||
|                         ).paddingSymmetric(horizontal: 8), | ||||
|                       ); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ) | ||||
|               else | ||||
|                 Card( | ||||
|                   child: ListTile( | ||||
|                     contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|                     trailing: const Icon(Icons.inbox_outlined), | ||||
|                     title: Text('notifyEmpty'.tr), | ||||
|                     subtitle: Text('notifyEmptyCaption'.tr), | ||||
|                   ), | ||||
|                 ).paddingSymmetric(horizontal: 8), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -12,14 +12,14 @@ import 'package:solian/widgets/app_bar_leading.dart'; | ||||
| import 'package:solian/widgets/posts/post_shuffle_swiper.dart'; | ||||
| import 'package:solian/widgets/posts/post_warped_list.dart'; | ||||
| 
 | ||||
| class HomeScreen extends StatefulWidget { | ||||
|   const HomeScreen({super.key}); | ||||
| class FeedScreen extends StatefulWidget { | ||||
|   const FeedScreen({super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   State<HomeScreen> createState() => _HomeScreenState(); | ||||
|   State<FeedScreen> createState() => _FeedScreenState(); | ||||
| } | ||||
| 
 | ||||
| class _HomeScreenState extends State<HomeScreen> | ||||
| class _FeedScreenState extends State<FeedScreen> | ||||
|     with SingleTickerProviderStateMixin { | ||||
|   late final PostListController _postController; | ||||
|   late final TabController _tabController; | ||||
| @@ -8,6 +8,8 @@ const i18nEnglish = { | ||||
|   'home': 'Home', | ||||
|   'guest': 'Guest', | ||||
|   'draft': 'Draft', | ||||
|   'dashboard': 'Dashboard', | ||||
|   'today': 'Today', | ||||
|   'draftSave': 'Save', | ||||
|   'draftBox': 'Draft Box', | ||||
|   'more': 'More', | ||||
| @@ -40,6 +42,7 @@ const i18nEnglish = { | ||||
|   'openInAlbum': 'Open in album', | ||||
|   'openInBrowser': 'Open in browser', | ||||
|   'notification': 'Notification', | ||||
|   'notificationUnreadCount': '@count unread notifications', | ||||
|   'errorHappened': 'An error occurred', | ||||
|   'errorHappenedUnauthorized': | ||||
|       'Unauthorized request, please sign in or try resign in.', | ||||
|   | ||||
| @@ -24,6 +24,8 @@ const i18nSimplifiedChinese = { | ||||
|   'alias': '别名', | ||||
|   'feed': '资讯', | ||||
|   'unlink': '移除链接', | ||||
|   'dashboard': '仪表盘', | ||||
|   'today': '今日', | ||||
|   'feedSearch': '搜索资讯', | ||||
|   'feedSearchWithTag': '检索带有 #@key 标签的资讯', | ||||
|   'feedSearchWithCategory': '检索位于分类 @category 的资讯', | ||||
| @@ -40,6 +42,7 @@ const i18nSimplifiedChinese = { | ||||
|   'openInAlbum': '在相簿中打开', | ||||
|   'openInBrowser': '在浏览器中打开', | ||||
|   'notification': '通知', | ||||
|   'notificationUnreadCount': '@count 条未读通知', | ||||
|   'errorHappened': '发生错误了', | ||||
|   'errorHappenedUnauthorized': '未经授权的请求,请登录或尝试重新登录。', | ||||
|   'errorHappenedRequestBad': '请求错误,服务器拒绝处理该请求,请检查您的请求数据。', | ||||
|   | ||||
| @@ -2070,10 +2070,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vm_service | ||||
|       sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc | ||||
|       sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "14.2.4" | ||||
|     version: "14.2.5" | ||||
|   volume_controller: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user