Compare commits

...

6 Commits

Author SHA1 Message Date
3c4a9767e1 ♻️ Move the title, description out of the settings sheet 2025-08-06 20:55:13 +08:00
5df2445f3f 📝 Update user agreement 2025-08-06 20:30:59 +08:00
56543d7b4c Publisher page category filter 2025-08-06 20:17:07 +08:00
4c6fea1242 💄 Optimize cloud video 2025-08-06 18:49:04 +08:00
fff43de9e3 🐛 Dozens of bug fixes 2025-08-06 18:15:13 +08:00
b31a915544 🐛 Bug fixes 2025-08-06 14:57:42 +08:00
21 changed files with 239 additions and 439 deletions

View File

@@ -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({

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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(

View File

@@ -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),

View File

@@ -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:

View File

@@ -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) {

View File

@@ -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
View 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;
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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(

View File

@@ -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,

View File

@@ -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);

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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),

View File

@@ -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),

View File

@@ -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,

View File

@@ -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