Compare commits
No commits in common. "ddd6ff7eee64ba94478a2e1a7f6981e47a82f56b" and "3a10e9280cfc33b272713523a8a16611024e6c9e" have entirely different histories.
ddd6ff7eee
...
3a10e9280c
@ -7,5 +7,5 @@ meta {
|
|||||||
get {
|
get {
|
||||||
url: {{endpoint}}/cgi/re/well-known/sources
|
url: {{endpoint}}/cgi/re/well-known/sources
|
||||||
body: none
|
body: none
|
||||||
auth: inherit
|
auth: none
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ post {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"sources": ["taiwan-pts"],
|
"sources": ["taiwan-ltn"],
|
||||||
"eager": true
|
"eager": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -794,18 +794,5 @@
|
|||||||
"accountStatusPositive": "Positive",
|
"accountStatusPositive": "Positive",
|
||||||
"mixedFeed": "Mixed Feed",
|
"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.",
|
"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",
|
"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.",
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
|
@ -792,18 +792,5 @@
|
|||||||
"accountStatusPositive": "正面",
|
"accountStatusPositive": "正面",
|
||||||
"mixedFeed": "混合推荐流",
|
"mixedFeed": "混合推荐流",
|
||||||
"mixedFeedDescription": "探索页面可能不只会展示用户的帖子,更可能包含其他的内容。但该模式不适用分类和过滤。",
|
"mixedFeedDescription": "探索页面可能不只会展示用户的帖子,更可能包含其他的内容。但该模式不适用分类和过滤。",
|
||||||
"filterFeed": "探索队列调整",
|
"filterFeed": "探索队列调整"
|
||||||
"feedUnknownItem": "无法显示该内容,当前版本客户端不支持该类型的内容,请尝试更新应用程序后再试。",
|
|
||||||
"serviceStatusOperational": "所有服务正常",
|
|
||||||
"serviceStatusDowngraded": "部分服务异常",
|
|
||||||
"serviceStatusFailed": "服务状态异常",
|
|
||||||
"serviceStatusFailedDescription": "服务器炸了或者刚刚执行完维护程序。",
|
|
||||||
"serviceNameInsights": "总结、见解与洞察",
|
|
||||||
"serviceNameInteractive": "帖子与互动",
|
|
||||||
"serviceNameReader": "新闻与链接展开",
|
|
||||||
"serviceNameMessaging": "即使聊天",
|
|
||||||
"serviceNameMatrix": "矩阵市场",
|
|
||||||
"serviceNamePaperclip": "附件",
|
|
||||||
"serviceNameWallet": "源点钱包",
|
|
||||||
"serviceNamePassport": "身份验证与授权"
|
|
||||||
}
|
}
|
||||||
|
@ -789,21 +789,5 @@
|
|||||||
"fieldAccountStatusClearAt": "清除時間",
|
"fieldAccountStatusClearAt": "清除時間",
|
||||||
"accountStatusNegative": "負面",
|
"accountStatusNegative": "負面",
|
||||||
"accountStatusNeutral": "中性",
|
"accountStatusNeutral": "中性",
|
||||||
"accountStatusPositive": "正面",
|
"accountStatusPositive": "正面"
|
||||||
"mixedFeed": "混合推薦流",
|
|
||||||
"mixedFeedDescription": "探索頁面可能不只會展示用户的帖子,更可能包含其他的內容。但該模式不適用分類和過濾。",
|
|
||||||
"filterFeed": "探索隊列調整",
|
|
||||||
"feedUnknownItem": "無法顯示該內容,當前版本客户端不支持該類型的內容,請嘗試更新應用程序後再試。",
|
|
||||||
"serviceStatusOperational": "所有服務正常",
|
|
||||||
"serviceStatusDowngraded": "部分服務異常",
|
|
||||||
"serviceStatusFailed": "服務狀態異常",
|
|
||||||
"serviceStatusFailedDescription": "服務器炸了或者剛剛執行完維護程序。",
|
|
||||||
"serviceNameInsights": "總結、見解與洞察",
|
|
||||||
"serviceNameInteractive": "帖子與互動",
|
|
||||||
"serviceNameReader": "新聞與鏈接展開",
|
|
||||||
"serviceNameMessaging": "即使聊天",
|
|
||||||
"serviceNameMatrix": "矩陣市場",
|
|
||||||
"serviceNamePaperclip": "附件",
|
|
||||||
"serviceNameWallet": "源點錢包",
|
|
||||||
"serviceNamePassport": "身份驗證與授權"
|
|
||||||
}
|
}
|
||||||
|
@ -789,21 +789,5 @@
|
|||||||
"fieldAccountStatusClearAt": "清除時間",
|
"fieldAccountStatusClearAt": "清除時間",
|
||||||
"accountStatusNegative": "負面",
|
"accountStatusNegative": "負面",
|
||||||
"accountStatusNeutral": "中性",
|
"accountStatusNeutral": "中性",
|
||||||
"accountStatusPositive": "正面",
|
"accountStatusPositive": "正面"
|
||||||
"mixedFeed": "混合推薦流",
|
|
||||||
"mixedFeedDescription": "探索頁面可能不只會展示用戶的帖子,更可能包含其他的內容。但該模式不適用分類和過濾。",
|
|
||||||
"filterFeed": "探索隊列調整",
|
|
||||||
"feedUnknownItem": "無法顯示該內容,當前版本客戶端不支持該類型的內容,請嘗試更新應用程序後再試。",
|
|
||||||
"serviceStatusOperational": "所有服務正常",
|
|
||||||
"serviceStatusDowngraded": "部分服務異常",
|
|
||||||
"serviceStatusFailed": "服務狀態異常",
|
|
||||||
"serviceStatusFailedDescription": "服務器炸了或者剛剛執行完維護程序。",
|
|
||||||
"serviceNameInsights": "總結、見解與洞察",
|
|
||||||
"serviceNameInteractive": "帖子與互動",
|
|
||||||
"serviceNameReader": "新聞與鏈接展開",
|
|
||||||
"serviceNameMessaging": "即使聊天",
|
|
||||||
"serviceNameMatrix": "矩陣市場",
|
|
||||||
"serviceNamePaperclip": "附件",
|
|
||||||
"serviceNameWallet": "源點錢包",
|
|
||||||
"serviceNamePassport": "身份驗證與授權"
|
|
||||||
}
|
}
|
||||||
|
@ -17,20 +17,6 @@ import 'package:synchronized/synchronized.dart';
|
|||||||
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
|
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
|
||||||
import 'package:talker_dio_logger/talker_dio_logger_settings.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 = [
|
const kNetworkServerDirectory = [
|
||||||
('Solar Network', 'https://api.sn.solsynth.dev'),
|
('Solar Network', 'https://api.sn.solsynth.dev'),
|
||||||
('Local', 'http://localhost:8001'),
|
('Local', 'http://localhost:8001'),
|
||||||
|
@ -17,8 +17,6 @@ import 'package:surface/types/realm.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/feed/feed_news.dart';
|
|
||||||
import 'package:surface/widgets/feed/feed_unknown.dart';
|
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/fediverse_post_item.dart';
|
import 'package:surface/widgets/post/fediverse_post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
@ -461,12 +459,7 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
final result = await pt.getFeed(
|
final result = await pt.getFeed(cursor: _feed.lastOrNull?.createdAt);
|
||||||
cursor: _feed
|
|
||||||
.where((ele) => !['reader.news'].contains(ele.type))
|
|
||||||
.lastOrNull
|
|
||||||
?.createdAt,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
@ -505,12 +498,7 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final cfg = context.read<ConfigProvider>();
|
_fetchPosts();
|
||||||
if (cfg.mixedFeed) {
|
|
||||||
_fetchFeed();
|
|
||||||
} else {
|
|
||||||
_fetchPosts();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -550,10 +538,8 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
data: SnFediversePost.fromJson(ele.data),
|
data: SnFediversePost.fromJson(ele.data),
|
||||||
maxWidth: 640,
|
maxWidth: 640,
|
||||||
);
|
);
|
||||||
case 'reader.news':
|
|
||||||
return NewsFeedEntry(data: ele);
|
|
||||||
default:
|
default:
|
||||||
return FeedUnknownEntry(data: ele);
|
return Placeholder();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
separatorBuilder: (_, __) => const Gap(8),
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
@ -19,14 +20,13 @@ import 'package:surface/providers/special_day.dart';
|
|||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/providers/widget.dart';
|
import 'package:surface/providers/widget.dart';
|
||||||
import 'package:surface/types/check_in.dart';
|
import 'package:surface/types/check_in.dart';
|
||||||
|
import 'package:surface/types/news.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/updater.dart';
|
import 'package:surface/widgets/updater.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
class HomeScreenDashEntry {
|
class HomeScreenDashEntry {
|
||||||
final String name;
|
final String name;
|
||||||
@ -66,7 +66,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
),
|
),
|
||||||
HomeScreenDashEntry(
|
HomeScreenDashEntry(
|
||||||
name: 'dashEntryTodayNews',
|
name: 'dashEntryTodayNews',
|
||||||
child: _HomeDashServiceStatus(),
|
child: _HomeDashTodayNews(),
|
||||||
cols: MediaQuery.of(context).size.width >= 640 ? 3 : 2,
|
cols: MediaQuery.of(context).size.width >= 640 ? 3 : 2,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -245,31 +245,21 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeDashServiceStatus extends StatefulWidget {
|
class _HomeDashTodayNews extends StatefulWidget {
|
||||||
const _HomeDashServiceStatus();
|
const _HomeDashTodayNews();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_HomeDashServiceStatus> createState() => _HomeDashServiceStatusState();
|
State<_HomeDashTodayNews> createState() => _HomeDashTodayNewsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeDashServiceStatusState extends State<_HomeDashServiceStatus> {
|
class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
||||||
Map<String, dynamic>? _statuses;
|
SnNewsArticle? _article;
|
||||||
ServiceStatus? _serviceStatus;
|
|
||||||
|
|
||||||
Future<void> _fetchStatuses() async {
|
Future<void> _fetchArticle() async {
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/directory/status');
|
final resp = await sn.client.get('/cgi/re/news/today');
|
||||||
_statuses = resp.data;
|
_article = SnNewsArticle.fromJson(resp.data['data']);
|
||||||
if (_statuses!.values.contains(false)) {
|
|
||||||
if (_statuses!.values.contains(true)) {
|
|
||||||
_serviceStatus = ServiceStatus.downgraded;
|
|
||||||
} else {
|
|
||||||
_serviceStatus = ServiceStatus.failed;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_serviceStatus = ServiceStatus.operational;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -282,7 +272,7 @@ class _HomeDashServiceStatusState extends State<_HomeDashServiceStatus> {
|
|||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchStatuses();
|
_fetchArticle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -294,124 +284,73 @@ class _HomeDashServiceStatusState extends State<_HomeDashServiceStatus> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.flare),
|
const Icon(Symbols.newspaper),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Text(
|
||||||
child: Text(
|
'newsToday',
|
||||||
'serviceStatus',
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
).tr()
|
||||||
).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');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 18, top: 12, bottom: 8),
|
).padding(horizontal: 18, top: 12, bottom: 8),
|
||||||
Container(
|
if (_article != null)
|
||||||
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(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: InkWell(
|
||||||
padding: EdgeInsets.only(top: 6),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
child: Wrap(
|
child: Column(
|
||||||
spacing: 8,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
for (final entry in _statuses!.entries)
|
Text(
|
||||||
Tooltip(
|
_article!.title,
|
||||||
message: kServicesName[entry.key] != null
|
style: Theme.of(context)
|
||||||
? 'serviceName${kServicesName[entry.key]}'.tr()
|
.textTheme
|
||||||
: 'unknown'.tr(),
|
.titleMedium!
|
||||||
child: Chip(
|
.copyWith(fontSize: 18),
|
||||||
avatar: entry.value
|
maxLines:
|
||||||
? const Icon(
|
MediaQuery.of(context).size.width >= 640 ? 2 : 1,
|
||||||
Symbols.circle,
|
overflow: TextOverflow.ellipsis,
|
||||||
color: Colors.green,
|
),
|
||||||
fill: 1,
|
Text(
|
||||||
size: 16,
|
parse(_article!.description)
|
||||||
)
|
.children
|
||||||
: AnimateWidgetExtensions(const Icon(
|
.map((e) => e.text.trim())
|
||||||
Symbols.error,
|
.join(),
|
||||||
color: Colors.red,
|
maxLines: 3,
|
||||||
fill: 1,
|
overflow: TextOverflow.ellipsis,
|
||||||
size: 16,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
))
|
),
|
||||||
.animate(onPlay: (e) => e.repeat())
|
Builder(builder: (context) {
|
||||||
.fadeIn(
|
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||||
duration: 500.ms, curve: Curves.easeOut)
|
return Row(
|
||||||
.then()
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
.fadeOut(
|
spacing: 2,
|
||||||
duration: 500.ms,
|
children: [
|
||||||
delay: 1000.ms,
|
Text(DateFormat().format(date)).textStyle(
|
||||||
curve: Curves.easeIn,
|
Theme.of(context).textTheme.bodySmall!),
|
||||||
),
|
Text(' · ')
|
||||||
label: Text(kServicesName[entry.key] ?? entry.key),
|
.textStyle(Theme.of(context).textTheme.bodySmall!)
|
||||||
),
|
.bold(),
|
||||||
),
|
Text(RelativeTime(context).format(date)).textStyle(
|
||||||
|
Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
).opacity(0.75);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 12),
|
).padding(horizontal: 16),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'newsDetail',
|
||||||
|
pathParameters: {'hash': _article!.hash},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
else
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -171,7 +171,7 @@ abstract class SnSubscription with _$SnSubscription {
|
|||||||
abstract class SnFeedEntry with _$SnFeedEntry {
|
abstract class SnFeedEntry with _$SnFeedEntry {
|
||||||
const factory SnFeedEntry({
|
const factory SnFeedEntry({
|
||||||
required String type,
|
required String type,
|
||||||
required dynamic data,
|
required Map<String, dynamic> data,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
}) = _SnFeedEntry;
|
}) = _SnFeedEntry;
|
||||||
|
|
||||||
|
@ -3123,7 +3123,7 @@ class __$SnSubscriptionCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnFeedEntry {
|
mixin _$SnFeedEntry {
|
||||||
String get type;
|
String get type;
|
||||||
dynamic get data;
|
Map<String, dynamic> get data;
|
||||||
DateTime get createdAt;
|
DateTime get createdAt;
|
||||||
|
|
||||||
/// Create a copy of SnFeedEntry
|
/// Create a copy of SnFeedEntry
|
||||||
@ -3164,7 +3164,7 @@ abstract mixin class $SnFeedEntryCopyWith<$Res> {
|
|||||||
SnFeedEntry value, $Res Function(SnFeedEntry) _then) =
|
SnFeedEntry value, $Res Function(SnFeedEntry) _then) =
|
||||||
_$SnFeedEntryCopyWithImpl;
|
_$SnFeedEntryCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({String type, dynamic data, DateTime createdAt});
|
$Res call({String type, Map<String, dynamic> data, DateTime createdAt});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -3180,7 +3180,7 @@ class _$SnFeedEntryCopyWithImpl<$Res> implements $SnFeedEntryCopyWith<$Res> {
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? type = null,
|
Object? type = null,
|
||||||
Object? data = freezed,
|
Object? data = null,
|
||||||
Object? createdAt = null,
|
Object? createdAt = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
@ -3188,10 +3188,10 @@ class _$SnFeedEntryCopyWithImpl<$Res> implements $SnFeedEntryCopyWith<$Res> {
|
|||||||
? _self.type
|
? _self.type
|
||||||
: type // ignore: cast_nullable_to_non_nullable
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
data: freezed == data
|
data: null == data
|
||||||
? _self.data
|
? _self.data
|
||||||
: data // ignore: cast_nullable_to_non_nullable
|
: data // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as Map<String, dynamic>,
|
||||||
createdAt: null == createdAt
|
createdAt: null == createdAt
|
||||||
? _self.createdAt
|
? _self.createdAt
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
@ -3204,14 +3204,23 @@ class _$SnFeedEntryCopyWithImpl<$Res> implements $SnFeedEntryCopyWith<$Res> {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _SnFeedEntry implements SnFeedEntry {
|
class _SnFeedEntry implements SnFeedEntry {
|
||||||
const _SnFeedEntry(
|
const _SnFeedEntry(
|
||||||
{required this.type, required this.data, required this.createdAt});
|
{required this.type,
|
||||||
|
required final Map<String, dynamic> data,
|
||||||
|
required this.createdAt})
|
||||||
|
: _data = data;
|
||||||
factory _SnFeedEntry.fromJson(Map<String, dynamic> json) =>
|
factory _SnFeedEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnFeedEntryFromJson(json);
|
_$SnFeedEntryFromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String type;
|
final String type;
|
||||||
|
final Map<String, dynamic> _data;
|
||||||
@override
|
@override
|
||||||
final dynamic data;
|
Map<String, dynamic> get data {
|
||||||
|
if (_data is EqualUnmodifiableMapView) return _data;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_data);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
|
|
||||||
@ -3236,7 +3245,7 @@ class _SnFeedEntry implements SnFeedEntry {
|
|||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _SnFeedEntry &&
|
other is _SnFeedEntry &&
|
||||||
(identical(other.type, type) || other.type == type) &&
|
(identical(other.type, type) || other.type == type) &&
|
||||||
const DeepCollectionEquality().equals(other.data, data) &&
|
const DeepCollectionEquality().equals(other._data, _data) &&
|
||||||
(identical(other.createdAt, createdAt) ||
|
(identical(other.createdAt, createdAt) ||
|
||||||
other.createdAt == createdAt));
|
other.createdAt == createdAt));
|
||||||
}
|
}
|
||||||
@ -3244,7 +3253,7 @@ class _SnFeedEntry implements SnFeedEntry {
|
|||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(
|
||||||
runtimeType, type, const DeepCollectionEquality().hash(data), createdAt);
|
runtimeType, type, const DeepCollectionEquality().hash(_data), createdAt);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -3260,7 +3269,7 @@ abstract mixin class _$SnFeedEntryCopyWith<$Res>
|
|||||||
__$SnFeedEntryCopyWithImpl;
|
__$SnFeedEntryCopyWithImpl;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({String type, dynamic data, DateTime createdAt});
|
$Res call({String type, Map<String, dynamic> data, DateTime createdAt});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -3276,7 +3285,7 @@ class __$SnFeedEntryCopyWithImpl<$Res> implements _$SnFeedEntryCopyWith<$Res> {
|
|||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? type = null,
|
Object? type = null,
|
||||||
Object? data = freezed,
|
Object? data = null,
|
||||||
Object? createdAt = null,
|
Object? createdAt = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_SnFeedEntry(
|
return _then(_SnFeedEntry(
|
||||||
@ -3284,10 +3293,10 @@ class __$SnFeedEntryCopyWithImpl<$Res> implements _$SnFeedEntryCopyWith<$Res> {
|
|||||||
? _self.type
|
? _self.type
|
||||||
: type // ignore: cast_nullable_to_non_nullable
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
data: freezed == data
|
data: null == data
|
||||||
? _self.data
|
? _self._data
|
||||||
: data // ignore: cast_nullable_to_non_nullable
|
: data // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as Map<String, dynamic>,
|
||||||
createdAt: null == createdAt
|
createdAt: null == createdAt
|
||||||
? _self.createdAt
|
? _self.createdAt
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
@ -285,7 +285,7 @@ Map<String, dynamic> _$SnSubscriptionToJson(_SnSubscription instance) =>
|
|||||||
|
|
||||||
_SnFeedEntry _$SnFeedEntryFromJson(Map<String, dynamic> json) => _SnFeedEntry(
|
_SnFeedEntry _$SnFeedEntryFromJson(Map<String, dynamic> json) => _SnFeedEntry(
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
data: json['data'],
|
data: json['data'] as Map<String, dynamic>,
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
|
||||||
import 'package:relative_time/relative_time.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
|
||||||
import 'package:surface/types/news.dart';
|
|
||||||
import 'package:surface/types/post.dart';
|
|
||||||
|
|
||||||
class NewsFeedEntry extends StatelessWidget {
|
|
||||||
final SnFeedEntry data;
|
|
||||||
const NewsFeedEntry({super.key, required this.data});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final List<SnNewsArticle> news = data.data
|
|
||||||
.map((ele) => SnNewsArticle.fromJson(ele))
|
|
||||||
.cast<SnNewsArticle>()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.newspaper),
|
|
||||||
const Gap(8),
|
|
||||||
Text(
|
|
||||||
'newsToday',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
).tr()
|
|
||||||
],
|
|
||||||
).padding(horizontal: 18, top: 12, bottom: 8),
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
|
||||||
height: 150,
|
|
||||||
child: ListView.separated(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: news.length,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
itemBuilder: (context, idx) {
|
|
||||||
return Container(
|
|
||||||
width: 360,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
elevation: 0,
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
news[idx].title,
|
|
||||||
maxLines: 2,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
).padding(horizontal: 16, top: 12, bottom: 4),
|
|
||||||
Text(
|
|
||||||
news[idx].description,
|
|
||||||
maxLines: 2,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
).padding(horizontal: 16, vertical: 4),
|
|
||||||
const Gap(4),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateFormat('y/M/d HH:mm')
|
|
||||||
.format(news[idx].createdAt.toLocal()),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
RelativeTime(context)
|
|
||||||
.format(news[idx].createdAt.toLocal()),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).opacity(0.8).padding(horizontal: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'newsDetail',
|
|
||||||
pathParameters: {'hash': news[idx].hash},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (_, __) => const Gap(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
|
||||||
import 'package:surface/types/post.dart';
|
|
||||||
|
|
||||||
class FeedUnknownEntry extends StatelessWidget {
|
|
||||||
final SnFeedEntry data;
|
|
||||||
const FeedUnknownEntry({super.key, required this.data});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Card(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.help, size: 36),
|
|
||||||
const Gap(4),
|
|
||||||
Text('feedUnknownItem').tr(),
|
|
||||||
Text(data.type, style: GoogleFonts.robotoMono()),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 12, vertical: 8),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user