✨ Dashboard basis
This commit is contained in:
parent
fff756cbe0
commit
597a8a802a
@ -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_detail.dart';
|
||||||
import 'package:solian/screens/channel/channel_organize.dart';
|
import 'package:solian/screens/channel/channel_organize.dart';
|
||||||
import 'package:solian/screens/chat.dart';
|
import 'package:solian/screens/chat.dart';
|
||||||
|
import 'package:solian/screens/dashboard.dart';
|
||||||
import 'package:solian/screens/feed/search.dart';
|
import 'package:solian/screens/feed/search.dart';
|
||||||
import 'package:solian/screens/posts/post_detail.dart';
|
import 'package:solian/screens/posts/post_detail.dart';
|
||||||
import 'package:solian/screens/feed/draft_box.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_detail.dart';
|
||||||
import 'package:solian/screens/realms/realm_organize.dart';
|
import 'package:solian/screens/realms/realm_organize.dart';
|
||||||
import 'package:solian/screens/realms/realm_view.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/posts/post_editor.dart';
|
||||||
import 'package:solian/screens/settings.dart';
|
import 'package:solian/screens/settings.dart';
|
||||||
import 'package:solian/shells/root_shell.dart';
|
import 'package:solian/shells/root_shell.dart';
|
||||||
@ -34,6 +35,14 @@ abstract class AppRouter {
|
|||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
name: 'dashboard',
|
||||||
|
builder: (context, state) => TitleShell(
|
||||||
|
state: state,
|
||||||
|
child: const DashboardScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
_feedRoute,
|
_feedRoute,
|
||||||
_chatRoute,
|
_chatRoute,
|
||||||
_realmRoute,
|
_realmRoute,
|
||||||
@ -63,9 +72,9 @@ abstract class AppRouter {
|
|||||||
builder: (context, state, child) => child,
|
builder: (context, state, child) => child,
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/',
|
path: '/feed',
|
||||||
name: 'home',
|
name: 'feed',
|
||||||
builder: (context, state) => const HomeScreen(),
|
builder: (context, state) => const FeedScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/feed/search',
|
path: '/feed/search',
|
||||||
|
@ -16,7 +16,7 @@ class NotificationScreen extends StatefulWidget {
|
|||||||
class _NotificationScreenState extends State<NotificationScreen> {
|
class _NotificationScreenState extends State<NotificationScreen> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
Future<void> markAllRead() async {
|
Future<void> _markAllRead() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) return;
|
if (auth.isAuthorized.isFalse) return;
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
setState(() => _isBusy = false);
|
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();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) return;
|
if (auth.isAuthorized.isFalse) return;
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final WebSocketProvider provider = Get.find();
|
final WebSocketProvider ws = Get.find();
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.85,
|
height: MediaQuery.of(context).size.height * 0.85,
|
||||||
@ -83,7 +83,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: const LinearProgressIndicator().animate().scaleX(),
|
child: const LinearProgressIndicator().animate().scaleX(),
|
||||||
),
|
),
|
||||||
if (provider.notifications.isEmpty)
|
if (ws.notifications.isEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
@ -96,7 +96,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (provider.notifications.isNotEmpty)
|
if (ws.notifications.isNotEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
@ -104,14 +104,14 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.checklist),
|
leading: const Icon(Icons.checklist),
|
||||||
title: Text('notifyAllRead'.tr),
|
title: Text('notifyAllRead'.tr),
|
||||||
onTap: _isBusy ? null : () => markAllRead(),
|
onTap: _isBusy ? null : () => _markAllRead(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverList.separated(
|
SliverList.separated(
|
||||||
itemCount: provider.notifications.length,
|
itemCount: ws.notifications.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
var element = provider.notifications[index];
|
var element = ws.notifications[index];
|
||||||
return Dismissible(
|
return Dismissible(
|
||||||
key: Key(const Uuid().v4()),
|
key: Key(const Uuid().v4()),
|
||||||
background: Container(
|
background: Container(
|
||||||
@ -135,7 +135,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onDismissed: (_) => markOneRead(element, index),
|
onDismissed: (_) => _markOneRead(element, index),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (_, __) =>
|
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_shuffle_swiper.dart';
|
||||||
import 'package:solian/widgets/posts/post_warped_list.dart';
|
import 'package:solian/widgets/posts/post_warped_list.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class FeedScreen extends StatefulWidget {
|
||||||
const HomeScreen({super.key});
|
const FeedScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<HomeScreen> createState() => _HomeScreenState();
|
State<FeedScreen> createState() => _FeedScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen>
|
class _FeedScreenState extends State<FeedScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final PostListController _postController;
|
late final PostListController _postController;
|
||||||
late final TabController _tabController;
|
late final TabController _tabController;
|
@ -8,6 +8,8 @@ const i18nEnglish = {
|
|||||||
'home': 'Home',
|
'home': 'Home',
|
||||||
'guest': 'Guest',
|
'guest': 'Guest',
|
||||||
'draft': 'Draft',
|
'draft': 'Draft',
|
||||||
|
'dashboard': 'Dashboard',
|
||||||
|
'today': 'Today',
|
||||||
'draftSave': 'Save',
|
'draftSave': 'Save',
|
||||||
'draftBox': 'Draft Box',
|
'draftBox': 'Draft Box',
|
||||||
'more': 'More',
|
'more': 'More',
|
||||||
@ -40,6 +42,7 @@ const i18nEnglish = {
|
|||||||
'openInAlbum': 'Open in album',
|
'openInAlbum': 'Open in album',
|
||||||
'openInBrowser': 'Open in browser',
|
'openInBrowser': 'Open in browser',
|
||||||
'notification': 'Notification',
|
'notification': 'Notification',
|
||||||
|
'notificationUnreadCount': '@count unread notifications',
|
||||||
'errorHappened': 'An error occurred',
|
'errorHappened': 'An error occurred',
|
||||||
'errorHappenedUnauthorized':
|
'errorHappenedUnauthorized':
|
||||||
'Unauthorized request, please sign in or try resign in.',
|
'Unauthorized request, please sign in or try resign in.',
|
||||||
|
@ -24,6 +24,8 @@ const i18nSimplifiedChinese = {
|
|||||||
'alias': '别名',
|
'alias': '别名',
|
||||||
'feed': '资讯',
|
'feed': '资讯',
|
||||||
'unlink': '移除链接',
|
'unlink': '移除链接',
|
||||||
|
'dashboard': '仪表盘',
|
||||||
|
'today': '今日',
|
||||||
'feedSearch': '搜索资讯',
|
'feedSearch': '搜索资讯',
|
||||||
'feedSearchWithTag': '检索带有 #@key 标签的资讯',
|
'feedSearchWithTag': '检索带有 #@key 标签的资讯',
|
||||||
'feedSearchWithCategory': '检索位于分类 @category 的资讯',
|
'feedSearchWithCategory': '检索位于分类 @category 的资讯',
|
||||||
@ -40,6 +42,7 @@ const i18nSimplifiedChinese = {
|
|||||||
'openInAlbum': '在相簿中打开',
|
'openInAlbum': '在相簿中打开',
|
||||||
'openInBrowser': '在浏览器中打开',
|
'openInBrowser': '在浏览器中打开',
|
||||||
'notification': '通知',
|
'notification': '通知',
|
||||||
|
'notificationUnreadCount': '@count 条未读通知',
|
||||||
'errorHappened': '发生错误了',
|
'errorHappened': '发生错误了',
|
||||||
'errorHappenedUnauthorized': '未经授权的请求,请登录或尝试重新登录。',
|
'errorHappenedUnauthorized': '未经授权的请求,请登录或尝试重新登录。',
|
||||||
'errorHappenedRequestBad': '请求错误,服务器拒绝处理该请求,请检查您的请求数据。',
|
'errorHappenedRequestBad': '请求错误,服务器拒绝处理该请求,请检查您的请求数据。',
|
||||||
|
@ -2070,10 +2070,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.4"
|
version: "14.2.5"
|
||||||
volume_controller:
|
volume_controller:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
Loading…
Reference in New Issue
Block a user