From 597a8a802ab43087dca959c8ca2897f8fdd02b99 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 1 Sep 2024 17:20:26 +0800 Subject: [PATCH] :sparkles: Dashboard basis --- lib/router.dart | 17 +++- lib/screens/account/notification.dart | 18 ++-- lib/screens/dashboard.dart | 116 ++++++++++++++++++++++++++ lib/screens/{home.dart => feed.dart} | 8 +- lib/translations/en_us.dart | 3 + lib/translations/zh_cn.dart | 3 + pubspec.lock | 4 +- 7 files changed, 150 insertions(+), 19 deletions(-) create mode 100644 lib/screens/dashboard.dart rename lib/screens/{home.dart => feed.dart} (97%) diff --git a/lib/router.dart b/lib/router.dart index c30d710..790ff83 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -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', diff --git a/lib/screens/account/notification.dart b/lib/screens/account/notification.dart index 756f477..519ffc1 100644 --- a/lib/screens/account/notification.dart +++ b/lib/screens/account/notification.dart @@ -16,7 +16,7 @@ class NotificationScreen extends StatefulWidget { class _NotificationScreenState extends State { bool _isBusy = false; - Future markAllRead() async { + Future _markAllRead() async { final AuthProvider auth = Get.find(); if (auth.isAuthorized.isFalse) return; @@ -40,7 +40,7 @@ class _NotificationScreenState extends State { setState(() => _isBusy = false); } - Future markOneRead(notify.Notification element, int index) async { + Future _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 { @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 { 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 { ), ), ), - 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 { 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 { ], ), ), - onDismissed: (_) => markOneRead(element, index), + onDismissed: (_) => _markOneRead(element, index), ); }, separatorBuilder: (_, __) => diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart new file mode 100644 index 0000000..c950bf6 --- /dev/null +++ b/lib/screens/dashboard.dart @@ -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 createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + 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), + ], + ), + ), + ], + ); + } +} diff --git a/lib/screens/home.dart b/lib/screens/feed.dart similarity index 97% rename from lib/screens/home.dart rename to lib/screens/feed.dart index 9af9f38..7c7c0ed 100644 --- a/lib/screens/home.dart +++ b/lib/screens/feed.dart @@ -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 createState() => _HomeScreenState(); + State createState() => _FeedScreenState(); } -class _HomeScreenState extends State +class _FeedScreenState extends State with SingleTickerProviderStateMixin { late final PostListController _postController; late final TabController _tabController; diff --git a/lib/translations/en_us.dart b/lib/translations/en_us.dart index 4605781..c471709 100644 --- a/lib/translations/en_us.dart +++ b/lib/translations/en_us.dart @@ -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.', diff --git a/lib/translations/zh_cn.dart b/lib/translations/zh_cn.dart index 05596e4..1cb8510 100644 --- a/lib/translations/zh_cn.dart +++ b/lib/translations/zh_cn.dart @@ -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': '请求错误,服务器拒绝处理该请求,请检查您的请求数据。', diff --git a/pubspec.lock b/pubspec.lock index 66830ec..69f766c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: