✨ News reader
This commit is contained in:
parent
963e538ae5
commit
5377161fb0
@ -5,7 +5,7 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{endpoint}}/cgi/re/news?take=10&offset=0&source=taiwan-pts
|
||||
url: {{endpoint}}/cgi/re/news?take=10&offset=0&source=shadiao
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@ -13,5 +13,5 @@ get {
|
||||
params:query {
|
||||
take: 10
|
||||
offset: 0
|
||||
source: taiwan-pts
|
||||
source: shadiao
|
||||
}
|
||||
|
@ -563,5 +563,6 @@
|
||||
"newsAllSources": "All News",
|
||||
"newsReadingProviderSwap": "Swap",
|
||||
"newsReadingFromReader": "You're reading from HyperNet.Reader",
|
||||
"newsReadingFromOriginal": "You're reading the original article"
|
||||
"newsReadingFromOriginal": "You're reading the original article",
|
||||
"newsDisclaimer": "This article is fetched from the Internet, we do not guarantee its authenticity, please judge for yourself. All content in this article belongs to the original author."
|
||||
}
|
||||
|
@ -561,5 +561,6 @@
|
||||
"newsAllSources": "所有新闻",
|
||||
"newsReadingProviderSwap": "切换",
|
||||
"newsReadingFromReader": "你正在从 HyperNet.Reader 阅读文章",
|
||||
"newsReadingFromOriginal": "你正在阅读原始文章"
|
||||
"newsReadingFromOriginal": "你正在阅读原始文章",
|
||||
"newsDisclaimer": "本文由 HyperNet.Reader 从互联网上获取,我们不担保其内容的真实性,请自行判断。本文章的所有内容版权归原作者所有。"
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
"screenAccountProfileEdit": "編輯資料",
|
||||
"screenAbuseReport": "濫用檢舉",
|
||||
"screenSettings": "設置",
|
||||
"screenNews": "新聞",
|
||||
"screenAlbum": "相冊",
|
||||
"screenChat": "聊天",
|
||||
"screenChatManage": "編輯聊天頻道",
|
||||
@ -194,6 +195,10 @@
|
||||
"settingsFeatures": "功能",
|
||||
"settingsNotifyWithHaptic": "新通知時振動",
|
||||
"settingsNotifyWithHapticDescription": "在應用在前台時收到新通知出現時出發輕量的振動。",
|
||||
"settingsExpandPostLink": "展開帖子鏈接",
|
||||
"settingsExpandPostLinkDescription": "在帖子列表中展開顯示帖子中的鏈接。",
|
||||
"settingsExpandChatLink": "展開聊天鏈接",
|
||||
"settingsExpandChatLinkDescription": "在聊天信息中展開顯示內容中的鏈接。",
|
||||
"settingsNetwork": "網絡",
|
||||
"settingsNetworkServer": "HyperNet 服務器",
|
||||
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
||||
@ -552,5 +557,10 @@
|
||||
"postCategoryKnowledge": "知識",
|
||||
"postCategoryLiterature": "文學",
|
||||
"postCategoryFunny": "搞笑",
|
||||
"postCategoryUncategorized": "未分類"
|
||||
"postCategoryUncategorized": "未分類",
|
||||
"newsAllSources": "所有新聞",
|
||||
"newsReadingProviderSwap": "切換",
|
||||
"newsReadingFromReader": "你正在從 HyperNet.Reader 閲讀文章",
|
||||
"newsReadingFromOriginal": "你正在閲讀原始文章",
|
||||
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。"
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
"screenAccountProfileEdit": "編輯資料",
|
||||
"screenAbuseReport": "濫用檢舉",
|
||||
"screenSettings": "設置",
|
||||
"screenNews": "新聞",
|
||||
"screenAlbum": "相冊",
|
||||
"screenChat": "聊天",
|
||||
"screenChatManage": "編輯聊天頻道",
|
||||
@ -194,6 +195,10 @@
|
||||
"settingsFeatures": "功能",
|
||||
"settingsNotifyWithHaptic": "新通知時振動",
|
||||
"settingsNotifyWithHapticDescription": "在應用在前臺時收到新通知出現時出發輕量的振動。",
|
||||
"settingsExpandPostLink": "展開帖子鏈接",
|
||||
"settingsExpandPostLinkDescription": "在帖子列表中展開顯示帖子中的鏈接。",
|
||||
"settingsExpandChatLink": "展開聊天鏈接",
|
||||
"settingsExpandChatLinkDescription": "在聊天信息中展開顯示內容中的鏈接。",
|
||||
"settingsNetwork": "網絡",
|
||||
"settingsNetworkServer": "HyperNet 服務器",
|
||||
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
||||
@ -552,5 +557,10 @@
|
||||
"postCategoryKnowledge": "知識",
|
||||
"postCategoryLiterature": "文學",
|
||||
"postCategoryFunny": "搞笑",
|
||||
"postCategoryUncategorized": "未分類"
|
||||
"postCategoryUncategorized": "未分類",
|
||||
"newsAllSources": "所有新聞",
|
||||
"newsReadingProviderSwap": "切換",
|
||||
"newsReadingFromReader": "你正在從 HyperNet.Reader 閱讀文章",
|
||||
"newsReadingFromOriginal": "你正在閱讀原始文章",
|
||||
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。"
|
||||
}
|
||||
|
@ -1,11 +1,19 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:html/dom.dart' as dom;
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/news.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class NewsDetailScreen extends StatefulWidget {
|
||||
final String hash;
|
||||
@ -18,12 +26,14 @@ class NewsDetailScreen extends StatefulWidget {
|
||||
|
||||
class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
||||
SnNewsArticle? _article;
|
||||
dom.Document? _articleFragment;
|
||||
|
||||
Future<void> _fetchArticle() async {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/re/news/${widget.hash}');
|
||||
_article = SnNewsArticle.fromJson(resp.data);
|
||||
_articleFragment = parse(_article!.content);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err).then((_) {
|
||||
@ -35,6 +45,96 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _parseHtmlToWidgets(Iterable<dom.Element>? elements) {
|
||||
if (elements == null) return [];
|
||||
|
||||
final List<Widget> widgets = [];
|
||||
|
||||
for (final node in elements) {
|
||||
switch (node.localName) {
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
case 'h6':
|
||||
widgets.add(Text(node.text.trim(), style: Theme.of(context).textTheme.titleMedium));
|
||||
break;
|
||||
case 'p':
|
||||
if (node.text.trim().isEmpty) continue;
|
||||
widgets.add(
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
text: node.text.trim(),
|
||||
children: [
|
||||
for (final child in node.children)
|
||||
switch (child.localName) {
|
||||
'a' => TextSpan(
|
||||
text: child.text.trim(),
|
||||
style: const TextStyle(decoration: TextDecoration.underline),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrlString(child.attributes['href']!);
|
||||
},
|
||||
),
|
||||
_ => TextSpan(text: child.text.trim()),
|
||||
},
|
||||
],
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case 'a':
|
||||
// drop single link
|
||||
break;
|
||||
case 'div':
|
||||
// ignore div text, normally it is not meaningful
|
||||
widgets.addAll(_parseHtmlToWidgets(node.children));
|
||||
break;
|
||||
case 'hr':
|
||||
widgets.add(const Divider());
|
||||
break;
|
||||
case 'img':
|
||||
var src = node.attributes['src'];
|
||||
if (src == null) break;
|
||||
final width = double.tryParse(node.attributes['width'] ?? 'null');
|
||||
final height = double.tryParse(node.attributes['height'] ?? 'null');
|
||||
final ratio = width != null && height != null ? width / height : 1.0;
|
||||
if (!src.startsWith('http')) {
|
||||
final baseUri = Uri.parse(_article!.url);
|
||||
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
||||
src = '$baseUrl/$src';
|
||||
}
|
||||
widgets.add(
|
||||
AspectRatio(
|
||||
aspectRatio: ratio,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
height: height ?? double.infinity,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
child: AutoResizeUniversalImage(src, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
widgets.addAll(_parseHtmlToWidgets(node.children));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -43,27 +143,6 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
||||
|
||||
bool _isReadingFromReader = true;
|
||||
|
||||
String get _htmlContent => """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color: #000000;
|
||||
color: #fff;
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${_article?.content ?? ''}
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
InAppWebViewController? _webViewController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppScaffold(
|
||||
@ -82,30 +161,68 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
||||
child: Text('newsReadingProviderSwap').tr(),
|
||||
onPressed: () {
|
||||
setState(() => _isReadingFromReader = !_isReadingFromReader);
|
||||
if (!_isReadingFromReader) {
|
||||
_webViewController?.goTo(historyItem: WebHistoryItem(url: WebUri(_article!.url)));
|
||||
} else {
|
||||
_webViewController?.goBack();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: InAppWebView(
|
||||
key: Key('news-detail-webview-${widget.hash}-$_isReadingFromReader'),
|
||||
onWebViewCreated: (controller) {
|
||||
_webViewController = controller;
|
||||
},
|
||||
initialUrlRequest: URLRequest(url: WebUri(_article!.url)),
|
||||
onLoadStop: (controller, url) {
|
||||
print("Loaded: $url");
|
||||
},
|
||||
onLoadError: (controller, url, code, message) {
|
||||
print("Error loading $url: $message ($code)");
|
||||
},
|
||||
if (_articleFragment != null && _isReadingFromReader)
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
|
||||
Builder(builder: (context) {
|
||||
final htmlDescription = parse(_article!.description);
|
||||
return Text(
|
||||
htmlDescription.children.map((ele) => ele.text.trim()).join(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
);
|
||||
}),
|
||||
Builder(builder: (context) {
|
||||
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||
return Row(
|
||||
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);
|
||||
}),
|
||||
Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
|
||||
const Divider(),
|
||||
..._parseHtmlToWidgets(_articleFragment!.children),
|
||||
const Divider(),
|
||||
InkWell(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Reference from original website',
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
const Gap(4),
|
||||
Icon(Icons.launch, size: 16),
|
||||
],
|
||||
).opacity(0.85),
|
||||
onTap: () {
|
||||
launchUrlString(_article!.url);
|
||||
},
|
||||
),
|
||||
Gap(MediaQuery.of(context).padding.bottom),
|
||||
],
|
||||
).padding(horizontal: 12, vertical: 16),
|
||||
),
|
||||
)
|
||||
else if (_article != null)
|
||||
Expanded(
|
||||
child: InAppWebView(
|
||||
key: GlobalKey(),
|
||||
initialUrlRequest: URLRequest(url: WebUri(_article!.url)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -2,7 +2,9 @@ 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:html/parser.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/news.dart';
|
||||
@ -81,8 +83,12 @@ class _NewsScreenState extends State<NewsScreen> {
|
||||
},
|
||||
body: TabBarView(
|
||||
children: [
|
||||
_NewsArticleListWidget(),
|
||||
for (final source in _sources!) _NewsArticleListWidget(source: source.id),
|
||||
_NewsArticleListWidget(allSources: _sources!),
|
||||
for (final source in _sources!)
|
||||
_NewsArticleListWidget(
|
||||
source: source.id,
|
||||
allSources: _sources!,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -93,8 +99,9 @@ class _NewsScreenState extends State<NewsScreen> {
|
||||
|
||||
class _NewsArticleListWidget extends StatefulWidget {
|
||||
final String? source;
|
||||
final List<SnNewsSource> allSources;
|
||||
|
||||
const _NewsArticleListWidget({this.source});
|
||||
const _NewsArticleListWidget({this.source, required this.allSources});
|
||||
|
||||
@override
|
||||
State<_NewsArticleListWidget> createState() => _NewsArticleListWidgetState();
|
||||
@ -154,6 +161,9 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> {
|
||||
final baseUri = Uri.parse(article.url);
|
||||
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
||||
|
||||
final htmlDescription = parse(article.description);
|
||||
final date = article.publishedAt ?? article.createdAt;
|
||||
|
||||
return Card(
|
||||
child: InkWell(
|
||||
radius: 8,
|
||||
@ -164,21 +174,43 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> {
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: AutoResizeUniversalImage('$baseUrl/${article.thumbnail}'),
|
||||
child: AutoResizeUniversalImage(
|
||||
article.thumbnail.startsWith('http') ? article.thumbnail : '$baseUrl/${article.thumbnail}',
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
Text(article.description).textStyle(Theme.of(context).textTheme.bodyMedium!),
|
||||
const Gap(16),
|
||||
Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16),
|
||||
const Gap(8),
|
||||
Text(article.source).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
|
||||
.textStyle(Theme.of(context).textTheme.bodyMedium!)
|
||||
.padding(horizontal: 16),
|
||||
const Gap(8),
|
||||
Row(
|
||||
spacing: 2,
|
||||
children: [
|
||||
Text(widget.allSources.where((x) => x.id == article.source).first.label)
|
||||
.textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
).opacity(0.75).padding(horizontal: 16),
|
||||
Row(
|
||||
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),
|
||||
const Gap(16),
|
||||
],
|
||||
).padding(all: 8),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -31,7 +31,7 @@ class SnNewsArticle with _$SnNewsArticle {
|
||||
required String url,
|
||||
required String hash,
|
||||
required String source,
|
||||
required dynamic publishedAt,
|
||||
required DateTime? publishedAt,
|
||||
}) = _SnNewsArticle;
|
||||
|
||||
factory SnNewsArticle.fromJson(Map<String, dynamic> json) => _$SnNewsArticleFromJson(json);
|
||||
|
@ -285,7 +285,7 @@ mixin _$SnNewsArticle {
|
||||
String get url => throw _privateConstructorUsedError;
|
||||
String get hash => throw _privateConstructorUsedError;
|
||||
String get source => throw _privateConstructorUsedError;
|
||||
dynamic get publishedAt => throw _privateConstructorUsedError;
|
||||
DateTime? get publishedAt => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnNewsArticle to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -315,7 +315,7 @@ abstract class $SnNewsArticleCopyWith<$Res> {
|
||||
String url,
|
||||
String hash,
|
||||
String source,
|
||||
dynamic publishedAt});
|
||||
DateTime? publishedAt});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -394,7 +394,7 @@ class _$SnNewsArticleCopyWithImpl<$Res, $Val extends SnNewsArticle>
|
||||
publishedAt: freezed == publishedAt
|
||||
? _value.publishedAt
|
||||
: publishedAt // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
as DateTime?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@ -419,7 +419,7 @@ abstract class _$$SnNewsArticleImplCopyWith<$Res>
|
||||
String url,
|
||||
String hash,
|
||||
String source,
|
||||
dynamic publishedAt});
|
||||
DateTime? publishedAt});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -496,7 +496,7 @@ class __$$SnNewsArticleImplCopyWithImpl<$Res>
|
||||
publishedAt: freezed == publishedAt
|
||||
? _value.publishedAt
|
||||
: publishedAt // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -544,7 +544,7 @@ class _$SnNewsArticleImpl implements _SnNewsArticle {
|
||||
@override
|
||||
final String source;
|
||||
@override
|
||||
final dynamic publishedAt;
|
||||
final DateTime? publishedAt;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -571,8 +571,8 @@ class _$SnNewsArticleImpl implements _SnNewsArticle {
|
||||
(identical(other.url, url) || other.url == url) &&
|
||||
(identical(other.hash, hash) || other.hash == hash) &&
|
||||
(identical(other.source, source) || other.source == source) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.publishedAt, publishedAt));
|
||||
(identical(other.publishedAt, publishedAt) ||
|
||||
other.publishedAt == publishedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -590,7 +590,7 @@ class _$SnNewsArticleImpl implements _SnNewsArticle {
|
||||
url,
|
||||
hash,
|
||||
source,
|
||||
const DeepCollectionEquality().hash(publishedAt));
|
||||
publishedAt);
|
||||
|
||||
/// Create a copy of SnNewsArticle
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -621,7 +621,7 @@ abstract class _SnNewsArticle implements SnNewsArticle {
|
||||
required final String url,
|
||||
required final String hash,
|
||||
required final String source,
|
||||
required final dynamic publishedAt}) = _$SnNewsArticleImpl;
|
||||
required final DateTime? publishedAt}) = _$SnNewsArticleImpl;
|
||||
|
||||
factory _SnNewsArticle.fromJson(Map<String, dynamic> json) =
|
||||
_$SnNewsArticleImpl.fromJson;
|
||||
@ -649,7 +649,7 @@ abstract class _SnNewsArticle implements SnNewsArticle {
|
||||
@override
|
||||
String get source;
|
||||
@override
|
||||
dynamic get publishedAt;
|
||||
DateTime? get publishedAt;
|
||||
|
||||
/// Create a copy of SnNewsArticle
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
@ -39,7 +39,9 @@ _$SnNewsArticleImpl _$$SnNewsArticleImplFromJson(Map<String, dynamic> json) =>
|
||||
url: json['url'] as String,
|
||||
hash: json['hash'] as String,
|
||||
source: json['source'] as String,
|
||||
publishedAt: json['published_at'],
|
||||
publishedAt: json['published_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['published_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnNewsArticleImplToJson(_$SnNewsArticleImpl instance) =>
|
||||
@ -55,5 +57,5 @@ Map<String, dynamic> _$$SnNewsArticleImplToJson(_$SnNewsArticleImpl instance) =>
|
||||
'url': instance.url,
|
||||
'hash': instance.hash,
|
||||
'source': instance.source,
|
||||
'published_at': instance.publishedAt,
|
||||
'published_at': instance.publishedAt?.toIso8601String(),
|
||||
};
|
||||
|
@ -939,7 +939,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: html
|
||||
sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec"
|
||||
|
@ -116,6 +116,7 @@ dependencies:
|
||||
video_compress: ^3.1.3
|
||||
cached_network_image: ^3.4.1
|
||||
flutter_inappwebview: ^6.1.5
|
||||
html: ^0.15.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user