From d06df3d278522acfa619b55248e1feda24ee9e67 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 4 Jan 2025 21:26:28 +0800 Subject: [PATCH] :sparkles: Stickers --- api/Paperclip/Activate Boost.bru | 6 +- .../Stickers/Create Sticker Pack.bru | 19 + api/Paperclip/Stickers/Create Sticker.bru | 20 + api/Passport/Developer Notify All Users.bru | 6 +- api/collection.bru | 7 + lib/controllers/post_write_controller.dart | 6 +- lib/main.dart | 2 + lib/providers/sn_sticker.dart | 38 + lib/providers/sticker.dart | 3 - lib/types/attachment.dart | 36 + lib/types/attachment.freezed.dart | 735 ++++++++++++++++++ lib/types/attachment.g.dart | 63 ++ lib/widgets/markdown_content.dart | 60 ++ lib/widgets/post/post_item.dart | 1 + pubspec.lock | 36 +- 15 files changed, 1004 insertions(+), 34 deletions(-) create mode 100644 api/Paperclip/Stickers/Create Sticker Pack.bru create mode 100644 api/Paperclip/Stickers/Create Sticker.bru create mode 100644 api/collection.bru create mode 100644 lib/providers/sn_sticker.dart delete mode 100644 lib/providers/sticker.dart diff --git a/api/Paperclip/Activate Boost.bru b/api/Paperclip/Activate Boost.bru index 72d4959..772cc28 100644 --- a/api/Paperclip/Activate Boost.bru +++ b/api/Paperclip/Activate Boost.bru @@ -7,11 +7,7 @@ meta { post { url: {{endpoint}}/cgi/uc/boosts/1/activate body: none - auth: bearer -} - -auth:bearer { - token: {{atk}} + auth: inherit } body:json { diff --git a/api/Paperclip/Stickers/Create Sticker Pack.bru b/api/Paperclip/Stickers/Create Sticker Pack.bru new file mode 100644 index 0000000..008bf03 --- /dev/null +++ b/api/Paperclip/Stickers/Create Sticker Pack.bru @@ -0,0 +1,19 @@ +meta { + name: Create Sticker Pack + type: http + seq: 1 +} + +post { + url: {{endpoint}}/cgi/uc/stickers/packs + body: json + auth: inherit +} + +body:json { + { + "prefix": "cat", + "name": "Solar Network full of Cats!", + "description": "The sticker packs is full of stickers which related with cats!" + } +} diff --git a/api/Paperclip/Stickers/Create Sticker.bru b/api/Paperclip/Stickers/Create Sticker.bru new file mode 100644 index 0000000..61a9079 --- /dev/null +++ b/api/Paperclip/Stickers/Create Sticker.bru @@ -0,0 +1,20 @@ +meta { + name: Create Sticker + type: http + seq: 2 +} + +post { + url: {{endpoint}}/cgi/uc/stickers + body: json + auth: inherit +} + +body:json { + { + "alias": "AteChip", + "name": "Cat ate chips", + "attachment_id": "d0b692cc64054463", + "pack_id": 2 + } +} diff --git a/api/Passport/Developer Notify All Users.bru b/api/Passport/Developer Notify All Users.bru index 0609e3b..52c4455 100644 --- a/api/Passport/Developer Notify All Users.bru +++ b/api/Passport/Developer Notify All Users.bru @@ -7,11 +7,7 @@ meta { post { url: {{endpoint}}/cgi/id/dev/notify/all body: json - auth: bearer -} - -auth:bearer { - token: {{atk}} + auth: inherit } body:json { diff --git a/api/collection.bru b/api/collection.bru new file mode 100644 index 0000000..ff5eaae --- /dev/null +++ b/api/collection.bru @@ -0,0 +1,7 @@ +auth { + mode: bearer +} + +auth:bearer { + token: {{atk}} +} diff --git a/lib/controllers/post_write_controller.dart b/lib/controllers/post_write_controller.dart index d15c43e..302e156 100644 --- a/lib/controllers/post_write_controller.dart +++ b/lib/controllers/post_write_controller.dart @@ -627,9 +627,9 @@ class PostWriteController extends ChangeNotifier { descriptionController.clear(); contentController.clear(); aliasController.clear(); - tags.clear(); - categories.clear(); - attachments.clear(); + tags = List.empty(growable: true); + categories = List.empty(growable: true); + attachments = List.empty(growable: true); editingPost = null; replyingPost = null; repostingPost = null; diff --git a/lib/main.dart b/lib/main.dart index f8e1368..b25c550 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,7 @@ import 'package:surface/providers/post.dart'; import 'package:surface/providers/relationship.dart'; import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/sn_sticker.dart'; import 'package:surface/providers/special_day.dart'; import 'package:surface/providers/theme.dart'; import 'package:surface/providers/user_directory.dart'; @@ -144,6 +145,7 @@ class SolianApp extends StatelessWidget { Provider(create: (ctx) => SnPostContentProvider(ctx)), Provider(create: (ctx) => SnRelationshipProvider(ctx)), Provider(create: (ctx) => SnLinkPreviewProvider(ctx)), + Provider(create: (ctx) => SnStickerProvider(ctx)), ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)), ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)), diff --git a/lib/providers/sn_sticker.dart b/lib/providers/sn_sticker.dart new file mode 100644 index 0000000..bde22e4 --- /dev/null +++ b/lib/providers/sn_sticker.dart @@ -0,0 +1,38 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/types/attachment.dart'; + +class SnStickerProvider { + late final SnNetworkProvider _sn; + final Map _cache = {}; + + SnStickerProvider(BuildContext context) { + _sn = context.read(); + } + + bool hasNotSticker(String alias) { + return _cache.containsKey(alias) && _cache[alias] == null; + } + + Future lookupSticker(String alias) async { + if (_cache.containsKey(alias)) { + return _cache[alias]; + } + + try { + final resp = await _sn.client.get('/cgi/uc/stickers/lookup/$alias'); + final sticker = SnSticker.fromJson(resp.data); + _cache[alias] = sticker; + + return sticker; + } catch (err) { + _cache[alias] = null; + log('[Sticker] Failed to lookup sticker $alias: $err'); + } + + return null; + } +} diff --git a/lib/providers/sticker.dart b/lib/providers/sticker.dart deleted file mode 100644 index aa80d0b..0000000 --- a/lib/providers/sticker.dart +++ /dev/null @@ -1,3 +0,0 @@ -class StickerProvider { - -} \ No newline at end of file diff --git a/lib/types/attachment.dart b/lib/types/attachment.dart index e8a43be..c47da1f 100644 --- a/lib/types/attachment.dart +++ b/lib/types/attachment.dart @@ -141,3 +141,39 @@ class SnAttachmentBoost with _$SnAttachmentBoost { factory SnAttachmentBoost.fromJson(Map json) => _$SnAttachmentBoostFromJson(json); } + +@freezed +class SnSticker with _$SnSticker { + const factory SnSticker({ + required int id, + required DateTime createdAt, + required DateTime updatedAt, + required DateTime? deletedAt, + required String alias, + required String name, + required int attachmentId, + required SnAttachment attachment, + required int packId, + required SnStickerPack pack, + required int accountId, + }) = _SnSticker; + + factory SnSticker.fromJson(Map json) => _$SnStickerFromJson(json); +} + +@freezed +class SnStickerPack with _$SnStickerPack { + const factory SnStickerPack({ + required int id, + required DateTime createdAt, + required DateTime updatedAt, + required DateTime? deletedAt, + required String prefix, + required String name, + required String description, + required List? stickers, + required int accountId, + }) = _SnStickerPack; + + factory SnStickerPack.fromJson(Map json) => _$SnStickerPackFromJson(json); +} diff --git a/lib/types/attachment.freezed.dart b/lib/types/attachment.freezed.dart index 1894796..65db707 100644 --- a/lib/types/attachment.freezed.dart +++ b/lib/types/attachment.freezed.dart @@ -2272,3 +2272,738 @@ abstract class _SnAttachmentBoost implements SnAttachmentBoost { _$$SnAttachmentBoostImplCopyWith<_$SnAttachmentBoostImpl> get copyWith => throw _privateConstructorUsedError; } + +SnSticker _$SnStickerFromJson(Map json) { + return _SnSticker.fromJson(json); +} + +/// @nodoc +mixin _$SnSticker { + int get id => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + DateTime? get deletedAt => throw _privateConstructorUsedError; + String get alias => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + int get attachmentId => throw _privateConstructorUsedError; + SnAttachment get attachment => throw _privateConstructorUsedError; + int get packId => throw _privateConstructorUsedError; + SnStickerPack get pack => throw _privateConstructorUsedError; + int get accountId => throw _privateConstructorUsedError; + + /// Serializes this SnSticker to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnSticker + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnStickerCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnStickerCopyWith<$Res> { + factory $SnStickerCopyWith(SnSticker value, $Res Function(SnSticker) then) = + _$SnStickerCopyWithImpl<$Res, SnSticker>; + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + String alias, + String name, + int attachmentId, + SnAttachment attachment, + int packId, + SnStickerPack pack, + int accountId}); + + $SnAttachmentCopyWith<$Res> get attachment; + $SnStickerPackCopyWith<$Res> get pack; +} + +/// @nodoc +class _$SnStickerCopyWithImpl<$Res, $Val extends SnSticker> + implements $SnStickerCopyWith<$Res> { + _$SnStickerCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnSticker + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? alias = null, + Object? name = null, + Object? attachmentId = null, + Object? attachment = null, + Object? packId = null, + Object? pack = null, + Object? accountId = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + alias: null == alias + ? _value.alias + : alias // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + attachmentId: null == attachmentId + ? _value.attachmentId + : attachmentId // ignore: cast_nullable_to_non_nullable + as int, + attachment: null == attachment + ? _value.attachment + : attachment // ignore: cast_nullable_to_non_nullable + as SnAttachment, + packId: null == packId + ? _value.packId + : packId // ignore: cast_nullable_to_non_nullable + as int, + pack: null == pack + ? _value.pack + : pack // ignore: cast_nullable_to_non_nullable + as SnStickerPack, + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } + + /// Create a copy of SnSticker + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SnAttachmentCopyWith<$Res> get attachment { + return $SnAttachmentCopyWith<$Res>(_value.attachment, (value) { + return _then(_value.copyWith(attachment: value) as $Val); + }); + } + + /// Create a copy of SnSticker + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SnStickerPackCopyWith<$Res> get pack { + return $SnStickerPackCopyWith<$Res>(_value.pack, (value) { + return _then(_value.copyWith(pack: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SnStickerImplCopyWith<$Res> + implements $SnStickerCopyWith<$Res> { + factory _$$SnStickerImplCopyWith( + _$SnStickerImpl value, $Res Function(_$SnStickerImpl) then) = + __$$SnStickerImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + String alias, + String name, + int attachmentId, + SnAttachment attachment, + int packId, + SnStickerPack pack, + int accountId}); + + @override + $SnAttachmentCopyWith<$Res> get attachment; + @override + $SnStickerPackCopyWith<$Res> get pack; +} + +/// @nodoc +class __$$SnStickerImplCopyWithImpl<$Res> + extends _$SnStickerCopyWithImpl<$Res, _$SnStickerImpl> + implements _$$SnStickerImplCopyWith<$Res> { + __$$SnStickerImplCopyWithImpl( + _$SnStickerImpl _value, $Res Function(_$SnStickerImpl) _then) + : super(_value, _then); + + /// Create a copy of SnSticker + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? alias = null, + Object? name = null, + Object? attachmentId = null, + Object? attachment = null, + Object? packId = null, + Object? pack = null, + Object? accountId = null, + }) { + return _then(_$SnStickerImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + alias: null == alias + ? _value.alias + : alias // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + attachmentId: null == attachmentId + ? _value.attachmentId + : attachmentId // ignore: cast_nullable_to_non_nullable + as int, + attachment: null == attachment + ? _value.attachment + : attachment // ignore: cast_nullable_to_non_nullable + as SnAttachment, + packId: null == packId + ? _value.packId + : packId // ignore: cast_nullable_to_non_nullable + as int, + pack: null == pack + ? _value.pack + : pack // ignore: cast_nullable_to_non_nullable + as SnStickerPack, + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnStickerImpl implements _SnSticker { + const _$SnStickerImpl( + {required this.id, + required this.createdAt, + required this.updatedAt, + required this.deletedAt, + required this.alias, + required this.name, + required this.attachmentId, + required this.attachment, + required this.packId, + required this.pack, + required this.accountId}); + + factory _$SnStickerImpl.fromJson(Map json) => + _$$SnStickerImplFromJson(json); + + @override + final int id; + @override + final DateTime createdAt; + @override + final DateTime updatedAt; + @override + final DateTime? deletedAt; + @override + final String alias; + @override + final String name; + @override + final int attachmentId; + @override + final SnAttachment attachment; + @override + final int packId; + @override + final SnStickerPack pack; + @override + final int accountId; + + @override + String toString() { + return 'SnSticker(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, attachmentId: $attachmentId, attachment: $attachment, packId: $packId, pack: $pack, accountId: $accountId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnStickerImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && + (identical(other.alias, alias) || other.alias == alias) && + (identical(other.name, name) || other.name == name) && + (identical(other.attachmentId, attachmentId) || + other.attachmentId == attachmentId) && + (identical(other.attachment, attachment) || + other.attachment == attachment) && + (identical(other.packId, packId) || other.packId == packId) && + (identical(other.pack, pack) || other.pack == pack) && + (identical(other.accountId, accountId) || + other.accountId == accountId)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + createdAt, + updatedAt, + deletedAt, + alias, + name, + attachmentId, + attachment, + packId, + pack, + accountId); + + /// Create a copy of SnSticker + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnStickerImplCopyWith<_$SnStickerImpl> get copyWith => + __$$SnStickerImplCopyWithImpl<_$SnStickerImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SnStickerImplToJson( + this, + ); + } +} + +abstract class _SnSticker implements SnSticker { + const factory _SnSticker( + {required final int id, + required final DateTime createdAt, + required final DateTime updatedAt, + required final DateTime? deletedAt, + required final String alias, + required final String name, + required final int attachmentId, + required final SnAttachment attachment, + required final int packId, + required final SnStickerPack pack, + required final int accountId}) = _$SnStickerImpl; + + factory _SnSticker.fromJson(Map json) = + _$SnStickerImpl.fromJson; + + @override + int get id; + @override + DateTime get createdAt; + @override + DateTime get updatedAt; + @override + DateTime? get deletedAt; + @override + String get alias; + @override + String get name; + @override + int get attachmentId; + @override + SnAttachment get attachment; + @override + int get packId; + @override + SnStickerPack get pack; + @override + int get accountId; + + /// Create a copy of SnSticker + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnStickerImplCopyWith<_$SnStickerImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SnStickerPack _$SnStickerPackFromJson(Map json) { + return _SnStickerPack.fromJson(json); +} + +/// @nodoc +mixin _$SnStickerPack { + int get id => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + DateTime? get deletedAt => throw _privateConstructorUsedError; + String get prefix => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + List? get stickers => throw _privateConstructorUsedError; + int get accountId => throw _privateConstructorUsedError; + + /// Serializes this SnStickerPack to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SnStickerPack + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SnStickerPackCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SnStickerPackCopyWith<$Res> { + factory $SnStickerPackCopyWith( + SnStickerPack value, $Res Function(SnStickerPack) then) = + _$SnStickerPackCopyWithImpl<$Res, SnStickerPack>; + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + String prefix, + String name, + String description, + List? stickers, + int accountId}); +} + +/// @nodoc +class _$SnStickerPackCopyWithImpl<$Res, $Val extends SnStickerPack> + implements $SnStickerPackCopyWith<$Res> { + _$SnStickerPackCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SnStickerPack + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? prefix = null, + Object? name = null, + Object? description = null, + Object? stickers = freezed, + Object? accountId = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + prefix: null == prefix + ? _value.prefix + : prefix // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + stickers: freezed == stickers + ? _value.stickers + : stickers // ignore: cast_nullable_to_non_nullable + as List?, + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SnStickerPackImplCopyWith<$Res> + implements $SnStickerPackCopyWith<$Res> { + factory _$$SnStickerPackImplCopyWith( + _$SnStickerPackImpl value, $Res Function(_$SnStickerPackImpl) then) = + __$$SnStickerPackImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + DateTime createdAt, + DateTime updatedAt, + DateTime? deletedAt, + String prefix, + String name, + String description, + List? stickers, + int accountId}); +} + +/// @nodoc +class __$$SnStickerPackImplCopyWithImpl<$Res> + extends _$SnStickerPackCopyWithImpl<$Res, _$SnStickerPackImpl> + implements _$$SnStickerPackImplCopyWith<$Res> { + __$$SnStickerPackImplCopyWithImpl( + _$SnStickerPackImpl _value, $Res Function(_$SnStickerPackImpl) _then) + : super(_value, _then); + + /// Create a copy of SnStickerPack + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? deletedAt = freezed, + Object? prefix = null, + Object? name = null, + Object? description = null, + Object? stickers = freezed, + Object? accountId = null, + }) { + return _then(_$SnStickerPackImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + deletedAt: freezed == deletedAt + ? _value.deletedAt + : deletedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + prefix: null == prefix + ? _value.prefix + : prefix // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + stickers: freezed == stickers + ? _value._stickers + : stickers // ignore: cast_nullable_to_non_nullable + as List?, + accountId: null == accountId + ? _value.accountId + : accountId // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SnStickerPackImpl implements _SnStickerPack { + const _$SnStickerPackImpl( + {required this.id, + required this.createdAt, + required this.updatedAt, + required this.deletedAt, + required this.prefix, + required this.name, + required this.description, + required final List? stickers, + required this.accountId}) + : _stickers = stickers; + + factory _$SnStickerPackImpl.fromJson(Map json) => + _$$SnStickerPackImplFromJson(json); + + @override + final int id; + @override + final DateTime createdAt; + @override + final DateTime updatedAt; + @override + final DateTime? deletedAt; + @override + final String prefix; + @override + final String name; + @override + final String description; + final List? _stickers; + @override + List? get stickers { + final value = _stickers; + if (value == null) return null; + if (_stickers is EqualUnmodifiableListView) return _stickers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final int accountId; + + @override + String toString() { + return 'SnStickerPack(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, prefix: $prefix, name: $name, description: $description, stickers: $stickers, accountId: $accountId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SnStickerPackImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + (identical(other.deletedAt, deletedAt) || + other.deletedAt == deletedAt) && + (identical(other.prefix, prefix) || other.prefix == prefix) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality().equals(other._stickers, _stickers) && + (identical(other.accountId, accountId) || + other.accountId == accountId)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + createdAt, + updatedAt, + deletedAt, + prefix, + name, + description, + const DeepCollectionEquality().hash(_stickers), + accountId); + + /// Create a copy of SnStickerPack + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith => + __$$SnStickerPackImplCopyWithImpl<_$SnStickerPackImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SnStickerPackImplToJson( + this, + ); + } +} + +abstract class _SnStickerPack implements SnStickerPack { + const factory _SnStickerPack( + {required final int id, + required final DateTime createdAt, + required final DateTime updatedAt, + required final DateTime? deletedAt, + required final String prefix, + required final String name, + required final String description, + required final List? stickers, + required final int accountId}) = _$SnStickerPackImpl; + + factory _SnStickerPack.fromJson(Map json) = + _$SnStickerPackImpl.fromJson; + + @override + int get id; + @override + DateTime get createdAt; + @override + DateTime get updatedAt; + @override + DateTime? get deletedAt; + @override + String get prefix; + @override + String get name; + @override + String get description; + @override + List? get stickers; + @override + int get accountId; + + /// Create a copy of SnStickerPack + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/types/attachment.g.dart b/lib/types/attachment.g.dart index d50f9aa..1337a46 100644 --- a/lib/types/attachment.g.dart +++ b/lib/types/attachment.g.dart @@ -218,3 +218,66 @@ Map _$$SnAttachmentBoostImplToJson( 'attachment': instance.attachment.toJson(), 'account': instance.account, }; + +_$SnStickerImpl _$$SnStickerImplFromJson(Map json) => + _$SnStickerImpl( + id: (json['id'] as num).toInt(), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + alias: json['alias'] as String, + name: json['name'] as String, + attachmentId: (json['attachment_id'] as num).toInt(), + attachment: + SnAttachment.fromJson(json['attachment'] as Map), + packId: (json['pack_id'] as num).toInt(), + pack: SnStickerPack.fromJson(json['pack'] as Map), + accountId: (json['account_id'] as num).toInt(), + ); + +Map _$$SnStickerImplToJson(_$SnStickerImpl instance) => + { + 'id': instance.id, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'alias': instance.alias, + 'name': instance.name, + 'attachment_id': instance.attachmentId, + 'attachment': instance.attachment.toJson(), + 'pack_id': instance.packId, + 'pack': instance.pack.toJson(), + 'account_id': instance.accountId, + }; + +_$SnStickerPackImpl _$$SnStickerPackImplFromJson(Map json) => + _$SnStickerPackImpl( + id: (json['id'] as num).toInt(), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + prefix: json['prefix'] as String, + name: json['name'] as String, + description: json['description'] as String, + stickers: (json['stickers'] as List?) + ?.map((e) => SnSticker.fromJson(e as Map)) + .toList(), + accountId: (json['account_id'] as num).toInt(), + ); + +Map _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) => + { + 'id': instance.id, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'prefix': instance.prefix, + 'name': instance.name, + 'description': instance.description, + 'stickers': instance.stickers?.map((e) => e.toJson()).toList(), + 'account_id': instance.accountId, + }; diff --git a/lib/widgets/markdown_content.dart b/lib/widgets/markdown_content.dart index 6eb0e02..6917b4e 100644 --- a/lib/widgets/markdown_content.dart +++ b/lib/widgets/markdown_content.dart @@ -6,7 +6,10 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:markdown/markdown.dart' as markdown; +import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/sn_sticker.dart'; import 'package:surface/types/attachment.dart'; import 'package:surface/widgets/attachment/attachment_item.dart'; import 'package:surface/widgets/universal_image.dart'; @@ -21,6 +24,7 @@ class MarkdownTextContent extends StatelessWidget { final String content; final bool isSelectable; final bool isAutoWarp; + final bool isEnlargeSticker; final TextScaler? textScaler; final List? attachments; @@ -29,6 +33,7 @@ class MarkdownTextContent extends StatelessWidget { required this.content, this.isSelectable = false, this.isAutoWarp = false, + this.isEnlargeSticker = false, this.textScaler, this.attachments, }); @@ -78,6 +83,7 @@ class MarkdownTextContent extends StatelessWidget { [ if (isAutoWarp) markdown.LineBreakSyntax(), _UserNameCardInlineSyntax(), + _CustomEmoteInlineSyntax(context), markdown.AutolinkSyntax(), markdown.AutolinkExtensionSyntax(), markdown.CodeSyntax(), @@ -108,6 +114,38 @@ class MarkdownTextContent extends StatelessWidget { if (url.startsWith('solink://')) { final segments = url.replaceFirst('solink://', '').split('/'); switch (segments[0]) { + case 'stickers': + final alias = segments[1]; + final st = context.read(); + final sn = context.read(); + final double size = isEnlargeSticker ? 128 : 32; + return Container( + width: size, + height: size, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8)), + color: Theme.of(context).colorScheme.surfaceContainerHigh, + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: FutureBuilder( + future: st.lookupSticker(alias), + builder: (context, snapshot) { + if (snapshot.hasData) { + return UniversalImage( + sn.getAttachmentUrl(snapshot.data!.attachment.rid), + fit: BoxFit.cover, + width: size, + height: size, + cacheHeight: size, + cacheWidth: size, + ); + } + return const SizedBox.shrink(); + }, + ), + ), + ); case 'attachments': final attachment = attachments?.firstWhere( (ele) => ele?.rid == segments[1], @@ -194,6 +232,28 @@ class _UserNameCardInlineSyntax extends markdown.InlineSyntax { } } +class _CustomEmoteInlineSyntax extends markdown.InlineSyntax { + final BuildContext context; + + _CustomEmoteInlineSyntax(this.context) : super(r':([-\w]+):'); + + @override + bool onMatch(markdown.InlineParser parser, Match match) { + final SnStickerProvider st = context.read(); + final alias = match[1]!.toUpperCase(); + if (st.hasNotSticker(alias)) { + parser.advanceBy(1); + return false; + } + + final element = markdown.Element.empty('img'); + element.attributes['src'] = 'solink://stickers/$alias'; + parser.addNode(element); + + return true; + } +} + class _MarkdownTextCodeElement extends MarkdownElementBuilder { @override Widget? visitElementAfter( diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 424cdde..0e5d957 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -876,6 +876,7 @@ class _PostContentBody extends StatelessWidget { if (data.body['content'] == null) return const SizedBox.shrink(); return MarkdownTextContent( isSelectable: isSelectable, + isEnlargeSticker: true, textScaler: isEnlarge ? TextScaler.linear(1.1) : null, content: data.body['content'], attachments: data.preload?.attachments, diff --git a/pubspec.lock b/pubspec.lock index 01d82d8..d1e6787 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -708,10 +708,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: "1152ab0067ca5a2ebeb862fe0a762057202cceb22b7e62692dcbabf6483891bb" + sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -766,10 +766,10 @@ packages: dependency: "direct main" description: name: flutter_webrtc - sha256: "3efe9828f19a07d29a51a726759ad0c70a840d231548a1c7d0332075a94db1df" + sha256: e82ffd0d0b79621c5554eed73509d7f5bd286d57fef29a573846785c65237fb1 url: "https://pub.dev" source: hosted - version: "0.12.5+hotfix.1" + version: "0.12.5+hotfix.2" freezed: dependency: "direct dev" description: @@ -902,10 +902,10 @@ packages: dependency: transitive description: name: http_parser - sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.2" icons_launcher: dependency: "direct dev" description: @@ -950,10 +950,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" url: "https://pub.dev" source: hosted - version: "0.8.12+1" + version: "0.8.12+2" image_picker_linux: dependency: transitive description: @@ -1086,10 +1086,10 @@ packages: dependency: "direct main" description: name: livekit_client - sha256: "7cdeb3eaeec7fb70a4cf88d9caabccbef9e3bd5f0b23c086320bc5c9acb2770b" + sha256: a19bcf8640b45e0730b1e3e3e78be7882dad680c6ebe8ae75294fd8d4612450d url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.4+hotfix.2" logging: dependency: transitive description: @@ -1142,10 +1142,10 @@ packages: dependency: "direct main" description: name: material_symbols_icons - sha256: "64404f47f8e0a9d20478468e5decef867a688660bad7173adcd20418d7f892c9" + sha256: "89aac72d25dd49303f71b3b1e70f8374791846729365b25bebc2a2531e5b86cd" url: "https://pub.dev" source: hosted - version: "4.2801.0" + version: "4.2801.1" media_kit: dependency: "direct main" description: @@ -1646,10 +1646,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93" + sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_android: dependency: transitive description: @@ -1795,10 +1795,10 @@ packages: dependency: transitive description: name: sqflite_darwin - sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474" + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.1+1" sqflite_platform_interface: dependency: transitive description: @@ -2131,10 +2131,10 @@ packages: dependency: transitive description: name: win32 - sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" + sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.10.0" win32_registry: dependency: transitive description: