Compare commits
	
		
			15 Commits
		
	
	
		
			3.1.0+116
			...
			f00135c4bf
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f00135c4bf | |||
| 30b8a6c30f | |||
| b9c4ee31b1 | |||
| 87870af866 | |||
| b83cb0fb0b | |||
| 7fd1fe34e5 | |||
| 1c18330891 | |||
| d320879ad0 | |||
| 950150e119 | |||
| 3c4a9767e1 | |||
| 5df2445f3f | |||
| 56543d7b4c | |||
| 4c6fea1242 | |||
| fff43de9e3 | |||
| b31a915544 | 
@@ -761,5 +761,12 @@
 | 
				
			|||||||
  "publisher": "Publisher",
 | 
					  "publisher": "Publisher",
 | 
				
			||||||
  "publisherHint": "Enter the publisher name",
 | 
					  "publisherHint": "Enter the publisher name",
 | 
				
			||||||
  "publisherCannotBeEmpty": "Publisher cannot be empty",
 | 
					  "publisherCannotBeEmpty": "Publisher cannot be empty",
 | 
				
			||||||
  "operationFailed": "Operation failed: {}"
 | 
					  "operationFailed": "Operation failed: {}",
 | 
				
			||||||
 | 
					  "stickerMarketplace": "Sticker Marketplace",
 | 
				
			||||||
 | 
					  "stickerPackAdded": "Sticker pack added to your collection",
 | 
				
			||||||
 | 
					  "stickerPackRemoved": "Sticker pack removed from your collection",
 | 
				
			||||||
 | 
					  "addPack": "Add Pack",
 | 
				
			||||||
 | 
					  "removePack": "Remove Pack",
 | 
				
			||||||
 | 
					  "browseAndAddStickers": "Browse and add sticker packs",
 | 
				
			||||||
 | 
					  "stickerPack": "Sticker Pack"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,3 +90,19 @@ enum SnPollQuestionType {
 | 
				
			|||||||
  @JsonValue(4)
 | 
					  @JsonValue(4)
 | 
				
			||||||
  freeText,
 | 
					  freeText,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class SnPollAnswer with _$SnPollAnswer {
 | 
				
			||||||
 | 
					  const factory SnPollAnswer({
 | 
				
			||||||
 | 
					    required String id,
 | 
				
			||||||
 | 
					    required Map<String, dynamic> answer,
 | 
				
			||||||
 | 
					    required String accountId,
 | 
				
			||||||
 | 
					    required String pollId,
 | 
				
			||||||
 | 
					    required DateTime createdAt,
 | 
				
			||||||
 | 
					    required DateTime updatedAt,
 | 
				
			||||||
 | 
					    required DateTime? deletedAt,
 | 
				
			||||||
 | 
					  }) = _SnPollAnswer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory SnPollAnswer.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$SnPollAnswerFromJson(json);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1181,6 +1181,287 @@ as int,
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$SnPollAnswer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 String get id; Map<String, dynamic> get answer; String get accountId; String get pollId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
 | 
				
			||||||
 | 
					/// Create a copy of SnPollAnswer
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnPollAnswerCopyWith<SnPollAnswer> get copyWith => _$SnPollAnswerCopyWithImpl<SnPollAnswer>(this as SnPollAnswer, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this SnPollAnswer to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other.answer, answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(answer),accountId,pollId,createdAt,updatedAt,deletedAt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $SnPollAnswerCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $SnPollAnswerCopyWith(SnPollAnswer value, $Res Function(SnPollAnswer) _then) = _$SnPollAnswerCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$SnPollAnswerCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $SnPollAnswerCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$SnPollAnswerCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final SnPollAnswer _self;
 | 
				
			||||||
 | 
					  final $Res Function(SnPollAnswer) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnPollAnswer
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Adds pattern-matching-related methods to [SnPollAnswer].
 | 
				
			||||||
 | 
					extension SnPollAnswerPatterns on SnPollAnswer {
 | 
				
			||||||
 | 
					/// 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( _SnPollAnswer value)?  $default,{required TResult orElse(),}){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnPollAnswer() 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( _SnPollAnswer value)  $default,){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnPollAnswer():
 | 
				
			||||||
 | 
					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( _SnPollAnswer value)?  $default,){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnPollAnswer() 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( String id,  Map<String, dynamic> answer,  String accountId,  String pollId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnPollAnswer() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);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( String id,  Map<String, dynamic> answer,  String accountId,  String pollId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnPollAnswer():
 | 
				
			||||||
 | 
					return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// 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( String id,  Map<String, dynamic> answer,  String accountId,  String pollId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnPollAnswer() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
 | 
				
			||||||
 | 
					  return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SnPollAnswer implements SnPollAnswer {
 | 
				
			||||||
 | 
					  const _SnPollAnswer({required this.id, required final  Map<String, dynamic> answer, required this.accountId, required this.pollId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _answer = answer;
 | 
				
			||||||
 | 
					  factory _SnPollAnswer.fromJson(Map<String, dynamic> json) => _$SnPollAnswerFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String id;
 | 
				
			||||||
 | 
					 final  Map<String, dynamic> _answer;
 | 
				
			||||||
 | 
					@override Map<String, dynamic> get answer {
 | 
				
			||||||
 | 
					  if (_answer is EqualUnmodifiableMapView) return _answer;
 | 
				
			||||||
 | 
					  // ignore: implicit_dynamic_type
 | 
				
			||||||
 | 
					  return EqualUnmodifiableMapView(_answer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String accountId;
 | 
				
			||||||
 | 
					@override final  String pollId;
 | 
				
			||||||
 | 
					@override final  DateTime createdAt;
 | 
				
			||||||
 | 
					@override final  DateTime updatedAt;
 | 
				
			||||||
 | 
					@override final  DateTime? deletedAt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnPollAnswer
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					_$SnPollAnswerCopyWith<_SnPollAnswer> get copyWith => __$SnPollAnswerCopyWithImpl<_SnPollAnswer>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$SnPollAnswerToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._answer, _answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(_answer),accountId,pollId,createdAt,updatedAt,deletedAt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class _$SnPollAnswerCopyWith<$Res> implements $SnPollAnswerCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory _$SnPollAnswerCopyWith(_SnPollAnswer value, $Res Function(_SnPollAnswer) _then) = __$SnPollAnswerCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class __$SnPollAnswerCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements _$SnPollAnswerCopyWith<$Res> {
 | 
				
			||||||
 | 
					  __$SnPollAnswerCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final _SnPollAnswer _self;
 | 
				
			||||||
 | 
					  final $Res Function(_SnPollAnswer) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnPollAnswer
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_SnPollAnswer(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,answer: null == answer ? _self._answer : answer // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// dart format on
 | 
					// dart format on
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -131,3 +131,28 @@ Map<String, dynamic> _$SnPollOptionToJson(_SnPollOption instance) =>
 | 
				
			|||||||
      'description': instance.description,
 | 
					      'description': instance.description,
 | 
				
			||||||
      'order': instance.order,
 | 
					      'order': instance.order,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_SnPollAnswer _$SnPollAnswerFromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					    _SnPollAnswer(
 | 
				
			||||||
 | 
					      id: json['id'] as String,
 | 
				
			||||||
 | 
					      answer: json['answer'] as Map<String, dynamic>,
 | 
				
			||||||
 | 
					      accountId: json['account_id'] as String,
 | 
				
			||||||
 | 
					      pollId: json['poll_id'] as String,
 | 
				
			||||||
 | 
					      createdAt: DateTime.parse(json['created_at'] as String),
 | 
				
			||||||
 | 
					      updatedAt: DateTime.parse(json['updated_at'] as String),
 | 
				
			||||||
 | 
					      deletedAt:
 | 
				
			||||||
 | 
					          json['deleted_at'] == null
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : DateTime.parse(json['deleted_at'] as String),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$SnPollAnswerToJson(_SnPollAnswer instance) =>
 | 
				
			||||||
 | 
					    <String, dynamic>{
 | 
				
			||||||
 | 
					      'id': instance.id,
 | 
				
			||||||
 | 
					      'answer': instance.answer,
 | 
				
			||||||
 | 
					      'account_id': instance.accountId,
 | 
				
			||||||
 | 
					      'poll_id': instance.pollId,
 | 
				
			||||||
 | 
					      'created_at': instance.createdAt.toIso8601String(),
 | 
				
			||||||
 | 
					      'updated_at': instance.updatedAt.toIso8601String(),
 | 
				
			||||||
 | 
					      'deleted_at': instance.deletedAt?.toIso8601String(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,7 @@ sealed class SnStickerPack with _$SnStickerPack {
 | 
				
			|||||||
    required DateTime createdAt,
 | 
					    required DateTime createdAt,
 | 
				
			||||||
    required DateTime updatedAt,
 | 
					    required DateTime updatedAt,
 | 
				
			||||||
    required DateTime? deletedAt,
 | 
					    required DateTime? deletedAt,
 | 
				
			||||||
 | 
					    @Default([]) List<SnSticker> stickers,
 | 
				
			||||||
  }) = _SnStickerPack;
 | 
					  }) = _SnStickerPack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  factory SnStickerPack.fromJson(Map<String, dynamic> json) =>
 | 
					  factory SnStickerPack.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -338,7 +338,7 @@ $SnStickerPackCopyWith<$Res>? get pack {
 | 
				
			|||||||
/// @nodoc
 | 
					/// @nodoc
 | 
				
			||||||
mixin _$SnStickerPack {
 | 
					mixin _$SnStickerPack {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
 | 
					 String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnSticker> get stickers;
 | 
				
			||||||
/// Create a copy of SnStickerPack
 | 
					/// Create a copy of SnStickerPack
 | 
				
			||||||
/// with the given fields replaced by the non-null parameter values.
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
@JsonKey(includeFromJson: false, includeToJson: false)
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
@@ -351,16 +351,16 @@ $SnStickerPackCopyWith<SnStickerPack> get copyWith => _$SnStickerPackCopyWithImp
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@override
 | 
					@override
 | 
				
			||||||
bool operator ==(Object other) {
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.stickers, stickers));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@JsonKey(includeFromJson: false, includeToJson: false)
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
@override
 | 
					@override
 | 
				
			||||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
 | 
					int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(stickers));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@override
 | 
					@override
 | 
				
			||||||
String toString() {
 | 
					String toString() {
 | 
				
			||||||
  return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
					  return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -371,7 +371,7 @@ abstract mixin class $SnStickerPackCopyWith<$Res>  {
 | 
				
			|||||||
  factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl;
 | 
					  factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl;
 | 
				
			||||||
@useResult
 | 
					@useResult
 | 
				
			||||||
$Res call({
 | 
					$Res call({
 | 
				
			||||||
 String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
					 String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -388,7 +388,7 @@ class _$SnStickerPackCopyWithImpl<$Res>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Create a copy of SnStickerPack
 | 
					/// Create a copy of SnStickerPack
 | 
				
			||||||
/// with the given fields replaced by the non-null parameter values.
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
 | 
				
			||||||
  return _then(_self.copyWith(
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
 | 
					as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
@@ -399,7 +399,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
 | 
				
			|||||||
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
					as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime?,
 | 
					as DateTime?,stickers: null == stickers ? _self.stickers : stickers // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as List<SnSticker>,
 | 
				
			||||||
  ));
 | 
					  ));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
/// Create a copy of SnStickerPack
 | 
					/// Create a copy of SnStickerPack
 | 
				
			||||||
@@ -493,10 +494,10 @@ return $default(_that);case _:
 | 
				
			|||||||
/// }
 | 
					/// }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this;
 | 
					@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  List<SnSticker> stickers)?  $default,{required TResult orElse(),}) {final _that = this;
 | 
				
			||||||
switch (_that) {
 | 
					switch (_that) {
 | 
				
			||||||
case _SnStickerPack() when $default != null:
 | 
					case _SnStickerPack() when $default != null:
 | 
				
			||||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
 | 
					return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
 | 
				
			||||||
  return orElse();
 | 
					  return orElse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -514,10 +515,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
 | 
				
			|||||||
/// }
 | 
					/// }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this;
 | 
					@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  List<SnSticker> stickers)  $default,) {final _that = this;
 | 
				
			||||||
switch (_that) {
 | 
					switch (_that) {
 | 
				
			||||||
case _SnStickerPack():
 | 
					case _SnStickerPack():
 | 
				
			||||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);}
 | 
					return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
/// A variant of `when` that fallback to returning `null`
 | 
					/// A variant of `when` that fallback to returning `null`
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
@@ -531,10 +532,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
 | 
				
			|||||||
/// }
 | 
					/// }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this;
 | 
					@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  List<SnSticker> stickers)?  $default,) {final _that = this;
 | 
				
			||||||
switch (_that) {
 | 
					switch (_that) {
 | 
				
			||||||
case _SnStickerPack() when $default != null:
 | 
					case _SnStickerPack() when $default != null:
 | 
				
			||||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
 | 
					return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
 | 
				
			||||||
  return null;
 | 
					  return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -546,7 +547,7 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
 | 
				
			|||||||
@JsonSerializable()
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _SnStickerPack implements SnStickerPack {
 | 
					class _SnStickerPack implements SnStickerPack {
 | 
				
			||||||
  const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt});
 | 
					  const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt, final  List<SnSticker> stickers = const []}): _stickers = stickers;
 | 
				
			||||||
  factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json);
 | 
					  factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@override final  String id;
 | 
					@override final  String id;
 | 
				
			||||||
@@ -558,6 +559,13 @@ class _SnStickerPack implements SnStickerPack {
 | 
				
			|||||||
@override final  DateTime createdAt;
 | 
					@override final  DateTime createdAt;
 | 
				
			||||||
@override final  DateTime updatedAt;
 | 
					@override final  DateTime updatedAt;
 | 
				
			||||||
@override final  DateTime? deletedAt;
 | 
					@override final  DateTime? deletedAt;
 | 
				
			||||||
 | 
					 final  List<SnSticker> _stickers;
 | 
				
			||||||
 | 
					@override@JsonKey() List<SnSticker> get stickers {
 | 
				
			||||||
 | 
					  if (_stickers is EqualUnmodifiableListView) return _stickers;
 | 
				
			||||||
 | 
					  // ignore: implicit_dynamic_type
 | 
				
			||||||
 | 
					  return EqualUnmodifiableListView(_stickers);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Create a copy of SnStickerPack
 | 
					/// Create a copy of SnStickerPack
 | 
				
			||||||
/// with the given fields replaced by the non-null parameter values.
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
@@ -572,16 +580,16 @@ Map<String, dynamic> toJson() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@override
 | 
					@override
 | 
				
			||||||
bool operator ==(Object other) {
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._stickers, _stickers));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@JsonKey(includeFromJson: false, includeToJson: false)
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
@override
 | 
					@override
 | 
				
			||||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
 | 
					int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_stickers));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@override
 | 
					@override
 | 
				
			||||||
String toString() {
 | 
					String toString() {
 | 
				
			||||||
  return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
					  return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -592,7 +600,7 @@ abstract mixin class _$SnStickerPackCopyWith<$Res> implements $SnStickerPackCopy
 | 
				
			|||||||
  factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl;
 | 
					  factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl;
 | 
				
			||||||
@override @useResult
 | 
					@override @useResult
 | 
				
			||||||
$Res call({
 | 
					$Res call({
 | 
				
			||||||
 String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
					 String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -609,7 +617,7 @@ class __$SnStickerPackCopyWithImpl<$Res>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Create a copy of SnStickerPack
 | 
					/// Create a copy of SnStickerPack
 | 
				
			||||||
/// with the given fields replaced by the non-null parameter values.
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
 | 
				
			||||||
  return _then(_SnStickerPack(
 | 
					  return _then(_SnStickerPack(
 | 
				
			||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
 | 
					as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
@@ -620,7 +628,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
 | 
				
			|||||||
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
					as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime?,
 | 
					as DateTime?,stickers: null == stickers ? _self._stickers : stickers // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as List<SnSticker>,
 | 
				
			||||||
  ));
 | 
					  ));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,11 @@ _SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) =>
 | 
				
			|||||||
          json['deleted_at'] == null
 | 
					          json['deleted_at'] == null
 | 
				
			||||||
              ? null
 | 
					              ? null
 | 
				
			||||||
              : DateTime.parse(json['deleted_at'] as String),
 | 
					              : DateTime.parse(json['deleted_at'] as String),
 | 
				
			||||||
 | 
					      stickers:
 | 
				
			||||||
 | 
					          (json['stickers'] as List<dynamic>?)
 | 
				
			||||||
 | 
					              ?.map((e) => SnSticker.fromJson(e as Map<String, dynamic>))
 | 
				
			||||||
 | 
					              .toList() ??
 | 
				
			||||||
 | 
					          const [],
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
 | 
					Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
 | 
				
			||||||
@@ -67,4 +72,5 @@ Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
 | 
				
			|||||||
      'created_at': instance.createdAt.toIso8601String(),
 | 
					      'created_at': instance.createdAt.toIso8601String(),
 | 
				
			||||||
      'updated_at': instance.updatedAt.toIso8601String(),
 | 
					      'updated_at': instance.updatedAt.toIso8601String(),
 | 
				
			||||||
      'deleted_at': instance.deletedAt?.toIso8601String(),
 | 
					      'deleted_at': instance.deletedAt?.toIso8601String(),
 | 
				
			||||||
 | 
					      'stickers': instance.stickers.map((e) => e.toJson()).toList(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,8 @@ import 'package:island/screens/creators/hub.dart';
 | 
				
			|||||||
import 'package:island/screens/creators/posts/post_manage_list.dart';
 | 
					import 'package:island/screens/creators/posts/post_manage_list.dart';
 | 
				
			||||||
import 'package:island/screens/creators/stickers/stickers.dart';
 | 
					import 'package:island/screens/creators/stickers/stickers.dart';
 | 
				
			||||||
import 'package:island/screens/creators/stickers/pack_detail.dart';
 | 
					import 'package:island/screens/creators/stickers/pack_detail.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/stickers/marketplace.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/stickers/pack_detail.dart';
 | 
				
			||||||
import 'package:island/screens/creators/poll/poll_list.dart';
 | 
					import 'package:island/screens/creators/poll/poll_list.dart';
 | 
				
			||||||
import 'package:island/screens/creators/publishers.dart';
 | 
					import 'package:island/screens/creators/publishers.dart';
 | 
				
			||||||
import 'package:island/screens/creators/webfeed/webfeed_list.dart';
 | 
					import 'package:island/screens/creators/webfeed/webfeed_list.dart';
 | 
				
			||||||
@@ -451,6 +453,23 @@ final routerProvider = Provider<GoRouter>((ref) {
 | 
				
			|||||||
                    path: '/account',
 | 
					                    path: '/account',
 | 
				
			||||||
                    builder: (context, state) => const AccountScreen(),
 | 
					                    builder: (context, state) => const AccountScreen(),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					                  // Sticker marketplace (user-facing, no publisher)
 | 
				
			||||||
 | 
					                  GoRoute(
 | 
				
			||||||
 | 
					                    name: 'stickerMarketplace',
 | 
				
			||||||
 | 
					                    path: '/stickers',
 | 
				
			||||||
 | 
					                    builder:
 | 
				
			||||||
 | 
					                        (context, state) => const MarketplaceStickersScreen(),
 | 
				
			||||||
 | 
					                    routes: [
 | 
				
			||||||
 | 
					                      GoRoute(
 | 
				
			||||||
 | 
					                        name: 'stickerPackDetail',
 | 
				
			||||||
 | 
					                        path: ':packId',
 | 
				
			||||||
 | 
					                        builder: (context, state) {
 | 
				
			||||||
 | 
					                          final packId = state.pathParameters['packId']!;
 | 
				
			||||||
 | 
					                          return MarketplaceStickerPackDetailScreen(id: packId);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                  GoRoute(
 | 
					                  GoRoute(
 | 
				
			||||||
                    name: 'notifications',
 | 
					                    name: 'notifications',
 | 
				
			||||||
                    path: '/account/notifications',
 | 
					                    path: '/account/notifications',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -189,7 +189,6 @@ class AccountScreen extends HookConsumerWidget {
 | 
				
			|||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ).padding(horizontal: 8),
 | 
					            ).padding(horizontal: 8),
 | 
				
			||||||
            const Gap(8),
 | 
					 | 
				
			||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              minTileHeight: 48,
 | 
					              minTileHeight: 48,
 | 
				
			||||||
              leading: const Icon(Symbols.notifications),
 | 
					              leading: const Icon(Symbols.notifications),
 | 
				
			||||||
@@ -228,6 +227,16 @@ class AccountScreen extends HookConsumerWidget {
 | 
				
			|||||||
                context.pushNamed('relationships');
 | 
					                context.pushNamed('relationships');
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            ListTile(
 | 
				
			||||||
 | 
					              minTileHeight: 48,
 | 
				
			||||||
 | 
					              leading: const Icon(Symbols.emoji_emotions),
 | 
				
			||||||
 | 
					              trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					              contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					              title: Text('stickers').tr(),
 | 
				
			||||||
 | 
					              onTap: () {
 | 
				
			||||||
 | 
					                context.pushNamed('stickerMarketplace');
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              minTileHeight: 48,
 | 
					              minTileHeight: 48,
 | 
				
			||||||
              title: Text('abuseReports').tr(),
 | 
					              title: Text('abuseReports').tr(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,7 @@ import 'package:material_symbols_icons/symbols.dart';
 | 
				
			|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
import 'chat.dart';
 | 
					import 'chat.dart';
 | 
				
			||||||
import 'package:island/widgets/chat/call_button.dart';
 | 
					import 'package:island/widgets/chat/call_button.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/stickers/picker.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part 'room.g.dart';
 | 
					part 'room.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1060,13 +1061,16 @@ class _ChatInput extends HookConsumerWidget {
 | 
				
			|||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          if (attachments.isNotEmpty)
 | 
					          if (attachments.isNotEmpty)
 | 
				
			||||||
            SizedBox(
 | 
					            SizedBox(
 | 
				
			||||||
              height: 280,
 | 
					              height: 324,
 | 
				
			||||||
              child: ListView.separated(
 | 
					              child: ListView.separated(
 | 
				
			||||||
                padding: EdgeInsets.symmetric(horizontal: 12),
 | 
					                padding: EdgeInsets.symmetric(horizontal: 12),
 | 
				
			||||||
                scrollDirection: Axis.horizontal,
 | 
					                scrollDirection: Axis.horizontal,
 | 
				
			||||||
                itemCount: attachments.length,
 | 
					                itemCount: attachments.length,
 | 
				
			||||||
                itemBuilder: (context, idx) {
 | 
					                itemBuilder: (context, idx) {
 | 
				
			||||||
                  return AttachmentPreview(
 | 
					                  return SizedBox(
 | 
				
			||||||
 | 
					                    height: 320,
 | 
				
			||||||
 | 
					                    width: 280,
 | 
				
			||||||
 | 
					                    child: AttachmentPreview(
 | 
				
			||||||
                      item: attachments[idx],
 | 
					                      item: attachments[idx],
 | 
				
			||||||
                      onRequestUpload: () => onUploadAttachment(idx),
 | 
					                      onRequestUpload: () => onUploadAttachment(idx),
 | 
				
			||||||
                      onDelete: () => onDeleteAttachment(idx),
 | 
					                      onDelete: () => onDeleteAttachment(idx),
 | 
				
			||||||
@@ -1075,6 +1079,7 @@ class _ChatInput extends HookConsumerWidget {
 | 
				
			|||||||
                        onAttachmentsChanged(attachments);
 | 
					                        onAttachmentsChanged(attachments);
 | 
				
			||||||
                      },
 | 
					                      },
 | 
				
			||||||
                      onMove: (delta) => onMoveAttachment(idx, delta),
 | 
					                      onMove: (delta) => onMoveAttachment(idx, delta),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                  );
 | 
					                  );
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                separatorBuilder: (_, _) => const Gap(8),
 | 
					                separatorBuilder: (_, _) => const Gap(8),
 | 
				
			||||||
@@ -1129,6 +1134,49 @@ class _ChatInput extends HookConsumerWidget {
 | 
				
			|||||||
            padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
 | 
					            padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
 | 
				
			||||||
            child: Row(
 | 
					            child: Row(
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
 | 
					                Row(
 | 
				
			||||||
 | 
					                  mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    IconButton(
 | 
				
			||||||
 | 
					                      tooltip: 'stickers'.tr(),
 | 
				
			||||||
 | 
					                      icon: const Icon(Symbols.emoji_symbols),
 | 
				
			||||||
 | 
					                      onPressed: () {
 | 
				
			||||||
 | 
					                        final size = MediaQuery.of(context).size;
 | 
				
			||||||
 | 
					                        showStickerPickerPopover(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          Offset(
 | 
				
			||||||
 | 
					                            20,
 | 
				
			||||||
 | 
					                            size.height -
 | 
				
			||||||
 | 
					                                480 -
 | 
				
			||||||
 | 
					                                MediaQuery.of(context).padding.bottom,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          onPick: (placeholder) {
 | 
				
			||||||
 | 
					                            // Insert placeholder at current cursor position
 | 
				
			||||||
 | 
					                            final text = messageController.text;
 | 
				
			||||||
 | 
					                            final selection = messageController.selection;
 | 
				
			||||||
 | 
					                            final start =
 | 
				
			||||||
 | 
					                                selection.start >= 0
 | 
				
			||||||
 | 
					                                    ? selection.start
 | 
				
			||||||
 | 
					                                    : text.length;
 | 
				
			||||||
 | 
					                            final end =
 | 
				
			||||||
 | 
					                                selection.end >= 0
 | 
				
			||||||
 | 
					                                    ? selection.end
 | 
				
			||||||
 | 
					                                    : text.length;
 | 
				
			||||||
 | 
					                            final newText = text.replaceRange(
 | 
				
			||||||
 | 
					                              start,
 | 
				
			||||||
 | 
					                              end,
 | 
				
			||||||
 | 
					                              placeholder,
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                            messageController.value = TextEditingValue(
 | 
				
			||||||
 | 
					                              text: newText,
 | 
				
			||||||
 | 
					                              selection: TextSelection.collapsed(
 | 
				
			||||||
 | 
					                                offset: start + placeholder.length,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                    PopupMenuButton(
 | 
					                    PopupMenuButton(
 | 
				
			||||||
                      icon: const Icon(Symbols.photo_library),
 | 
					                      icon: const Icon(Symbols.photo_library),
 | 
				
			||||||
                      itemBuilder:
 | 
					                      itemBuilder:
 | 
				
			||||||
@@ -1155,6 +1203,8 @@ class _ChatInput extends HookConsumerWidget {
 | 
				
			|||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                          ],
 | 
					                          ],
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
                Expanded(
 | 
					                Expanded(
 | 
				
			||||||
                  child: RawKeyboardListener(
 | 
					                  child: RawKeyboardListener(
 | 
				
			||||||
                    focusNode: FocusNode(),
 | 
					                    focusNode: FocusNode(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_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';
 | 
				
			||||||
 | 
					import 'package:island/widgets/poll/poll_feedback.dart';
 | 
				
			||||||
import 'package:material_symbols_icons/symbols.dart';
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
					import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
				
			||||||
@@ -164,10 +165,13 @@ class _CreatorPollItem extends StatelessWidget {
 | 
				
			|||||||
              ],
 | 
					              ],
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        onTap: () {
 | 
					        onTap: () {
 | 
				
			||||||
          // Open editor for edit
 | 
					          showModalBottomSheet(
 | 
				
			||||||
          // Navigator push by path to keep consistency with rest of app:
 | 
					            context: context,
 | 
				
			||||||
          // Note: pub name string may be required in route; when absent, route may need query or pick later.
 | 
					            useRootNavigator: true,
 | 
				
			||||||
          // For safety, just do nothing if no publisher in list item.
 | 
					            isScrollControlled: true,
 | 
				
			||||||
 | 
					            builder:
 | 
				
			||||||
 | 
					                (context) => PollFeedbackSheet(pollId: poll.id, poll: poll),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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(
 | 
				
			||||||
@@ -321,7 +337,18 @@ class PublisherProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                          child: CustomScrollView(
 | 
					                          child: CustomScrollView(
 | 
				
			||||||
                            slivers: [
 | 
					                            slivers: [
 | 
				
			||||||
                              SliverGap(16),
 | 
					                              SliverGap(16),
 | 
				
			||||||
                              SliverPostList(pubName: name),
 | 
					                              SliverToBoxAdapter(
 | 
				
			||||||
 | 
					                                child: publisherCategoryTabWidget(),
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                              SliverPostList(
 | 
				
			||||||
 | 
					                                key: ValueKey(categoryTab.value),
 | 
				
			||||||
 | 
					                                pubName: name,
 | 
				
			||||||
 | 
					                                type: switch (categoryTab.value) {
 | 
				
			||||||
 | 
					                                  1 => 0,
 | 
				
			||||||
 | 
					                                  2 => 1,
 | 
				
			||||||
 | 
					                                  _ => null,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
                              SliverGap(
 | 
					                              SliverGap(
 | 
				
			||||||
                                MediaQuery.of(context).padding.bottom + 16,
 | 
					                                MediaQuery.of(context).padding.bottom + 16,
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
@@ -334,9 +361,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                            alignment: Alignment.topLeft,
 | 
					                            alignment: Alignment.topLeft,
 | 
				
			||||||
                            child: SingleChildScrollView(
 | 
					                            child: SingleChildScrollView(
 | 
				
			||||||
                              child: Column(
 | 
					                              child: Column(
 | 
				
			||||||
                                crossAxisAlignment: CrossAxisAlignment.start,
 | 
					                                crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
                                children: [
 | 
					                                children: [
 | 
				
			||||||
                                  publisherBasisWidget(data),
 | 
					                                  publisherBasisWidget(data).padding(bottom: 8),
 | 
				
			||||||
                                  publisherBadgesWidget(data),
 | 
					                                  publisherBadgesWidget(data),
 | 
				
			||||||
                                  publisherVerificationWidget(data),
 | 
					                                  publisherVerificationWidget(data),
 | 
				
			||||||
                                  publisherBioWidget(data),
 | 
					                                  publisherBioWidget(data),
 | 
				
			||||||
@@ -398,7 +425,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),
 | 
				
			||||||
                      ],
 | 
					                      ],
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										103
									
								
								lib/screens/stickers/marketplace.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/screens/stickers/marketplace.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
 | 
					import 'package:gap/gap.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/sticker.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'marketplace.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@riverpod
 | 
				
			||||||
 | 
					class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier
 | 
				
			||||||
 | 
					    with CursorPagingNotifierMixin<SnStickerPack> {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<CursorPagingData<SnStickerPack>> build() {
 | 
				
			||||||
 | 
					    return fetch(cursor: null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<CursorPagingData<SnStickerPack>> fetch({
 | 
				
			||||||
 | 
					    required String? cursor,
 | 
				
			||||||
 | 
					  }) async {
 | 
				
			||||||
 | 
					    final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					    final offset = cursor == null ? 0 : int.parse(cursor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final response = await client.get(
 | 
				
			||||||
 | 
					      '/sphere/stickers',
 | 
				
			||||||
 | 
					      queryParameters: {'offset': offset, 'take': 20},
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final total = int.parse(response.headers.value('X-Total') ?? '0');
 | 
				
			||||||
 | 
					    final List<dynamic> data = response.data;
 | 
				
			||||||
 | 
					    final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final hasMore = offset + stickers.length < total;
 | 
				
			||||||
 | 
					    final nextCursor = hasMore ? (offset + stickers.length).toString() : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return CursorPagingData(
 | 
				
			||||||
 | 
					      items: stickers,
 | 
				
			||||||
 | 
					      hasMore: hasMore,
 | 
				
			||||||
 | 
					      nextCursor: nextCursor,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// User-facing marketplace screen for browsing sticker packs.
 | 
				
			||||||
 | 
					/// This version does NOT rely on publisher name (no pubName).
 | 
				
			||||||
 | 
					class MarketplaceStickersScreen extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  const MarketplaceStickersScreen({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    return AppScaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(
 | 
				
			||||||
 | 
					        title: const Text('stickers').tr(),
 | 
				
			||||||
 | 
					        actions: const [Gap(8)],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      body: const SliverMarketplaceStickerPacksList(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SliverMarketplaceStickerPacksList extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  const SliverMarketplaceStickerPacksList({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    return PagingHelperView(
 | 
				
			||||||
 | 
					      provider: marketplaceStickerPacksNotifierProvider,
 | 
				
			||||||
 | 
					      futureRefreshable: marketplaceStickerPacksNotifierProvider.future,
 | 
				
			||||||
 | 
					      notifierRefreshable: marketplaceStickerPacksNotifierProvider.notifier,
 | 
				
			||||||
 | 
					      contentBuilder:
 | 
				
			||||||
 | 
					          (data, widgetCount, endItemView) => ListView.builder(
 | 
				
			||||||
 | 
					            padding: EdgeInsets.zero,
 | 
				
			||||||
 | 
					            itemCount: widgetCount,
 | 
				
			||||||
 | 
					            itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					              if (index == widgetCount - 1) {
 | 
				
			||||||
 | 
					                return endItemView;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              final pack = data.items[index];
 | 
				
			||||||
 | 
					              return ListTile(
 | 
				
			||||||
 | 
					                title: Text(pack.name),
 | 
				
			||||||
 | 
					                subtitle: Text(pack.description),
 | 
				
			||||||
 | 
					                trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					                onTap: () {
 | 
				
			||||||
 | 
					                  // Navigate to user-facing sticker pack detail page.
 | 
				
			||||||
 | 
					                  // Adjust the route name/parameters if your app uses different ones.
 | 
				
			||||||
 | 
					                  context.pushNamed(
 | 
				
			||||||
 | 
					                    'stickerPackDetail',
 | 
				
			||||||
 | 
					                    pathParameters: {'packId': pack.id},
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								lib/screens/stickers/marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/screens/stickers/marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'marketplace.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// RiverpodGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String _$marketplaceStickerPacksNotifierHash() =>
 | 
				
			||||||
 | 
					    r'b62ae8b7f5c4f8bb3be8c17fc005ea26da355187';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [MarketplaceStickerPacksNotifier].
 | 
				
			||||||
 | 
					@ProviderFor(MarketplaceStickerPacksNotifier)
 | 
				
			||||||
 | 
					final marketplaceStickerPacksNotifierProvider =
 | 
				
			||||||
 | 
					    AutoDisposeAsyncNotifierProvider<
 | 
				
			||||||
 | 
					      MarketplaceStickerPacksNotifier,
 | 
				
			||||||
 | 
					      CursorPagingData<SnStickerPack>
 | 
				
			||||||
 | 
					    >.internal(
 | 
				
			||||||
 | 
					      MarketplaceStickerPacksNotifier.new,
 | 
				
			||||||
 | 
					      name: r'marketplaceStickerPacksNotifierProvider',
 | 
				
			||||||
 | 
					      debugGetCreateSourceHash:
 | 
				
			||||||
 | 
					          const bool.fromEnvironment('dart.vm.product')
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : _$marketplaceStickerPacksNotifierHash,
 | 
				
			||||||
 | 
					      dependencies: null,
 | 
				
			||||||
 | 
					      allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef _$MarketplaceStickerPacksNotifier =
 | 
				
			||||||
 | 
					    AutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>>;
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
							
								
								
									
										230
									
								
								lib/screens/stickers/pack_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								lib/screens/stickers/pack_detail.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					import 'package:gap/gap.dart';
 | 
				
			||||||
 | 
					import 'package:google_fonts/google_fonts.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/sticker.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/creators/stickers/stickers.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/alert.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/cloud_files.dart';
 | 
				
			||||||
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'pack_detail.g.dart'; // generated by riverpod_annotation build_runner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Marketplace version of sticker pack detail page (no publisher dependency).
 | 
				
			||||||
 | 
					/// Shows all stickers in the pack and provides a button to add the sticker.
 | 
				
			||||||
 | 
					/// API interactions are intentionally left blank per request.
 | 
				
			||||||
 | 
					@riverpod
 | 
				
			||||||
 | 
					Future<List<SnSticker>> marketplaceStickerPackContent(
 | 
				
			||||||
 | 
					  Ref ref, {
 | 
				
			||||||
 | 
					  required String packId,
 | 
				
			||||||
 | 
					}) async {
 | 
				
			||||||
 | 
					  final apiClient = ref.watch(apiClientProvider);
 | 
				
			||||||
 | 
					  final resp = await apiClient.get('/sphere/stickers/$packId/content');
 | 
				
			||||||
 | 
					  return (resp.data as List).map((e) => SnSticker.fromJson(e)).toList();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@riverpod
 | 
				
			||||||
 | 
					Future<bool> marketplaceStickerPackOwnership(
 | 
				
			||||||
 | 
					  Ref ref, {
 | 
				
			||||||
 | 
					  required String packId,
 | 
				
			||||||
 | 
					}) async {
 | 
				
			||||||
 | 
					  final api = ref.watch(apiClientProvider);
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    await api.get('/sphere/stickers/$packId/own');
 | 
				
			||||||
 | 
					    // If not 404, consider owned
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  } on Object catch (e) {
 | 
				
			||||||
 | 
					    // Dio error handling agnostic: treat 404 as not-owned, rethrow others
 | 
				
			||||||
 | 
					    final msg = e.toString();
 | 
				
			||||||
 | 
					    if (msg.contains('404')) return false;
 | 
				
			||||||
 | 
					    rethrow;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MarketplaceStickerPackDetailScreen extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  final String id;
 | 
				
			||||||
 | 
					  const MarketplaceStickerPackDetailScreen({super.key, required this.id});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    // Pack metadata provider exists globally in creators file; reuse it.
 | 
				
			||||||
 | 
					    final pack = ref.watch(stickerPackProvider(id));
 | 
				
			||||||
 | 
					    final packContent = ref.watch(
 | 
				
			||||||
 | 
					      marketplaceStickerPackContentProvider(packId: id),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    final owned = ref.watch(
 | 
				
			||||||
 | 
					      marketplaceStickerPackOwnershipProvider(packId: id),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add entire pack to user's collection
 | 
				
			||||||
 | 
					    Future<void> addPackToMyCollection() async {
 | 
				
			||||||
 | 
					      final apiClient = ref.watch(apiClientProvider);
 | 
				
			||||||
 | 
					      await apiClient.post('/sphere/stickers/$id/own');
 | 
				
			||||||
 | 
					      HapticFeedback.selectionClick();
 | 
				
			||||||
 | 
					      ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
 | 
				
			||||||
 | 
					      if (!context.mounted) return;
 | 
				
			||||||
 | 
					      showSnackBar('stickerPackAdded'.tr());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove ownership of the pack
 | 
				
			||||||
 | 
					    Future<void> removePackFromMyCollection() async {
 | 
				
			||||||
 | 
					      final apiClient = ref.watch(apiClientProvider);
 | 
				
			||||||
 | 
					      await apiClient.delete('/sphere/stickers/$id/own');
 | 
				
			||||||
 | 
					      HapticFeedback.selectionClick();
 | 
				
			||||||
 | 
					      ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
 | 
				
			||||||
 | 
					      if (!context.mounted) return;
 | 
				
			||||||
 | 
					      showSnackBar('stickerPackRemoved'.tr());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AppScaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(title: Text(pack.value?.name ?? 'loading'.tr())),
 | 
				
			||||||
 | 
					      body: pack.when(
 | 
				
			||||||
 | 
					        data: (p) {
 | 
				
			||||||
 | 
					          return Column(
 | 
				
			||||||
 | 
					            crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              // Pack meta
 | 
				
			||||||
 | 
					              Column(
 | 
				
			||||||
 | 
					                crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
 | 
					                children: [
 | 
				
			||||||
 | 
					                  Text(p?.description ?? ''),
 | 
				
			||||||
 | 
					                  Row(
 | 
				
			||||||
 | 
					                    spacing: 4,
 | 
				
			||||||
 | 
					                    children: [
 | 
				
			||||||
 | 
					                      const Icon(Symbols.folder, size: 16),
 | 
				
			||||||
 | 
					                      Text(
 | 
				
			||||||
 | 
					                        '${packContent.value?.length ?? 0}/24',
 | 
				
			||||||
 | 
					                        style: GoogleFonts.robotoMono(),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ).opacity(0.85),
 | 
				
			||||||
 | 
					                  Row(
 | 
				
			||||||
 | 
					                    spacing: 4,
 | 
				
			||||||
 | 
					                    children: [
 | 
				
			||||||
 | 
					                      const Icon(Symbols.sell, size: 16),
 | 
				
			||||||
 | 
					                      Text(p?.prefix ?? '', style: GoogleFonts.robotoMono()),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ).opacity(0.85),
 | 
				
			||||||
 | 
					                  Row(
 | 
				
			||||||
 | 
					                    spacing: 4,
 | 
				
			||||||
 | 
					                    children: [
 | 
				
			||||||
 | 
					                      const Icon(Symbols.tag, size: 16),
 | 
				
			||||||
 | 
					                      SelectableText(
 | 
				
			||||||
 | 
					                        p?.id ?? id,
 | 
				
			||||||
 | 
					                        style: GoogleFonts.robotoMono(),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ).opacity(0.85),
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					              ).padding(horizontal: 24, vertical: 24),
 | 
				
			||||||
 | 
					              const Divider(height: 1),
 | 
				
			||||||
 | 
					              // Stickers grid
 | 
				
			||||||
 | 
					              Expanded(
 | 
				
			||||||
 | 
					                child: packContent.when(
 | 
				
			||||||
 | 
					                  data:
 | 
				
			||||||
 | 
					                      (stickers) => RefreshIndicator(
 | 
				
			||||||
 | 
					                        onRefresh:
 | 
				
			||||||
 | 
					                            () => ref.refresh(
 | 
				
			||||||
 | 
					                              marketplaceStickerPackContentProvider(
 | 
				
			||||||
 | 
					                                packId: id,
 | 
				
			||||||
 | 
					                              ).future,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                        child: GridView.builder(
 | 
				
			||||||
 | 
					                          padding: const EdgeInsets.symmetric(
 | 
				
			||||||
 | 
					                            horizontal: 24,
 | 
				
			||||||
 | 
					                            vertical: 20,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          gridDelegate:
 | 
				
			||||||
 | 
					                              const SliverGridDelegateWithMaxCrossAxisExtent(
 | 
				
			||||||
 | 
					                                maxCrossAxisExtent: 96,
 | 
				
			||||||
 | 
					                                mainAxisSpacing: 12,
 | 
				
			||||||
 | 
					                                crossAxisSpacing: 12,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                          itemCount: stickers.length,
 | 
				
			||||||
 | 
					                          itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                            final sticker = stickers[index];
 | 
				
			||||||
 | 
					                            return Tooltip(
 | 
				
			||||||
 | 
					                              message: ':${p?.prefix ?? ''}${sticker.slug}:',
 | 
				
			||||||
 | 
					                              child: ClipRRect(
 | 
				
			||||||
 | 
					                                borderRadius: const BorderRadius.all(
 | 
				
			||||||
 | 
					                                  Radius.circular(8),
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                                child: Container(
 | 
				
			||||||
 | 
					                                  decoration: BoxDecoration(
 | 
				
			||||||
 | 
					                                    color:
 | 
				
			||||||
 | 
					                                        Theme.of(
 | 
				
			||||||
 | 
					                                          context,
 | 
				
			||||||
 | 
					                                        ).colorScheme.surfaceContainer,
 | 
				
			||||||
 | 
					                                    borderRadius: const BorderRadius.all(
 | 
				
			||||||
 | 
					                                      Radius.circular(8),
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                  child: AspectRatio(
 | 
				
			||||||
 | 
					                                    aspectRatio: 1,
 | 
				
			||||||
 | 
					                                    child: CloudImageWidget(
 | 
				
			||||||
 | 
					                                      fileId: sticker.imageId,
 | 
				
			||||||
 | 
					                                      fit: BoxFit.contain,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                  error:
 | 
				
			||||||
 | 
					                      (err, _) =>
 | 
				
			||||||
 | 
					                          Text(
 | 
				
			||||||
 | 
					                            'Error: $err',
 | 
				
			||||||
 | 
					                          ).textAlignment(TextAlign.center).center(),
 | 
				
			||||||
 | 
					                  loading: () => const CircularProgressIndicator().center(),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              Padding(
 | 
				
			||||||
 | 
					                padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
 | 
				
			||||||
 | 
					                child: owned.when(
 | 
				
			||||||
 | 
					                  data:
 | 
				
			||||||
 | 
					                      (isOwned) => FilledButton.icon(
 | 
				
			||||||
 | 
					                        onPressed:
 | 
				
			||||||
 | 
					                            isOwned
 | 
				
			||||||
 | 
					                                ? removePackFromMyCollection
 | 
				
			||||||
 | 
					                                : addPackToMyCollection,
 | 
				
			||||||
 | 
					                        icon: Icon(
 | 
				
			||||||
 | 
					                          isOwned ? Symbols.remove_circle : Symbols.add_circle,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        label: Text(
 | 
				
			||||||
 | 
					                          isOwned ? 'removePack'.tr() : 'addPack'.tr(),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                  loading:
 | 
				
			||||||
 | 
					                      () => const SizedBox(
 | 
				
			||||||
 | 
					                        height: 32,
 | 
				
			||||||
 | 
					                        width: 32,
 | 
				
			||||||
 | 
					                        child: CircularProgressIndicator(strokeWidth: 2),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                  error:
 | 
				
			||||||
 | 
					                      (_, _) => OutlinedButton.icon(
 | 
				
			||||||
 | 
					                        onPressed: addPackToMyCollection,
 | 
				
			||||||
 | 
					                        icon: const Icon(Symbols.add_circle),
 | 
				
			||||||
 | 
					                        label: Text('addPack').tr(),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              Gap(MediaQuery.of(context).padding.bottom),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        error:
 | 
				
			||||||
 | 
					            (err, _) =>
 | 
				
			||||||
 | 
					                Text('Error: $err').textAlignment(TextAlign.center).center(),
 | 
				
			||||||
 | 
					        loading: () => const CircularProgressIndicator().center(),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										317
									
								
								lib/screens/stickers/pack_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								lib/screens/stickers/pack_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,317 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'pack_detail.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// RiverpodGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String _$marketplaceStickerPackContentHash() =>
 | 
				
			||||||
 | 
					    r'886f8305c978dbea6e5d990a7d555048ac704a5d';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Copied from Dart SDK
 | 
				
			||||||
 | 
					class _SystemHash {
 | 
				
			||||||
 | 
					  _SystemHash._();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static int combine(int hash, int value) {
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + value);
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
 | 
				
			||||||
 | 
					    return hash ^ (hash >> 6);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static int finish(int hash) {
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = hash ^ (hash >> 11);
 | 
				
			||||||
 | 
					    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Marketplace version of sticker pack detail page (no publisher dependency).
 | 
				
			||||||
 | 
					/// Shows all stickers in the pack and provides a button to add the sticker.
 | 
				
			||||||
 | 
					/// API interactions are intentionally left blank per request.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Copied from [marketplaceStickerPackContent].
 | 
				
			||||||
 | 
					@ProviderFor(marketplaceStickerPackContent)
 | 
				
			||||||
 | 
					const marketplaceStickerPackContentProvider =
 | 
				
			||||||
 | 
					    MarketplaceStickerPackContentFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Marketplace version of sticker pack detail page (no publisher dependency).
 | 
				
			||||||
 | 
					/// Shows all stickers in the pack and provides a button to add the sticker.
 | 
				
			||||||
 | 
					/// API interactions are intentionally left blank per request.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Copied from [marketplaceStickerPackContent].
 | 
				
			||||||
 | 
					class MarketplaceStickerPackContentFamily
 | 
				
			||||||
 | 
					    extends Family<AsyncValue<List<SnSticker>>> {
 | 
				
			||||||
 | 
					  /// Marketplace version of sticker pack detail page (no publisher dependency).
 | 
				
			||||||
 | 
					  /// Shows all stickers in the pack and provides a button to add the sticker.
 | 
				
			||||||
 | 
					  /// API interactions are intentionally left blank per request.
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// Copied from [marketplaceStickerPackContent].
 | 
				
			||||||
 | 
					  const MarketplaceStickerPackContentFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Marketplace version of sticker pack detail page (no publisher dependency).
 | 
				
			||||||
 | 
					  /// Shows all stickers in the pack and provides a button to add the sticker.
 | 
				
			||||||
 | 
					  /// API interactions are intentionally left blank per request.
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// Copied from [marketplaceStickerPackContent].
 | 
				
			||||||
 | 
					  MarketplaceStickerPackContentProvider call({required String packId}) {
 | 
				
			||||||
 | 
					    return MarketplaceStickerPackContentProvider(packId: packId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  MarketplaceStickerPackContentProvider getProviderOverride(
 | 
				
			||||||
 | 
					    covariant MarketplaceStickerPackContentProvider provider,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return call(packId: provider.packId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _dependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get dependencies => _dependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
 | 
				
			||||||
 | 
					      _allTransitiveDependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String? get name => r'marketplaceStickerPackContentProvider';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Marketplace version of sticker pack detail page (no publisher dependency).
 | 
				
			||||||
 | 
					/// Shows all stickers in the pack and provides a button to add the sticker.
 | 
				
			||||||
 | 
					/// API interactions are intentionally left blank per request.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Copied from [marketplaceStickerPackContent].
 | 
				
			||||||
 | 
					class MarketplaceStickerPackContentProvider
 | 
				
			||||||
 | 
					    extends AutoDisposeFutureProvider<List<SnSticker>> {
 | 
				
			||||||
 | 
					  /// Marketplace version of sticker pack detail page (no publisher dependency).
 | 
				
			||||||
 | 
					  /// Shows all stickers in the pack and provides a button to add the sticker.
 | 
				
			||||||
 | 
					  /// API interactions are intentionally left blank per request.
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// Copied from [marketplaceStickerPackContent].
 | 
				
			||||||
 | 
					  MarketplaceStickerPackContentProvider({required String packId})
 | 
				
			||||||
 | 
					    : this._internal(
 | 
				
			||||||
 | 
					        (ref) => marketplaceStickerPackContent(
 | 
				
			||||||
 | 
					          ref as MarketplaceStickerPackContentRef,
 | 
				
			||||||
 | 
					          packId: packId,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        from: marketplaceStickerPackContentProvider,
 | 
				
			||||||
 | 
					        name: r'marketplaceStickerPackContentProvider',
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash:
 | 
				
			||||||
 | 
					            const bool.fromEnvironment('dart.vm.product')
 | 
				
			||||||
 | 
					                ? null
 | 
				
			||||||
 | 
					                : _$marketplaceStickerPackContentHash,
 | 
				
			||||||
 | 
					        dependencies: MarketplaceStickerPackContentFamily._dependencies,
 | 
				
			||||||
 | 
					        allTransitiveDependencies:
 | 
				
			||||||
 | 
					            MarketplaceStickerPackContentFamily._allTransitiveDependencies,
 | 
				
			||||||
 | 
					        packId: packId,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MarketplaceStickerPackContentProvider._internal(
 | 
				
			||||||
 | 
					    super._createNotifier, {
 | 
				
			||||||
 | 
					    required super.name,
 | 
				
			||||||
 | 
					    required super.dependencies,
 | 
				
			||||||
 | 
					    required super.allTransitiveDependencies,
 | 
				
			||||||
 | 
					    required super.debugGetCreateSourceHash,
 | 
				
			||||||
 | 
					    required super.from,
 | 
				
			||||||
 | 
					    required this.packId,
 | 
				
			||||||
 | 
					  }) : super.internal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String packId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Override overrideWith(
 | 
				
			||||||
 | 
					    FutureOr<List<SnSticker>> Function(
 | 
				
			||||||
 | 
					      MarketplaceStickerPackContentRef provider,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    create,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return ProviderOverride(
 | 
				
			||||||
 | 
					      origin: this,
 | 
				
			||||||
 | 
					      override: MarketplaceStickerPackContentProvider._internal(
 | 
				
			||||||
 | 
					        (ref) => create(ref as MarketplaceStickerPackContentRef),
 | 
				
			||||||
 | 
					        from: from,
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        dependencies: null,
 | 
				
			||||||
 | 
					        allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash: null,
 | 
				
			||||||
 | 
					        packId: packId,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  AutoDisposeFutureProviderElement<List<SnSticker>> createElement() {
 | 
				
			||||||
 | 
					    return _MarketplaceStickerPackContentProviderElement(this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool operator ==(Object other) {
 | 
				
			||||||
 | 
					    return other is MarketplaceStickerPackContentProvider &&
 | 
				
			||||||
 | 
					        other.packId == packId;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  int get hashCode {
 | 
				
			||||||
 | 
					    var hash = _SystemHash.combine(0, runtimeType.hashCode);
 | 
				
			||||||
 | 
					    hash = _SystemHash.combine(hash, packId.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return _SystemHash.finish(hash);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
 | 
					// ignore: unused_element
 | 
				
			||||||
 | 
					mixin MarketplaceStickerPackContentRef
 | 
				
			||||||
 | 
					    on AutoDisposeFutureProviderRef<List<SnSticker>> {
 | 
				
			||||||
 | 
					  /// The parameter `packId` of this provider.
 | 
				
			||||||
 | 
					  String get packId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _MarketplaceStickerPackContentProviderElement
 | 
				
			||||||
 | 
					    extends AutoDisposeFutureProviderElement<List<SnSticker>>
 | 
				
			||||||
 | 
					    with MarketplaceStickerPackContentRef {
 | 
				
			||||||
 | 
					  _MarketplaceStickerPackContentProviderElement(super.provider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String get packId => (origin as MarketplaceStickerPackContentProvider).packId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String _$marketplaceStickerPackOwnershipHash() =>
 | 
				
			||||||
 | 
					    r'e5dd301c309fac958729d13d984ce7a77edbe7e6';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [marketplaceStickerPackOwnership].
 | 
				
			||||||
 | 
					@ProviderFor(marketplaceStickerPackOwnership)
 | 
				
			||||||
 | 
					const marketplaceStickerPackOwnershipProvider =
 | 
				
			||||||
 | 
					    MarketplaceStickerPackOwnershipFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [marketplaceStickerPackOwnership].
 | 
				
			||||||
 | 
					class MarketplaceStickerPackOwnershipFamily extends Family<AsyncValue<bool>> {
 | 
				
			||||||
 | 
					  /// See also [marketplaceStickerPackOwnership].
 | 
				
			||||||
 | 
					  const MarketplaceStickerPackOwnershipFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// See also [marketplaceStickerPackOwnership].
 | 
				
			||||||
 | 
					  MarketplaceStickerPackOwnershipProvider call({required String packId}) {
 | 
				
			||||||
 | 
					    return MarketplaceStickerPackOwnershipProvider(packId: packId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  MarketplaceStickerPackOwnershipProvider getProviderOverride(
 | 
				
			||||||
 | 
					    covariant MarketplaceStickerPackOwnershipProvider provider,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return call(packId: provider.packId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _dependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get dependencies => _dependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
 | 
				
			||||||
 | 
					      _allTransitiveDependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String? get name => r'marketplaceStickerPackOwnershipProvider';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [marketplaceStickerPackOwnership].
 | 
				
			||||||
 | 
					class MarketplaceStickerPackOwnershipProvider
 | 
				
			||||||
 | 
					    extends AutoDisposeFutureProvider<bool> {
 | 
				
			||||||
 | 
					  /// See also [marketplaceStickerPackOwnership].
 | 
				
			||||||
 | 
					  MarketplaceStickerPackOwnershipProvider({required String packId})
 | 
				
			||||||
 | 
					    : this._internal(
 | 
				
			||||||
 | 
					        (ref) => marketplaceStickerPackOwnership(
 | 
				
			||||||
 | 
					          ref as MarketplaceStickerPackOwnershipRef,
 | 
				
			||||||
 | 
					          packId: packId,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        from: marketplaceStickerPackOwnershipProvider,
 | 
				
			||||||
 | 
					        name: r'marketplaceStickerPackOwnershipProvider',
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash:
 | 
				
			||||||
 | 
					            const bool.fromEnvironment('dart.vm.product')
 | 
				
			||||||
 | 
					                ? null
 | 
				
			||||||
 | 
					                : _$marketplaceStickerPackOwnershipHash,
 | 
				
			||||||
 | 
					        dependencies: MarketplaceStickerPackOwnershipFamily._dependencies,
 | 
				
			||||||
 | 
					        allTransitiveDependencies:
 | 
				
			||||||
 | 
					            MarketplaceStickerPackOwnershipFamily._allTransitiveDependencies,
 | 
				
			||||||
 | 
					        packId: packId,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MarketplaceStickerPackOwnershipProvider._internal(
 | 
				
			||||||
 | 
					    super._createNotifier, {
 | 
				
			||||||
 | 
					    required super.name,
 | 
				
			||||||
 | 
					    required super.dependencies,
 | 
				
			||||||
 | 
					    required super.allTransitiveDependencies,
 | 
				
			||||||
 | 
					    required super.debugGetCreateSourceHash,
 | 
				
			||||||
 | 
					    required super.from,
 | 
				
			||||||
 | 
					    required this.packId,
 | 
				
			||||||
 | 
					  }) : super.internal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String packId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Override overrideWith(
 | 
				
			||||||
 | 
					    FutureOr<bool> Function(MarketplaceStickerPackOwnershipRef provider) create,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return ProviderOverride(
 | 
				
			||||||
 | 
					      origin: this,
 | 
				
			||||||
 | 
					      override: MarketplaceStickerPackOwnershipProvider._internal(
 | 
				
			||||||
 | 
					        (ref) => create(ref as MarketplaceStickerPackOwnershipRef),
 | 
				
			||||||
 | 
					        from: from,
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        dependencies: null,
 | 
				
			||||||
 | 
					        allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash: null,
 | 
				
			||||||
 | 
					        packId: packId,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  AutoDisposeFutureProviderElement<bool> createElement() {
 | 
				
			||||||
 | 
					    return _MarketplaceStickerPackOwnershipProviderElement(this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool operator ==(Object other) {
 | 
				
			||||||
 | 
					    return other is MarketplaceStickerPackOwnershipProvider &&
 | 
				
			||||||
 | 
					        other.packId == packId;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  int get hashCode {
 | 
				
			||||||
 | 
					    var hash = _SystemHash.combine(0, runtimeType.hashCode);
 | 
				
			||||||
 | 
					    hash = _SystemHash.combine(hash, packId.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return _SystemHash.finish(hash);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
 | 
					// ignore: unused_element
 | 
				
			||||||
 | 
					mixin MarketplaceStickerPackOwnershipRef on AutoDisposeFutureProviderRef<bool> {
 | 
				
			||||||
 | 
					  /// The parameter `packId` of this provider.
 | 
				
			||||||
 | 
					  String get packId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _MarketplaceStickerPackOwnershipProviderElement
 | 
				
			||||||
 | 
					    extends AutoDisposeFutureProviderElement<bool>
 | 
				
			||||||
 | 
					    with MarketplaceStickerPackOwnershipRef {
 | 
				
			||||||
 | 
					  _MarketplaceStickerPackOwnershipProviderElement(super.provider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String get packId =>
 | 
				
			||||||
 | 
					      (origin as MarketplaceStickerPackOwnershipProvider).packId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
							
								
								
									
										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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -130,9 +130,22 @@ class AccountStatusWidget extends HookConsumerWidget {
 | 
				
			|||||||
              size: 16,
 | 
					              size: 16,
 | 
				
			||||||
            ).padding(right: 4),
 | 
					            ).padding(right: 4),
 | 
				
			||||||
          if (status.value?.isCustomized ?? false)
 | 
					          if (status.value?.isCustomized ?? false)
 | 
				
			||||||
            Text(status.value?.label ?? 'unknown'.tr())
 | 
					            Flexible(
 | 
				
			||||||
 | 
					              child: Text(
 | 
				
			||||||
 | 
					                status.value?.label ?? 'unknown'.tr(),
 | 
				
			||||||
 | 
					                maxLines: 1,
 | 
				
			||||||
 | 
					                overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
          else
 | 
					          else
 | 
				
			||||||
            Text((status.value?.label ?? 'offline').toLowerCase()).tr(),
 | 
					            Flexible(
 | 
				
			||||||
 | 
					              child:
 | 
				
			||||||
 | 
					                  Text(
 | 
				
			||||||
 | 
					                    (status.value?.label ?? 'offline').toLowerCase(),
 | 
				
			||||||
 | 
					                    maxLines: 1,
 | 
				
			||||||
 | 
					                    overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					                  ).tr(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
          if (!(status.value?.isOnline ?? false) &&
 | 
					          if (!(status.value?.isOnline ?? false) &&
 | 
				
			||||||
              account.value?.profile.lastSeenAt != null)
 | 
					              account.value?.profile.lastSeenAt != null)
 | 
				
			||||||
            Flexible(
 | 
					            Flexible(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										236
									
								
								lib/widgets/poll/poll_feedback.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								lib/widgets/poll/poll_feedback.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,236 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/poll.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/services/time.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/sheet.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
				
			||||||
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'poll_feedback.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@riverpod
 | 
				
			||||||
 | 
					class PollFeedbackNotifier extends _$PollFeedbackNotifier
 | 
				
			||||||
 | 
					    with CursorPagingNotifierMixin<SnPollAnswer> {
 | 
				
			||||||
 | 
					  static const int _pageSize = 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<CursorPagingData<SnPollAnswer>> build(String id) {
 | 
				
			||||||
 | 
					    // immediately load first page
 | 
				
			||||||
 | 
					    return fetch(cursor: null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<CursorPagingData<SnPollAnswer>> fetch({
 | 
				
			||||||
 | 
					    required String? cursor,
 | 
				
			||||||
 | 
					  }) async {
 | 
				
			||||||
 | 
					    final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					    final offset = cursor == null ? 0 : int.parse(cursor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final queryParams = {'offset': offset, 'take': _pageSize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final response = await client.get(
 | 
				
			||||||
 | 
					      '/sphere/polls/$id/feedback',
 | 
				
			||||||
 | 
					      queryParameters: queryParams,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    final total = int.parse(response.headers.value('X-Total') ?? '0');
 | 
				
			||||||
 | 
					    final List<dynamic> data = response.data;
 | 
				
			||||||
 | 
					    final items = data.map((json) => SnPollAnswer.fromJson(json)).toList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final hasMore = offset + items.length < total;
 | 
				
			||||||
 | 
					    final nextCursor = hasMore ? (offset + items.length).toString() : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return CursorPagingData(
 | 
				
			||||||
 | 
					      items: items,
 | 
				
			||||||
 | 
					      hasMore: hasMore,
 | 
				
			||||||
 | 
					      nextCursor: nextCursor,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PollFeedbackSheet extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  final String pollId;
 | 
				
			||||||
 | 
					  final String? title;
 | 
				
			||||||
 | 
					  final SnPoll poll;
 | 
				
			||||||
 | 
					  final Map<String, dynamic>? stats; // stats object similar to PollSubmit
 | 
				
			||||||
 | 
					  const PollFeedbackSheet({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.pollId,
 | 
				
			||||||
 | 
					    required this.poll,
 | 
				
			||||||
 | 
					    this.title,
 | 
				
			||||||
 | 
					    this.stats,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    return SheetScaffold(
 | 
				
			||||||
 | 
					      titleText: title ?? 'Poll feedback',
 | 
				
			||||||
 | 
					      child: Column(
 | 
				
			||||||
 | 
					        crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          _PollHeader(poll: poll, stats: stats),
 | 
				
			||||||
 | 
					          const Divider(height: 1),
 | 
				
			||||||
 | 
					          Expanded(
 | 
				
			||||||
 | 
					            child: PagingHelperView(
 | 
				
			||||||
 | 
					              provider: pollFeedbackNotifierProvider(pollId),
 | 
				
			||||||
 | 
					              futureRefreshable: pollFeedbackNotifierProvider(pollId).future,
 | 
				
			||||||
 | 
					              notifierRefreshable:
 | 
				
			||||||
 | 
					                  pollFeedbackNotifierProvider(pollId).notifier,
 | 
				
			||||||
 | 
					              contentBuilder:
 | 
				
			||||||
 | 
					                  (data, widgetCount, endItemView) => ListView.separated(
 | 
				
			||||||
 | 
					                    padding: const EdgeInsets.symmetric(vertical: 4),
 | 
				
			||||||
 | 
					                    itemCount: widgetCount,
 | 
				
			||||||
 | 
					                    itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                      if (index == widgetCount - 1) {
 | 
				
			||||||
 | 
					                        // Provided by PagingHelperView to indicate end/loading
 | 
				
			||||||
 | 
					                        return endItemView;
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                      final answer = data.items[index];
 | 
				
			||||||
 | 
					                      return _PollAnswerTile(answer: answer, poll: poll);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    separatorBuilder:
 | 
				
			||||||
 | 
					                        (context, index) =>
 | 
				
			||||||
 | 
					                            const Divider(height: 1).padding(vertical: 4),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _PollHeader extends StatelessWidget {
 | 
				
			||||||
 | 
					  const _PollHeader({required this.poll, this.stats});
 | 
				
			||||||
 | 
					  final SnPoll poll;
 | 
				
			||||||
 | 
					  final Map<String, dynamic>? stats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final theme = Theme.of(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Column(
 | 
				
			||||||
 | 
					      crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        if (poll.title != null)
 | 
				
			||||||
 | 
					          Text(poll.title!, style: theme.textTheme.titleLarge),
 | 
				
			||||||
 | 
					        if (poll.description != null)
 | 
				
			||||||
 | 
					          Padding(
 | 
				
			||||||
 | 
					            padding: const EdgeInsets.only(top: 2),
 | 
				
			||||||
 | 
					            child: Text(
 | 
				
			||||||
 | 
					              poll.description!,
 | 
				
			||||||
 | 
					              style: theme.textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
 | 
					                color: theme.textTheme.bodyMedium?.color?.withOpacity(0.7),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    ).padding(horizontal: 20, vertical: 16);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _PollAnswerTile extends StatelessWidget {
 | 
				
			||||||
 | 
					  final SnPollAnswer answer;
 | 
				
			||||||
 | 
					  final SnPoll poll;
 | 
				
			||||||
 | 
					  const _PollAnswerTile({required this.answer, required this.poll});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String _formatPerQuestionAnswer(
 | 
				
			||||||
 | 
					    SnPollQuestion q,
 | 
				
			||||||
 | 
					    Map<String, dynamic> ansMap,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    switch (q.type) {
 | 
				
			||||||
 | 
					      case SnPollQuestionType.singleChoice:
 | 
				
			||||||
 | 
					        final val = ansMap[q.id];
 | 
				
			||||||
 | 
					        if (val is String) {
 | 
				
			||||||
 | 
					          final opt = q.options?.firstWhere(
 | 
				
			||||||
 | 
					            (o) => o.id == val,
 | 
				
			||||||
 | 
					            orElse: () => SnPollOption(id: val, label: '#$val', order: 0),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          return opt?.label ?? '#$val';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return '—';
 | 
				
			||||||
 | 
					      case SnPollQuestionType.multipleChoice:
 | 
				
			||||||
 | 
					        final val = ansMap[q.id];
 | 
				
			||||||
 | 
					        if (val is List) {
 | 
				
			||||||
 | 
					          final ids = val.whereType<String>().toList();
 | 
				
			||||||
 | 
					          if (ids.isEmpty) return '—';
 | 
				
			||||||
 | 
					          final labels =
 | 
				
			||||||
 | 
					              ids.map((id) {
 | 
				
			||||||
 | 
					                final opt = q.options?.firstWhere(
 | 
				
			||||||
 | 
					                  (o) => o.id == id,
 | 
				
			||||||
 | 
					                  orElse: () => SnPollOption(id: id, label: '#$id', order: 0),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                return opt?.label ?? '#$id';
 | 
				
			||||||
 | 
					              }).toList();
 | 
				
			||||||
 | 
					          return labels.join(', ');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return '—';
 | 
				
			||||||
 | 
					      case SnPollQuestionType.yesNo:
 | 
				
			||||||
 | 
					        final val = ansMap[q.id];
 | 
				
			||||||
 | 
					        if (val is bool) {
 | 
				
			||||||
 | 
					          return val ? 'Yes' : 'No';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return '—';
 | 
				
			||||||
 | 
					      case SnPollQuestionType.rating:
 | 
				
			||||||
 | 
					        final val = ansMap[q.id];
 | 
				
			||||||
 | 
					        if (val is int) return val.toString();
 | 
				
			||||||
 | 
					        if (val is num) return val.toString();
 | 
				
			||||||
 | 
					        return '—';
 | 
				
			||||||
 | 
					      case SnPollQuestionType.freeText:
 | 
				
			||||||
 | 
					        final val = ansMap[q.id];
 | 
				
			||||||
 | 
					        if (val is String && val.trim().isNotEmpty) return val;
 | 
				
			||||||
 | 
					        return '—';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    // Submit date/time (title)
 | 
				
			||||||
 | 
					    final submitText = answer.createdAt.formatSystem();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Compose content from poll questions if provided, otherwise fallback to joined key-values
 | 
				
			||||||
 | 
					    String content;
 | 
				
			||||||
 | 
					    if (poll.questions.isNotEmpty) {
 | 
				
			||||||
 | 
					      final questions = [...poll.questions]
 | 
				
			||||||
 | 
					        ..sort((a, b) => a.order.compareTo(b.order));
 | 
				
			||||||
 | 
					      final buffer = StringBuffer();
 | 
				
			||||||
 | 
					      for (final q in questions) {
 | 
				
			||||||
 | 
					        final formatted = _formatPerQuestionAnswer(q, answer.answer);
 | 
				
			||||||
 | 
					        buffer.writeln('${q.title}: $formatted');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      content = buffer.toString().trimRight();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Fallback formatting without poll context. We still want to show the question title
 | 
				
			||||||
 | 
					      // instead of the raw question id key if we can derive it from the answer map itself.
 | 
				
			||||||
 | 
					      // Since we don't have poll metadata here, we cannot resolve the title; therefore we
 | 
				
			||||||
 | 
					      // will show only values line-by-line without exposing the raw id.
 | 
				
			||||||
 | 
					      if (answer.answer.isEmpty) {
 | 
				
			||||||
 | 
					        content = '—';
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        final parts = <String>[];
 | 
				
			||||||
 | 
					        answer.answer.forEach((key, value) {
 | 
				
			||||||
 | 
					          var question = poll.questions.firstWhere((q) => q.id == key);
 | 
				
			||||||
 | 
					          if (value is List) {
 | 
				
			||||||
 | 
					            parts.add('${question.title}: ${value.join(', ')}');
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            parts.add('${question.title}: $value');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        content = parts.join('\n');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ListTile(
 | 
				
			||||||
 | 
					      contentPadding: const EdgeInsets.symmetric(horizontal: 20),
 | 
				
			||||||
 | 
					      isThreeLine: true,
 | 
				
			||||||
 | 
					      leading: const CircleAvatar(
 | 
				
			||||||
 | 
					        radius: 16,
 | 
				
			||||||
 | 
					        child: Icon(Icons.how_to_vote, size: 16),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      title: Text(submitText),
 | 
				
			||||||
 | 
					      subtitle: Text(content),
 | 
				
			||||||
 | 
					      trailing: null,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										180
									
								
								lib/widgets/poll/poll_feedback.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								lib/widgets/poll/poll_feedback.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'poll_feedback.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// RiverpodGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String _$pollFeedbackNotifierHash() =>
 | 
				
			||||||
 | 
					    r'1bf3925b5b751cfd1a9abafb75274f1e95e7f27e';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Copied from Dart SDK
 | 
				
			||||||
 | 
					class _SystemHash {
 | 
				
			||||||
 | 
					  _SystemHash._();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static int combine(int hash, int value) {
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + value);
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
 | 
				
			||||||
 | 
					    return hash ^ (hash >> 6);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static int finish(int hash) {
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = hash ^ (hash >> 11);
 | 
				
			||||||
 | 
					    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class _$PollFeedbackNotifier
 | 
				
			||||||
 | 
					    extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPollAnswer>> {
 | 
				
			||||||
 | 
					  late final String id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FutureOr<CursorPagingData<SnPollAnswer>> build(String id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [PollFeedbackNotifier].
 | 
				
			||||||
 | 
					@ProviderFor(PollFeedbackNotifier)
 | 
				
			||||||
 | 
					const pollFeedbackNotifierProvider = PollFeedbackNotifierFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [PollFeedbackNotifier].
 | 
				
			||||||
 | 
					class PollFeedbackNotifierFamily
 | 
				
			||||||
 | 
					    extends Family<AsyncValue<CursorPagingData<SnPollAnswer>>> {
 | 
				
			||||||
 | 
					  /// See also [PollFeedbackNotifier].
 | 
				
			||||||
 | 
					  const PollFeedbackNotifierFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// See also [PollFeedbackNotifier].
 | 
				
			||||||
 | 
					  PollFeedbackNotifierProvider call(String id) {
 | 
				
			||||||
 | 
					    return PollFeedbackNotifierProvider(id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  PollFeedbackNotifierProvider getProviderOverride(
 | 
				
			||||||
 | 
					    covariant PollFeedbackNotifierProvider provider,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return call(provider.id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _dependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get dependencies => _dependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
 | 
				
			||||||
 | 
					      _allTransitiveDependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String? get name => r'pollFeedbackNotifierProvider';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [PollFeedbackNotifier].
 | 
				
			||||||
 | 
					class PollFeedbackNotifierProvider
 | 
				
			||||||
 | 
					    extends
 | 
				
			||||||
 | 
					        AutoDisposeAsyncNotifierProviderImpl<
 | 
				
			||||||
 | 
					          PollFeedbackNotifier,
 | 
				
			||||||
 | 
					          CursorPagingData<SnPollAnswer>
 | 
				
			||||||
 | 
					        > {
 | 
				
			||||||
 | 
					  /// See also [PollFeedbackNotifier].
 | 
				
			||||||
 | 
					  PollFeedbackNotifierProvider(String id)
 | 
				
			||||||
 | 
					    : this._internal(
 | 
				
			||||||
 | 
					        () => PollFeedbackNotifier()..id = id,
 | 
				
			||||||
 | 
					        from: pollFeedbackNotifierProvider,
 | 
				
			||||||
 | 
					        name: r'pollFeedbackNotifierProvider',
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash:
 | 
				
			||||||
 | 
					            const bool.fromEnvironment('dart.vm.product')
 | 
				
			||||||
 | 
					                ? null
 | 
				
			||||||
 | 
					                : _$pollFeedbackNotifierHash,
 | 
				
			||||||
 | 
					        dependencies: PollFeedbackNotifierFamily._dependencies,
 | 
				
			||||||
 | 
					        allTransitiveDependencies:
 | 
				
			||||||
 | 
					            PollFeedbackNotifierFamily._allTransitiveDependencies,
 | 
				
			||||||
 | 
					        id: id,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PollFeedbackNotifierProvider._internal(
 | 
				
			||||||
 | 
					    super._createNotifier, {
 | 
				
			||||||
 | 
					    required super.name,
 | 
				
			||||||
 | 
					    required super.dependencies,
 | 
				
			||||||
 | 
					    required super.allTransitiveDependencies,
 | 
				
			||||||
 | 
					    required super.debugGetCreateSourceHash,
 | 
				
			||||||
 | 
					    required super.from,
 | 
				
			||||||
 | 
					    required this.id,
 | 
				
			||||||
 | 
					  }) : super.internal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  FutureOr<CursorPagingData<SnPollAnswer>> runNotifierBuild(
 | 
				
			||||||
 | 
					    covariant PollFeedbackNotifier notifier,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return notifier.build(id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Override overrideWith(PollFeedbackNotifier Function() create) {
 | 
				
			||||||
 | 
					    return ProviderOverride(
 | 
				
			||||||
 | 
					      origin: this,
 | 
				
			||||||
 | 
					      override: PollFeedbackNotifierProvider._internal(
 | 
				
			||||||
 | 
					        () => create()..id = id,
 | 
				
			||||||
 | 
					        from: from,
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        dependencies: null,
 | 
				
			||||||
 | 
					        allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash: null,
 | 
				
			||||||
 | 
					        id: id,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  AutoDisposeAsyncNotifierProviderElement<
 | 
				
			||||||
 | 
					    PollFeedbackNotifier,
 | 
				
			||||||
 | 
					    CursorPagingData<SnPollAnswer>
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					  createElement() {
 | 
				
			||||||
 | 
					    return _PollFeedbackNotifierProviderElement(this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool operator ==(Object other) {
 | 
				
			||||||
 | 
					    return other is PollFeedbackNotifierProvider && other.id == id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  int get hashCode {
 | 
				
			||||||
 | 
					    var hash = _SystemHash.combine(0, runtimeType.hashCode);
 | 
				
			||||||
 | 
					    hash = _SystemHash.combine(hash, id.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return _SystemHash.finish(hash);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
 | 
					// ignore: unused_element
 | 
				
			||||||
 | 
					mixin PollFeedbackNotifierRef
 | 
				
			||||||
 | 
					    on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPollAnswer>> {
 | 
				
			||||||
 | 
					  /// The parameter `id` of this provider.
 | 
				
			||||||
 | 
					  String get id;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _PollFeedbackNotifierProviderElement
 | 
				
			||||||
 | 
					    extends
 | 
				
			||||||
 | 
					        AutoDisposeAsyncNotifierProviderElement<
 | 
				
			||||||
 | 
					          PollFeedbackNotifier,
 | 
				
			||||||
 | 
					          CursorPagingData<SnPollAnswer>
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					    with PollFeedbackNotifierRef {
 | 
				
			||||||
 | 
					  _PollFeedbackNotifierProviderElement(super.provider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String get id => (origin as PollFeedbackNotifierProvider).id;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
@@ -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),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -535,7 +535,7 @@ class PostItem extends HookConsumerWidget {
 | 
				
			|||||||
              right: renderingPadding.horizontal,
 | 
					              right: renderingPadding.horizontal,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        if (item.attachments.isNotEmpty)
 | 
					        if (item.attachments.isNotEmpty && item.type != 1)
 | 
				
			||||||
          CloudFileList(
 | 
					          CloudFileList(
 | 
				
			||||||
            files: item.attachments,
 | 
					            files: item.attachments,
 | 
				
			||||||
            padding: EdgeInsets.symmetric(
 | 
					            padding: EdgeInsets.symmetric(
 | 
				
			||||||
@@ -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'78222d62957f85713d17aecd95af0305b764e86c';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// 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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										307
									
								
								lib/widgets/stickers/picker.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								lib/widgets/stickers/picker.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,307 @@
 | 
				
			|||||||
 | 
					import 'dart:math' as math;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					import 'package:gap/gap.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/sticker.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/cloud_files.dart';
 | 
				
			||||||
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_popup_card/flutter_popup_card.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'picker.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Fetch user-added sticker packs (with stickers) from API:
 | 
				
			||||||
 | 
					/// GET /sphere/stickers/me
 | 
				
			||||||
 | 
					@riverpod
 | 
				
			||||||
 | 
					Future<List<SnStickerPack>> myStickerPacks(Ref ref) async {
 | 
				
			||||||
 | 
					  final api = ref.watch(apiClientProvider);
 | 
				
			||||||
 | 
					  final resp = await api.get('/sphere/stickers/me');
 | 
				
			||||||
 | 
					  final data = resp.data;
 | 
				
			||||||
 | 
					  if (data is List) {
 | 
				
			||||||
 | 
					    return data
 | 
				
			||||||
 | 
					        .map((e) => SnStickerPack.fromJson(e as Map<String, dynamic>))
 | 
				
			||||||
 | 
					        .toList();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return const <SnStickerPack>[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Sticker Picker popover dialog
 | 
				
			||||||
 | 
					/// - Displays user-owned sticker packs as tabs (chips)
 | 
				
			||||||
 | 
					/// - Shows grid of stickers in selected pack
 | 
				
			||||||
 | 
					/// - On tap, returns placeholder string :{prefix}{slug}: via onPick callback
 | 
				
			||||||
 | 
					class StickerPicker extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  final void Function(String placeholder) onPick;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const StickerPicker({super.key, required this.onPick});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    final packsAsync = ref.watch(myStickerPacksProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return PopupCard(
 | 
				
			||||||
 | 
					      elevation: 8,
 | 
				
			||||||
 | 
					      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
 | 
				
			||||||
 | 
					      child: ConstrainedBox(
 | 
				
			||||||
 | 
					        constraints: const BoxConstraints(maxWidth: 520, maxHeight: 520),
 | 
				
			||||||
 | 
					        child: packsAsync.when(
 | 
				
			||||||
 | 
					          data: (packs) {
 | 
				
			||||||
 | 
					            if (packs.isEmpty) {
 | 
				
			||||||
 | 
					              return _EmptyState(
 | 
				
			||||||
 | 
					                onRefresh: () async {
 | 
				
			||||||
 | 
					                  ref.invalidate(myStickerPacksProvider);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Maintain selected index locally with a ValueNotifier to avoid hooks dependency
 | 
				
			||||||
 | 
					            return _PackSwitcher(
 | 
				
			||||||
 | 
					              packs: packs,
 | 
				
			||||||
 | 
					              onPick: (pack, sticker) {
 | 
				
			||||||
 | 
					                final placeholder = ':${pack.prefix}${sticker.slug}:';
 | 
				
			||||||
 | 
					                HapticFeedback.selectionClick();
 | 
				
			||||||
 | 
					                onPick(placeholder);
 | 
				
			||||||
 | 
					                if (Navigator.of(context).canPop()) {
 | 
				
			||||||
 | 
					                  Navigator.of(context).pop();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              onRefresh: () async {
 | 
				
			||||||
 | 
					                ref.invalidate(myStickerPacksProvider);
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          loading:
 | 
				
			||||||
 | 
					              () => const SizedBox(
 | 
				
			||||||
 | 
					                width: 320,
 | 
				
			||||||
 | 
					                height: 320,
 | 
				
			||||||
 | 
					                child: Center(child: CircularProgressIndicator()),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					          error:
 | 
				
			||||||
 | 
					              (err, _) => SizedBox(
 | 
				
			||||||
 | 
					                width: 360,
 | 
				
			||||||
 | 
					                height: 200,
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    const Icon(Symbols.error, size: 28),
 | 
				
			||||||
 | 
					                    const Gap(8),
 | 
				
			||||||
 | 
					                    Text('Error: $err', textAlign: TextAlign.center),
 | 
				
			||||||
 | 
					                    const Gap(12),
 | 
				
			||||||
 | 
					                    FilledButton.icon(
 | 
				
			||||||
 | 
					                      onPressed: () => ref.invalidate(myStickerPacksProvider),
 | 
				
			||||||
 | 
					                      icon: const Icon(Symbols.refresh),
 | 
				
			||||||
 | 
					                      label: Text('retry').tr(),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ).padding(all: 16),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _EmptyState extends StatelessWidget {
 | 
				
			||||||
 | 
					  final Future<void> Function() onRefresh;
 | 
				
			||||||
 | 
					  const _EmptyState({required this.onRefresh});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return SizedBox(
 | 
				
			||||||
 | 
					      width: 360,
 | 
				
			||||||
 | 
					      height: 220,
 | 
				
			||||||
 | 
					      child: Column(
 | 
				
			||||||
 | 
					        mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          const Icon(Symbols.emoji_symbols, size: 28),
 | 
				
			||||||
 | 
					          const Gap(8),
 | 
				
			||||||
 | 
					          Text('noStickerPacks'.tr(), textAlign: TextAlign.center),
 | 
				
			||||||
 | 
					          const Gap(12),
 | 
				
			||||||
 | 
					          OutlinedButton.icon(
 | 
				
			||||||
 | 
					            onPressed: onRefresh,
 | 
				
			||||||
 | 
					            icon: const Icon(Symbols.refresh),
 | 
				
			||||||
 | 
					            label: Text('refresh').tr(),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ).padding(all: 16),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _PackSwitcher extends StatefulWidget {
 | 
				
			||||||
 | 
					  final List<SnStickerPack> packs;
 | 
				
			||||||
 | 
					  final void Function(SnStickerPack pack, SnSticker sticker) onPick;
 | 
				
			||||||
 | 
					  final Future<void> Function() onRefresh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const _PackSwitcher({
 | 
				
			||||||
 | 
					    required this.packs,
 | 
				
			||||||
 | 
					    required this.onPick,
 | 
				
			||||||
 | 
					    required this.onRefresh,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<_PackSwitcher> createState() => _PackSwitcherState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _PackSwitcherState extends State<_PackSwitcher> {
 | 
				
			||||||
 | 
					  int _index = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final packs = widget.packs;
 | 
				
			||||||
 | 
					    _index = _index.clamp(0, packs.length - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final selectedPack = packs[_index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Column(
 | 
				
			||||||
 | 
					      crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        // Header
 | 
				
			||||||
 | 
					        Row(
 | 
				
			||||||
 | 
					          children: [
 | 
				
			||||||
 | 
					            const Icon(Symbols.sticky_note_2, size: 20),
 | 
				
			||||||
 | 
					            const Gap(8),
 | 
				
			||||||
 | 
					            Text(
 | 
				
			||||||
 | 
					              'stickers'.tr(),
 | 
				
			||||||
 | 
					              style: Theme.of(context).textTheme.titleMedium,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            const Spacer(),
 | 
				
			||||||
 | 
					            IconButton(
 | 
				
			||||||
 | 
					              padding: EdgeInsets.zero,
 | 
				
			||||||
 | 
					              visualDensity: VisualDensity.compact,
 | 
				
			||||||
 | 
					              tooltip: 'close'.tr(),
 | 
				
			||||||
 | 
					              onPressed: () => Navigator.of(context).maybePop(),
 | 
				
			||||||
 | 
					              icon: const Icon(Symbols.close),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        ).padding(horizontal: 12, top: 8),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Vertical, scrollable packs rail like common emoji pickers
 | 
				
			||||||
 | 
					        SizedBox(
 | 
				
			||||||
 | 
					          height: 48,
 | 
				
			||||||
 | 
					          child: ListView.separated(
 | 
				
			||||||
 | 
					            padding: const EdgeInsets.symmetric(horizontal: 8),
 | 
				
			||||||
 | 
					            scrollDirection: Axis.horizontal,
 | 
				
			||||||
 | 
					            itemCount: packs.length,
 | 
				
			||||||
 | 
					            separatorBuilder: (_, _) => const Gap(4),
 | 
				
			||||||
 | 
					            itemBuilder: (context, i) {
 | 
				
			||||||
 | 
					              final selected = _index == i;
 | 
				
			||||||
 | 
					              return Tooltip(
 | 
				
			||||||
 | 
					                message: packs[i].name,
 | 
				
			||||||
 | 
					                child: FilterChip(
 | 
				
			||||||
 | 
					                  label: Text(packs[i].name, overflow: TextOverflow.ellipsis),
 | 
				
			||||||
 | 
					                  selected: selected,
 | 
				
			||||||
 | 
					                  onSelected: (_) {
 | 
				
			||||||
 | 
					                    setState(() => _index = i);
 | 
				
			||||||
 | 
					                    HapticFeedback.selectionClick();
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ).padding(bottom: 8),
 | 
				
			||||||
 | 
					        const Divider(height: 1),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Content
 | 
				
			||||||
 | 
					        Expanded(
 | 
				
			||||||
 | 
					          child: RefreshIndicator(
 | 
				
			||||||
 | 
					            onRefresh: widget.onRefresh,
 | 
				
			||||||
 | 
					            child: _StickersGrid(
 | 
				
			||||||
 | 
					              pack: selectedPack,
 | 
				
			||||||
 | 
					              onPick: (sticker) => widget.onPick(selectedPack, sticker),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _StickersGrid extends StatelessWidget {
 | 
				
			||||||
 | 
					  final SnStickerPack pack;
 | 
				
			||||||
 | 
					  final void Function(SnSticker sticker) onPick;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const _StickersGrid({required this.pack, required this.onPick});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final stickers = pack.stickers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (stickers.isEmpty) {
 | 
				
			||||||
 | 
					      return Center(child: Text('noStickersInPack'.tr()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return GridView.builder(
 | 
				
			||||||
 | 
					      physics: const AlwaysScrollableScrollPhysics(),
 | 
				
			||||||
 | 
					      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
 | 
				
			||||||
 | 
					      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
 | 
				
			||||||
 | 
					        maxCrossAxisExtent: 96,
 | 
				
			||||||
 | 
					        mainAxisSpacing: 12,
 | 
				
			||||||
 | 
					        crossAxisSpacing: 12,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      itemCount: stickers.length,
 | 
				
			||||||
 | 
					      itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					        final sticker = stickers[index];
 | 
				
			||||||
 | 
					        final placeholder = ':${pack.prefix}${sticker.slug}:';
 | 
				
			||||||
 | 
					        return Tooltip(
 | 
				
			||||||
 | 
					          message: placeholder,
 | 
				
			||||||
 | 
					          child: InkWell(
 | 
				
			||||||
 | 
					            borderRadius: const BorderRadius.all(Radius.circular(8)),
 | 
				
			||||||
 | 
					            onTap: () => onPick(sticker),
 | 
				
			||||||
 | 
					            child: ClipRRect(
 | 
				
			||||||
 | 
					              borderRadius: const BorderRadius.all(Radius.circular(8)),
 | 
				
			||||||
 | 
					              child: DecoratedBox(
 | 
				
			||||||
 | 
					                decoration: BoxDecoration(
 | 
				
			||||||
 | 
					                  color: Theme.of(context).colorScheme.surfaceContainer,
 | 
				
			||||||
 | 
					                  borderRadius: const BorderRadius.all(Radius.circular(8)),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                child: AspectRatio(
 | 
				
			||||||
 | 
					                  aspectRatio: 1,
 | 
				
			||||||
 | 
					                  child: CloudImageWidget(
 | 
				
			||||||
 | 
					                    fileId: sticker.imageId,
 | 
				
			||||||
 | 
					                    fit: BoxFit.contain,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Helper to show sticker picker as an anchored popover near the trigger.
 | 
				
			||||||
 | 
					/// Provide the button's BuildContext (typically from the onPressed closure).
 | 
				
			||||||
 | 
					/// Fallbacks to dialog if overlay cannot be found (e.g., during tests).
 | 
				
			||||||
 | 
					Future<void> showStickerPickerPopover(
 | 
				
			||||||
 | 
					  BuildContext context,
 | 
				
			||||||
 | 
					  Offset offset, {
 | 
				
			||||||
 | 
					  required void Function(String placeholder) onPick,
 | 
				
			||||||
 | 
					}) async {
 | 
				
			||||||
 | 
					  // Use flutter_popup_card to present the anchored popup near trigger.
 | 
				
			||||||
 | 
					  await showPopupCard<void>(
 | 
				
			||||||
 | 
					    context: context,
 | 
				
			||||||
 | 
					    offset: offset,
 | 
				
			||||||
 | 
					    alignment: Alignment.topLeft,
 | 
				
			||||||
 | 
					    dimBackground: true,
 | 
				
			||||||
 | 
					    builder:
 | 
				
			||||||
 | 
					        (ctx) => SizedBox(
 | 
				
			||||||
 | 
					          width: math.min(480, MediaQuery.of(context).size.width * 0.9),
 | 
				
			||||||
 | 
					          height: 480,
 | 
				
			||||||
 | 
					          child: ProviderScope(
 | 
				
			||||||
 | 
					            parent: ProviderScope.containerOf(context),
 | 
				
			||||||
 | 
					            child: StickerPicker(
 | 
				
			||||||
 | 
					              onPick: (ph) {
 | 
				
			||||||
 | 
					                onPick(ph);
 | 
				
			||||||
 | 
					                Navigator.of(ctx).maybePop();
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								lib/widgets/stickers/picker.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/widgets/stickers/picker.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'picker.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// RiverpodGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String _$myStickerPacksHash() => r'1e19832e8ab1cb139ad18aebfa5aebdf4fdea499';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Fetch user-added sticker packs (with stickers) from API:
 | 
				
			||||||
 | 
					/// GET /sphere/stickers/me
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Copied from [myStickerPacks].
 | 
				
			||||||
 | 
					@ProviderFor(myStickerPacks)
 | 
				
			||||||
 | 
					final myStickerPacksProvider =
 | 
				
			||||||
 | 
					    AutoDisposeFutureProvider<List<SnStickerPack>>.internal(
 | 
				
			||||||
 | 
					      myStickerPacks,
 | 
				
			||||||
 | 
					      name: r'myStickerPacksProvider',
 | 
				
			||||||
 | 
					      debugGetCreateSourceHash:
 | 
				
			||||||
 | 
					          const bool.fromEnvironment('dart.vm.product')
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : _$myStickerPacksHash,
 | 
				
			||||||
 | 
					      dependencies: null,
 | 
				
			||||||
 | 
					      allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
 | 
					// ignore: unused_element
 | 
				
			||||||
 | 
					typedef MyStickerPacksRef = AutoDisposeFutureProviderRef<List<SnStickerPack>>;
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
		Reference in New Issue
	
	Block a user