Create boost

This commit is contained in:
LittleSheep 2024-12-29 02:13:31 +08:00
parent 03943a7138
commit d4fbdd397e
14 changed files with 956 additions and 41 deletions

View File

@ -0,0 +1,30 @@
meta {
name: Activate Boost
type: http
seq: 1
}
post {
url: {{endpoint}}/cgi/uc/boosts/1/activate
body: none
auth: bearer
}
auth:bearer {
token: {{atk}}
}
body:json {
{
"client_id": "{{third_client_id}}",
"client_secret":"{{third_client_tk}}",
"type": "general",
"subject": "Merry Christmas!",
"subtitle": "一条来自 Solar Network 团队的信息",
"content": "今天是 12 月 25 日 (UTC+8),小羊祝您圣诞快乐 🎄",
"metadata": {
"image": "6EqsYQwmFRCkbmhR"
},
"priority": 10
}
}

View File

@ -309,7 +309,15 @@
"attachmentCompressQualityHint": "Solar Network doesn't prevent you from uploading large files, high resolution, high bitrate videos. But for your network conditions, we suggest you choose a suitable compression quality.",
"attachmentUploaded": "Uploaded",
"attachmentPending": "Pending",
"attachmentCopyCompressed": "Has compressed copy",
"attachmentCopyCompressed": "Copy compressed",
"attachmentGotBoosted": "Boosted",
"attachmentBoost": "Boost",
"attachmentCreateBoost": "Create Boost",
"attachmentBoostHint": "Boost is a feature that allows you to upload attachments to a server closer to your audience or a faster content network. This feature is currently in beta and is subject to change. It's all free for now, you can feel free to try, you will get notified when the pricing plan changed.",
"attachmentDestinationRegion": "Destination Region",
"attachmentDestinationRegionAPAC": "Asia Pacific",
"attachmentDestinationRegionNGB": "Ning Bo, China, Zhejiang",
"attachmentDestinationRegionHKG": "Hong Kong",
"notification": "Notification",
"notificationUnreadCount": {
"zero": "All notifications read",

View File

@ -308,6 +308,14 @@
"attachmentUploaded": "已上传",
"attachmentPending": "未上传",
"attachmentCopyCompressed": "有压缩副本",
"attachmentGotBoosted": "有加速传递",
"attachmentBoost": "加速包",
"attachmentCreateBoost": "加速传递",
"attachmentBoostHint": "加速传递允许您将附件上传到更近的受众或更快的内容网络。该功能目前处于 Beta 阶段。该功能限时免费,当有价格计划更改时,您将会被通知。",
"attachmentDestinationRegion": "目标节点",
"attachmentDestinationRegionAPAC": "亚太地区",
"attachmentDestinationRegionNGB": "中国 · 浙江 · 宁波",
"attachmentDestinationRegionHKG": "香港",
"notification": "通知",
"notificationUnreadCount": {
"zero": "无未读通知",

View File

@ -308,6 +308,14 @@
"attachmentUploaded": "已上傳",
"attachmentPending": "未上傳",
"attachmentCopyCompressed": "有壓縮副本",
"attachmentGotBoosted": "有加速傳遞",
"attachmentBoost": "加速包",
"attachmentCreateBoost": "加速傳遞",
"attachmentBoostHint": "加速傳遞允許您將附件上傳到更近的受眾或更快的內容網絡。該功能目前處於 Beta 階段。該功能限時免費,當有價格計劃更改時,您將會被通知。",
"attachmentDestinationRegion": "目標節點",
"attachmentDestinationRegionAPAC": "亞太地區",
"attachmentDestinationRegionNGB": "中國 · 浙江 · 寧波",
"attachmentDestinationRegionHKG": "香港",
"notification": "通知",
"notificationUnreadCount": {
"zero": "無未讀通知",

View File

@ -308,6 +308,14 @@
"attachmentUploaded": "已上傳",
"attachmentPending": "未上傳",
"attachmentCopyCompressed": "有壓縮副本",
"attachmentGotBoosted": "有加速傳遞",
"attachmentBoost": "加速包",
"attachmentCreateBoost": "加速傳遞",
"attachmentBoostHint": "加速傳遞允許您將附件上傳到更近的受眾或更快的內容網絡。該功能目前處於 Beta 階段。該功能限時免費,當有價格計劃更改時,您將會被通知。",
"attachmentDestinationRegion": "目標節點",
"attachmentDestinationRegionAPAC": "亞太地區",
"attachmentDestinationRegionNGB": "中國 · 浙江 · 寧波",
"attachmentDestinationRegionHKG": "香港",
"notification": "通知",
"notificationUnreadCount": {
"zero": "無未讀通知",

View File

@ -231,7 +231,8 @@ class PostWriteController extends ChangeNotifier {
}
}
Future<SnAttachment> _uploadAttachment(BuildContext context, PostWriteMedia media, {bool isCompressed = false}) async {
Future<SnAttachment> _uploadAttachment(BuildContext context, PostWriteMedia media,
{bool isCompressed = false}) async {
final attach = context.read<SnAttachmentProvider>();
final place = await attach.chunkedUploadInitialize(
@ -242,7 +243,7 @@ class PostWriteController extends ChangeNotifier {
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
);
final item = await attach.chunkedUploadParts(
var item = await attach.chunkedUploadParts(
media.toFile()!,
place.$1,
place.$2,
@ -253,9 +254,13 @@ class PostWriteController extends ChangeNotifier {
);
if (media.type == SnMediaType.video && !isCompressed && context.mounted) {
final compressedAttachment = await _tryCompressVideoCopy(context, media);
if (compressedAttachment != null) {
await attach.updateOne(item, compressedId: compressedAttachment.id);
try {
final compressedAttachment = await _tryCompressVideoCopy(context, media);
if (compressedAttachment != null) {
item = await attach.updateOne(item, compressedId: compressedAttachment.id);
}
} catch (err) {
if (context.mounted) context.showErrorDialog(err);
}
}
@ -336,7 +341,7 @@ class PostWriteController extends ChangeNotifier {
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
);
final item = await attach.chunkedUploadParts(
var item = await attach.chunkedUploadParts(
media.toFile()!,
place.$1,
place.$2,
@ -347,11 +352,13 @@ class PostWriteController extends ChangeNotifier {
},
);
if (media.type == SnMediaType.video && context.mounted) {
try {
final compressedAttachment = await _tryCompressVideoCopy(context, media);
if (compressedAttachment != null) {
await attach.updateOne(item, compressedId: compressedAttachment.id);
item = await attach.updateOne(item, compressedId: compressedAttachment.id);
}
} catch (err) {
if (context.mounted) context.showErrorDialog(err);
}
progress = (i + 1) / attachments.length * kAttachmentProgressWeight;

View File

@ -86,6 +86,7 @@ class SnAttachmentProvider {
Map<String, dynamic>? metadata, {
String? mimetype,
Function(double progress)? onProgress,
bool analyzeNow = false,
}) async {
final filePayload = MultipartFile.fromBytes(data, filename: filename);
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename;
@ -108,6 +109,7 @@ class SnAttachmentProvider {
final resp = await _sn.client.post(
'/cgi/uc/attachments',
data: formData,
queryParameters: {'analyzeNow': analyzeNow},
onSendProgress: (count, total) {
if (onProgress != null) {
onProgress(count / total);

View File

@ -38,12 +38,13 @@ class SnAttachment with _$SnAttachment {
required SnAttachment? ref,
required int? refId,
required SnAttachmentPool? pool,
required int poolId,
required int? poolId,
required int accountId,
int? thumbnailId,
SnAttachment? thumbnail,
int? compressedId,
SnAttachment? compressed,
@Default([]) List<SnAttachmentBoost> boosts,
@Default({}) Map<String, dynamic> usermeta,
@Default({}) Map<String, dynamic> metadata,
}) = _SnAttachment;
@ -110,3 +111,33 @@ class SnAttachmentPool with _$SnAttachmentPool {
factory SnAttachmentPool.fromJson(Map<String, Object?> json) => _$SnAttachmentPoolFromJson(json);
}
@freezed
class SnAttachmentDestination with _$SnAttachmentDestination {
const factory SnAttachmentDestination({
@Default(0) int id,
required String type,
required String label,
required String region,
required bool isBoost,
}) = _SnAttachmentDestination;
factory SnAttachmentDestination.fromJson(Map<String, Object?> json) => _$SnAttachmentDestinationFromJson(json);
}
@freezed
class SnAttachmentBoost with _$SnAttachmentBoost {
const factory SnAttachmentBoost({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required int status,
required int destination,
required int attachmentId,
required SnAttachment attachment,
required int account,
}) = _SnAttachmentBoost;
factory SnAttachmentBoost.fromJson(Map<String, Object?> json) => _$SnAttachmentBoostFromJson(json);
}

View File

@ -42,12 +42,13 @@ mixin _$SnAttachment {
SnAttachment? get ref => throw _privateConstructorUsedError;
int? get refId => throw _privateConstructorUsedError;
SnAttachmentPool? get pool => throw _privateConstructorUsedError;
int get poolId => throw _privateConstructorUsedError;
int? get poolId => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError;
int? get thumbnailId => throw _privateConstructorUsedError;
SnAttachment? get thumbnail => throw _privateConstructorUsedError;
int? get compressedId => throw _privateConstructorUsedError;
SnAttachment? get compressed => throw _privateConstructorUsedError;
List<SnAttachmentBoost> get boosts => throw _privateConstructorUsedError;
Map<String, dynamic> get usermeta => throw _privateConstructorUsedError;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
@ -90,12 +91,13 @@ abstract class $SnAttachmentCopyWith<$Res> {
SnAttachment? ref,
int? refId,
SnAttachmentPool? pool,
int poolId,
int? poolId,
int accountId,
int? thumbnailId,
SnAttachment? thumbnail,
int? compressedId,
SnAttachment? compressed,
List<SnAttachmentBoost> boosts,
Map<String, dynamic> usermeta,
Map<String, dynamic> metadata});
@ -142,12 +144,13 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
Object? ref = freezed,
Object? refId = freezed,
Object? pool = freezed,
Object? poolId = null,
Object? poolId = freezed,
Object? accountId = null,
Object? thumbnailId = freezed,
Object? thumbnail = freezed,
Object? compressedId = freezed,
Object? compressed = freezed,
Object? boosts = null,
Object? usermeta = null,
Object? metadata = null,
}) {
@ -240,10 +243,10 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
? _value.pool
: pool // ignore: cast_nullable_to_non_nullable
as SnAttachmentPool?,
poolId: null == poolId
poolId: freezed == poolId
? _value.poolId
: poolId // ignore: cast_nullable_to_non_nullable
as int,
as int?,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
@ -264,6 +267,10 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
? _value.compressed
: compressed // ignore: cast_nullable_to_non_nullable
as SnAttachment?,
boosts: null == boosts
? _value.boosts
: boosts // ignore: cast_nullable_to_non_nullable
as List<SnAttachmentBoost>,
usermeta: null == usermeta
? _value.usermeta
: usermeta // ignore: cast_nullable_to_non_nullable
@ -363,12 +370,13 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
SnAttachment? ref,
int? refId,
SnAttachmentPool? pool,
int poolId,
int? poolId,
int accountId,
int? thumbnailId,
SnAttachment? thumbnail,
int? compressedId,
SnAttachment? compressed,
List<SnAttachmentBoost> boosts,
Map<String, dynamic> usermeta,
Map<String, dynamic> metadata});
@ -417,12 +425,13 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
Object? ref = freezed,
Object? refId = freezed,
Object? pool = freezed,
Object? poolId = null,
Object? poolId = freezed,
Object? accountId = null,
Object? thumbnailId = freezed,
Object? thumbnail = freezed,
Object? compressedId = freezed,
Object? compressed = freezed,
Object? boosts = null,
Object? usermeta = null,
Object? metadata = null,
}) {
@ -515,10 +524,10 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
? _value.pool
: pool // ignore: cast_nullable_to_non_nullable
as SnAttachmentPool?,
poolId: null == poolId
poolId: freezed == poolId
? _value.poolId
: poolId // ignore: cast_nullable_to_non_nullable
as int,
as int?,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
@ -539,6 +548,10 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
? _value.compressed
: compressed // ignore: cast_nullable_to_non_nullable
as SnAttachment?,
boosts: null == boosts
? _value._boosts
: boosts // ignore: cast_nullable_to_non_nullable
as List<SnAttachmentBoost>,
usermeta: null == usermeta
? _value._usermeta
: usermeta // ignore: cast_nullable_to_non_nullable
@ -583,9 +596,11 @@ class _$SnAttachmentImpl extends _SnAttachment {
this.thumbnail,
this.compressedId,
this.compressed,
final List<SnAttachmentBoost> boosts = const [],
final Map<String, dynamic> usermeta = const {},
final Map<String, dynamic> metadata = const {}})
: _usermeta = usermeta,
: _boosts = boosts,
_usermeta = usermeta,
_metadata = metadata,
super._();
@ -639,7 +654,7 @@ class _$SnAttachmentImpl extends _SnAttachment {
@override
final SnAttachmentPool? pool;
@override
final int poolId;
final int? poolId;
@override
final int accountId;
@override
@ -650,6 +665,15 @@ class _$SnAttachmentImpl extends _SnAttachment {
final int? compressedId;
@override
final SnAttachment? compressed;
final List<SnAttachmentBoost> _boosts;
@override
@JsonKey()
List<SnAttachmentBoost> get boosts {
if (_boosts is EqualUnmodifiableListView) return _boosts;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_boosts);
}
final Map<String, dynamic> _usermeta;
@override
@JsonKey()
@ -670,7 +694,7 @@ class _$SnAttachmentImpl extends _SnAttachment {
@override
String toString() {
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, contentRating: $contentRating, qualityRating: $qualityRating, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isSelfRef: $isSelfRef, isIndexable: $isIndexable, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, thumbnailId: $thumbnailId, thumbnail: $thumbnail, compressedId: $compressedId, compressed: $compressed, usermeta: $usermeta, metadata: $metadata)';
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, contentRating: $contentRating, qualityRating: $qualityRating, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isSelfRef: $isSelfRef, isIndexable: $isIndexable, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, thumbnailId: $thumbnailId, thumbnail: $thumbnail, compressedId: $compressedId, compressed: $compressed, boosts: $boosts, usermeta: $usermeta, metadata: $metadata)';
}
@override
@ -723,6 +747,7 @@ class _$SnAttachmentImpl extends _SnAttachment {
other.compressedId == compressedId) &&
(identical(other.compressed, compressed) ||
other.compressed == compressed) &&
const DeepCollectionEquality().equals(other._boosts, _boosts) &&
const DeepCollectionEquality().equals(other._usermeta, _usermeta) &&
const DeepCollectionEquality().equals(other._metadata, _metadata));
}
@ -759,6 +784,7 @@ class _$SnAttachmentImpl extends _SnAttachment {
thumbnail,
compressedId,
compressed,
const DeepCollectionEquality().hash(_boosts),
const DeepCollectionEquality().hash(_usermeta),
const DeepCollectionEquality().hash(_metadata)
]);
@ -803,12 +829,13 @@ abstract class _SnAttachment extends SnAttachment {
required final SnAttachment? ref,
required final int? refId,
required final SnAttachmentPool? pool,
required final int poolId,
required final int? poolId,
required final int accountId,
final int? thumbnailId,
final SnAttachment? thumbnail,
final int? compressedId,
final SnAttachment? compressed,
final List<SnAttachmentBoost> boosts,
final Map<String, dynamic> usermeta,
final Map<String, dynamic> metadata}) = _$SnAttachmentImpl;
const _SnAttachment._() : super._();
@ -861,7 +888,7 @@ abstract class _SnAttachment extends SnAttachment {
@override
SnAttachmentPool? get pool;
@override
int get poolId;
int? get poolId;
@override
int get accountId;
@override
@ -873,6 +900,8 @@ abstract class _SnAttachment extends SnAttachment {
@override
SnAttachment? get compressed;
@override
List<SnAttachmentBoost> get boosts;
@override
Map<String, dynamic> get usermeta;
@override
Map<String, dynamic> get metadata;
@ -1676,3 +1705,570 @@ abstract class _SnAttachmentPool implements SnAttachmentPool {
_$$SnAttachmentPoolImplCopyWith<_$SnAttachmentPoolImpl> get copyWith =>
throw _privateConstructorUsedError;
}
SnAttachmentDestination _$SnAttachmentDestinationFromJson(
Map<String, dynamic> json) {
return _SnAttachmentDestination.fromJson(json);
}
/// @nodoc
mixin _$SnAttachmentDestination {
int get id => throw _privateConstructorUsedError;
String get type => throw _privateConstructorUsedError;
String get label => throw _privateConstructorUsedError;
String get region => throw _privateConstructorUsedError;
bool get isBoost => throw _privateConstructorUsedError;
/// Serializes this SnAttachmentDestination to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnAttachmentDestination
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnAttachmentDestinationCopyWith<SnAttachmentDestination> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnAttachmentDestinationCopyWith<$Res> {
factory $SnAttachmentDestinationCopyWith(SnAttachmentDestination value,
$Res Function(SnAttachmentDestination) then) =
_$SnAttachmentDestinationCopyWithImpl<$Res, SnAttachmentDestination>;
@useResult
$Res call({int id, String type, String label, String region, bool isBoost});
}
/// @nodoc
class _$SnAttachmentDestinationCopyWithImpl<$Res,
$Val extends SnAttachmentDestination>
implements $SnAttachmentDestinationCopyWith<$Res> {
_$SnAttachmentDestinationCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnAttachmentDestination
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? type = null,
Object? label = null,
Object? region = null,
Object? isBoost = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String,
label: null == label
? _value.label
: label // ignore: cast_nullable_to_non_nullable
as String,
region: null == region
? _value.region
: region // ignore: cast_nullable_to_non_nullable
as String,
isBoost: null == isBoost
? _value.isBoost
: isBoost // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$SnAttachmentDestinationImplCopyWith<$Res>
implements $SnAttachmentDestinationCopyWith<$Res> {
factory _$$SnAttachmentDestinationImplCopyWith(
_$SnAttachmentDestinationImpl value,
$Res Function(_$SnAttachmentDestinationImpl) then) =
__$$SnAttachmentDestinationImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int id, String type, String label, String region, bool isBoost});
}
/// @nodoc
class __$$SnAttachmentDestinationImplCopyWithImpl<$Res>
extends _$SnAttachmentDestinationCopyWithImpl<$Res,
_$SnAttachmentDestinationImpl>
implements _$$SnAttachmentDestinationImplCopyWith<$Res> {
__$$SnAttachmentDestinationImplCopyWithImpl(
_$SnAttachmentDestinationImpl _value,
$Res Function(_$SnAttachmentDestinationImpl) _then)
: super(_value, _then);
/// Create a copy of SnAttachmentDestination
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? type = null,
Object? label = null,
Object? region = null,
Object? isBoost = null,
}) {
return _then(_$SnAttachmentDestinationImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String,
label: null == label
? _value.label
: label // ignore: cast_nullable_to_non_nullable
as String,
region: null == region
? _value.region
: region // ignore: cast_nullable_to_non_nullable
as String,
isBoost: null == isBoost
? _value.isBoost
: isBoost // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnAttachmentDestinationImpl implements _SnAttachmentDestination {
const _$SnAttachmentDestinationImpl(
{this.id = 0,
required this.type,
required this.label,
required this.region,
required this.isBoost});
factory _$SnAttachmentDestinationImpl.fromJson(Map<String, dynamic> json) =>
_$$SnAttachmentDestinationImplFromJson(json);
@override
@JsonKey()
final int id;
@override
final String type;
@override
final String label;
@override
final String region;
@override
final bool isBoost;
@override
String toString() {
return 'SnAttachmentDestination(id: $id, type: $type, label: $label, region: $region, isBoost: $isBoost)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnAttachmentDestinationImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.label, label) || other.label == label) &&
(identical(other.region, region) || other.region == region) &&
(identical(other.isBoost, isBoost) || other.isBoost == isBoost));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, id, type, label, region, isBoost);
/// Create a copy of SnAttachmentDestination
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnAttachmentDestinationImplCopyWith<_$SnAttachmentDestinationImpl>
get copyWith => __$$SnAttachmentDestinationImplCopyWithImpl<
_$SnAttachmentDestinationImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnAttachmentDestinationImplToJson(
this,
);
}
}
abstract class _SnAttachmentDestination implements SnAttachmentDestination {
const factory _SnAttachmentDestination(
{final int id,
required final String type,
required final String label,
required final String region,
required final bool isBoost}) = _$SnAttachmentDestinationImpl;
factory _SnAttachmentDestination.fromJson(Map<String, dynamic> json) =
_$SnAttachmentDestinationImpl.fromJson;
@override
int get id;
@override
String get type;
@override
String get label;
@override
String get region;
@override
bool get isBoost;
/// Create a copy of SnAttachmentDestination
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnAttachmentDestinationImplCopyWith<_$SnAttachmentDestinationImpl>
get copyWith => throw _privateConstructorUsedError;
}
SnAttachmentBoost _$SnAttachmentBoostFromJson(Map<String, dynamic> json) {
return _SnAttachmentBoost.fromJson(json);
}
/// @nodoc
mixin _$SnAttachmentBoost {
int get id => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
DateTime? get deletedAt => throw _privateConstructorUsedError;
int get status => throw _privateConstructorUsedError;
int get destination => throw _privateConstructorUsedError;
int get attachmentId => throw _privateConstructorUsedError;
SnAttachment get attachment => throw _privateConstructorUsedError;
int get account => throw _privateConstructorUsedError;
/// Serializes this SnAttachmentBoost to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnAttachmentBoost
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnAttachmentBoostCopyWith<SnAttachmentBoost> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnAttachmentBoostCopyWith<$Res> {
factory $SnAttachmentBoostCopyWith(
SnAttachmentBoost value, $Res Function(SnAttachmentBoost) then) =
_$SnAttachmentBoostCopyWithImpl<$Res, SnAttachmentBoost>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
int status,
int destination,
int attachmentId,
SnAttachment attachment,
int account});
$SnAttachmentCopyWith<$Res> get attachment;
}
/// @nodoc
class _$SnAttachmentBoostCopyWithImpl<$Res, $Val extends SnAttachmentBoost>
implements $SnAttachmentBoostCopyWith<$Res> {
_$SnAttachmentBoostCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnAttachmentBoost
/// 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? status = null,
Object? destination = null,
Object? attachmentId = null,
Object? attachment = null,
Object? account = 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?,
status: null == status
? _value.status
: status // ignore: cast_nullable_to_non_nullable
as int,
destination: null == destination
? _value.destination
: destination // ignore: cast_nullable_to_non_nullable
as int,
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,
account: null == account
? _value.account
: account // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
/// Create a copy of SnAttachmentBoost
/// 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);
});
}
}
/// @nodoc
abstract class _$$SnAttachmentBoostImplCopyWith<$Res>
implements $SnAttachmentBoostCopyWith<$Res> {
factory _$$SnAttachmentBoostImplCopyWith(_$SnAttachmentBoostImpl value,
$Res Function(_$SnAttachmentBoostImpl) then) =
__$$SnAttachmentBoostImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
int status,
int destination,
int attachmentId,
SnAttachment attachment,
int account});
@override
$SnAttachmentCopyWith<$Res> get attachment;
}
/// @nodoc
class __$$SnAttachmentBoostImplCopyWithImpl<$Res>
extends _$SnAttachmentBoostCopyWithImpl<$Res, _$SnAttachmentBoostImpl>
implements _$$SnAttachmentBoostImplCopyWith<$Res> {
__$$SnAttachmentBoostImplCopyWithImpl(_$SnAttachmentBoostImpl _value,
$Res Function(_$SnAttachmentBoostImpl) _then)
: super(_value, _then);
/// Create a copy of SnAttachmentBoost
/// 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? status = null,
Object? destination = null,
Object? attachmentId = null,
Object? attachment = null,
Object? account = null,
}) {
return _then(_$SnAttachmentBoostImpl(
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?,
status: null == status
? _value.status
: status // ignore: cast_nullable_to_non_nullable
as int,
destination: null == destination
? _value.destination
: destination // ignore: cast_nullable_to_non_nullable
as int,
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,
account: null == account
? _value.account
: account // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnAttachmentBoostImpl implements _SnAttachmentBoost {
const _$SnAttachmentBoostImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.status,
required this.destination,
required this.attachmentId,
required this.attachment,
required this.account});
factory _$SnAttachmentBoostImpl.fromJson(Map<String, dynamic> json) =>
_$$SnAttachmentBoostImplFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final int status;
@override
final int destination;
@override
final int attachmentId;
@override
final SnAttachment attachment;
@override
final int account;
@override
String toString() {
return 'SnAttachmentBoost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, status: $status, destination: $destination, attachmentId: $attachmentId, attachment: $attachment, account: $account)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnAttachmentBoostImpl &&
(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.status, status) || other.status == status) &&
(identical(other.destination, destination) ||
other.destination == destination) &&
(identical(other.attachmentId, attachmentId) ||
other.attachmentId == attachmentId) &&
(identical(other.attachment, attachment) ||
other.attachment == attachment) &&
(identical(other.account, account) || other.account == account));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, status, destination, attachmentId, attachment, account);
/// Create a copy of SnAttachmentBoost
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnAttachmentBoostImplCopyWith<_$SnAttachmentBoostImpl> get copyWith =>
__$$SnAttachmentBoostImplCopyWithImpl<_$SnAttachmentBoostImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnAttachmentBoostImplToJson(
this,
);
}
}
abstract class _SnAttachmentBoost implements SnAttachmentBoost {
const factory _SnAttachmentBoost(
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final int status,
required final int destination,
required final int attachmentId,
required final SnAttachment attachment,
required final int account}) = _$SnAttachmentBoostImpl;
factory _SnAttachmentBoost.fromJson(Map<String, dynamic> json) =
_$SnAttachmentBoostImpl.fromJson;
@override
int get id;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
DateTime? get deletedAt;
@override
int get status;
@override
int get destination;
@override
int get attachmentId;
@override
SnAttachment get attachment;
@override
int get account;
/// Create a copy of SnAttachmentBoost
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnAttachmentBoostImplCopyWith<_$SnAttachmentBoostImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -38,7 +38,7 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
pool: json['pool'] == null
? null
: SnAttachmentPool.fromJson(json['pool'] as Map<String, dynamic>),
poolId: (json['pool_id'] as num).toInt(),
poolId: (json['pool_id'] as num?)?.toInt(),
accountId: (json['account_id'] as num).toInt(),
thumbnailId: (json['thumbnail_id'] as num?)?.toInt(),
thumbnail: json['thumbnail'] == null
@ -48,6 +48,11 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
compressed: json['compressed'] == null
? null
: SnAttachment.fromJson(json['compressed'] as Map<String, dynamic>),
boosts: (json['boosts'] as List<dynamic>?)
?.map(
(e) => SnAttachmentBoost.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
usermeta: json['usermeta'] as Map<String, dynamic>? ?? const {},
metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
);
@ -82,6 +87,7 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'thumbnail': instance.thumbnail?.toJson(),
'compressed_id': instance.compressedId,
'compressed': instance.compressed?.toJson(),
'boosts': instance.boosts.map((e) => e.toJson()).toList(),
'usermeta': instance.usermeta,
'metadata': instance.metadata,
};
@ -161,3 +167,54 @@ Map<String, dynamic> _$$SnAttachmentPoolImplToJson(
'config': instance.config,
'account_id': instance.accountId,
};
_$SnAttachmentDestinationImpl _$$SnAttachmentDestinationImplFromJson(
Map<String, dynamic> json) =>
_$SnAttachmentDestinationImpl(
id: (json['id'] as num?)?.toInt() ?? 0,
type: json['type'] as String,
label: json['label'] as String,
region: json['region'] as String,
isBoost: json['is_boost'] as bool,
);
Map<String, dynamic> _$$SnAttachmentDestinationImplToJson(
_$SnAttachmentDestinationImpl instance) =>
<String, dynamic>{
'id': instance.id,
'type': instance.type,
'label': instance.label,
'region': instance.region,
'is_boost': instance.isBoost,
};
_$SnAttachmentBoostImpl _$$SnAttachmentBoostImplFromJson(
Map<String, dynamic> json) =>
_$SnAttachmentBoostImpl(
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),
status: (json['status'] as num).toInt(),
destination: (json['destination'] as num).toInt(),
attachmentId: (json['attachment_id'] as num).toInt(),
attachment:
SnAttachment.fromJson(json['attachment'] as Map<String, dynamic>),
account: (json['account'] as num).toInt(),
);
Map<String, dynamic> _$$SnAttachmentBoostImplToJson(
_$SnAttachmentBoostImpl instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'status': instance.status,
'destination': instance.destination,
'attachment_id': instance.attachmentId,
'attachment': instance.attachment.toJson(),
'account': instance.account,
};

View File

@ -10,8 +10,8 @@ import 'package:surface/widgets/dialog.dart';
class AttachmentInputDialog extends StatefulWidget {
final String? title;
const AttachmentInputDialog({super.key, required this.title});
final bool? analyzeNow;
const AttachmentInputDialog({super.key, required this.title, this.analyzeNow = false});
@override
State<AttachmentInputDialog> createState() => _AttachmentInputDialogState();
@ -53,6 +53,7 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
_thumbnailFile!.path,
'interactive',
null,
analyzeNow: widget.analyzeNow ?? false,
);
if (!mounted) return;
Navigator.pop(context, attachment);
@ -77,7 +78,8 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
controller: _randomIdController,
decoration: InputDecoration(
labelText: 'fieldAttachmentRandomId'.tr(),
border: const OutlineInputBorder(),
border: const UnderlineInputBorder(),
isDense: true,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),

View File

@ -0,0 +1,120 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/dialog.dart';
class PendingAttachmentBoostDialog extends StatefulWidget {
final PostWriteMedia media;
const PendingAttachmentBoostDialog({super.key, required this.media});
@override
State<PendingAttachmentBoostDialog> createState() => _PendingAttachmentBoostDialogState();
}
class _PendingAttachmentBoostDialogState extends State<PendingAttachmentBoostDialog> {
List<SnAttachmentDestination>? _regions;
SnAttachmentDestination? _selectedRegion;
Future<void> _fetchRegions() async {
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/uc/destinations');
setState(() {
_regions = List<SnAttachmentDestination>.from(
resp.data?.map((e) => SnAttachmentDestination.fromJson(e)) ?? [],
).cast<SnAttachmentDestination>().where((ele) => ele.isBoost).toList();
});
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
}
bool _isBusy = false;
Future<void> _performAction() async {
if (_isBusy) return;
if (_selectedRegion == null) return;
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.post('/cgi/uc/boosts', data: {
'attachment': widget.media.attachment!.id,
'destination': _selectedRegion!.id,
});
if (!mounted) return;
Navigator.pop(context, SnAttachmentBoost.fromJson(resp.data));
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchRegions();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('attachmentBoost').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('attachmentBoostHint').tr(),
const Gap(16),
Text('attachmentDestinationRegion').tr().fontSize(18),
const Gap(8),
Card(
child: _regions == null
? const CircularProgressIndicator().center().padding(all: 16)
: Column(
children: _regions!.map(
(ele) {
return RadioListTile(
title: Text(ele.label).tr(),
subtitle: Text(
'attachmentDestinationRegion${ele.region}'.trExists()
? 'attachmentDestinationRegion${ele.region}'.tr()
: ele.region,
),
selected: _selectedRegion == ele,
value: ele,
groupValue: _selectedRegion,
onChanged: (value) {
if (value != null) setState(() => _selectedRegion = value);
},
);
},
).toList(),
),
),
],
),
actions: [
TextButton(
onPressed: _isBusy ? null : () {
Navigator.pop(context);
},
child: Text('dialogDismiss'.tr()),
),
TextButton(
onPressed: _isBusy ? null : () => _performAction(),
child: Text('dialogConfirm'.tr()),
),
],
);
}
}

View File

@ -18,7 +18,6 @@ import 'package:screenshot/screenshot.dart';
import 'package:share_plus/share_plus.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/link_preview.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/post.dart';
@ -255,6 +254,10 @@ class PostItem extends StatelessWidget {
maxHeight: 560,
listPadding: const EdgeInsets.symmetric(horizontal: 12),
),
if (data.body['content'] != null)
LinkPreviewWidget(
text: data.body['content'],
).padding(horizontal: 4),
Container(
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
child: Column(
@ -336,10 +339,6 @@ class PostShareImageWidget extends StatelessWidget {
data: data.preload!.attachments!,
isFlatted: true,
).padding(horizontal: 16, bottom: 8),
if (data.body['content'] != null)
LinkPreviewWidget(
text: data.body['content'],
).padding(horizontal: 4),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -21,6 +21,7 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_input.dart';
import 'package:surface/widgets/attachment/attachment_zoom.dart';
import 'package:surface/widgets/attachment/pending_attachment_boost.dart';
import 'package:surface/widgets/context_menu.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/universal_image.dart';
@ -93,18 +94,23 @@ class PostMediaPendingList extends StatelessWidget {
context: context,
builder: (context) => AttachmentInputDialog(
title: 'attachmentSetThumbnail'.tr(),
analyzeNow: true,
),
);
if (thumbnail == null) return;
if (!context.mounted) return;
final attach = context.read<SnAttachmentProvider>();
final newAttach = await attach.updateOne(
attachments[idx].attachment!,
thumbnailId: thumbnail.id,
);
onUpdate!(idx, PostWriteMedia(newAttach));
try {
final attach = context.read<SnAttachmentProvider>();
final newAttach = await attach.updateOne(
attachments[idx].attachment!,
thumbnailId: thumbnail.id,
);
onUpdate!(idx, PostWriteMedia(newAttach));
} catch (err) {
if (!context.mounted) return;
context.showErrorDialog(err);
}
}
Future<void> _deleteAttachment(BuildContext context, int idx) async {
@ -124,6 +130,23 @@ class PostMediaPendingList extends StatelessWidget {
}
}
Future<void> _createBoost(BuildContext context, int idx) async {
if (attachments[idx].attachment == null) return;
final result = await showDialog<SnAttachmentBoost?>(
context: context,
builder: (context) => PendingAttachmentBoostDialog(media: attachments[idx]),
);
if (result == null) return;
final newAttach = attachments[idx].attachment!.copyWith(
boosts: [...attachments[idx].attachment!.boosts, result],
);
final newMedia = PostWriteMedia(newAttach);
onUpdate!(idx, newMedia);
}
Future<void> _compressVideo(BuildContext context, int idx) async {
final result = await showDialog<PostWriteMedia?>(
context: context,
@ -146,6 +169,14 @@ class PostMediaPendingList extends StatelessWidget {
_compressVideo(context, idx);
},
),
if (media.attachment != null)
MenuItem(
label: 'attachmentBoost'.tr(),
icon: Symbols.bolt,
onSelected: () {
_createBoost(context, idx);
},
),
if (media.attachment != null && media.type == SnMediaType.video)
MenuItem(
label: 'attachmentSetThumbnail'.tr(),
@ -389,11 +420,19 @@ class _PostMediaPendingItem extends StatelessWidget {
],
),
),
if (media.attachment != null && media.attachment!.compressedId != null)
if (media.attachment != null && media.attachment!.boosts.isNotEmpty)
Row(
children: [
Icon(Symbols.bolt, size: 16),
const Gap(4),
Text('attachmentGotBoosted').tr().fontSize(13),
],
),
if (media.attachment != null && media.attachment!.compressedId != null)
Row(
children: [
Icon(Symbols.compress, size: 16),
const Gap(4),
Text('attachmentCopyCompressed').tr().fontSize(13),
],
),