Post with attachment

This commit is contained in:
LittleSheep 2024-11-10 16:41:11 +08:00
parent 302691f557
commit ef51948fad
12 changed files with 191 additions and 50 deletions

View File

@ -30,6 +30,7 @@
"create": "Create", "create": "Create",
"preview": "Preview", "preview": "Preview",
"loading": "Loading...", "loading": "Loading...",
"delete": "Delete",
"fieldUsername": "Username", "fieldUsername": "Username",
"fieldNickname": "Nickname", "fieldNickname": "Nickname",
"fieldEmail": "Email address", "fieldEmail": "Email address",

View File

@ -30,6 +30,7 @@
"apply": "应用", "apply": "应用",
"create": "创建", "create": "创建",
"preview": "预览", "preview": "预览",
"delete": "删除",
"fieldUsername": "用户名", "fieldUsername": "用户名",
"fieldNickname": "显示名", "fieldNickname": "显示名",
"fieldEmail": "电子邮箱地址", "fieldEmail": "电子邮箱地址",

View File

@ -84,7 +84,12 @@ class _ExploreScreenState extends State<ExploreScreen> {
distance: 75, distance: 75,
type: ExpandableFabType.up, type: ExpandableFabType.up,
childrenAnimation: ExpandableFabAnimation.none, childrenAnimation: ExpandableFabAnimation.none,
overlayStyle: ExpandableFabOverlayStyle(blur: 10), overlayStyle: ExpandableFabOverlayStyle(
color: Theme.of(context)
.colorScheme
.surface
.withAlpha((255 * 0.5).round()),
),
openButtonBuilder: RotateFloatingActionButtonBuilder( openButtonBuilder: RotateFloatingActionButtonBuilder(
child: const Icon(Symbols.add, size: 28), child: const Icon(Symbols.add, size: 28),
fabSize: ExpandableFabSize.regular, fabSize: ExpandableFabSize.regular,

View File

@ -4,15 +4,19 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_media_pending_list.dart';
import 'package:surface/widgets/post/post_meta_editor.dart'; import 'package:surface/widgets/post/post_meta_editor.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:provider/provider.dart';
class PostEditorScreen extends StatefulWidget { class PostEditorScreen extends StatefulWidget {
final String mode; final String mode;
@ -33,6 +37,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
SnPublisher? _publisher; SnPublisher? _publisher;
List<SnPublisher>? _publishers; List<SnPublisher>? _publishers;
final List<XFile> _selectedMedia = List.empty(growable: true);
final List<SnAttachment> _attachments = List.empty(growable: true);
void _fetchPublishers() async { void _fetchPublishers() async {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/publishers'); final resp = await sn.client.get('/cgi/co/publishers');
@ -49,19 +56,41 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
final TextEditingController _contentController = TextEditingController(); final TextEditingController _contentController = TextEditingController();
double? _progress;
void _performAction() async { void _performAction() async {
if (_isBusy || _publisher == null) return; if (_isBusy || _publisher == null) return;
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final attach = context.read<SnAttachmentProvider>();
setState(() => _isBusy = true); setState(() => _isBusy = true);
// Uploading attachments
try {
for (final media in _selectedMedia) {
final place = await attach.chunkedUploadInitialize(
await media.length(),
media.name,
'interactive',
null,
);
final item = await attach.chunkedUploadParts(media, place.$1, place.$2);
_attachments.add(item);
}
} catch (err) {
context.showErrorDialog(err);
return;
}
// Finishing up
try { try {
await sn.client.post('/cgi/co/${widget.mode}', data: { await sn.client.post('/cgi/co/${widget.mode}', data: {
'publisher': _publisher!.id, 'publisher': _publisher!.id,
'content': _contentController.value.text, 'content': _contentController.value.text,
'title': _title, 'title': _title,
'description': _description, 'description': _description,
'attachments': _attachments.map((e) => e.rid).toList(),
}); });
Navigator.pop(context, true); Navigator.pop(context, true);
} catch (err) { } catch (err) {
@ -88,6 +117,15 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
}); });
} }
final _imagePicker = ImagePicker();
void _selectMedia() async {
final result = await _imagePicker.pickMultipleMedia();
if (result.isEmpty) return;
_selectedMedia.addAll(result);
setState(() {});
}
@override @override
void dispose() { void dispose() {
_contentController.dispose(); _contentController.dispose();
@ -248,13 +286,23 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
), ),
), ),
), ),
if (_selectedMedia.isNotEmpty)
PostMediaPendingList(
data: _selectedMedia,
onRemove: (idx) {
setState(() {
_selectedMedia.removeAt(idx);
});
},
).padding(bottom: 8),
Material( Material(
elevation: 2, elevation: 2,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (_isBusy) if (_isBusy)
const LinearProgressIndicator( LinearProgressIndicator(
value: _progress,
minHeight: 2, minHeight: 2,
), ),
Row( Row(
@ -268,7 +316,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
child: Row( child: Row(
children: [ children: [
IconButton( IconButton(
onPressed: () {}, onPressed: _isBusy ? null : _selectMedia,
icon: Icon( icon: Icon(
Symbols.add_photo_alternate, Symbols.add_photo_alternate,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,

View File

@ -21,7 +21,6 @@ class SnAttachment with _$SnAttachment {
required int refCount, required int refCount,
required dynamic fileChunks, required dynamic fileChunks,
required dynamic cleanedAt, required dynamic cleanedAt,
required Map<String, dynamic> metadata,
required bool isMature, required bool isMature,
required bool isAnalyzed, required bool isAnalyzed,
required bool isUploaded, required bool isUploaded,
@ -31,6 +30,7 @@ class SnAttachment with _$SnAttachment {
required SnAttachmentPool? pool, required SnAttachmentPool? pool,
required int poolId, required int poolId,
required int accountId, required int accountId,
@Default({}) Map<String, dynamic> metadata,
}) = _SnAttachment; }) = _SnAttachment;
factory SnAttachment.fromJson(Map<String, Object?> json) => factory SnAttachment.fromJson(Map<String, Object?> json) =>

View File

@ -35,7 +35,6 @@ mixin _$SnAttachment {
int get refCount => throw _privateConstructorUsedError; int get refCount => throw _privateConstructorUsedError;
dynamic get fileChunks => throw _privateConstructorUsedError; dynamic get fileChunks => throw _privateConstructorUsedError;
dynamic get cleanedAt => throw _privateConstructorUsedError; dynamic get cleanedAt => throw _privateConstructorUsedError;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
bool get isMature => throw _privateConstructorUsedError; bool get isMature => throw _privateConstructorUsedError;
bool get isAnalyzed => throw _privateConstructorUsedError; bool get isAnalyzed => throw _privateConstructorUsedError;
bool get isUploaded => throw _privateConstructorUsedError; bool get isUploaded => throw _privateConstructorUsedError;
@ -45,6 +44,7 @@ mixin _$SnAttachment {
SnAttachmentPool? get pool => throw _privateConstructorUsedError; SnAttachmentPool? get pool => throw _privateConstructorUsedError;
int get poolId => throw _privateConstructorUsedError; int get poolId => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError; int get accountId => throw _privateConstructorUsedError;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
/// Serializes this SnAttachment to a JSON map. /// Serializes this SnAttachment to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -78,7 +78,6 @@ abstract class $SnAttachmentCopyWith<$Res> {
int refCount, int refCount,
dynamic fileChunks, dynamic fileChunks,
dynamic cleanedAt, dynamic cleanedAt,
Map<String, dynamic> metadata,
bool isMature, bool isMature,
bool isAnalyzed, bool isAnalyzed,
bool isUploaded, bool isUploaded,
@ -87,7 +86,8 @@ abstract class $SnAttachmentCopyWith<$Res> {
dynamic refId, dynamic refId,
SnAttachmentPool? pool, SnAttachmentPool? pool,
int poolId, int poolId,
int accountId}); int accountId,
Map<String, dynamic> metadata});
$SnAttachmentPoolCopyWith<$Res>? get pool; $SnAttachmentPoolCopyWith<$Res>? get pool;
} }
@ -122,7 +122,6 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
Object? refCount = null, Object? refCount = null,
Object? fileChunks = freezed, Object? fileChunks = freezed,
Object? cleanedAt = freezed, Object? cleanedAt = freezed,
Object? metadata = null,
Object? isMature = null, Object? isMature = null,
Object? isAnalyzed = null, Object? isAnalyzed = null,
Object? isUploaded = null, Object? isUploaded = null,
@ -132,6 +131,7 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
Object? pool = freezed, Object? pool = freezed,
Object? poolId = null, Object? poolId = null,
Object? accountId = null, Object? accountId = null,
Object? metadata = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
id: null == id id: null == id
@ -194,10 +194,6 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
? _value.cleanedAt ? _value.cleanedAt
: cleanedAt // ignore: cast_nullable_to_non_nullable : cleanedAt // ignore: cast_nullable_to_non_nullable
as dynamic, as dynamic,
metadata: null == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
isMature: null == isMature isMature: null == isMature
? _value.isMature ? _value.isMature
: isMature // ignore: cast_nullable_to_non_nullable : isMature // ignore: cast_nullable_to_non_nullable
@ -234,6 +230,10 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
? _value.accountId ? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable : accountId // ignore: cast_nullable_to_non_nullable
as int, as int,
metadata: null == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
) as $Val); ) as $Val);
} }
@ -276,7 +276,6 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
int refCount, int refCount,
dynamic fileChunks, dynamic fileChunks,
dynamic cleanedAt, dynamic cleanedAt,
Map<String, dynamic> metadata,
bool isMature, bool isMature,
bool isAnalyzed, bool isAnalyzed,
bool isUploaded, bool isUploaded,
@ -285,7 +284,8 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
dynamic refId, dynamic refId,
SnAttachmentPool? pool, SnAttachmentPool? pool,
int poolId, int poolId,
int accountId}); int accountId,
Map<String, dynamic> metadata});
@override @override
$SnAttachmentPoolCopyWith<$Res>? get pool; $SnAttachmentPoolCopyWith<$Res>? get pool;
@ -319,7 +319,6 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
Object? refCount = null, Object? refCount = null,
Object? fileChunks = freezed, Object? fileChunks = freezed,
Object? cleanedAt = freezed, Object? cleanedAt = freezed,
Object? metadata = null,
Object? isMature = null, Object? isMature = null,
Object? isAnalyzed = null, Object? isAnalyzed = null,
Object? isUploaded = null, Object? isUploaded = null,
@ -329,6 +328,7 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
Object? pool = freezed, Object? pool = freezed,
Object? poolId = null, Object? poolId = null,
Object? accountId = null, Object? accountId = null,
Object? metadata = null,
}) { }) {
return _then(_$SnAttachmentImpl( return _then(_$SnAttachmentImpl(
id: null == id id: null == id
@ -391,10 +391,6 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
? _value.cleanedAt ? _value.cleanedAt
: cleanedAt // ignore: cast_nullable_to_non_nullable : cleanedAt // ignore: cast_nullable_to_non_nullable
as dynamic, as dynamic,
metadata: null == metadata
? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
isMature: null == isMature isMature: null == isMature
? _value.isMature ? _value.isMature
: isMature // ignore: cast_nullable_to_non_nullable : isMature // ignore: cast_nullable_to_non_nullable
@ -431,6 +427,10 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
? _value.accountId ? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable : accountId // ignore: cast_nullable_to_non_nullable
as int, as int,
metadata: null == metadata
? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
)); ));
} }
} }
@ -454,7 +454,6 @@ class _$SnAttachmentImpl implements _SnAttachment {
required this.refCount, required this.refCount,
required this.fileChunks, required this.fileChunks,
required this.cleanedAt, required this.cleanedAt,
required final Map<String, dynamic> metadata,
required this.isMature, required this.isMature,
required this.isAnalyzed, required this.isAnalyzed,
required this.isUploaded, required this.isUploaded,
@ -463,7 +462,8 @@ class _$SnAttachmentImpl implements _SnAttachment {
required this.refId, required this.refId,
required this.pool, required this.pool,
required this.poolId, required this.poolId,
required this.accountId}) required this.accountId,
final Map<String, dynamic> metadata = const {}})
: _metadata = metadata; : _metadata = metadata;
factory _$SnAttachmentImpl.fromJson(Map<String, dynamic> json) => factory _$SnAttachmentImpl.fromJson(Map<String, dynamic> json) =>
@ -499,14 +499,6 @@ class _$SnAttachmentImpl implements _SnAttachment {
final dynamic fileChunks; final dynamic fileChunks;
@override @override
final dynamic cleanedAt; final dynamic cleanedAt;
final Map<String, dynamic> _metadata;
@override
Map<String, dynamic> get metadata {
if (_metadata is EqualUnmodifiableMapView) return _metadata;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_metadata);
}
@override @override
final bool isMature; final bool isMature;
@override @override
@ -525,10 +517,18 @@ class _$SnAttachmentImpl implements _SnAttachment {
final int poolId; final int poolId;
@override @override
final int accountId; final int accountId;
final Map<String, dynamic> _metadata;
@override
@JsonKey()
Map<String, dynamic> get metadata {
if (_metadata is EqualUnmodifiableMapView) return _metadata;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_metadata);
}
@override @override
String toString() { 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, fileChunks: $fileChunks, cleanedAt: $cleanedAt, metadata: $metadata, isMature: $isMature, isAnalyzed: $isAnalyzed, isUploaded: $isUploaded, isSelfRef: $isSelfRef, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId)'; 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, fileChunks: $fileChunks, cleanedAt: $cleanedAt, isMature: $isMature, isAnalyzed: $isAnalyzed, isUploaded: $isUploaded, isSelfRef: $isSelfRef, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, metadata: $metadata)';
} }
@override @override
@ -557,7 +557,6 @@ class _$SnAttachmentImpl implements _SnAttachment {
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other.fileChunks, fileChunks) && .equals(other.fileChunks, fileChunks) &&
const DeepCollectionEquality().equals(other.cleanedAt, cleanedAt) && const DeepCollectionEquality().equals(other.cleanedAt, cleanedAt) &&
const DeepCollectionEquality().equals(other._metadata, _metadata) &&
(identical(other.isMature, isMature) || (identical(other.isMature, isMature) ||
other.isMature == isMature) && other.isMature == isMature) &&
(identical(other.isAnalyzed, isAnalyzed) || (identical(other.isAnalyzed, isAnalyzed) ||
@ -571,7 +570,8 @@ class _$SnAttachmentImpl implements _SnAttachment {
(identical(other.pool, pool) || other.pool == pool) && (identical(other.pool, pool) || other.pool == pool) &&
(identical(other.poolId, poolId) || other.poolId == poolId) && (identical(other.poolId, poolId) || other.poolId == poolId) &&
(identical(other.accountId, accountId) || (identical(other.accountId, accountId) ||
other.accountId == accountId)); other.accountId == accountId) &&
const DeepCollectionEquality().equals(other._metadata, _metadata));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -593,7 +593,6 @@ class _$SnAttachmentImpl implements _SnAttachment {
refCount, refCount,
const DeepCollectionEquality().hash(fileChunks), const DeepCollectionEquality().hash(fileChunks),
const DeepCollectionEquality().hash(cleanedAt), const DeepCollectionEquality().hash(cleanedAt),
const DeepCollectionEquality().hash(_metadata),
isMature, isMature,
isAnalyzed, isAnalyzed,
isUploaded, isUploaded,
@ -602,7 +601,8 @@ class _$SnAttachmentImpl implements _SnAttachment {
const DeepCollectionEquality().hash(refId), const DeepCollectionEquality().hash(refId),
pool, pool,
poolId, poolId,
accountId accountId,
const DeepCollectionEquality().hash(_metadata)
]); ]);
/// Create a copy of SnAttachment /// Create a copy of SnAttachment
@ -638,7 +638,6 @@ abstract class _SnAttachment implements SnAttachment {
required final int refCount, required final int refCount,
required final dynamic fileChunks, required final dynamic fileChunks,
required final dynamic cleanedAt, required final dynamic cleanedAt,
required final Map<String, dynamic> metadata,
required final bool isMature, required final bool isMature,
required final bool isAnalyzed, required final bool isAnalyzed,
required final bool isUploaded, required final bool isUploaded,
@ -647,7 +646,8 @@ abstract class _SnAttachment implements SnAttachment {
required final dynamic refId, required final dynamic refId,
required final SnAttachmentPool? pool, required final SnAttachmentPool? pool,
required final int poolId, required final int poolId,
required final int accountId}) = _$SnAttachmentImpl; required final int accountId,
final Map<String, dynamic> metadata}) = _$SnAttachmentImpl;
factory _SnAttachment.fromJson(Map<String, dynamic> json) = factory _SnAttachment.fromJson(Map<String, dynamic> json) =
_$SnAttachmentImpl.fromJson; _$SnAttachmentImpl.fromJson;
@ -683,8 +683,6 @@ abstract class _SnAttachment implements SnAttachment {
@override @override
dynamic get cleanedAt; dynamic get cleanedAt;
@override @override
Map<String, dynamic> get metadata;
@override
bool get isMature; bool get isMature;
@override @override
bool get isAnalyzed; bool get isAnalyzed;
@ -702,6 +700,8 @@ abstract class _SnAttachment implements SnAttachment {
int get poolId; int get poolId;
@override @override
int get accountId; int get accountId;
@override
Map<String, dynamic> get metadata;
/// Create a copy of SnAttachment /// Create a copy of SnAttachment
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.

View File

@ -23,7 +23,6 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
refCount: (json['ref_count'] as num).toInt(), refCount: (json['ref_count'] as num).toInt(),
fileChunks: json['file_chunks'], fileChunks: json['file_chunks'],
cleanedAt: json['cleaned_at'], cleanedAt: json['cleaned_at'],
metadata: json['metadata'] as Map<String, dynamic>,
isMature: json['is_mature'] as bool, isMature: json['is_mature'] as bool,
isAnalyzed: json['is_analyzed'] as bool, isAnalyzed: json['is_analyzed'] as bool,
isUploaded: json['is_uploaded'] as bool, isUploaded: json['is_uploaded'] as bool,
@ -35,6 +34,7 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
: SnAttachmentPool.fromJson(json['pool'] as Map<String, dynamic>), : 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(), accountId: (json['account_id'] as num).toInt(),
metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
); );
Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) => Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
@ -54,7 +54,6 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'ref_count': instance.refCount, 'ref_count': instance.refCount,
'file_chunks': instance.fileChunks, 'file_chunks': instance.fileChunks,
'cleaned_at': instance.cleanedAt, 'cleaned_at': instance.cleanedAt,
'metadata': instance.metadata,
'is_mature': instance.isMature, 'is_mature': instance.isMature,
'is_analyzed': instance.isAnalyzed, 'is_analyzed': instance.isAnalyzed,
'is_uploaded': instance.isUploaded, 'is_uploaded': instance.isUploaded,
@ -64,6 +63,7 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'pool': instance.pool?.toJson(), 'pool': instance.pool?.toJson(),
'pool_id': instance.poolId, 'pool_id': instance.poolId,
'account_id': instance.accountId, 'account_id': instance.accountId,
'metadata': instance.metadata,
}; };
_$SnAttachmentPoolImpl _$$SnAttachmentPoolImplFromJson( _$SnAttachmentPoolImpl _$$SnAttachmentPoolImplFromJson(

View File

@ -14,9 +14,9 @@ class AttachmentItem extends StatelessWidget {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
switch (tp) { switch (tp) {
case 'image': case 'image':
return AspectRatio( return UniversalImage(
aspectRatio: data.metadata['ratio']?.toDouble(), sn.getAttachmentUrl(data.rid),
child: UniversalImage(sn.getAttachmentUrl(data.rid)), fit: BoxFit.cover,
); );
default: default:
return const Placeholder(); return const Placeholder();

View File

@ -8,8 +8,12 @@ class AttachmentList extends StatelessWidget {
final List<SnAttachment> data; final List<SnAttachment> data;
final bool? bordered; final bool? bordered;
final double? maxListHeight; final double? maxListHeight;
const AttachmentList( const AttachmentList({
{super.key, required this.data, this.bordered, this.maxListHeight}); super.key,
required this.data,
this.bordered,
this.maxListHeight,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -37,14 +41,20 @@ class AttachmentList extends StatelessWidget {
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
const radius = BorderRadius.all(Radius.circular(8)); const radius = BorderRadius.all(Radius.circular(8));
return Container( return Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width - 20,
),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border(top: borderSide, bottom: borderSide), border: Border(top: borderSide, bottom: borderSide),
borderRadius: radius, borderRadius: radius,
), ),
child: AspectRatio(
aspectRatio: data[idx].metadata['ratio']?.toDouble() ?? 1,
child: ClipRRect( child: ClipRRect(
borderRadius: radius, borderRadius: radius,
child: AttachmentItem(data: data[idx]), child: AttachmentItem(data: data[idx]),
), ),
),
); );
}, },
separatorBuilder: (context, index) => const Gap(8), separatorBuilder: (context, index) => const Gap(8),

View File

@ -0,0 +1,67 @@
import 'dart:io';
import 'package:cross_file/cross_file.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_context_menu/flutter_context_menu.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
class PostMediaPendingList extends StatelessWidget {
final List<XFile> data;
final Function(int idx)? onRemove;
const PostMediaPendingList({
super.key,
required this.data,
this.onRemove,
});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxHeight: 120),
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8),
separatorBuilder: (context, index) => const Gap(8),
itemCount: data.length,
itemBuilder: (context, idx) {
final file = data[idx];
return ContextMenuRegion(
contextMenu: ContextMenu(
entries: [
if (onRemove != null)
MenuItem(
label: 'delete'.tr(),
icon: Symbols.delete,
onSelected: () {
onRemove!(idx);
},
),
],
),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 1,
child: kIsWeb
? Image.network(file.path, fit: BoxFit.cover)
: Image.file(File(file.path), fit: BoxFit.cover),
),
),
),
);
},
),
);
}
}

View File

@ -467,6 +467,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.1" version: "3.4.1"
flutter_context_menu:
dependency: "direct main"
description:
name: flutter_context_menu
sha256: "4bc1dc30ae5aa705ed99ebbeb875898c6341a6d092397a566fecd5184b392380"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
flutter_expandable_fab: flutter_expandable_fab:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -66,6 +66,7 @@ dependencies:
croppy: ^1.3.1 croppy: ^1.3.1
flutter_expandable_fab: ^2.3.0 flutter_expandable_fab: ^2.3.0
dropdown_button2: ^2.3.9 dropdown_button2: ^2.3.9
flutter_context_menu: ^0.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: