✨ Service status on home
🗑️ Remove news from home
This commit is contained in:
parent
b8f379796f
commit
ddd6ff7eee
@ -795,5 +795,17 @@
|
||||
"mixedFeed": "Mixed Feed",
|
||||
"mixedFeedDescription": "The Explore screen may not only display the user's posts, but may also contain other content. However, this mode does not apply to classification and filtering.",
|
||||
"filterFeed": "Exploring Adjust",
|
||||
"feedUnknownItem": "Unable to display this content, the current version of the client does not support the type of content, please try to update the application afterwards."
|
||||
"feedUnknownItem": "Unable to display this content, the current version of the client does not support the type of content, please try to update the application afterwards.",
|
||||
"serviceStatusOperational": "All services operational",
|
||||
"serviceStatusDowngraded": "Some services downgraded",
|
||||
"serviceStatusFailed": "All services unavailable",
|
||||
"serviceStatusFailedDescription": "The server is down or the maintenance is just finished.",
|
||||
"serviceNameInsights": "Summarize and Insights",
|
||||
"serviceNameInteractive": "Posts, Reactions and Explore",
|
||||
"serviceNameReader": "News and Link Previews",
|
||||
"serviceNameMessaging": "Chat",
|
||||
"serviceNameMatrix": "Matrix Software and Game Marketplace",
|
||||
"serviceNamePaperclip": "Attachments, Images and Files",
|
||||
"serviceNameWallet": "Source Points Wallet",
|
||||
"serviceNamePassport": "Authorization and Authentication"
|
||||
}
|
||||
|
@ -793,5 +793,17 @@
|
||||
"mixedFeed": "混合推荐流",
|
||||
"mixedFeedDescription": "探索页面可能不只会展示用户的帖子,更可能包含其他的内容。但该模式不适用分类和过滤。",
|
||||
"filterFeed": "探索队列调整",
|
||||
"feedUnknownItem": "无法显示该内容,当前版本客户端不支持该类型的内容,请尝试更新应用程序后再试。"
|
||||
"feedUnknownItem": "无法显示该内容,当前版本客户端不支持该类型的内容,请尝试更新应用程序后再试。",
|
||||
"serviceStatusOperational": "所有服务正常",
|
||||
"serviceStatusDowngraded": "部分服务异常",
|
||||
"serviceStatusFailed": "服务状态异常",
|
||||
"serviceStatusFailedDescription": "服务器炸了或者刚刚执行完维护程序。",
|
||||
"serviceNameInsights": "总结、见解与洞察",
|
||||
"serviceNameInteractive": "帖子与互动",
|
||||
"serviceNameReader": "新闻与链接展开",
|
||||
"serviceNameMessaging": "即使聊天",
|
||||
"serviceNameMatrix": "矩阵市场",
|
||||
"serviceNamePaperclip": "附件",
|
||||
"serviceNameWallet": "源点钱包",
|
||||
"serviceNamePassport": "身份验证与授权"
|
||||
}
|
||||
|
@ -789,5 +789,21 @@
|
||||
"fieldAccountStatusClearAt": "清除時間",
|
||||
"accountStatusNegative": "負面",
|
||||
"accountStatusNeutral": "中性",
|
||||
"accountStatusPositive": "正面"
|
||||
"accountStatusPositive": "正面",
|
||||
"mixedFeed": "混合推薦流",
|
||||
"mixedFeedDescription": "探索頁面可能不只會展示用户的帖子,更可能包含其他的內容。但該模式不適用分類和過濾。",
|
||||
"filterFeed": "探索隊列調整",
|
||||
"feedUnknownItem": "無法顯示該內容,當前版本客户端不支持該類型的內容,請嘗試更新應用程序後再試。",
|
||||
"serviceStatusOperational": "所有服務正常",
|
||||
"serviceStatusDowngraded": "部分服務異常",
|
||||
"serviceStatusFailed": "服務狀態異常",
|
||||
"serviceStatusFailedDescription": "服務器炸了或者剛剛執行完維護程序。",
|
||||
"serviceNameInsights": "總結、見解與洞察",
|
||||
"serviceNameInteractive": "帖子與互動",
|
||||
"serviceNameReader": "新聞與鏈接展開",
|
||||
"serviceNameMessaging": "即使聊天",
|
||||
"serviceNameMatrix": "矩陣市場",
|
||||
"serviceNamePaperclip": "附件",
|
||||
"serviceNameWallet": "源點錢包",
|
||||
"serviceNamePassport": "身份驗證與授權"
|
||||
}
|
||||
|
@ -789,5 +789,21 @@
|
||||
"fieldAccountStatusClearAt": "清除時間",
|
||||
"accountStatusNegative": "負面",
|
||||
"accountStatusNeutral": "中性",
|
||||
"accountStatusPositive": "正面"
|
||||
"accountStatusPositive": "正面",
|
||||
"mixedFeed": "混合推薦流",
|
||||
"mixedFeedDescription": "探索頁面可能不只會展示用戶的帖子,更可能包含其他的內容。但該模式不適用分類和過濾。",
|
||||
"filterFeed": "探索隊列調整",
|
||||
"feedUnknownItem": "無法顯示該內容,當前版本客戶端不支持該類型的內容,請嘗試更新應用程序後再試。",
|
||||
"serviceStatusOperational": "所有服務正常",
|
||||
"serviceStatusDowngraded": "部分服務異常",
|
||||
"serviceStatusFailed": "服務狀態異常",
|
||||
"serviceStatusFailedDescription": "服務器炸了或者剛剛執行完維護程序。",
|
||||
"serviceNameInsights": "總結、見解與洞察",
|
||||
"serviceNameInteractive": "帖子與互動",
|
||||
"serviceNameReader": "新聞與鏈接展開",
|
||||
"serviceNameMessaging": "即使聊天",
|
||||
"serviceNameMatrix": "矩陣市場",
|
||||
"serviceNamePaperclip": "附件",
|
||||
"serviceNameWallet": "源點錢包",
|
||||
"serviceNamePassport": "身份驗證與授權"
|
||||
}
|
||||
|
@ -17,6 +17,20 @@ import 'package:synchronized/synchronized.dart';
|
||||
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
|
||||
import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
|
||||
|
||||
enum ServiceStatus { operational, downgraded, failed }
|
||||
|
||||
const Map<String, String> kServicesName = {
|
||||
'ai': 'Insights',
|
||||
'co': 'Interactive',
|
||||
're': 'Reader',
|
||||
'im': 'Messaging',
|
||||
'ma': 'Matrix',
|
||||
'uc': 'Paperclip',
|
||||
'wa': 'Wallet',
|
||||
'id': 'Passport',
|
||||
'pusher': 'Pusher',
|
||||
};
|
||||
|
||||
const kNetworkServerDirectory = [
|
||||
('Solar Network', 'https://api.sn.solsynth.dev'),
|
||||
('Local', 'http://localhost:8001'),
|
||||
|
@ -6,7 +6,6 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
@ -20,13 +19,14 @@ import 'package:surface/providers/special_day.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/providers/widget.dart';
|
||||
import 'package:surface/types/check_in.dart';
|
||||
import 'package:surface/types/news.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
import 'package:surface/widgets/post/post_item.dart';
|
||||
import 'package:surface/widgets/updater.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class HomeScreenDashEntry {
|
||||
final String name;
|
||||
@ -66,7 +66,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
),
|
||||
HomeScreenDashEntry(
|
||||
name: 'dashEntryTodayNews',
|
||||
child: _HomeDashTodayNews(),
|
||||
child: _HomeDashServiceStatus(),
|
||||
cols: MediaQuery.of(context).size.width >= 640 ? 3 : 2,
|
||||
),
|
||||
];
|
||||
@ -245,21 +245,31 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeDashTodayNews extends StatefulWidget {
|
||||
const _HomeDashTodayNews();
|
||||
class _HomeDashServiceStatus extends StatefulWidget {
|
||||
const _HomeDashServiceStatus();
|
||||
|
||||
@override
|
||||
State<_HomeDashTodayNews> createState() => _HomeDashTodayNewsState();
|
||||
State<_HomeDashServiceStatus> createState() => _HomeDashServiceStatusState();
|
||||
}
|
||||
|
||||
class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
||||
SnNewsArticle? _article;
|
||||
class _HomeDashServiceStatusState extends State<_HomeDashServiceStatus> {
|
||||
Map<String, dynamic>? _statuses;
|
||||
ServiceStatus? _serviceStatus;
|
||||
|
||||
Future<void> _fetchArticle() async {
|
||||
Future<void> _fetchStatuses() async {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/re/news/today');
|
||||
_article = SnNewsArticle.fromJson(resp.data['data']);
|
||||
final resp = await sn.client.get('/directory/status');
|
||||
_statuses = resp.data;
|
||||
if (_statuses!.values.contains(false)) {
|
||||
if (_statuses!.values.contains(true)) {
|
||||
_serviceStatus = ServiceStatus.downgraded;
|
||||
} else {
|
||||
_serviceStatus = ServiceStatus.failed;
|
||||
}
|
||||
} else {
|
||||
_serviceStatus = ServiceStatus.operational;
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
@ -272,7 +282,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_fetchArticle();
|
||||
_fetchStatuses();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -284,73 +294,124 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Symbols.newspaper),
|
||||
const Icon(Symbols.flare),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'newsToday',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
).tr()
|
||||
],
|
||||
).padding(horizontal: 18, top: 12, bottom: 8),
|
||||
if (_article != null)
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
_article!.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 18),
|
||||
maxLines:
|
||||
MediaQuery.of(context).size.width >= 640 ? 2 : 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
parse(_article!.description)
|
||||
.children
|
||||
.map((e) => e.text.trim())
|
||||
.join(),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 2,
|
||||
children: [
|
||||
Text(DateFormat().format(date)).textStyle(
|
||||
Theme.of(context).textTheme.bodySmall!),
|
||||
Text(' · ')
|
||||
.textStyle(Theme.of(context).textTheme.bodySmall!)
|
||||
.bold(),
|
||||
Text(RelativeTime(context).format(date)).textStyle(
|
||||
Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
).opacity(0.75);
|
||||
}),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'newsDetail',
|
||||
pathParameters: {'hash': _article!.hash},
|
||||
);
|
||||
Expanded(
|
||||
child: Text(
|
||||
'serviceStatus',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
).tr(),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.launch, size: 20),
|
||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||
constraints: const BoxConstraints(),
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
launchUrlString('https://status.solsynth.dev');
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
],
|
||||
).padding(horizontal: 18, top: 12, bottom: 8),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 6),
|
||||
width: double.infinity,
|
||||
color: _serviceStatus == null
|
||||
? Theme.of(context).colorScheme.surfaceContainerHigh
|
||||
: switch (_serviceStatus) {
|
||||
ServiceStatus.operational => Colors.green[300],
|
||||
ServiceStatus.failed => Colors.red[300],
|
||||
_ => Colors.orange[300],
|
||||
},
|
||||
child: _serviceStatus == null
|
||||
? Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.more_horiz,
|
||||
size: 20,
|
||||
),
|
||||
const Gap(10),
|
||||
Text('serviceStatusOperational').tr(),
|
||||
],
|
||||
)
|
||||
: switch (_serviceStatus) {
|
||||
ServiceStatus.operational => Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.check,
|
||||
size: 20,
|
||||
),
|
||||
const Gap(10),
|
||||
Text('serviceStatusOperational').tr(),
|
||||
],
|
||||
),
|
||||
ServiceStatus.failed => Tooltip(
|
||||
message: 'serviceStatusFailedDescription'.tr(),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.dangerous,
|
||||
size: 20,
|
||||
),
|
||||
const Gap(10),
|
||||
Text('serviceStatusFailed').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
_ => Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.error,
|
||||
size: 20,
|
||||
),
|
||||
const Gap(10),
|
||||
Text('serviceStatusDowngraded').tr(),
|
||||
],
|
||||
),
|
||||
},
|
||||
),
|
||||
if (_statuses != null)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final entry in _statuses!.entries)
|
||||
Tooltip(
|
||||
message: kServicesName[entry.key] != null
|
||||
? 'serviceName${kServicesName[entry.key]}'.tr()
|
||||
: 'unknown'.tr(),
|
||||
child: Chip(
|
||||
avatar: entry.value
|
||||
? const Icon(
|
||||
Symbols.circle,
|
||||
color: Colors.green,
|
||||
fill: 1,
|
||||
size: 16,
|
||||
)
|
||||
: AnimateWidgetExtensions(const Icon(
|
||||
Symbols.error,
|
||||
color: Colors.red,
|
||||
fill: 1,
|
||||
size: 16,
|
||||
))
|
||||
.animate(onPlay: (e) => e.repeat())
|
||||
.fadeIn(
|
||||
duration: 500.ms, curve: Curves.easeOut)
|
||||
.then()
|
||||
.fadeOut(
|
||||
duration: 500.ms,
|
||||
delay: 1000.ms,
|
||||
curve: Curves.easeIn,
|
||||
),
|
||||
label: Text(kServicesName[entry.key] ?? entry.key),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 12),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user