diff --git a/api/Reader/Trigger Scan News.bru b/api/Reader/Trigger Scan News.bru index 631df8a..69c2d8f 100644 --- a/api/Reader/Trigger Scan News.bru +++ b/api/Reader/Trigger Scan News.bru @@ -11,8 +11,5 @@ post { } body:json { - { - "sources": ["taiwan-pts"], - "eager": true - } + {} } diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 85c751a..bfe9f82 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -17,10 +17,9 @@ import 'package:surface/types/realm.dart'; import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/dialog.dart'; -import 'package:surface/widgets/feed/feed_news.dart'; +import 'package:surface/widgets/feed/feed_reader.dart'; import 'package:surface/widgets/feed/feed_unknown.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; -import 'package:surface/widgets/post/fediverse_post_item.dart'; import 'package:surface/widgets/post/post_item.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @@ -549,12 +548,7 @@ class _PostListWidgetState extends State<_PostListWidget> { refreshPosts(); }, ); - case 'fediverse.post': - return FediversePostWidget( - data: SnFediversePost.fromJson(ele.data), - maxWidth: 640, - ); - case 'reader.news': + case 'reader.feed': return Center( child: Container( constraints: BoxConstraints(maxWidth: 640), diff --git a/lib/screens/news/news_detail.dart b/lib/screens/news/news_detail.dart index 7ca1433..41aec50 100644 --- a/lib/screens/news/news_detail.dart +++ b/lib/screens/news/news_detail.dart @@ -24,13 +24,13 @@ class NewsDetailScreen extends StatefulWidget { } class _NewsDetailScreenState extends State { - SnNewsArticle? _article; + SnSubscriptionItem? _article; Future _fetchArticle() async { try { final sn = context.read(); final resp = await sn.client.get('/cgi/re/news/${widget.hash}'); - _article = SnNewsArticle.fromJson(resp.data); + _article = SnSubscriptionItem.fromJson(resp.data); } catch (err) { if (!mounted) return; context.showErrorDialog(err).then((_) { diff --git a/lib/screens/news/news_list.dart b/lib/screens/news/news_list.dart index 4a55ed6..621b35f 100644 --- a/lib/screens/news/news_list.dart +++ b/lib/screens/news/news_list.dart @@ -66,7 +66,8 @@ class _NewsScreenState extends State { headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + handle: + NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: SliverAppBar( leading: AutoAppBarLeading(), title: Text('screenNews').tr(), @@ -75,10 +76,13 @@ class _NewsScreenState extends State { bottom: TabBar( isScrollable: true, tabs: [ - Tab(child: Text('newsAllSources'.tr()).textColor(Theme.of(context).appBarTheme.foregroundColor)), + Tab( + child: Text('newsAllSources'.tr()).textColor( + Theme.of(context).appBarTheme.foregroundColor)), for (final source in _sources!) Tab( - child: Text(source.label).textColor(Theme.of(context).appBarTheme.foregroundColor), + child: Text(source.label).textColor( + Theme.of(context).appBarTheme.foregroundColor), ), ], ), @@ -116,7 +120,7 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> { bool _isBusy = false; int? _totalCount; - final List _articles = List.empty(growable: true); + final List _articles = List.empty(growable: true); Future _fetchArticles() async { setState(() => _isBusy = true); @@ -129,8 +133,8 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> { if (widget.source != null) 'source': widget.source, }); _totalCount = resp.data['count']; - _articles.addAll(List.from( - resp.data['data']?.map((e) => SnNewsArticle.fromJson(e)) ?? [], + _articles.addAll(List.from( + resp.data['data']?.map((e) => SnSubscriptionItem.fromJson(e)) ?? [], )); } catch (err) { if (!mounted) return; @@ -159,7 +163,8 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> { child: InfiniteList( isLoading: _isBusy, itemCount: _articles.length, - hasReachedMax: _totalCount != null && _articles.length >= _totalCount!, + hasReachedMax: + _totalCount != null && _articles.length >= _totalCount!, onFetchData: () { _fetchArticles(); }, @@ -184,7 +189,8 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg')) + if (article.thumbnail.isNotEmpty && + !article.thumbnail.endsWith('.svg')) ClipRRect( borderRadius: BorderRadius.only( topRight: Radius.circular(8), @@ -193,7 +199,9 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> { child: AspectRatio( aspectRatio: 16 / 9, child: Container( - color: Theme.of(context).colorScheme.surfaceContainer, + color: Theme.of(context) + .colorScheme + .surfaceContainer, child: AutoResizeUniversalImage( article.thumbnail.startsWith('http') ? article.thumbnail @@ -203,25 +211,38 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> { ), ), const Gap(16), - Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16), + Text(article.title) + .textStyle(Theme.of(context).textTheme.titleLarge!) + .padding(horizontal: 16), const Gap(8), - Text(htmlDescription.children.map((ele) => ele.text.trim()).join()) + 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!), + Text(widget.allSources + .where((x) => x.id == article.feedId) + .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!), + 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), diff --git a/lib/types/news.dart b/lib/types/news.dart index 83f4138..06d17ec 100644 --- a/lib/types/news.dart +++ b/lib/types/news.dart @@ -14,25 +14,27 @@ abstract class SnNewsSource with _$SnNewsSource { required bool enabled, }) = _SnNewsSource; - factory SnNewsSource.fromJson(Map json) => _$SnNewsSourceFromJson(json); + factory SnNewsSource.fromJson(Map json) => + _$SnNewsSourceFromJson(json); } @freezed -abstract class SnNewsArticle with _$SnNewsArticle { - const factory SnNewsArticle({ +abstract class SnSubscriptionItem with _$SnSubscriptionItem { + const factory SnSubscriptionItem({ required int id, required DateTime createdAt, required DateTime updatedAt, - required dynamic deletedAt, + required DateTime? deletedAt, required String thumbnail, required String title, required String description, required String content, required String url, required String hash, - required String source, + required int feedId, required DateTime? publishedAt, - }) = _SnNewsArticle; + }) = _SnSubscriptionItem; - factory SnNewsArticle.fromJson(Map json) => _$SnNewsArticleFromJson(json); + factory SnSubscriptionItem.fromJson(Map json) => + _$SnSubscriptionItemFromJson(json); } diff --git a/lib/types/news.freezed.dart b/lib/types/news.freezed.dart index 5887bc8..a995cc1 100644 --- a/lib/types/news.freezed.dart +++ b/lib/types/news.freezed.dart @@ -252,42 +252,43 @@ class __$SnNewsSourceCopyWithImpl<$Res> } /// @nodoc -mixin _$SnNewsArticle { +mixin _$SnSubscriptionItem { int get id; DateTime get createdAt; DateTime get updatedAt; - dynamic get deletedAt; + DateTime? get deletedAt; String get thumbnail; String get title; String get description; String get content; String get url; String get hash; - String get source; + int get feedId; DateTime? get publishedAt; - /// Create a copy of SnNewsArticle + /// Create a copy of SnSubscriptionItem /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') - $SnNewsArticleCopyWith get copyWith => - _$SnNewsArticleCopyWithImpl( - this as SnNewsArticle, _$identity); + $SnSubscriptionItemCopyWith get copyWith => + _$SnSubscriptionItemCopyWithImpl( + this as SnSubscriptionItem, _$identity); - /// Serializes this SnNewsArticle to a JSON map. + /// Serializes this SnSubscriptionItem to a JSON map. Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is SnNewsArticle && + other is SnSubscriptionItem && (identical(other.id, id) || other.id == id) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt) && - const DeepCollectionEquality().equals(other.deletedAt, deletedAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && (identical(other.thumbnail, thumbnail) || other.thumbnail == thumbnail) && (identical(other.title, title) || other.title == title) && @@ -296,7 +297,7 @@ mixin _$SnNewsArticle { (identical(other.content, content) || other.content == content) && (identical(other.url, url) || other.url == url) && (identical(other.hash, hash) || other.hash == hash) && - (identical(other.source, source) || other.source == source) && + (identical(other.feedId, feedId) || other.feedId == feedId) && (identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); } @@ -308,52 +309,52 @@ mixin _$SnNewsArticle { id, createdAt, updatedAt, - const DeepCollectionEquality().hash(deletedAt), + deletedAt, thumbnail, title, description, content, url, hash, - source, + feedId, publishedAt); @override String toString() { - return 'SnNewsArticle(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, thumbnail: $thumbnail, title: $title, description: $description, content: $content, url: $url, hash: $hash, source: $source, publishedAt: $publishedAt)'; + return 'SnSubscriptionItem(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, thumbnail: $thumbnail, title: $title, description: $description, content: $content, url: $url, hash: $hash, feedId: $feedId, publishedAt: $publishedAt)'; } } /// @nodoc -abstract mixin class $SnNewsArticleCopyWith<$Res> { - factory $SnNewsArticleCopyWith( - SnNewsArticle value, $Res Function(SnNewsArticle) _then) = - _$SnNewsArticleCopyWithImpl; +abstract mixin class $SnSubscriptionItemCopyWith<$Res> { + factory $SnSubscriptionItemCopyWith( + SnSubscriptionItem value, $Res Function(SnSubscriptionItem) _then) = + _$SnSubscriptionItemCopyWithImpl; @useResult $Res call( {int id, DateTime createdAt, DateTime updatedAt, - dynamic deletedAt, + DateTime? deletedAt, String thumbnail, String title, String description, String content, String url, String hash, - String source, + int feedId, DateTime? publishedAt}); } /// @nodoc -class _$SnNewsArticleCopyWithImpl<$Res> - implements $SnNewsArticleCopyWith<$Res> { - _$SnNewsArticleCopyWithImpl(this._self, this._then); +class _$SnSubscriptionItemCopyWithImpl<$Res> + implements $SnSubscriptionItemCopyWith<$Res> { + _$SnSubscriptionItemCopyWithImpl(this._self, this._then); - final SnNewsArticle _self; - final $Res Function(SnNewsArticle) _then; + final SnSubscriptionItem _self; + final $Res Function(SnSubscriptionItem) _then; - /// Create a copy of SnNewsArticle + /// Create a copy of SnSubscriptionItem /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -368,7 +369,7 @@ class _$SnNewsArticleCopyWithImpl<$Res> Object? content = null, Object? url = null, Object? hash = null, - Object? source = null, + Object? feedId = null, Object? publishedAt = freezed, }) { return _then(_self.copyWith( @@ -387,7 +388,7 @@ class _$SnNewsArticleCopyWithImpl<$Res> deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable - as dynamic, + as DateTime?, thumbnail: null == thumbnail ? _self.thumbnail : thumbnail // ignore: cast_nullable_to_non_nullable @@ -412,10 +413,10 @@ class _$SnNewsArticleCopyWithImpl<$Res> ? _self.hash : hash // ignore: cast_nullable_to_non_nullable as String, - source: null == source - ? _self.source - : source // ignore: cast_nullable_to_non_nullable - as String, + feedId: null == feedId + ? _self.feedId + : feedId // ignore: cast_nullable_to_non_nullable + as int, publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable @@ -426,8 +427,8 @@ class _$SnNewsArticleCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _SnNewsArticle implements SnNewsArticle { - const _SnNewsArticle( +class _SnSubscriptionItem implements SnSubscriptionItem { + const _SnSubscriptionItem( {required this.id, required this.createdAt, required this.updatedAt, @@ -438,10 +439,10 @@ class _SnNewsArticle implements SnNewsArticle { required this.content, required this.url, required this.hash, - required this.source, + required this.feedId, required this.publishedAt}); - factory _SnNewsArticle.fromJson(Map json) => - _$SnNewsArticleFromJson(json); + factory _SnSubscriptionItem.fromJson(Map json) => + _$SnSubscriptionItemFromJson(json); @override final int id; @@ -450,7 +451,7 @@ class _SnNewsArticle implements SnNewsArticle { @override final DateTime updatedAt; @override - final dynamic deletedAt; + final DateTime? deletedAt; @override final String thumbnail; @override @@ -464,21 +465,21 @@ class _SnNewsArticle implements SnNewsArticle { @override final String hash; @override - final String source; + final int feedId; @override final DateTime? publishedAt; - /// Create a copy of SnNewsArticle + /// Create a copy of SnSubscriptionItem /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') - _$SnNewsArticleCopyWith<_SnNewsArticle> get copyWith => - __$SnNewsArticleCopyWithImpl<_SnNewsArticle>(this, _$identity); + _$SnSubscriptionItemCopyWith<_SnSubscriptionItem> get copyWith => + __$SnSubscriptionItemCopyWithImpl<_SnSubscriptionItem>(this, _$identity); @override Map toJson() { - return _$SnNewsArticleToJson( + return _$SnSubscriptionItemToJson( this, ); } @@ -487,13 +488,14 @@ class _SnNewsArticle implements SnNewsArticle { bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _SnNewsArticle && + other is _SnSubscriptionItem && (identical(other.id, id) || other.id == id) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt) && - const DeepCollectionEquality().equals(other.deletedAt, deletedAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && (identical(other.thumbnail, thumbnail) || other.thumbnail == thumbnail) && (identical(other.title, title) || other.title == title) && @@ -502,7 +504,7 @@ class _SnNewsArticle implements SnNewsArticle { (identical(other.content, content) || other.content == content) && (identical(other.url, url) || other.url == url) && (identical(other.hash, hash) || other.hash == hash) && - (identical(other.source, source) || other.source == source) && + (identical(other.feedId, feedId) || other.feedId == feedId) && (identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); } @@ -514,54 +516,54 @@ class _SnNewsArticle implements SnNewsArticle { id, createdAt, updatedAt, - const DeepCollectionEquality().hash(deletedAt), + deletedAt, thumbnail, title, description, content, url, hash, - source, + feedId, publishedAt); @override String toString() { - return 'SnNewsArticle(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, thumbnail: $thumbnail, title: $title, description: $description, content: $content, url: $url, hash: $hash, source: $source, publishedAt: $publishedAt)'; + return 'SnSubscriptionItem(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, thumbnail: $thumbnail, title: $title, description: $description, content: $content, url: $url, hash: $hash, feedId: $feedId, publishedAt: $publishedAt)'; } } /// @nodoc -abstract mixin class _$SnNewsArticleCopyWith<$Res> - implements $SnNewsArticleCopyWith<$Res> { - factory _$SnNewsArticleCopyWith( - _SnNewsArticle value, $Res Function(_SnNewsArticle) _then) = - __$SnNewsArticleCopyWithImpl; +abstract mixin class _$SnSubscriptionItemCopyWith<$Res> + implements $SnSubscriptionItemCopyWith<$Res> { + factory _$SnSubscriptionItemCopyWith( + _SnSubscriptionItem value, $Res Function(_SnSubscriptionItem) _then) = + __$SnSubscriptionItemCopyWithImpl; @override @useResult $Res call( {int id, DateTime createdAt, DateTime updatedAt, - dynamic deletedAt, + DateTime? deletedAt, String thumbnail, String title, String description, String content, String url, String hash, - String source, + int feedId, DateTime? publishedAt}); } /// @nodoc -class __$SnNewsArticleCopyWithImpl<$Res> - implements _$SnNewsArticleCopyWith<$Res> { - __$SnNewsArticleCopyWithImpl(this._self, this._then); +class __$SnSubscriptionItemCopyWithImpl<$Res> + implements _$SnSubscriptionItemCopyWith<$Res> { + __$SnSubscriptionItemCopyWithImpl(this._self, this._then); - final _SnNewsArticle _self; - final $Res Function(_SnNewsArticle) _then; + final _SnSubscriptionItem _self; + final $Res Function(_SnSubscriptionItem) _then; - /// Create a copy of SnNewsArticle + /// Create a copy of SnSubscriptionItem /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') @@ -576,10 +578,10 @@ class __$SnNewsArticleCopyWithImpl<$Res> Object? content = null, Object? url = null, Object? hash = null, - Object? source = null, + Object? feedId = null, Object? publishedAt = freezed, }) { - return _then(_SnNewsArticle( + return _then(_SnSubscriptionItem( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable @@ -595,7 +597,7 @@ class __$SnNewsArticleCopyWithImpl<$Res> deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable - as dynamic, + as DateTime?, thumbnail: null == thumbnail ? _self.thumbnail : thumbnail // ignore: cast_nullable_to_non_nullable @@ -620,10 +622,10 @@ class __$SnNewsArticleCopyWithImpl<$Res> ? _self.hash : hash // ignore: cast_nullable_to_non_nullable as String, - source: null == source - ? _self.source - : source // ignore: cast_nullable_to_non_nullable - as String, + feedId: null == feedId + ? _self.feedId + : feedId // ignore: cast_nullable_to_non_nullable + as int, publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable diff --git a/lib/types/news.g.dart b/lib/types/news.g.dart index 35815bb..b03451e 100644 --- a/lib/types/news.g.dart +++ b/lib/types/news.g.dart @@ -26,36 +26,38 @@ Map _$SnNewsSourceToJson(_SnNewsSource instance) => 'enabled': instance.enabled, }; -_SnNewsArticle _$SnNewsArticleFromJson(Map json) => - _SnNewsArticle( +_SnSubscriptionItem _$SnSubscriptionItemFromJson(Map json) => + _SnSubscriptionItem( id: (json['id'] as num).toInt(), createdAt: DateTime.parse(json['created_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'], + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), thumbnail: json['thumbnail'] as String, title: json['title'] as String, description: json['description'] as String, content: json['content'] as String, url: json['url'] as String, hash: json['hash'] as String, - source: json['source'] as String, + feedId: (json['feed_id'] as num).toInt(), publishedAt: json['published_at'] == null ? null : DateTime.parse(json['published_at'] as String), ); -Map _$SnNewsArticleToJson(_SnNewsArticle instance) => +Map _$SnSubscriptionItemToJson(_SnSubscriptionItem instance) => { 'id': instance.id, 'created_at': instance.createdAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(), - 'deleted_at': instance.deletedAt, + 'deleted_at': instance.deletedAt?.toIso8601String(), 'thumbnail': instance.thumbnail, 'title': instance.title, 'description': instance.description, 'content': instance.content, 'url': instance.url, 'hash': instance.hash, - 'source': instance.source, + 'feed_id': instance.feedId, 'published_at': instance.publishedAt?.toIso8601String(), }; diff --git a/lib/types/post.dart b/lib/types/post.dart index 9428908..a3c58c8 100644 --- a/lib/types/post.dart +++ b/lib/types/post.dart @@ -181,41 +181,3 @@ abstract class SnFeedEntry with _$SnFeedEntry { factory SnFeedEntry.fromJson(Map json) => _$SnFeedEntryFromJson(json); } - -@freezed -abstract class SnFediversePost with _$SnFediversePost { - const factory SnFediversePost({ - required int id, - required DateTime createdAt, - required DateTime updatedAt, - required DateTime? deletedAt, - required String identifier, - required String origin, - required String content, - required String language, - required List images, - required SnFediverseUser user, - required int userId, - }) = _SnFediversePost; - - factory SnFediversePost.fromJson(Map json) => - _$SnFediversePostFromJson(json); -} - -@freezed -abstract class SnFediverseUser with _$SnFediverseUser { - const factory SnFediverseUser({ - required int id, - required DateTime createdAt, - required DateTime updatedAt, - required DateTime? deletedAt, - required String identifier, - required String origin, - required String avatar, - required String name, - required String nick, - }) = _SnFediverseUser; - - factory SnFediverseUser.fromJson(Map json) => - _$SnFediverseUserFromJson(json); -} diff --git a/lib/types/post.freezed.dart b/lib/types/post.freezed.dart index 29d3b44..45f7caa 100644 --- a/lib/types/post.freezed.dart +++ b/lib/types/post.freezed.dart @@ -3400,698 +3400,4 @@ class __$SnFeedEntryCopyWithImpl<$Res> implements _$SnFeedEntryCopyWith<$Res> { } } -/// @nodoc -mixin _$SnFediversePost { - int get id; - DateTime get createdAt; - DateTime get updatedAt; - DateTime? get deletedAt; - String get identifier; - String get origin; - String get content; - String get language; - List get images; - SnFediverseUser get user; - int get userId; - - /// Create a copy of SnFediversePost - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @pragma('vm:prefer-inline') - $SnFediversePostCopyWith get copyWith => - _$SnFediversePostCopyWithImpl( - this as SnFediversePost, _$identity); - - /// Serializes this SnFediversePost to a JSON map. - Map toJson(); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is SnFediversePost && - (identical(other.id, id) || other.id == id) && - (identical(other.createdAt, createdAt) || - other.createdAt == createdAt) && - (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt) && - (identical(other.deletedAt, deletedAt) || - other.deletedAt == deletedAt) && - (identical(other.identifier, identifier) || - other.identifier == identifier) && - (identical(other.origin, origin) || other.origin == origin) && - (identical(other.content, content) || other.content == content) && - (identical(other.language, language) || - other.language == language) && - const DeepCollectionEquality().equals(other.images, images) && - (identical(other.user, user) || other.user == user) && - (identical(other.userId, userId) || other.userId == userId)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - id, - createdAt, - updatedAt, - deletedAt, - identifier, - origin, - content, - language, - const DeepCollectionEquality().hash(images), - user, - userId); - - @override - String toString() { - return 'SnFediversePost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, content: $content, language: $language, images: $images, user: $user, userId: $userId)'; - } -} - -/// @nodoc -abstract mixin class $SnFediversePostCopyWith<$Res> { - factory $SnFediversePostCopyWith( - SnFediversePost value, $Res Function(SnFediversePost) _then) = - _$SnFediversePostCopyWithImpl; - @useResult - $Res call( - {int id, - DateTime createdAt, - DateTime updatedAt, - DateTime? deletedAt, - String identifier, - String origin, - String content, - String language, - List images, - SnFediverseUser user, - int userId}); - - $SnFediverseUserCopyWith<$Res> get user; -} - -/// @nodoc -class _$SnFediversePostCopyWithImpl<$Res> - implements $SnFediversePostCopyWith<$Res> { - _$SnFediversePostCopyWithImpl(this._self, this._then); - - final SnFediversePost _self; - final $Res Function(SnFediversePost) _then; - - /// Create a copy of SnFediversePost - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? createdAt = null, - Object? updatedAt = null, - Object? deletedAt = freezed, - Object? identifier = null, - Object? origin = null, - Object? content = null, - Object? language = null, - Object? images = null, - Object? user = null, - Object? userId = null, - }) { - return _then(_self.copyWith( - id: null == id - ? _self.id - : id // ignore: cast_nullable_to_non_nullable - as int, - createdAt: null == createdAt - ? _self.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime, - updatedAt: null == updatedAt - ? _self.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime, - deletedAt: freezed == deletedAt - ? _self.deletedAt - : deletedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - identifier: null == identifier - ? _self.identifier - : identifier // ignore: cast_nullable_to_non_nullable - as String, - origin: null == origin - ? _self.origin - : origin // ignore: cast_nullable_to_non_nullable - as String, - content: null == content - ? _self.content - : content // ignore: cast_nullable_to_non_nullable - as String, - language: null == language - ? _self.language - : language // ignore: cast_nullable_to_non_nullable - as String, - images: null == images - ? _self.images - : images // ignore: cast_nullable_to_non_nullable - as List, - user: null == user - ? _self.user - : user // ignore: cast_nullable_to_non_nullable - as SnFediverseUser, - userId: null == userId - ? _self.userId - : userId // ignore: cast_nullable_to_non_nullable - as int, - )); - } - - /// Create a copy of SnFediversePost - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SnFediverseUserCopyWith<$Res> get user { - return $SnFediverseUserCopyWith<$Res>(_self.user, (value) { - return _then(_self.copyWith(user: value)); - }); - } -} - -/// @nodoc -@JsonSerializable() -class _SnFediversePost implements SnFediversePost { - const _SnFediversePost( - {required this.id, - required this.createdAt, - required this.updatedAt, - required this.deletedAt, - required this.identifier, - required this.origin, - required this.content, - required this.language, - required final List images, - required this.user, - required this.userId}) - : _images = images; - factory _SnFediversePost.fromJson(Map json) => - _$SnFediversePostFromJson(json); - - @override - final int id; - @override - final DateTime createdAt; - @override - final DateTime updatedAt; - @override - final DateTime? deletedAt; - @override - final String identifier; - @override - final String origin; - @override - final String content; - @override - final String language; - final List _images; - @override - List get images { - if (_images is EqualUnmodifiableListView) return _images; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_images); - } - - @override - final SnFediverseUser user; - @override - final int userId; - - /// Create a copy of SnFediversePost - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - @pragma('vm:prefer-inline') - _$SnFediversePostCopyWith<_SnFediversePost> get copyWith => - __$SnFediversePostCopyWithImpl<_SnFediversePost>(this, _$identity); - - @override - Map toJson() { - return _$SnFediversePostToJson( - this, - ); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _SnFediversePost && - (identical(other.id, id) || other.id == id) && - (identical(other.createdAt, createdAt) || - other.createdAt == createdAt) && - (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt) && - (identical(other.deletedAt, deletedAt) || - other.deletedAt == deletedAt) && - (identical(other.identifier, identifier) || - other.identifier == identifier) && - (identical(other.origin, origin) || other.origin == origin) && - (identical(other.content, content) || other.content == content) && - (identical(other.language, language) || - other.language == language) && - const DeepCollectionEquality().equals(other._images, _images) && - (identical(other.user, user) || other.user == user) && - (identical(other.userId, userId) || other.userId == userId)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - id, - createdAt, - updatedAt, - deletedAt, - identifier, - origin, - content, - language, - const DeepCollectionEquality().hash(_images), - user, - userId); - - @override - String toString() { - return 'SnFediversePost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, content: $content, language: $language, images: $images, user: $user, userId: $userId)'; - } -} - -/// @nodoc -abstract mixin class _$SnFediversePostCopyWith<$Res> - implements $SnFediversePostCopyWith<$Res> { - factory _$SnFediversePostCopyWith( - _SnFediversePost value, $Res Function(_SnFediversePost) _then) = - __$SnFediversePostCopyWithImpl; - @override - @useResult - $Res call( - {int id, - DateTime createdAt, - DateTime updatedAt, - DateTime? deletedAt, - String identifier, - String origin, - String content, - String language, - List images, - SnFediverseUser user, - int userId}); - - @override - $SnFediverseUserCopyWith<$Res> get user; -} - -/// @nodoc -class __$SnFediversePostCopyWithImpl<$Res> - implements _$SnFediversePostCopyWith<$Res> { - __$SnFediversePostCopyWithImpl(this._self, this._then); - - final _SnFediversePost _self; - final $Res Function(_SnFediversePost) _then; - - /// Create a copy of SnFediversePost - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $Res call({ - Object? id = null, - Object? createdAt = null, - Object? updatedAt = null, - Object? deletedAt = freezed, - Object? identifier = null, - Object? origin = null, - Object? content = null, - Object? language = null, - Object? images = null, - Object? user = null, - Object? userId = null, - }) { - return _then(_SnFediversePost( - id: null == id - ? _self.id - : id // ignore: cast_nullable_to_non_nullable - as int, - createdAt: null == createdAt - ? _self.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime, - updatedAt: null == updatedAt - ? _self.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime, - deletedAt: freezed == deletedAt - ? _self.deletedAt - : deletedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - identifier: null == identifier - ? _self.identifier - : identifier // ignore: cast_nullable_to_non_nullable - as String, - origin: null == origin - ? _self.origin - : origin // ignore: cast_nullable_to_non_nullable - as String, - content: null == content - ? _self.content - : content // ignore: cast_nullable_to_non_nullable - as String, - language: null == language - ? _self.language - : language // ignore: cast_nullable_to_non_nullable - as String, - images: null == images - ? _self._images - : images // ignore: cast_nullable_to_non_nullable - as List, - user: null == user - ? _self.user - : user // ignore: cast_nullable_to_non_nullable - as SnFediverseUser, - userId: null == userId - ? _self.userId - : userId // ignore: cast_nullable_to_non_nullable - as int, - )); - } - - /// Create a copy of SnFediversePost - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SnFediverseUserCopyWith<$Res> get user { - return $SnFediverseUserCopyWith<$Res>(_self.user, (value) { - return _then(_self.copyWith(user: value)); - }); - } -} - -/// @nodoc -mixin _$SnFediverseUser { - int get id; - DateTime get createdAt; - DateTime get updatedAt; - DateTime? get deletedAt; - String get identifier; - String get origin; - String get avatar; - String get name; - String get nick; - - /// Create a copy of SnFediverseUser - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @pragma('vm:prefer-inline') - $SnFediverseUserCopyWith get copyWith => - _$SnFediverseUserCopyWithImpl( - this as SnFediverseUser, _$identity); - - /// Serializes this SnFediverseUser to a JSON map. - Map toJson(); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is SnFediverseUser && - (identical(other.id, id) || other.id == id) && - (identical(other.createdAt, createdAt) || - other.createdAt == createdAt) && - (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt) && - (identical(other.deletedAt, deletedAt) || - other.deletedAt == deletedAt) && - (identical(other.identifier, identifier) || - other.identifier == identifier) && - (identical(other.origin, origin) || other.origin == origin) && - (identical(other.avatar, avatar) || other.avatar == avatar) && - (identical(other.name, name) || other.name == name) && - (identical(other.nick, nick) || other.nick == nick)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt, - deletedAt, identifier, origin, avatar, name, nick); - - @override - String toString() { - return 'SnFediverseUser(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, avatar: $avatar, name: $name, nick: $nick)'; - } -} - -/// @nodoc -abstract mixin class $SnFediverseUserCopyWith<$Res> { - factory $SnFediverseUserCopyWith( - SnFediverseUser value, $Res Function(SnFediverseUser) _then) = - _$SnFediverseUserCopyWithImpl; - @useResult - $Res call( - {int id, - DateTime createdAt, - DateTime updatedAt, - DateTime? deletedAt, - String identifier, - String origin, - String avatar, - String name, - String nick}); -} - -/// @nodoc -class _$SnFediverseUserCopyWithImpl<$Res> - implements $SnFediverseUserCopyWith<$Res> { - _$SnFediverseUserCopyWithImpl(this._self, this._then); - - final SnFediverseUser _self; - final $Res Function(SnFediverseUser) _then; - - /// Create a copy of SnFediverseUser - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? createdAt = null, - Object? updatedAt = null, - Object? deletedAt = freezed, - Object? identifier = null, - Object? origin = null, - Object? avatar = null, - Object? name = null, - Object? nick = null, - }) { - return _then(_self.copyWith( - id: null == id - ? _self.id - : id // ignore: cast_nullable_to_non_nullable - as int, - createdAt: null == createdAt - ? _self.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime, - updatedAt: null == updatedAt - ? _self.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime, - deletedAt: freezed == deletedAt - ? _self.deletedAt - : deletedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - identifier: null == identifier - ? _self.identifier - : identifier // ignore: cast_nullable_to_non_nullable - as String, - origin: null == origin - ? _self.origin - : origin // ignore: cast_nullable_to_non_nullable - as String, - avatar: null == avatar - ? _self.avatar - : avatar // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _self.name - : name // ignore: cast_nullable_to_non_nullable - as String, - nick: null == nick - ? _self.nick - : nick // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _SnFediverseUser implements SnFediverseUser { - const _SnFediverseUser( - {required this.id, - required this.createdAt, - required this.updatedAt, - required this.deletedAt, - required this.identifier, - required this.origin, - required this.avatar, - required this.name, - required this.nick}); - factory _SnFediverseUser.fromJson(Map json) => - _$SnFediverseUserFromJson(json); - - @override - final int id; - @override - final DateTime createdAt; - @override - final DateTime updatedAt; - @override - final DateTime? deletedAt; - @override - final String identifier; - @override - final String origin; - @override - final String avatar; - @override - final String name; - @override - final String nick; - - /// Create a copy of SnFediverseUser - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - @pragma('vm:prefer-inline') - _$SnFediverseUserCopyWith<_SnFediverseUser> get copyWith => - __$SnFediverseUserCopyWithImpl<_SnFediverseUser>(this, _$identity); - - @override - Map toJson() { - return _$SnFediverseUserToJson( - this, - ); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _SnFediverseUser && - (identical(other.id, id) || other.id == id) && - (identical(other.createdAt, createdAt) || - other.createdAt == createdAt) && - (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt) && - (identical(other.deletedAt, deletedAt) || - other.deletedAt == deletedAt) && - (identical(other.identifier, identifier) || - other.identifier == identifier) && - (identical(other.origin, origin) || other.origin == origin) && - (identical(other.avatar, avatar) || other.avatar == avatar) && - (identical(other.name, name) || other.name == name) && - (identical(other.nick, nick) || other.nick == nick)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt, - deletedAt, identifier, origin, avatar, name, nick); - - @override - String toString() { - return 'SnFediverseUser(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, avatar: $avatar, name: $name, nick: $nick)'; - } -} - -/// @nodoc -abstract mixin class _$SnFediverseUserCopyWith<$Res> - implements $SnFediverseUserCopyWith<$Res> { - factory _$SnFediverseUserCopyWith( - _SnFediverseUser value, $Res Function(_SnFediverseUser) _then) = - __$SnFediverseUserCopyWithImpl; - @override - @useResult - $Res call( - {int id, - DateTime createdAt, - DateTime updatedAt, - DateTime? deletedAt, - String identifier, - String origin, - String avatar, - String name, - String nick}); -} - -/// @nodoc -class __$SnFediverseUserCopyWithImpl<$Res> - implements _$SnFediverseUserCopyWith<$Res> { - __$SnFediverseUserCopyWithImpl(this._self, this._then); - - final _SnFediverseUser _self; - final $Res Function(_SnFediverseUser) _then; - - /// Create a copy of SnFediverseUser - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $Res call({ - Object? id = null, - Object? createdAt = null, - Object? updatedAt = null, - Object? deletedAt = freezed, - Object? identifier = null, - Object? origin = null, - Object? avatar = null, - Object? name = null, - Object? nick = null, - }) { - return _then(_SnFediverseUser( - id: null == id - ? _self.id - : id // ignore: cast_nullable_to_non_nullable - as int, - createdAt: null == createdAt - ? _self.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime, - updatedAt: null == updatedAt - ? _self.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime, - deletedAt: freezed == deletedAt - ? _self.deletedAt - : deletedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - identifier: null == identifier - ? _self.identifier - : identifier // ignore: cast_nullable_to_non_nullable - as String, - origin: null == origin - ? _self.origin - : origin // ignore: cast_nullable_to_non_nullable - as String, - avatar: null == avatar - ? _self.avatar - : avatar // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _self.name - : name // ignore: cast_nullable_to_non_nullable - as String, - nick: null == nick - ? _self.nick - : nick // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - // dart format on diff --git a/lib/types/post.g.dart b/lib/types/post.g.dart index 7d25773..9267d68 100644 --- a/lib/types/post.g.dart +++ b/lib/types/post.g.dart @@ -303,64 +303,3 @@ Map _$SnFeedEntryToJson(_SnFeedEntry instance) => 'data': instance.data, 'created_at': instance.createdAt.toIso8601String(), }; - -_SnFediversePost _$SnFediversePostFromJson(Map json) => - _SnFediversePost( - id: (json['id'] as num).toInt(), - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - identifier: json['identifier'] as String, - origin: json['origin'] as String, - content: json['content'] as String, - language: json['language'] as String, - images: - (json['images'] as List).map((e) => e as String).toList(), - user: SnFediverseUser.fromJson(json['user'] as Map), - userId: (json['user_id'] as num).toInt(), - ); - -Map _$SnFediversePostToJson(_SnFediversePost instance) => - { - 'id': instance.id, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'deleted_at': instance.deletedAt?.toIso8601String(), - 'identifier': instance.identifier, - 'origin': instance.origin, - 'content': instance.content, - 'language': instance.language, - 'images': instance.images, - 'user': instance.user.toJson(), - 'user_id': instance.userId, - }; - -_SnFediverseUser _$SnFediverseUserFromJson(Map json) => - _SnFediverseUser( - id: (json['id'] as num).toInt(), - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - identifier: json['identifier'] as String, - origin: json['origin'] as String, - avatar: json['avatar'] as String, - name: json['name'] as String, - nick: json['nick'] as String, - ); - -Map _$SnFediverseUserToJson(_SnFediverseUser instance) => - { - 'id': instance.id, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'deleted_at': instance.deletedAt?.toIso8601String(), - 'identifier': instance.identifier, - 'origin': instance.origin, - 'avatar': instance.avatar, - 'name': instance.name, - 'nick': instance.nick, - }; diff --git a/lib/widgets/feed/feed_news.dart b/lib/widgets/feed/feed_news.dart deleted file mode 100644 index 08acd27..0000000 --- a/lib/widgets/feed/feed_news.dart +++ /dev/null @@ -1,105 +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 news = data.data - .map((ele) => SnNewsArticle.fromJson(ele)) - .cast() - .toList(); - - return 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), - ), - ), - ], - ); - } -} diff --git a/lib/widgets/feed/feed_reader.dart b/lib/widgets/feed/feed_reader.dart new file mode 100644 index 0000000..9bff7bf --- /dev/null +++ b/lib/widgets/feed/feed_reader.dart @@ -0,0 +1,63 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/types/news.dart'; +import 'package:surface/types/post.dart'; +import 'package:surface/widgets/universal_image.dart'; + +class NewsFeedEntry extends StatelessWidget { + final SnFeedEntry data; + const NewsFeedEntry({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + final ele = SnSubscriptionItem.fromJson(data.data); + + return Card( + elevation: 0, + color: Colors.transparent, + margin: EdgeInsets.zero, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (ele.thumbnail.isNotEmpty && ele.thumbnail.startsWith('http')) + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(8)), + child: AspectRatio( + aspectRatio: 16 / 9, + child: Container( + color: Theme.of(context).colorScheme.surfaceContainer, + child: AutoResizeUniversalImage(ele.thumbnail), + ), + ), + ).padding(horizontal: 16, bottom: 8, top: 4), + Row( + children: [ + const Icon(Symbols.globe), + const Gap(8), + Expanded( + child: Text( + ele.title, + style: Theme.of(context).textTheme.titleLarge, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ) + ], + ).padding(horizontal: 18, vertical: 4), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(ele.description), + Text(DateFormat().format(ele.createdAt.toLocal())) + .tr() + .opacity(0.8), + ], + ).padding(horizontal: 16), + ], + ), + ); + } +} diff --git a/lib/widgets/post/fediverse_post_item.dart b/lib/widgets/post/fediverse_post_item.dart deleted file mode 100644 index 01f8ae5..0000000 --- a/lib/widgets/post/fediverse_post_item.dart +++ /dev/null @@ -1,168 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; -import 'package:html2md/html2md.dart' as html2md; -import 'package:relative_time/relative_time.dart'; -import 'package:styled_widget/styled_widget.dart'; -import 'package:surface/types/post.dart'; -import 'package:surface/widgets/account/account_image.dart'; -import 'package:surface/widgets/attachment/attachment_list.dart'; -import 'package:surface/widgets/markdown_content.dart'; -import 'package:surface/widgets/universal_image.dart'; - -class FediversePostWidget extends StatelessWidget { - final SnFediversePost data; - final double maxWidth; - const FediversePostWidget({ - super.key, - required this.data, - required this.maxWidth, - }); - - @override - Widget build(BuildContext context) { - return Center( - child: Container( - constraints: BoxConstraints(maxWidth: maxWidth), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - AccountImage( - content: data.user.avatar, - radius: 20, - ), - const Gap(12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - data.user.nick.isNotEmpty - ? data.user.nick - : '@${data.user.name}', - maxLines: 1, - ).bold(), - Row( - children: [ - Text( - data.user.identifier.contains('@') - ? data.user.identifier - : '${data.user.identifier}@${data.user.origin}', - maxLines: 1, - ).fontSize(13), - const Gap(4), - Text( - RelativeTime(context) - .format(data.createdAt.toLocal()), - ).fontSize(13), - ], - ), - ], - ), - ], - ).padding(horizontal: 12, vertical: 8), - MarkdownTextContent( - isAutoWarp: true, - content: html2md.convert(data.content), - ).padding(horizontal: 16, bottom: 6), - if (data.images.isNotEmpty) - _FediversePostImageList( - data: data, - maxWidth: maxWidth, - ), - ], - ), - ), - ); - } -} - -class _FediversePostImageList extends StatelessWidget { - const _FediversePostImageList({ - required this.data, - required this.maxWidth, - }); - - final SnFediversePost data; - final double maxWidth; - - @override - Widget build(BuildContext context) { - final borderSide = - BorderSide(width: 1, color: Theme.of(context).dividerColor); - final backgroundColor = Theme.of(context).colorScheme.surfaceContainer; - - if (data.images.length == 1) { - return AspectRatio( - aspectRatio: 1, - child: Container( - constraints: BoxConstraints(maxWidth: maxWidth), - decoration: BoxDecoration( - color: backgroundColor, - border: Border( - top: borderSide, - bottom: borderSide, - ), - borderRadius: AttachmentList.kDefaultRadius, - ), - child: ClipRRect( - borderRadius: AttachmentList.kDefaultRadius, - child: AutoResizeUniversalImage( - data.images.first, - ), - ), - ), - ).padding(horizontal: 8); - } - - return AspectRatio( - aspectRatio: 1, - child: ScrollConfiguration( - behavior: AttachmentListScrollBehavior(), - child: ListView.separated( - shrinkWrap: true, - itemCount: data.images.length, - itemBuilder: (context, idx) { - return Container( - constraints: BoxConstraints(maxWidth: maxWidth), - child: AspectRatio( - aspectRatio: 1, - child: Stack( - fit: StackFit.expand, - children: [ - Container( - decoration: BoxDecoration( - color: backgroundColor, - border: Border( - top: borderSide, - bottom: borderSide, - ), - borderRadius: AttachmentList.kDefaultRadius, - ), - child: ClipRRect( - borderRadius: AttachmentList.kDefaultRadius, - child: AutoResizeUniversalImage( - data.images[idx], - ), - ), - ), - Positioned( - right: 8, - bottom: 8, - child: Chip( - label: Text('${idx + 1}/${data.images.length}'), - ), - ), - ], - ), - ), - ); - }, - separatorBuilder: (context, index) => const Gap(8), - physics: const BouncingScrollPhysics(), - scrollDirection: Axis.horizontal, - ), - ), - ); - } -}