Compare commits
6 Commits
3.1.0+116
...
3c4a9767e1
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c4a9767e1 | |||
| 5df2445f3f | |||
| 56543d7b4c | |||
| 4c6fea1242 | |||
| fff43de9e3 | |||
| b31a915544 |
@@ -3,25 +3,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
part 'embed.freezed.dart';
|
part 'embed.freezed.dart';
|
||||||
part 'embed.g.dart';
|
part 'embed.g.dart';
|
||||||
|
|
||||||
@freezed
|
|
||||||
sealed class SnEmbedLink with _$SnEmbedLink {
|
|
||||||
const factory SnEmbedLink({
|
|
||||||
@JsonKey(name: 'Type') required String type,
|
|
||||||
@JsonKey(name: 'Url') required String url,
|
|
||||||
@JsonKey(name: 'Title') required String title,
|
|
||||||
@JsonKey(name: 'Description') required String? description,
|
|
||||||
@JsonKey(name: 'ImageUrl') required String? imageUrl,
|
|
||||||
@JsonKey(name: 'FaviconUrl') @Default("") String faviconUrl,
|
|
||||||
@JsonKey(name: 'SiteName') @Default("") String siteName,
|
|
||||||
@JsonKey(name: 'ContentType') required String? contentType,
|
|
||||||
@JsonKey(name: 'Author') required String? author,
|
|
||||||
@JsonKey(name: 'PublishedDate') required DateTime? publishedDate,
|
|
||||||
}) = _SnEmbedLink;
|
|
||||||
|
|
||||||
factory SnEmbedLink.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnEmbedLinkFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnScrappedLink with _$SnScrappedLink {
|
sealed class SnScrappedLink with _$SnScrappedLink {
|
||||||
const factory SnScrappedLink({
|
const factory SnScrappedLink({
|
||||||
|
|||||||
@@ -12,290 +12,6 @@ part of 'embed.dart';
|
|||||||
// dart format off
|
// dart format off
|
||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SnEmbedLink {
|
|
||||||
|
|
||||||
@JsonKey(name: 'Type') String get type;@JsonKey(name: 'Url') String get url;@JsonKey(name: 'Title') String get title;@JsonKey(name: 'Description') String? get description;@JsonKey(name: 'ImageUrl') String? get imageUrl;@JsonKey(name: 'FaviconUrl') String get faviconUrl;@JsonKey(name: 'SiteName') String get siteName;@JsonKey(name: 'ContentType') String? get contentType;@JsonKey(name: 'Author') String? get author;@JsonKey(name: 'PublishedDate') DateTime? get publishedDate;
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnEmbedLinkCopyWith<SnEmbedLink> get copyWith => _$SnEmbedLinkCopyWithImpl<SnEmbedLink>(this as SnEmbedLink, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SnEmbedLink to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SnEmbedLinkCopyWith<$Res> {
|
|
||||||
factory $SnEmbedLinkCopyWith(SnEmbedLink value, $Res Function(SnEmbedLink) _then) = _$SnEmbedLinkCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$SnEmbedLinkCopyWithImpl<$Res>
|
|
||||||
implements $SnEmbedLinkCopyWith<$Res> {
|
|
||||||
_$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SnEmbedLink _self;
|
|
||||||
final $Res Function(SnEmbedLink) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
type: null == type ? _self.type : type // 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?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [SnEmbedLink].
|
|
||||||
extension SnEmbedLinkPatterns on SnEmbedLink {
|
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnEmbedLink value)? $default,{required TResult orElse(),}){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// Callbacks receives the raw object, upcasted.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case final Subclass2 value:
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnEmbedLink value) $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink():
|
|
||||||
return $default(_that);}
|
|
||||||
}
|
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnEmbedLink value)? $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to an `orElse` callback.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() when $default != null:
|
|
||||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// As opposed to `map`, this offers destructuring.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case Subclass2(:final field2):
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate) $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink():
|
|
||||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to returning `null`
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() when $default != null:
|
|
||||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _SnEmbedLink implements SnEmbedLink {
|
|
||||||
const _SnEmbedLink({@JsonKey(name: 'Type') required this.type, @JsonKey(name: 'Url') required this.url, @JsonKey(name: 'Title') required this.title, @JsonKey(name: 'Description') required this.description, @JsonKey(name: 'ImageUrl') required this.imageUrl, @JsonKey(name: 'FaviconUrl') this.faviconUrl = "", @JsonKey(name: 'SiteName') this.siteName = "", @JsonKey(name: 'ContentType') required this.contentType, @JsonKey(name: 'Author') required this.author, @JsonKey(name: 'PublishedDate') required this.publishedDate});
|
|
||||||
factory _SnEmbedLink.fromJson(Map<String, dynamic> json) => _$SnEmbedLinkFromJson(json);
|
|
||||||
|
|
||||||
@override@JsonKey(name: 'Type') final String type;
|
|
||||||
@override@JsonKey(name: 'Url') final String url;
|
|
||||||
@override@JsonKey(name: 'Title') final String title;
|
|
||||||
@override@JsonKey(name: 'Description') final String? description;
|
|
||||||
@override@JsonKey(name: 'ImageUrl') final String? imageUrl;
|
|
||||||
@override@JsonKey(name: 'FaviconUrl') final String faviconUrl;
|
|
||||||
@override@JsonKey(name: 'SiteName') final String siteName;
|
|
||||||
@override@JsonKey(name: 'ContentType') final String? contentType;
|
|
||||||
@override@JsonKey(name: 'Author') final String? author;
|
|
||||||
@override@JsonKey(name: 'PublishedDate') final DateTime? publishedDate;
|
|
||||||
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SnEmbedLinkCopyWith<_SnEmbedLink> get copyWith => __$SnEmbedLinkCopyWithImpl<_SnEmbedLink>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SnEmbedLinkToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SnEmbedLinkCopyWith<$Res> implements $SnEmbedLinkCopyWith<$Res> {
|
|
||||||
factory _$SnEmbedLinkCopyWith(_SnEmbedLink value, $Res Function(_SnEmbedLink) _then) = __$SnEmbedLinkCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$SnEmbedLinkCopyWithImpl<$Res>
|
|
||||||
implements _$SnEmbedLinkCopyWith<$Res> {
|
|
||||||
__$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SnEmbedLink _self;
|
|
||||||
final $Res Function(_SnEmbedLink) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
|
||||||
return _then(_SnEmbedLink(
|
|
||||||
type: null == type ? _self.type : type // 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?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnScrappedLink {
|
mixin _$SnScrappedLink {
|
||||||
|
|
||||||
|
|||||||
@@ -6,36 +6,6 @@ part of 'embed.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
_SnEmbedLink _$SnEmbedLinkFromJson(Map<String, dynamic> json) => _SnEmbedLink(
|
|
||||||
type: json['Type'] as String,
|
|
||||||
url: json['Url'] as String,
|
|
||||||
title: json['Title'] as String,
|
|
||||||
description: json['Description'] as String?,
|
|
||||||
imageUrl: json['ImageUrl'] as String?,
|
|
||||||
faviconUrl: json['FaviconUrl'] as String? ?? "",
|
|
||||||
siteName: json['SiteName'] as String? ?? "",
|
|
||||||
contentType: json['ContentType'] as String?,
|
|
||||||
author: json['Author'] as String?,
|
|
||||||
publishedDate:
|
|
||||||
json['PublishedDate'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['PublishedDate'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SnEmbedLinkToJson(_SnEmbedLink instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'Type': instance.type,
|
|
||||||
'Url': instance.url,
|
|
||||||
'Title': instance.title,
|
|
||||||
'Description': instance.description,
|
|
||||||
'ImageUrl': instance.imageUrl,
|
|
||||||
'FaviconUrl': instance.faviconUrl,
|
|
||||||
'SiteName': instance.siteName,
|
|
||||||
'ContentType': instance.contentType,
|
|
||||||
'Author': instance.author,
|
|
||||||
'PublishedDate': instance.publishedDate?.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) =>
|
_SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) =>
|
||||||
_SnScrappedLink(
|
_SnScrappedLink(
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
||||||
onTap:
|
onTap:
|
||||||
() => _launchURL(
|
() => _launchURL(
|
||||||
'https://solsynth.dev/terms/basic-law',
|
'https://solsynth.dev/terms/user-agreement',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
|
|||||||
@@ -218,6 +218,11 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
right: 12,
|
right: 12,
|
||||||
top: 16,
|
top: 16,
|
||||||
),
|
),
|
||||||
|
onChecked: () {
|
||||||
|
ref.invalidate(
|
||||||
|
eventCalendarProvider(query.value),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Card(
|
Card(
|
||||||
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
||||||
|
|||||||
@@ -207,8 +207,6 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => ComposeSettingsSheet(
|
(context) => ComposeSettingsSheet(
|
||||||
titleController: state.titleController,
|
|
||||||
descriptionController: state.descriptionController,
|
|
||||||
visibility: state.visibility,
|
visibility: state.visibility,
|
||||||
tagsController: state.tagsController,
|
tagsController: state.tagsController,
|
||||||
categoriesController: state.categoriesController,
|
categoriesController: state.categoriesController,
|
||||||
@@ -370,14 +368,52 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
// Post content form
|
// Post content form
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: state.titleController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: state.descriptionController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postDescription'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(
|
||||||
|
8,
|
||||||
|
4,
|
||||||
|
8,
|
||||||
|
12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 3,
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
),
|
||||||
// Content field with borderless design
|
// Content field with borderless design
|
||||||
RawKeyboardListener(
|
KeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
onKey:
|
onKeyEvent:
|
||||||
(event) => ComposeLogic.handleKeyPress(
|
(event) => ComposeLogic.handleKeyPress(
|
||||||
event,
|
event,
|
||||||
state,
|
state,
|
||||||
@@ -393,7 +429,11 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
hintText: 'postContent'.tr(),
|
hintText: 'postContent'.tr(),
|
||||||
contentPadding: const EdgeInsets.all(8),
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
|
|||||||
@@ -140,8 +140,6 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => ComposeSettingsSheet(
|
(context) => ComposeSettingsSheet(
|
||||||
titleController: state.titleController,
|
|
||||||
descriptionController: state.descriptionController,
|
|
||||||
visibility: state.visibility,
|
visibility: state.visibility,
|
||||||
tagsController: state.tagsController,
|
tagsController: state.tagsController,
|
||||||
categoriesController: state.categoriesController,
|
categoriesController: state.categoriesController,
|
||||||
@@ -242,10 +240,39 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: state.titleController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: state.descriptionController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postDescription'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 3,
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RawKeyboardListener(
|
child: KeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
onKey:
|
onKeyEvent:
|
||||||
(event) => _handleKeyPress(
|
(event) => _handleKeyPress(
|
||||||
event,
|
event,
|
||||||
state,
|
state,
|
||||||
@@ -454,7 +481,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
flex: showPreview.value ? 1 : 2,
|
flex: showPreview.value ? 1 : 2,
|
||||||
child: buildEditorPane(),
|
child: buildEditorPane(),
|
||||||
),
|
),
|
||||||
const VerticalDivider(),
|
if (showPreview.value) const VerticalDivider(),
|
||||||
if (showPreview.value)
|
if (showPreview.value)
|
||||||
Expanded(child: buildPreviewPane()),
|
Expanded(child: buildPreviewPane()),
|
||||||
],
|
],
|
||||||
@@ -475,7 +502,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Helper method to handle keyboard shortcuts
|
// Helper method to handle keyboard shortcuts
|
||||||
void _handleKeyPress(
|
void _handleKeyPress(
|
||||||
RawKeyEvent event,
|
KeyEvent event,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -485,7 +512,9 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||||
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
||||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
final isModifierPressed =
|
||||||
|
HardwareKeyboard.instance.isMetaPressed ||
|
||||||
|
HardwareKeyboard.instance.isControlPressed;
|
||||||
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
||||||
|
|
||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
|
|||||||
@@ -87,6 +87,12 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
publisherAppbarForcegroundColorProvider(name),
|
publisherAppbarForcegroundColorProvider(name),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final categoryTabController = useTabController(initialLength: 3);
|
||||||
|
final categoryTab = useState(0);
|
||||||
|
categoryTabController.addListener(() {
|
||||||
|
categoryTab.value = categoryTabController.index;
|
||||||
|
});
|
||||||
|
|
||||||
final subscribing = useState(false);
|
final subscribing = useState(false);
|
||||||
|
|
||||||
Future<void> subscribe() async {
|
Future<void> subscribe() async {
|
||||||
@@ -268,6 +274,16 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
).padding(horizontal: 20, vertical: 16),
|
).padding(horizontal: 20, vertical: 16),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Widget publisherCategoryTabWidget() => Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: TabBar(
|
||||||
|
controller: categoryTabController,
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
tabs: [Tab(text: 'All'), Tab(text: 'Posts'), Tab(text: 'Articles')],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return publisher.when(
|
return publisher.when(
|
||||||
data:
|
data:
|
||||||
(data) => AppScaffold(
|
(data) => AppScaffold(
|
||||||
@@ -398,7 +414,16 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
child: publisherVerificationWidget(data),
|
child: publisherVerificationWidget(data),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
||||||
SliverPostList(pubName: name),
|
SliverToBoxAdapter(child: publisherCategoryTabWidget()),
|
||||||
|
SliverPostList(
|
||||||
|
key: ValueKey(categoryTab.value),
|
||||||
|
pubName: name,
|
||||||
|
type: switch (categoryTab.value) {
|
||||||
|
1 => 0,
|
||||||
|
2 => 1,
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
|
),
|
||||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
30
lib/utils/mapping.dart
Normal file
30
lib/utils/mapping.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
String _upperCamelToLowerSnake(String input) {
|
||||||
|
final regex = RegExp(r'(?<=[a-z0-9])([A-Z])');
|
||||||
|
return input
|
||||||
|
.replaceAllMapped(regex, (match) => '_${match.group(0)}')
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> convertMapKeysToSnakeCase(Map<String, dynamic> input) {
|
||||||
|
final result = <String, dynamic>{};
|
||||||
|
|
||||||
|
input.forEach((key, value) {
|
||||||
|
final newKey = _upperCamelToLowerSnake(key);
|
||||||
|
|
||||||
|
if (value is Map<String, dynamic>) {
|
||||||
|
result[newKey] = convertMapKeysToSnakeCase(value);
|
||||||
|
} else if (value is List) {
|
||||||
|
result[newKey] =
|
||||||
|
value.map((item) {
|
||||||
|
if (item is Map<String, dynamic>) {
|
||||||
|
return convertMapKeysToSnakeCase(item);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}).toList();
|
||||||
|
} else {
|
||||||
|
result[newKey] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import 'package:island/models/embed.dart';
|
|||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
import 'package:island/pods/translate.dart';
|
import 'package:island/pods/translate.dart';
|
||||||
import 'package:island/screens/chat/room.dart';
|
import 'package:island/screens/chat/room.dart';
|
||||||
|
import 'package:island/utils/mapping.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/account/account_pfc.dart';
|
import 'package:island/widgets/account/account_pfc.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@@ -292,12 +293,11 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (remoteMessage.meta['embeds'] != null)
|
if (remoteMessage.meta['embeds'] != null)
|
||||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||||
.where((embed) => embed['Type'] == 'link')
|
|
||||||
.map(
|
.map(
|
||||||
(embed) => SnEmbedLink.fromJson(
|
(embed) => convertMapKeysToSnakeCase(embed),
|
||||||
embed as Map<String, dynamic>,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
.where((embed) => embed['type'] == 'link')
|
||||||
|
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||||
.map(
|
.map(
|
||||||
(link) => LayoutBuilder(
|
(link) => LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ Future<SnCheckInResult?> checkInResultToday(Ref ref) async {
|
|||||||
|
|
||||||
class CheckInWidget extends HookConsumerWidget {
|
class CheckInWidget extends HookConsumerWidget {
|
||||||
final EdgeInsets? margin;
|
final EdgeInsets? margin;
|
||||||
const CheckInWidget({super.key, this.margin});
|
final VoidCallback? onChecked;
|
||||||
|
const CheckInWidget({super.key, this.margin, this.onChecked});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -52,6 +53,7 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
ref.invalidate(checkInResultTodayProvider);
|
ref.invalidate(checkInResultTodayProvider);
|
||||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
userNotifier.fetchUser();
|
userNotifier.fetchUser();
|
||||||
|
onChecked?.call();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err is DioException) {
|
if (err is DioException) {
|
||||||
if (err.response?.statusCode == 423 && context.mounted) {
|
if (err.response?.statusCode == 423 && context.mounted) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -103,6 +102,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
Symbols.play_arrow,
|
Symbols.play_arrow,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
size: 32,
|
size: 32,
|
||||||
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@@ -114,6 +114,26 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
height: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.surface.withOpacity(0.85),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -133,6 +153,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
.toInt(),
|
.toInt(),
|
||||||
).formatDuration(),
|
).formatDuration(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@@ -147,6 +168,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
'${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps',
|
'${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@@ -161,7 +183,10 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
item.name,
|
item.name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class EmbedLinkWidget extends StatelessWidget {
|
class EmbedLinkWidget extends StatelessWidget {
|
||||||
final SnEmbedLink link;
|
final SnScrappedLink link;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
final EdgeInsetsGeometry? margin;
|
final EdgeInsetsGeometry? margin;
|
||||||
|
|
||||||
@@ -116,7 +116,8 @@ class EmbedLinkWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
if (link.description != null && link.description!.isNotEmpty) ...[
|
if (link.description != null &&
|
||||||
|
link.description!.isNotEmpty) ...[
|
||||||
Text(
|
Text(
|
||||||
link.description!,
|
link.description!,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
|||||||
@@ -3,21 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:island/models/poll.dart';
|
import 'package:island/models/poll.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
|
||||||
/// A poll answering widget that shows one question at a time and collects answers.
|
|
||||||
///
|
|
||||||
/// Usage:
|
|
||||||
/// PollSubmit(
|
|
||||||
/// poll: poll,
|
|
||||||
/// onSubmit: (answers) {
|
|
||||||
/// // answers is Map<String, dynamic>: questionId -> answer
|
|
||||||
/// // answer types by question:
|
|
||||||
/// // - singleChoice: String optionId
|
|
||||||
/// // - multipleChoice: List<String> optionIds
|
|
||||||
/// // - yesNo: bool
|
|
||||||
/// // - rating: int (1..5)
|
|
||||||
/// // - freeText: String
|
|
||||||
/// },
|
|
||||||
/// )
|
|
||||||
class PollSubmit extends ConsumerStatefulWidget {
|
class PollSubmit extends ConsumerStatefulWidget {
|
||||||
const PollSubmit({
|
const PollSubmit({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
@@ -189,8 +189,8 @@ class ComposePollSheet extends HookConsumerWidget {
|
|||||||
Widget? _buildPollSubtitle(SnPoll poll) {
|
Widget? _buildPollSubtitle(SnPoll poll) {
|
||||||
try {
|
try {
|
||||||
final SnPoll dyn = poll;
|
final SnPoll dyn = poll;
|
||||||
final List<SnPollQuestion>? options = dyn.questions;
|
final List<SnPollQuestion> options = dyn.questions;
|
||||||
if (options == null || options.isEmpty) return null;
|
if (options.isEmpty) return null;
|
||||||
final preview = options.take(3).map((e) => e.title).join(' · ');
|
final preview = options.take(3).map((e) => e.title).join(' · ');
|
||||||
if (preview.trim().isEmpty) return null;
|
if (preview.trim().isEmpty) return null;
|
||||||
return Text(preview);
|
return Text(preview);
|
||||||
|
|||||||
@@ -99,8 +99,6 @@ class ChipTagInputField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ComposeSettingsSheet extends HookWidget {
|
class ComposeSettingsSheet extends HookWidget {
|
||||||
final TextEditingController titleController;
|
|
||||||
final TextEditingController descriptionController;
|
|
||||||
final ValueNotifier<int> visibility;
|
final ValueNotifier<int> visibility;
|
||||||
final VoidCallback? onVisibilityChanged;
|
final VoidCallback? onVisibilityChanged;
|
||||||
final StringTagController tagsController;
|
final StringTagController tagsController;
|
||||||
@@ -108,8 +106,6 @@ class ComposeSettingsSheet extends HookWidget {
|
|||||||
|
|
||||||
const ComposeSettingsSheet({
|
const ComposeSettingsSheet({
|
||||||
super.key,
|
super.key,
|
||||||
required this.titleController,
|
|
||||||
required this.descriptionController,
|
|
||||||
required this.visibility,
|
required this.visibility,
|
||||||
this.onVisibilityChanged,
|
this.onVisibilityChanged,
|
||||||
required this.tagsController,
|
required this.tagsController,
|
||||||
@@ -216,39 +212,6 @@ class ComposeSettingsSheet extends HookWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
// Title field
|
|
||||||
TextField(
|
|
||||||
controller: titleController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'postTitle'.tr(),
|
|
||||||
hintText: 'postTitle'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
style: theme.textTheme.titleMedium,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Description field
|
|
||||||
TextField(
|
|
||||||
controller: descriptionController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'postDescription'.tr(),
|
|
||||||
hintText: 'postDescription'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
maxLines: 3,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Tags field
|
// Tags field
|
||||||
TextFieldTags(
|
TextFieldTags(
|
||||||
textfieldTagsController: tagsController,
|
textfieldTagsController: tagsController,
|
||||||
|
|||||||
@@ -689,7 +689,7 @@ class ComposeLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void handleKeyPress(
|
static void handleKeyPress(
|
||||||
RawKeyEvent event,
|
KeyEvent event,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -701,7 +701,9 @@ class ComposeLogic {
|
|||||||
|
|
||||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||||
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
||||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
final isModifierPressed =
|
||||||
|
HardwareKeyboard.instance.isMetaPressed ||
|
||||||
|
HardwareKeyboard.instance.isControlPressed;
|
||||||
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
||||||
|
|
||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class ComposeToolbar extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 560),
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
|
|||||||
@@ -547,7 +547,9 @@ class PostItem extends HookConsumerWidget {
|
|||||||
...((item.meta!['embeds'] as List<dynamic>).map(
|
...((item.meta!['embeds'] as List<dynamic>).map(
|
||||||
(embedData) => switch (embedData['type']) {
|
(embedData) => switch (embedData['type']) {
|
||||||
'link' => EmbedLinkWidget(
|
'link' => EmbedLinkWidget(
|
||||||
link: SnEmbedLink.fromJson(embedData as Map<String, dynamic>),
|
link: SnScrappedLink.fromJson(
|
||||||
|
embedData as Map<String, dynamic>,
|
||||||
|
),
|
||||||
maxWidth: math.min(
|
maxWidth: math.min(
|
||||||
MediaQuery.of(context).size.width,
|
MediaQuery.of(context).size.width,
|
||||||
kWideScreenWidth,
|
kWideScreenWidth,
|
||||||
@@ -770,7 +772,7 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
final posts = useState<List<SnPost>>([]);
|
final posts = useState<List<SnPost>>([]);
|
||||||
final loading = useState(false);
|
final loading = useState(false);
|
||||||
|
|
||||||
Future<void> fetchMoreReplies({int pageSize = 1}) async {
|
Future<void> fetchMoreReplies({int pageSize = 3}) async {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
@@ -877,38 +879,40 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: featuredReply!.when(
|
: (featuredReply!).map(
|
||||||
data:
|
data:
|
||||||
(value) => Row(
|
(data) => Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
file: value?.publisher.picture,
|
file: data.value?.publisher.picture,
|
||||||
radius: 12,
|
radius: 12,
|
||||||
).padding(top: 4),
|
).padding(top: 4),
|
||||||
if (value?.content?.isNotEmpty ?? false)
|
if (data.value?.content?.isNotEmpty ?? false)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MarkdownTextContent(content: value!.content!),
|
child: MarkdownTextContent(
|
||||||
|
content: data.value!.content!,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'postHasAttachments',
|
'postHasAttachments',
|
||||||
).plural(value?.attachments.length ?? 0),
|
).plural(data.value?.attachments.length ?? 0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
error:
|
error:
|
||||||
(error, _) => Row(
|
(e) => Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.close, size: 18),
|
const Icon(Symbols.close, size: 18),
|
||||||
Text(error.toString()),
|
Text(e.error.toString()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
loading:
|
loading:
|
||||||
() => Row(
|
(_) => Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -939,7 +943,6 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text('repliesCount')
|
Text('repliesCount')
|
||||||
.plural(parent.repliesCount)
|
.plural(parent.repliesCount)
|
||||||
.tr()
|
|
||||||
.fontSize(15)
|
.fontSize(15)
|
||||||
.bold()
|
.bold()
|
||||||
.padding(horizontal: 5),
|
.padding(horizontal: 5),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class PostListNotifier extends _$PostListNotifier
|
|||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<CursorPagingData<SnPost>> build(String? pubName) {
|
Future<CursorPagingData<SnPost>> build(String? pubName, int? type) {
|
||||||
return fetch(cursor: null);
|
return fetch(cursor: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ class PostListNotifier extends _$PostListNotifier
|
|||||||
'offset': offset,
|
'offset': offset,
|
||||||
'take': _pageSize,
|
'take': _pageSize,
|
||||||
if (pubName != null) 'pub': pubName,
|
if (pubName != null) 'pub': pubName,
|
||||||
|
if (type != null) 'type': type,
|
||||||
};
|
};
|
||||||
|
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
@@ -60,6 +61,7 @@ enum PostItemType {
|
|||||||
|
|
||||||
class SliverPostList extends HookConsumerWidget {
|
class SliverPostList extends HookConsumerWidget {
|
||||||
final String? pubName;
|
final String? pubName;
|
||||||
|
final int? type;
|
||||||
final PostItemType itemType;
|
final PostItemType itemType;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
@@ -70,6 +72,7 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
const SliverPostList({
|
const SliverPostList({
|
||||||
super.key,
|
super.key,
|
||||||
this.pubName,
|
this.pubName,
|
||||||
|
this.type,
|
||||||
this.itemType = PostItemType.regular,
|
this.itemType = PostItemType.regular,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.padding,
|
this.padding,
|
||||||
@@ -81,9 +84,9 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return PagingHelperSliverView(
|
return PagingHelperSliverView(
|
||||||
provider: postListNotifierProvider(pubName),
|
provider: postListNotifierProvider(pubName, type),
|
||||||
futureRefreshable: postListNotifierProvider(pubName).future,
|
futureRefreshable: postListNotifierProvider(pubName, type).future,
|
||||||
notifierRefreshable: postListNotifierProvider(pubName).notifier,
|
notifierRefreshable: postListNotifierProvider(pubName, type).notifier,
|
||||||
contentBuilder:
|
contentBuilder:
|
||||||
(data, widgetCount, endItemView) => SliverList.builder(
|
(data, widgetCount, endItemView) => SliverList.builder(
|
||||||
itemCount: widgetCount,
|
itemCount: widgetCount,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'post_list.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$postListNotifierHash() => r'2e4fb36123d3f97ac1edf9945043251d4eb519a2';
|
String _$postListNotifierHash() => r'c7c82c8cedf6649ac0806bbbfea148dfa1422fc0';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
@@ -32,8 +32,9 @@ class _SystemHash {
|
|||||||
abstract class _$PostListNotifier
|
abstract class _$PostListNotifier
|
||||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> {
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> {
|
||||||
late final String? pubName;
|
late final String? pubName;
|
||||||
|
late final int? type;
|
||||||
|
|
||||||
FutureOr<CursorPagingData<SnPost>> build(String? pubName);
|
FutureOr<CursorPagingData<SnPost>> build(String? pubName, int? type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [PostListNotifier].
|
/// See also [PostListNotifier].
|
||||||
@@ -47,15 +48,15 @@ class PostListNotifierFamily
|
|||||||
const PostListNotifierFamily();
|
const PostListNotifierFamily();
|
||||||
|
|
||||||
/// See also [PostListNotifier].
|
/// See also [PostListNotifier].
|
||||||
PostListNotifierProvider call(String? pubName) {
|
PostListNotifierProvider call(String? pubName, int? type) {
|
||||||
return PostListNotifierProvider(pubName);
|
return PostListNotifierProvider(pubName, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PostListNotifierProvider getProviderOverride(
|
PostListNotifierProvider getProviderOverride(
|
||||||
covariant PostListNotifierProvider provider,
|
covariant PostListNotifierProvider provider,
|
||||||
) {
|
) {
|
||||||
return call(provider.pubName);
|
return call(provider.pubName, provider.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
@@ -81,9 +82,12 @@ class PostListNotifierProvider
|
|||||||
CursorPagingData<SnPost>
|
CursorPagingData<SnPost>
|
||||||
> {
|
> {
|
||||||
/// See also [PostListNotifier].
|
/// See also [PostListNotifier].
|
||||||
PostListNotifierProvider(String? pubName)
|
PostListNotifierProvider(String? pubName, int? type)
|
||||||
: this._internal(
|
: this._internal(
|
||||||
() => PostListNotifier()..pubName = pubName,
|
() =>
|
||||||
|
PostListNotifier()
|
||||||
|
..pubName = pubName
|
||||||
|
..type = type,
|
||||||
from: postListNotifierProvider,
|
from: postListNotifierProvider,
|
||||||
name: r'postListNotifierProvider',
|
name: r'postListNotifierProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
@@ -94,6 +98,7 @@ class PostListNotifierProvider
|
|||||||
allTransitiveDependencies:
|
allTransitiveDependencies:
|
||||||
PostListNotifierFamily._allTransitiveDependencies,
|
PostListNotifierFamily._allTransitiveDependencies,
|
||||||
pubName: pubName,
|
pubName: pubName,
|
||||||
|
type: type,
|
||||||
);
|
);
|
||||||
|
|
||||||
PostListNotifierProvider._internal(
|
PostListNotifierProvider._internal(
|
||||||
@@ -104,15 +109,17 @@ class PostListNotifierProvider
|
|||||||
required super.debugGetCreateSourceHash,
|
required super.debugGetCreateSourceHash,
|
||||||
required super.from,
|
required super.from,
|
||||||
required this.pubName,
|
required this.pubName,
|
||||||
|
required this.type,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final String? pubName;
|
final String? pubName;
|
||||||
|
final int? type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
|
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
|
||||||
covariant PostListNotifier notifier,
|
covariant PostListNotifier notifier,
|
||||||
) {
|
) {
|
||||||
return notifier.build(pubName);
|
return notifier.build(pubName, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -120,13 +127,17 @@ class PostListNotifierProvider
|
|||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: PostListNotifierProvider._internal(
|
override: PostListNotifierProvider._internal(
|
||||||
() => create()..pubName = pubName,
|
() =>
|
||||||
|
create()
|
||||||
|
..pubName = pubName
|
||||||
|
..type = type,
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
pubName: pubName,
|
pubName: pubName,
|
||||||
|
type: type,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -142,13 +153,16 @@ class PostListNotifierProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is PostListNotifierProvider && other.pubName == pubName;
|
return other is PostListNotifierProvider &&
|
||||||
|
other.pubName == pubName &&
|
||||||
|
other.type == type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
hash = _SystemHash.combine(hash, pubName.hashCode);
|
hash = _SystemHash.combine(hash, pubName.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, type.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
@@ -160,6 +174,9 @@ mixin PostListNotifierRef
|
|||||||
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> {
|
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> {
|
||||||
/// The parameter `pubName` of this provider.
|
/// The parameter `pubName` of this provider.
|
||||||
String? get pubName;
|
String? get pubName;
|
||||||
|
|
||||||
|
/// The parameter `type` of this provider.
|
||||||
|
int? get type;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostListNotifierProviderElement
|
class _PostListNotifierProviderElement
|
||||||
@@ -173,6 +190,8 @@ class _PostListNotifierProviderElement
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String? get pubName => (origin as PostListNotifierProvider).pubName;
|
String? get pubName => (origin as PostListNotifierProvider).pubName;
|
||||||
|
@override
|
||||||
|
int? get type => (origin as PostListNotifierProvider).type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
|||||||
Reference in New Issue
Block a user