Compare commits

...

2 Commits

Author SHA1 Message Date
e367fc3f5c Web articles detail page & explore feed 2025-07-01 13:19:14 +08:00
8a1af120ea Discover article 2025-07-01 01:34:34 +08:00
18 changed files with 581 additions and 219 deletions

View File

@ -675,5 +675,6 @@
"publisherFeatureDevelop": "Developer Program",
"publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.",
"publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.",
"learnMore": "Learn More"
"learnMore": "Learn More",
"discoverWebArticles": "Articles from external sites"
}

View File

@ -7,57 +7,58 @@ part 'webfeed.freezed.dart';
part 'webfeed.g.dart';
@freezed
sealed class WebFeedConfig with _$WebFeedConfig {
const factory WebFeedConfig({@Default(false) bool scrapPage}) =
_WebFeedConfig;
sealed class SnWebFeedConfig with _$SnWebFeedConfig {
const factory SnWebFeedConfig({@Default(false) bool scrapPage}) =
_SnWebFeedConfig;
factory WebFeedConfig.fromJson(Map<String, dynamic> json) =>
_$WebFeedConfigFromJson(json);
factory SnWebFeedConfig.fromJson(Map<String, dynamic> json) =>
_$SnWebFeedConfigFromJson(json);
}
@freezed
sealed class WebFeed with _$WebFeed {
const factory WebFeed({
sealed class SnWebFeed with _$SnWebFeed {
const factory SnWebFeed({
required String id,
required String url,
required String title,
String? description,
SnScrappedLink? preview,
@Default(WebFeedConfig()) WebFeedConfig config,
@Default(SnWebFeedConfig()) SnWebFeedConfig config,
required String publisherId,
@Default([]) List<WebArticle> articles,
@Default([]) List<SnWebArticle> articles,
required DateTime createdAt,
required DateTime updatedAt,
DateTime? deletedAt,
}) = _WebFeed;
}) = _SnWebFeed;
factory WebFeed.fromJson(Map<String, dynamic> json) =>
_$WebFeedFromJson(json);
factory SnWebFeed.fromJson(Map<String, dynamic> json) =>
_$SnWebFeedFromJson(json);
factory WebFeed.fromJsonString(String jsonString) =>
WebFeed.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
factory SnWebFeed.fromJsonString(String jsonString) =>
SnWebFeed.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
}
@freezed
sealed class WebArticle with _$WebArticle {
const factory WebArticle({
sealed class SnWebArticle with _$SnWebArticle {
const factory SnWebArticle({
required String id,
required String title,
required String url,
String? author,
Map<String, dynamic>? meta,
SnScrappedLink? preview,
SnWebFeed? feed,
String? content,
DateTime? publishedAt,
required String feedId,
required DateTime createdAt,
required DateTime updatedAt,
DateTime? deletedAt,
}) = _WebArticle;
}) = _SnWebArticle;
factory WebArticle.fromJson(Map<String, dynamic> json) =>
_$WebArticleFromJson(json);
factory SnWebArticle.fromJson(Map<String, dynamic> json) =>
_$SnWebArticleFromJson(json);
factory WebArticle.fromJsonString(String jsonString) =>
WebArticle.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
factory SnWebArticle.fromJsonString(String jsonString) =>
SnWebArticle.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
}

View File

@ -14,22 +14,22 @@ part of 'webfeed.dart';
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$WebFeedConfig {
mixin _$SnWebFeedConfig {
bool get scrapPage;
/// Create a copy of WebFeedConfig
/// Create a copy of SnWebFeedConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$WebFeedConfigCopyWith<WebFeedConfig> get copyWith => _$WebFeedConfigCopyWithImpl<WebFeedConfig>(this as WebFeedConfig, _$identity);
$SnWebFeedConfigCopyWith<SnWebFeedConfig> get copyWith => _$SnWebFeedConfigCopyWithImpl<SnWebFeedConfig>(this as SnWebFeedConfig, _$identity);
/// Serializes this WebFeedConfig to a JSON map.
/// Serializes this SnWebFeedConfig to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -38,15 +38,15 @@ int get hashCode => Object.hash(runtimeType,scrapPage);
@override
String toString() {
return 'WebFeedConfig(scrapPage: $scrapPage)';
return 'SnWebFeedConfig(scrapPage: $scrapPage)';
}
}
/// @nodoc
abstract mixin class $WebFeedConfigCopyWith<$Res> {
factory $WebFeedConfigCopyWith(WebFeedConfig value, $Res Function(WebFeedConfig) _then) = _$WebFeedConfigCopyWithImpl;
abstract mixin class $SnWebFeedConfigCopyWith<$Res> {
factory $SnWebFeedConfigCopyWith(SnWebFeedConfig value, $Res Function(SnWebFeedConfig) _then) = _$SnWebFeedConfigCopyWithImpl;
@useResult
$Res call({
bool scrapPage
@ -57,14 +57,14 @@ $Res call({
}
/// @nodoc
class _$WebFeedConfigCopyWithImpl<$Res>
implements $WebFeedConfigCopyWith<$Res> {
_$WebFeedConfigCopyWithImpl(this._self, this._then);
class _$SnWebFeedConfigCopyWithImpl<$Res>
implements $SnWebFeedConfigCopyWith<$Res> {
_$SnWebFeedConfigCopyWithImpl(this._self, this._then);
final WebFeedConfig _self;
final $Res Function(WebFeedConfig) _then;
final SnWebFeedConfig _self;
final $Res Function(SnWebFeedConfig) _then;
/// Create a copy of WebFeedConfig
/// Create a copy of SnWebFeedConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? scrapPage = null,}) {
return _then(_self.copyWith(
@ -79,26 +79,26 @@ as bool,
/// @nodoc
@JsonSerializable()
class _WebFeedConfig implements WebFeedConfig {
const _WebFeedConfig({this.scrapPage = false});
factory _WebFeedConfig.fromJson(Map<String, dynamic> json) => _$WebFeedConfigFromJson(json);
class _SnWebFeedConfig implements SnWebFeedConfig {
const _SnWebFeedConfig({this.scrapPage = false});
factory _SnWebFeedConfig.fromJson(Map<String, dynamic> json) => _$SnWebFeedConfigFromJson(json);
@override@JsonKey() final bool scrapPage;
/// Create a copy of WebFeedConfig
/// Create a copy of SnWebFeedConfig
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$WebFeedConfigCopyWith<_WebFeedConfig> get copyWith => __$WebFeedConfigCopyWithImpl<_WebFeedConfig>(this, _$identity);
_$SnWebFeedConfigCopyWith<_SnWebFeedConfig> get copyWith => __$SnWebFeedConfigCopyWithImpl<_SnWebFeedConfig>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$WebFeedConfigToJson(this, );
return _$SnWebFeedConfigToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -107,15 +107,15 @@ int get hashCode => Object.hash(runtimeType,scrapPage);
@override
String toString() {
return 'WebFeedConfig(scrapPage: $scrapPage)';
return 'SnWebFeedConfig(scrapPage: $scrapPage)';
}
}
/// @nodoc
abstract mixin class _$WebFeedConfigCopyWith<$Res> implements $WebFeedConfigCopyWith<$Res> {
factory _$WebFeedConfigCopyWith(_WebFeedConfig value, $Res Function(_WebFeedConfig) _then) = __$WebFeedConfigCopyWithImpl;
abstract mixin class _$SnWebFeedConfigCopyWith<$Res> implements $SnWebFeedConfigCopyWith<$Res> {
factory _$SnWebFeedConfigCopyWith(_SnWebFeedConfig value, $Res Function(_SnWebFeedConfig) _then) = __$SnWebFeedConfigCopyWithImpl;
@override @useResult
$Res call({
bool scrapPage
@ -126,17 +126,17 @@ $Res call({
}
/// @nodoc
class __$WebFeedConfigCopyWithImpl<$Res>
implements _$WebFeedConfigCopyWith<$Res> {
__$WebFeedConfigCopyWithImpl(this._self, this._then);
class __$SnWebFeedConfigCopyWithImpl<$Res>
implements _$SnWebFeedConfigCopyWith<$Res> {
__$SnWebFeedConfigCopyWithImpl(this._self, this._then);
final _WebFeedConfig _self;
final $Res Function(_WebFeedConfig) _then;
final _SnWebFeedConfig _self;
final $Res Function(_SnWebFeedConfig) _then;
/// Create a copy of WebFeedConfig
/// Create a copy of SnWebFeedConfig
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? scrapPage = null,}) {
return _then(_WebFeedConfig(
return _then(_SnWebFeedConfig(
scrapPage: null == scrapPage ? _self.scrapPage : scrapPage // ignore: cast_nullable_to_non_nullable
as bool,
));
@ -147,22 +147,22 @@ as bool,
/// @nodoc
mixin _$WebFeed {
mixin _$SnWebFeed {
String get id; String get url; String get title; String? get description; SnScrappedLink? get preview; WebFeedConfig get config; String get publisherId; List<WebArticle> get articles; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of WebFeed
String get id; String get url; String get title; String? get description; SnScrappedLink? get preview; SnWebFeedConfig get config; String get publisherId; List<SnWebArticle> get articles; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$WebFeedCopyWith<WebFeed> get copyWith => _$WebFeedCopyWithImpl<WebFeed>(this as WebFeed, _$identity);
$SnWebFeedCopyWith<SnWebFeed> get copyWith => _$SnWebFeedCopyWithImpl<SnWebFeed>(this as SnWebFeed, _$identity);
/// Serializes this WebFeed to a JSON map.
/// Serializes this SnWebFeed to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other.articles, articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other.articles, articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -171,33 +171,33 @@ int get hashCode => Object.hash(runtimeType,id,url,title,description,preview,con
@override
String toString() {
return 'WebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnWebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $WebFeedCopyWith<$Res> {
factory $WebFeedCopyWith(WebFeed value, $Res Function(WebFeed) _then) = _$WebFeedCopyWithImpl;
abstract mixin class $SnWebFeedCopyWith<$Res> {
factory $SnWebFeedCopyWith(SnWebFeed value, $Res Function(SnWebFeed) _then) = _$SnWebFeedCopyWithImpl;
@useResult
$Res call({
String id, String url, String title, String? description, SnScrappedLink? preview, WebFeedConfig config, String publisherId, List<WebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String url, String title, String? description, SnScrappedLink? preview, SnWebFeedConfig config, String publisherId, List<SnWebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnScrappedLinkCopyWith<$Res>? get preview;$WebFeedConfigCopyWith<$Res> get config;
$SnScrappedLinkCopyWith<$Res>? get preview;$SnWebFeedConfigCopyWith<$Res> get config;
}
/// @nodoc
class _$WebFeedCopyWithImpl<$Res>
implements $WebFeedCopyWith<$Res> {
_$WebFeedCopyWithImpl(this._self, this._then);
class _$SnWebFeedCopyWithImpl<$Res>
implements $SnWebFeedCopyWith<$Res> {
_$SnWebFeedCopyWithImpl(this._self, this._then);
final WebFeed _self;
final $Res Function(WebFeed) _then;
final SnWebFeed _self;
final $Res Function(SnWebFeed) _then;
/// Create a copy of WebFeed
/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
@ -207,15 +207,15 @@ as String,title: null == title ? _self.title : title // ignore: cast_nullable_to
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
as WebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as SnWebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as String,articles: null == articles ? _self.articles : articles // ignore: cast_nullable_to_non_nullable
as List<WebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as List<SnWebArticle>,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?,
));
}
/// Create a copy of WebFeed
/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
@ -227,13 +227,13 @@ $SnScrappedLinkCopyWith<$Res>? get preview {
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
return _then(_self.copyWith(preview: value));
});
}/// Create a copy of WebFeed
}/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$WebFeedConfigCopyWith<$Res> get config {
$SnWebFeedConfigCopyWith<$Res> get config {
return $WebFeedConfigCopyWith<$Res>(_self.config, (value) {
return $SnWebFeedConfigCopyWith<$Res>(_self.config, (value) {
return _then(_self.copyWith(config: value));
});
}
@ -243,19 +243,19 @@ $WebFeedConfigCopyWith<$Res> get config {
/// @nodoc
@JsonSerializable()
class _WebFeed implements WebFeed {
const _WebFeed({required this.id, required this.url, required this.title, this.description, this.preview, this.config = const WebFeedConfig(), required this.publisherId, final List<WebArticle> articles = const [], required this.createdAt, required this.updatedAt, this.deletedAt}): _articles = articles;
factory _WebFeed.fromJson(Map<String, dynamic> json) => _$WebFeedFromJson(json);
class _SnWebFeed implements SnWebFeed {
const _SnWebFeed({required this.id, required this.url, required this.title, this.description, this.preview, this.config = const SnWebFeedConfig(), required this.publisherId, final List<SnWebArticle> articles = const [], required this.createdAt, required this.updatedAt, this.deletedAt}): _articles = articles;
factory _SnWebFeed.fromJson(Map<String, dynamic> json) => _$SnWebFeedFromJson(json);
@override final String id;
@override final String url;
@override final String title;
@override final String? description;
@override final SnScrappedLink? preview;
@override@JsonKey() final WebFeedConfig config;
@override@JsonKey() final SnWebFeedConfig config;
@override final String publisherId;
final List<WebArticle> _articles;
@override@JsonKey() List<WebArticle> get articles {
final List<SnWebArticle> _articles;
@override@JsonKey() List<SnWebArticle> get articles {
if (_articles is EqualUnmodifiableListView) return _articles;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_articles);
@ -265,20 +265,20 @@ class _WebFeed implements WebFeed {
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of WebFeed
/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$WebFeedCopyWith<_WebFeed> get copyWith => __$WebFeedCopyWithImpl<_WebFeed>(this, _$identity);
_$SnWebFeedCopyWith<_SnWebFeed> get copyWith => __$SnWebFeedCopyWithImpl<_SnWebFeed>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$WebFeedToJson(this, );
return _$SnWebFeedToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other._articles, _articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other._articles, _articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -287,52 +287,52 @@ int get hashCode => Object.hash(runtimeType,id,url,title,description,preview,con
@override
String toString() {
return 'WebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnWebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$WebFeedCopyWith<$Res> implements $WebFeedCopyWith<$Res> {
factory _$WebFeedCopyWith(_WebFeed value, $Res Function(_WebFeed) _then) = __$WebFeedCopyWithImpl;
abstract mixin class _$SnWebFeedCopyWith<$Res> implements $SnWebFeedCopyWith<$Res> {
factory _$SnWebFeedCopyWith(_SnWebFeed value, $Res Function(_SnWebFeed) _then) = __$SnWebFeedCopyWithImpl;
@override @useResult
$Res call({
String id, String url, String title, String? description, SnScrappedLink? preview, WebFeedConfig config, String publisherId, List<WebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String url, String title, String? description, SnScrappedLink? preview, SnWebFeedConfig config, String publisherId, List<SnWebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnScrappedLinkCopyWith<$Res>? get preview;@override $WebFeedConfigCopyWith<$Res> get config;
@override $SnScrappedLinkCopyWith<$Res>? get preview;@override $SnWebFeedConfigCopyWith<$Res> get config;
}
/// @nodoc
class __$WebFeedCopyWithImpl<$Res>
implements _$WebFeedCopyWith<$Res> {
__$WebFeedCopyWithImpl(this._self, this._then);
class __$SnWebFeedCopyWithImpl<$Res>
implements _$SnWebFeedCopyWith<$Res> {
__$SnWebFeedCopyWithImpl(this._self, this._then);
final _WebFeed _self;
final $Res Function(_WebFeed) _then;
final _SnWebFeed _self;
final $Res Function(_SnWebFeed) _then;
/// Create a copy of WebFeed
/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_WebFeed(
return _then(_SnWebFeed(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
as WebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as SnWebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as String,articles: null == articles ? _self._articles : articles // ignore: cast_nullable_to_non_nullable
as List<WebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as List<SnWebArticle>,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?,
));
}
/// Create a copy of WebFeed
/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
@ -344,13 +344,13 @@ $SnScrappedLinkCopyWith<$Res>? get preview {
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
return _then(_self.copyWith(preview: value));
});
}/// Create a copy of WebFeed
}/// Create a copy of SnWebFeed
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$WebFeedConfigCopyWith<$Res> get config {
$SnWebFeedConfigCopyWith<$Res> get config {
return $WebFeedConfigCopyWith<$Res>(_self.config, (value) {
return $SnWebFeedConfigCopyWith<$Res>(_self.config, (value) {
return _then(_self.copyWith(config: value));
});
}
@ -358,59 +358,59 @@ $WebFeedConfigCopyWith<$Res> get config {
/// @nodoc
mixin _$WebArticle {
mixin _$SnWebArticle {
String get id; String get title; String get url; String? get author; Map<String, dynamic>? get meta; SnScrappedLink? get preview; String? get content; DateTime? get publishedAt; String get feedId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of WebArticle
String get id; String get title; String get url; String? get author; Map<String, dynamic>? get meta; SnScrappedLink? get preview; SnWebFeed? get feed; String? get content; DateTime? get publishedAt; String get feedId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$WebArticleCopyWith<WebArticle> get copyWith => _$WebArticleCopyWithImpl<WebArticle>(this as WebArticle, _$identity);
$SnWebArticleCopyWith<SnWebArticle> get copyWith => _$SnWebArticleCopyWithImpl<SnWebArticle>(this as SnWebArticle, _$identity);
/// Serializes this WebArticle to a JSON map.
/// Serializes this SnWebArticle to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.feed, feed) || other.feed == feed)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(meta),preview,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(meta),preview,feed,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'WebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnWebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, feed: $feed, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $WebArticleCopyWith<$Res> {
factory $WebArticleCopyWith(WebArticle value, $Res Function(WebArticle) _then) = _$WebArticleCopyWithImpl;
abstract mixin class $SnWebArticleCopyWith<$Res> {
factory $SnWebArticleCopyWith(SnWebArticle value, $Res Function(SnWebArticle) _then) = _$SnWebArticleCopyWithImpl;
@useResult
$Res call({
String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, SnWebFeed? feed, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnScrappedLinkCopyWith<$Res>? get preview;
$SnScrappedLinkCopyWith<$Res>? get preview;$SnWebFeedCopyWith<$Res>? get feed;
}
/// @nodoc
class _$WebArticleCopyWithImpl<$Res>
implements $WebArticleCopyWith<$Res> {
_$WebArticleCopyWithImpl(this._self, this._then);
class _$SnWebArticleCopyWithImpl<$Res>
implements $SnWebArticleCopyWith<$Res> {
_$SnWebArticleCopyWithImpl(this._self, this._then);
final WebArticle _self;
final $Res Function(WebArticle) _then;
final SnWebArticle _self;
final $Res Function(SnWebArticle) _then;
/// Create a copy of WebArticle
/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? feed = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@ -418,7 +418,8 @@ as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nul
as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
as SnScrappedLink?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as SnScrappedLink?,feed: freezed == feed ? _self.feed : feed // ignore: cast_nullable_to_non_nullable
as SnWebFeed?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
@ -427,7 +428,7 @@ as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ign
as DateTime?,
));
}
/// Create a copy of WebArticle
/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
@ -439,6 +440,18 @@ $SnScrappedLinkCopyWith<$Res>? get preview {
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
return _then(_self.copyWith(preview: value));
});
}/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnWebFeedCopyWith<$Res>? get feed {
if (_self.feed == null) {
return null;
}
return $SnWebFeedCopyWith<$Res>(_self.feed!, (value) {
return _then(_self.copyWith(feed: value));
});
}
}
@ -446,9 +459,9 @@ $SnScrappedLinkCopyWith<$Res>? get preview {
/// @nodoc
@JsonSerializable()
class _WebArticle implements WebArticle {
const _WebArticle({required this.id, required this.title, required this.url, this.author, final Map<String, dynamic>? meta, this.preview, this.content, this.publishedAt, required this.feedId, required this.createdAt, required this.updatedAt, this.deletedAt}): _meta = meta;
factory _WebArticle.fromJson(Map<String, dynamic> json) => _$WebArticleFromJson(json);
class _SnWebArticle implements SnWebArticle {
const _SnWebArticle({required this.id, required this.title, required this.url, this.author, final Map<String, dynamic>? meta, this.preview, this.feed, this.content, this.publishedAt, required this.feedId, required this.createdAt, required this.updatedAt, this.deletedAt}): _meta = meta;
factory _SnWebArticle.fromJson(Map<String, dynamic> json) => _$SnWebArticleFromJson(json);
@override final String id;
@override final String title;
@ -464,6 +477,7 @@ class _WebArticle implements WebArticle {
}
@override final SnScrappedLink? preview;
@override final SnWebFeed? feed;
@override final String? content;
@override final DateTime? publishedAt;
@override final String feedId;
@ -471,65 +485,66 @@ class _WebArticle implements WebArticle {
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of WebArticle
/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$WebArticleCopyWith<_WebArticle> get copyWith => __$WebArticleCopyWithImpl<_WebArticle>(this, _$identity);
_$SnWebArticleCopyWith<_SnWebArticle> get copyWith => __$SnWebArticleCopyWithImpl<_SnWebArticle>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$WebArticleToJson(this, );
return _$SnWebArticleToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.feed, feed) || other.feed == feed)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(_meta),preview,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(_meta),preview,feed,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'WebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnWebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, feed: $feed, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$WebArticleCopyWith<$Res> implements $WebArticleCopyWith<$Res> {
factory _$WebArticleCopyWith(_WebArticle value, $Res Function(_WebArticle) _then) = __$WebArticleCopyWithImpl;
abstract mixin class _$SnWebArticleCopyWith<$Res> implements $SnWebArticleCopyWith<$Res> {
factory _$SnWebArticleCopyWith(_SnWebArticle value, $Res Function(_SnWebArticle) _then) = __$SnWebArticleCopyWithImpl;
@override @useResult
$Res call({
String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, SnWebFeed? feed, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnScrappedLinkCopyWith<$Res>? get preview;
@override $SnScrappedLinkCopyWith<$Res>? get preview;@override $SnWebFeedCopyWith<$Res>? get feed;
}
/// @nodoc
class __$WebArticleCopyWithImpl<$Res>
implements _$WebArticleCopyWith<$Res> {
__$WebArticleCopyWithImpl(this._self, this._then);
class __$SnWebArticleCopyWithImpl<$Res>
implements _$SnWebArticleCopyWith<$Res> {
__$SnWebArticleCopyWithImpl(this._self, this._then);
final _WebArticle _self;
final $Res Function(_WebArticle) _then;
final _SnWebArticle _self;
final $Res Function(_SnWebArticle) _then;
/// Create a copy of WebArticle
/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_WebArticle(
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? feed = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnWebArticle(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String?,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
as SnScrappedLink?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as SnScrappedLink?,feed: freezed == feed ? _self.feed : feed // ignore: cast_nullable_to_non_nullable
as SnWebFeed?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
@ -539,7 +554,7 @@ as DateTime?,
));
}
/// Create a copy of WebArticle
/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
@ -551,6 +566,18 @@ $SnScrappedLinkCopyWith<$Res>? get preview {
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
return _then(_self.copyWith(preview: value));
});
}/// Create a copy of SnWebArticle
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnWebFeedCopyWith<$Res>? get feed {
if (_self.feed == null) {
return null;
}
return $SnWebFeedCopyWith<$Res>(_self.feed!, (value) {
return _then(_self.copyWith(feed: value));
});
}
}

View File

@ -6,13 +6,13 @@ part of 'webfeed.dart';
// JsonSerializableGenerator
// **************************************************************************
_WebFeedConfig _$WebFeedConfigFromJson(Map<String, dynamic> json) =>
_WebFeedConfig(scrapPage: json['scrap_page'] as bool? ?? false);
_SnWebFeedConfig _$SnWebFeedConfigFromJson(Map<String, dynamic> json) =>
_SnWebFeedConfig(scrapPage: json['scrap_page'] as bool? ?? false);
Map<String, dynamic> _$WebFeedConfigToJson(_WebFeedConfig instance) =>
Map<String, dynamic> _$SnWebFeedConfigToJson(_SnWebFeedConfig instance) =>
<String, dynamic>{'scrap_page': instance.scrapPage};
_WebFeed _$WebFeedFromJson(Map<String, dynamic> json) => _WebFeed(
_SnWebFeed _$SnWebFeedFromJson(Map<String, dynamic> json) => _SnWebFeed(
id: json['id'] as String,
url: json['url'] as String,
title: json['title'] as String,
@ -23,12 +23,12 @@ _WebFeed _$WebFeedFromJson(Map<String, dynamic> json) => _WebFeed(
: SnScrappedLink.fromJson(json['preview'] as Map<String, dynamic>),
config:
json['config'] == null
? const WebFeedConfig()
: WebFeedConfig.fromJson(json['config'] as Map<String, dynamic>),
? const SnWebFeedConfig()
: SnWebFeedConfig.fromJson(json['config'] as Map<String, dynamic>),
publisherId: json['publisher_id'] as String,
articles:
(json['articles'] as List<dynamic>?)
?.map((e) => WebArticle.fromJson(e as Map<String, dynamic>))
?.map((e) => SnWebArticle.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
createdAt: DateTime.parse(json['created_at'] as String),
@ -39,7 +39,8 @@ _WebFeed _$WebFeedFromJson(Map<String, dynamic> json) => _WebFeed(
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$WebFeedToJson(_WebFeed instance) => <String, dynamic>{
Map<String, dynamic> _$SnWebFeedToJson(_SnWebFeed instance) =>
<String, dynamic>{
'id': instance.id,
'url': instance.url,
'title': instance.title,
@ -51,9 +52,10 @@ Map<String, dynamic> _$WebFeedToJson(_WebFeed instance) => <String, dynamic>{
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
};
_WebArticle _$WebArticleFromJson(Map<String, dynamic> json) => _WebArticle(
_SnWebArticle _$SnWebArticleFromJson(Map<String, dynamic> json) =>
_SnWebArticle(
id: json['id'] as String,
title: json['title'] as String,
url: json['url'] as String,
@ -62,7 +64,13 @@ _WebArticle _$WebArticleFromJson(Map<String, dynamic> json) => _WebArticle(
preview:
json['preview'] == null
? null
: SnScrappedLink.fromJson(json['preview'] as Map<String, dynamic>),
: SnScrappedLink.fromJson(
json['preview'] as Map<String, dynamic>,
),
feed:
json['feed'] == null
? null
: SnWebFeed.fromJson(json['feed'] as Map<String, dynamic>),
content: json['content'] as String?,
publishedAt:
json['published_at'] == null
@ -75,9 +83,9 @@ _WebArticle _$WebArticleFromJson(Map<String, dynamic> json) => _WebArticle(
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
);
Map<String, dynamic> _$WebArticleToJson(_WebArticle instance) =>
Map<String, dynamic> _$SnWebArticleToJson(_SnWebArticle instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
@ -85,6 +93,7 @@ Map<String, dynamic> _$WebArticleToJson(_WebArticle instance) =>
'author': instance.author,
'meta': instance.meta,
'preview': instance.preview?.toJson(),
'feed': instance.feed?.toJson(),
'content': instance.content,
'published_at': instance.publishedAt?.toIso8601String(),
'feed_id': instance.feedId,

View File

@ -0,0 +1,31 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
import 'package:island/models/webfeed.dart';
import 'package:island/pods/network.dart';
/// Provider that fetches a single article by its ID
final articleDetailProvider = FutureProvider.autoDispose.family<SnWebArticle, String>(
(ref, articleId) async {
final dio = ref.watch(apiClientProvider);
try {
final response = await dio.get<Map<String, dynamic>>(
'/feeds/articles/$articleId',
);
if (response.statusCode == 200 && response.data != null) {
return SnWebArticle.fromJson(response.data!);
} else {
throw Exception('Failed to load article');
}
} on DioException catch (e) {
if (e.response?.statusCode == 404) {
throw Exception('Article not found');
} else {
throw Exception('Failed to load article: ${e.message}');
}
} catch (e) {
throw Exception('Failed to load article: $e');
}
},
);

View File

@ -0,0 +1 @@

View File

@ -1,28 +1,31 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/models/webfeed.dart';
import 'package:island/pods/network.dart';
final webFeedListProvider = FutureProvider.family<List<WebFeed>, String>((
final webFeedListProvider = FutureProvider.family<List<SnWebFeed>, String>((
ref,
pubName,
) async {
final client = ref.watch(apiClientProvider);
final response = await client.get('/publishers/$pubName/feeds');
return (response.data as List).map((json) => WebFeed.fromJson(json)).toList();
return (response.data as List)
.map((json) => SnWebFeed.fromJson(json))
.toList();
});
class WebFeedNotifier
extends
AutoDisposeFamilyAsyncNotifier<
WebFeed,
SnWebFeed,
({String pubName, String? feedId})
> {
@override
FutureOr<WebFeed> build(({String pubName, String? feedId}) arg) async {
FutureOr<SnWebFeed> build(({String pubName, String? feedId}) arg) async {
if (arg.feedId == null || arg.feedId!.isEmpty) {
return WebFeed(
return SnWebFeed(
id: '',
url: '',
title: '',
@ -38,13 +41,13 @@ class WebFeedNotifier
final response = await client.get(
'/publishers/${arg.pubName}/feeds/${arg.feedId}',
);
return WebFeed.fromJson(response.data);
return SnWebFeed.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<void> saveFeed(WebFeed feed) async {
Future<void> saveFeed(SnWebFeed feed) async {
state = const AsyncValue.loading();
try {
final client = ref.read(apiClientProvider);
@ -55,7 +58,7 @@ class WebFeedNotifier
? await client.post(url, data: feed.toJson())
: await client.patch('$url/${feed.id}', data: feed.toJson());
state = AsyncValue.data(WebFeed.fromJson(response.data));
state = AsyncValue.data(SnWebFeed.fromJson(response.data));
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
rethrow;
@ -71,7 +74,7 @@ class WebFeedNotifier
final client = ref.read(apiClientProvider);
await client.delete('/publishers/${arg.pubName}/feeds/$feedId');
state = AsyncValue.data(
WebFeed(
SnWebFeed(
id: '',
url: '',
title: '',
@ -94,13 +97,19 @@ class WebFeedNotifier
state = const AsyncValue.loading();
try {
final client = ref.read(apiClientProvider);
await client.post('/publishers/${arg.pubName}/feeds/$feedId/scrap');
await client.post(
'/publishers/${arg.pubName}/feeds/$feedId/scrap',
options: Options(
sendTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 180),
),
);
// Reload the feed
final response = await client.get(
'/publishers/${arg.pubName}/feeds/$feedId',
);
state = AsyncValue.data(WebFeed.fromJson(response.data));
state = AsyncValue.data(SnWebFeed.fromJson(response.data));
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
rethrow;
@ -109,6 +118,6 @@ class WebFeedNotifier
}
final webFeedNotifierProvider = AsyncNotifierProvider.autoDispose
.family<WebFeedNotifier, WebFeed, ({String pubName, String? feedId})>(
.family<WebFeedNotifier, SnWebFeed, ({String pubName, String? feedId})>(
WebFeedNotifier.new,
);

View File

@ -9,6 +9,7 @@ import 'package:island/widgets/app_wrapper.dart';
import 'package:island/screens/tabs.dart';
import 'package:island/screens/explore.dart';
import 'package:island/screens/article_detail_screen.dart';
import 'package:island/screens/account.dart';
import 'package:island/screens/notification.dart';
import 'package:island/screens/wallet.dart';
@ -242,6 +243,18 @@ final routerProvider = Provider<GoRouter>((ref) {
return TabsScreen(child: child);
},
routes: [
// Article detail route
GoRoute(
path: '/articles/:id',
pageBuilder: (context, state) {
final id = state.pathParameters['id']!;
return MaterialPage(
key: state.pageKey,
child: ArticleDetailScreen(articleId: id),
);
},
),
// Explore tab
ShellRoute(
builder:

View File

@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/widgets/content/markdown.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:island/models/webfeed.dart';
import 'package:island/pods/article_detail.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/loading_indicator.dart';
import 'package:html2md/html2md.dart' as html2md;
class ArticleDetailScreen extends ConsumerWidget {
final String articleId;
const ArticleDetailScreen({super.key, required this.articleId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final articleAsync = ref.watch(articleDetailProvider(articleId));
return AppScaffold(
body: articleAsync.when(
data:
(article) => AppScaffold(
appBar: AppBar(
leading: const BackButton(),
title: Text(article.title),
),
body: _ArticleDetailContent(article: article),
),
loading: () => const Center(child: LoadingIndicator()),
error:
(error, stackTrace) =>
Center(child: Text('Failed to load article: $error')),
),
);
}
}
class _ArticleDetailContent extends HookConsumerWidget {
final SnWebArticle article;
const _ArticleDetailContent({required this.article});
@override
Widget build(BuildContext context, WidgetRef ref) {
final markdownContent = useMemoized(
() => html2md.convert(article.content ?? ''),
[article],
);
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (article.preview?.imageUrl != null)
Image.network(
article.preview!.imageUrl!,
width: double.infinity,
height: 200,
fit: BoxFit.cover,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
article.title,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
if (article.feed?.title != null)
Text(
article.feed!.title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const Divider(height: 32),
if (article.content != null)
...MarkdownTextContent.buildGenerator(
isDark: Theme.of(context).brightness == Brightness.dark,
).buildWidgets(markdownContent)
else if (article.preview?.description != null)
Text(article.preview!.description!),
const Gap(24),
FilledButton(
onPressed:
() => launchUrlString(
article.url,
mode: LaunchMode.externalApplication,
),
child: const Text('Read Full Article'),
),
Gap(MediaQuery.of(context).padding.bottom),
],
),
),
],
),
);
}
}

View File

@ -40,12 +40,12 @@ class WebFeedEditScreen extends HookConsumerWidget {
isLoading.value = true;
try {
final feed = WebFeed(
final feed = SnWebFeed(
id: feedId ?? '',
title: titleController.text,
url: urlController.text,
description: descriptionController.text,
config: WebFeedConfig(scrapPage: isScrapEnabled.value),
config: SnWebFeedConfig(scrapPage: isScrapEnabled.value),
publisherId: pubName,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),

View File

@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/activity.dart';
import 'package:island/models/publisher.dart';
import 'package:island/models/realm.dart';
import 'package:island/models/webfeed.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/app_scaffold.dart';
@ -21,6 +22,7 @@ import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/realm/realm_card.dart';
import 'package:island/widgets/publisher/publisher_card.dart';
import 'package:island/widgets/web_article_card.dart';
import 'package:styled_widget/styled_widget.dart';
part 'explore.g.dart';
@ -196,6 +198,7 @@ class _DiscoveryActivityItem extends StatelessWidget {
(switch (type) {
'realm' => 'discoverRealms',
'publisher' => 'discoverPublishers',
'article' => 'discoverWebArticles',
_ => 'unknown',
}).tr(),
style: Theme.of(context).textTheme.titleMedium,
@ -221,6 +224,11 @@ class _DiscoveryActivityItem extends StatelessWidget {
publisher: SnPublisher.fromJson(item['data']),
maxWidth: 280,
);
case 'article':
return WebArticleCard(
article: SnWebArticle.fromJson(item['data']),
maxWidth: 280,
);
default:
return Placeholder();
}
@ -342,7 +350,7 @@ class ActivityListNotifier extends _$ActivityListNotifier
if (cursor != null) 'cursor': cursor,
'take': take,
if (filter != null) 'filter': filter,
if (kDebugMode) 'debugInclude': 'realms,publishers',
if (kDebugMode) 'debugInclude': 'realms,publishers,articles',
};
final response = await client.get(

View File

@ -7,7 +7,7 @@ part of 'explore.dart';
// **************************************************************************
String _$activityListNotifierHash() =>
r'57e9dcec944a9f88f8508b69fc91342592f5b349';
r'98b62fb9b958023d2c9e320af7ec1f1244836f49';
/// Copied from Dart SDK
class _SystemHash {

View File

@ -0,0 +1 @@

View File

@ -74,9 +74,7 @@ class MarkdownTextContent extends HookConsumerWidget {
final url = Uri.tryParse(href);
if (url != null) {
if (url.scheme == 'solian') {
context.push(
['', url.host, ...url.pathSegments].join('/'),
);
context.push(['', url.host, ...url.pathSegments].join('/'));
return;
}
final whitelistDomains = ['solian.app', 'solsynth.dev'];
@ -143,7 +141,18 @@ class MarkdownTextContent extends HookConsumerWidget {
),
],
),
generator: MarkdownGenerator(
generator: MarkdownTextContent.buildGenerator(
isDark: isDark,
linesMargin: linesMargin,
),
);
}
static MarkdownGenerator buildGenerator({
bool isDark = false,
EdgeInsets? linesMargin,
}) {
return MarkdownGenerator(
generators: [latexGenerator],
inlineSyntaxList: [
_UserNameCardInlineSyntax(),
@ -151,7 +160,6 @@ class MarkdownTextContent extends HookConsumerWidget {
LatexSyntax(isDark),
],
linesMargin: linesMargin ?? EdgeInsets.symmetric(vertical: 4),
),
);
}
}

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
/// A simple loading indicator widget that can be used throughout the app
class LoadingIndicator extends StatelessWidget {
/// The size of the loading indicator
final double size;
/// The color of the loading indicator
final Color? color;
/// Creates a loading indicator
const LoadingIndicator({
super.key,
this.size = 24.0,
this.color,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor: color != null
? AlwaysStoppedAnimation<Color>(
color!,
)
: null,
),
);
}
}

View File

@ -0,0 +1,106 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:island/models/webfeed.dart';
class WebArticleCard extends StatelessWidget {
final SnWebArticle article;
final double? maxWidth;
const WebArticleCard({super.key, required this.article, this.maxWidth});
void _onTap(BuildContext context) {
context.push('/articles/${article.id}');
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
child: Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () => _onTap(context),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Stack(
fit: StackFit.expand,
children: [
// Image or fallback
article.preview?.imageUrl != null
? CachedNetworkImage(
imageUrl: article.preview!.imageUrl!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
)
: ColoredBox(
color: colorScheme.secondaryContainer,
child: const Center(
child: Icon(
Icons.article_outlined,
size: 48,
color: Colors.white,
),
),
),
// Gradient overlay
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.7),
],
),
),
),
// Title
Align(
alignment: Alignment.bottomLeft,
child: Container(
padding: const EdgeInsets.only(
left: 12,
right: 12,
bottom: 8,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Text(
article.title,
style: theme.textTheme.titleSmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
height: 1.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
article.feed?.title ?? 'Unknown Source',
style: const TextStyle(
fontSize: 9,
color: Colors.white70,
),
),
],
),
),
),
],
),
),
),
),
);
}
}

View File

@ -1093,6 +1093,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.15.6"
html2md:
dependency: "direct main"
description:
name: html2md
sha256: "465cf8ffa1b510fe0e97941579bf5b22e2d575f2cecb500a9c0254efe33a8036"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
http:
dependency: transitive
description:

View File

@ -127,6 +127,7 @@ dependencies:
url: https://github.com/lionelmennig/textfield_tags.git
ref: fixes/allow-controller-re-registration
mime: ^2.0.0
html2md: ^1.3.2
dev_dependencies:
flutter_test: