✨ Post with attachment
This commit is contained in:
parent
302691f557
commit
ef51948fad
@ -30,6 +30,7 @@
|
||||
"create": "Create",
|
||||
"preview": "Preview",
|
||||
"loading": "Loading...",
|
||||
"delete": "Delete",
|
||||
"fieldUsername": "Username",
|
||||
"fieldNickname": "Nickname",
|
||||
"fieldEmail": "Email address",
|
||||
|
@ -30,6 +30,7 @@
|
||||
"apply": "应用",
|
||||
"create": "创建",
|
||||
"preview": "预览",
|
||||
"delete": "删除",
|
||||
"fieldUsername": "用户名",
|
||||
"fieldNickname": "显示名",
|
||||
"fieldEmail": "电子邮箱地址",
|
||||
|
@ -84,7 +84,12 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
distance: 75,
|
||||
type: ExpandableFabType.up,
|
||||
childrenAnimation: ExpandableFabAnimation.none,
|
||||
overlayStyle: ExpandableFabOverlayStyle(blur: 10),
|
||||
overlayStyle: ExpandableFabOverlayStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withAlpha((255 * 0.5).round()),
|
||||
),
|
||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||
child: const Icon(Symbols.add, size: 28),
|
||||
fabSize: ExpandableFabSize.regular,
|
||||
|
@ -4,15 +4,19 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.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/types/attachment.dart';
|
||||
import 'package:surface/types/post.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/post/post_media_pending_list.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 {
|
||||
final String mode;
|
||||
@ -33,6 +37,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
SnPublisher? _publisher;
|
||||
List<SnPublisher>? _publishers;
|
||||
|
||||
final List<XFile> _selectedMedia = List.empty(growable: true);
|
||||
final List<SnAttachment> _attachments = List.empty(growable: true);
|
||||
|
||||
void _fetchPublishers() async {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/publishers');
|
||||
@ -49,19 +56,41 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
|
||||
final TextEditingController _contentController = TextEditingController();
|
||||
|
||||
double? _progress;
|
||||
|
||||
void _performAction() async {
|
||||
if (_isBusy || _publisher == null) return;
|
||||
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final attach = context.read<SnAttachmentProvider>();
|
||||
|
||||
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 {
|
||||
await sn.client.post('/cgi/co/${widget.mode}', data: {
|
||||
'publisher': _publisher!.id,
|
||||
'content': _contentController.value.text,
|
||||
'title': _title,
|
||||
'description': _description,
|
||||
'attachments': _attachments.map((e) => e.rid).toList(),
|
||||
});
|
||||
Navigator.pop(context, true);
|
||||
} 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
|
||||
void 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(
|
||||
elevation: 2,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_isBusy)
|
||||
const LinearProgressIndicator(
|
||||
LinearProgressIndicator(
|
||||
value: _progress,
|
||||
minHeight: 2,
|
||||
),
|
||||
Row(
|
||||
@ -268,7 +316,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
onPressed: _isBusy ? null : _selectMedia,
|
||||
icon: Icon(
|
||||
Symbols.add_photo_alternate,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
|
@ -21,7 +21,6 @@ class SnAttachment with _$SnAttachment {
|
||||
required int refCount,
|
||||
required dynamic fileChunks,
|
||||
required dynamic cleanedAt,
|
||||
required Map<String, dynamic> metadata,
|
||||
required bool isMature,
|
||||
required bool isAnalyzed,
|
||||
required bool isUploaded,
|
||||
@ -31,6 +30,7 @@ class SnAttachment with _$SnAttachment {
|
||||
required SnAttachmentPool? pool,
|
||||
required int poolId,
|
||||
required int accountId,
|
||||
@Default({}) Map<String, dynamic> metadata,
|
||||
}) = _SnAttachment;
|
||||
|
||||
factory SnAttachment.fromJson(Map<String, Object?> json) =>
|
||||
|
@ -35,7 +35,6 @@ mixin _$SnAttachment {
|
||||
int get refCount => throw _privateConstructorUsedError;
|
||||
dynamic get fileChunks => throw _privateConstructorUsedError;
|
||||
dynamic get cleanedAt => throw _privateConstructorUsedError;
|
||||
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
|
||||
bool get isMature => throw _privateConstructorUsedError;
|
||||
bool get isAnalyzed => throw _privateConstructorUsedError;
|
||||
bool get isUploaded => throw _privateConstructorUsedError;
|
||||
@ -45,6 +44,7 @@ mixin _$SnAttachment {
|
||||
SnAttachmentPool? get pool => throw _privateConstructorUsedError;
|
||||
int get poolId => throw _privateConstructorUsedError;
|
||||
int get accountId => throw _privateConstructorUsedError;
|
||||
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnAttachment to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -78,7 +78,6 @@ abstract class $SnAttachmentCopyWith<$Res> {
|
||||
int refCount,
|
||||
dynamic fileChunks,
|
||||
dynamic cleanedAt,
|
||||
Map<String, dynamic> metadata,
|
||||
bool isMature,
|
||||
bool isAnalyzed,
|
||||
bool isUploaded,
|
||||
@ -87,7 +86,8 @@ abstract class $SnAttachmentCopyWith<$Res> {
|
||||
dynamic refId,
|
||||
SnAttachmentPool? pool,
|
||||
int poolId,
|
||||
int accountId});
|
||||
int accountId,
|
||||
Map<String, dynamic> metadata});
|
||||
|
||||
$SnAttachmentPoolCopyWith<$Res>? get pool;
|
||||
}
|
||||
@ -122,7 +122,6 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
Object? refCount = null,
|
||||
Object? fileChunks = freezed,
|
||||
Object? cleanedAt = freezed,
|
||||
Object? metadata = null,
|
||||
Object? isMature = null,
|
||||
Object? isAnalyzed = null,
|
||||
Object? isUploaded = null,
|
||||
@ -132,6 +131,7 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
Object? pool = freezed,
|
||||
Object? poolId = null,
|
||||
Object? accountId = null,
|
||||
Object? metadata = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@ -194,10 +194,6 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
? _value.cleanedAt
|
||||
: cleanedAt // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
metadata: null == metadata
|
||||
? _value.metadata
|
||||
: metadata // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
isMature: null == isMature
|
||||
? _value.isMature
|
||||
: isMature // ignore: cast_nullable_to_non_nullable
|
||||
@ -234,6 +230,10 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
? _value.accountId
|
||||
: accountId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
metadata: null == metadata
|
||||
? _value.metadata
|
||||
: metadata // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@ -276,7 +276,6 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
|
||||
int refCount,
|
||||
dynamic fileChunks,
|
||||
dynamic cleanedAt,
|
||||
Map<String, dynamic> metadata,
|
||||
bool isMature,
|
||||
bool isAnalyzed,
|
||||
bool isUploaded,
|
||||
@ -285,7 +284,8 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
|
||||
dynamic refId,
|
||||
SnAttachmentPool? pool,
|
||||
int poolId,
|
||||
int accountId});
|
||||
int accountId,
|
||||
Map<String, dynamic> metadata});
|
||||
|
||||
@override
|
||||
$SnAttachmentPoolCopyWith<$Res>? get pool;
|
||||
@ -319,7 +319,6 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
Object? refCount = null,
|
||||
Object? fileChunks = freezed,
|
||||
Object? cleanedAt = freezed,
|
||||
Object? metadata = null,
|
||||
Object? isMature = null,
|
||||
Object? isAnalyzed = null,
|
||||
Object? isUploaded = null,
|
||||
@ -329,6 +328,7 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
Object? pool = freezed,
|
||||
Object? poolId = null,
|
||||
Object? accountId = null,
|
||||
Object? metadata = null,
|
||||
}) {
|
||||
return _then(_$SnAttachmentImpl(
|
||||
id: null == id
|
||||
@ -391,10 +391,6 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
? _value.cleanedAt
|
||||
: cleanedAt // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
metadata: null == metadata
|
||||
? _value._metadata
|
||||
: metadata // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
isMature: null == isMature
|
||||
? _value.isMature
|
||||
: isMature // ignore: cast_nullable_to_non_nullable
|
||||
@ -431,6 +427,10 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
? _value.accountId
|
||||
: accountId // ignore: cast_nullable_to_non_nullable
|
||||
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.fileChunks,
|
||||
required this.cleanedAt,
|
||||
required final Map<String, dynamic> metadata,
|
||||
required this.isMature,
|
||||
required this.isAnalyzed,
|
||||
required this.isUploaded,
|
||||
@ -463,7 +462,8 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
required this.refId,
|
||||
required this.pool,
|
||||
required this.poolId,
|
||||
required this.accountId})
|
||||
required this.accountId,
|
||||
final Map<String, dynamic> metadata = const {}})
|
||||
: _metadata = metadata;
|
||||
|
||||
factory _$SnAttachmentImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@ -499,14 +499,6 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
final dynamic fileChunks;
|
||||
@override
|
||||
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
|
||||
final bool isMature;
|
||||
@override
|
||||
@ -525,10 +517,18 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
final int poolId;
|
||||
@override
|
||||
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
|
||||
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
|
||||
@ -557,7 +557,6 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.fileChunks, fileChunks) &&
|
||||
const DeepCollectionEquality().equals(other.cleanedAt, cleanedAt) &&
|
||||
const DeepCollectionEquality().equals(other._metadata, _metadata) &&
|
||||
(identical(other.isMature, isMature) ||
|
||||
other.isMature == isMature) &&
|
||||
(identical(other.isAnalyzed, isAnalyzed) ||
|
||||
@ -571,7 +570,8 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
(identical(other.pool, pool) || other.pool == pool) &&
|
||||
(identical(other.poolId, poolId) || other.poolId == poolId) &&
|
||||
(identical(other.accountId, accountId) ||
|
||||
other.accountId == accountId));
|
||||
other.accountId == accountId) &&
|
||||
const DeepCollectionEquality().equals(other._metadata, _metadata));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -593,7 +593,6 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
refCount,
|
||||
const DeepCollectionEquality().hash(fileChunks),
|
||||
const DeepCollectionEquality().hash(cleanedAt),
|
||||
const DeepCollectionEquality().hash(_metadata),
|
||||
isMature,
|
||||
isAnalyzed,
|
||||
isUploaded,
|
||||
@ -602,7 +601,8 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
const DeepCollectionEquality().hash(refId),
|
||||
pool,
|
||||
poolId,
|
||||
accountId
|
||||
accountId,
|
||||
const DeepCollectionEquality().hash(_metadata)
|
||||
]);
|
||||
|
||||
/// Create a copy of SnAttachment
|
||||
@ -638,7 +638,6 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
required final int refCount,
|
||||
required final dynamic fileChunks,
|
||||
required final dynamic cleanedAt,
|
||||
required final Map<String, dynamic> metadata,
|
||||
required final bool isMature,
|
||||
required final bool isAnalyzed,
|
||||
required final bool isUploaded,
|
||||
@ -647,7 +646,8 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
required final dynamic refId,
|
||||
required final SnAttachmentPool? pool,
|
||||
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) =
|
||||
_$SnAttachmentImpl.fromJson;
|
||||
@ -683,8 +683,6 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
@override
|
||||
dynamic get cleanedAt;
|
||||
@override
|
||||
Map<String, dynamic> get metadata;
|
||||
@override
|
||||
bool get isMature;
|
||||
@override
|
||||
bool get isAnalyzed;
|
||||
@ -702,6 +700,8 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
int get poolId;
|
||||
@override
|
||||
int get accountId;
|
||||
@override
|
||||
Map<String, dynamic> get metadata;
|
||||
|
||||
/// Create a copy of SnAttachment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
@ -23,7 +23,6 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
|
||||
refCount: (json['ref_count'] as num).toInt(),
|
||||
fileChunks: json['file_chunks'],
|
||||
cleanedAt: json['cleaned_at'],
|
||||
metadata: json['metadata'] as Map<String, dynamic>,
|
||||
isMature: json['is_mature'] as bool,
|
||||
isAnalyzed: json['is_analyzed'] 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>),
|
||||
poolId: (json['pool_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) =>
|
||||
@ -54,7 +54,6 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
|
||||
'ref_count': instance.refCount,
|
||||
'file_chunks': instance.fileChunks,
|
||||
'cleaned_at': instance.cleanedAt,
|
||||
'metadata': instance.metadata,
|
||||
'is_mature': instance.isMature,
|
||||
'is_analyzed': instance.isAnalyzed,
|
||||
'is_uploaded': instance.isUploaded,
|
||||
@ -64,6 +63,7 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
|
||||
'pool': instance.pool?.toJson(),
|
||||
'pool_id': instance.poolId,
|
||||
'account_id': instance.accountId,
|
||||
'metadata': instance.metadata,
|
||||
};
|
||||
|
||||
_$SnAttachmentPoolImpl _$$SnAttachmentPoolImplFromJson(
|
||||
|
@ -14,9 +14,9 @@ class AttachmentItem extends StatelessWidget {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
switch (tp) {
|
||||
case 'image':
|
||||
return AspectRatio(
|
||||
aspectRatio: data.metadata['ratio']?.toDouble(),
|
||||
child: UniversalImage(sn.getAttachmentUrl(data.rid)),
|
||||
return UniversalImage(
|
||||
sn.getAttachmentUrl(data.rid),
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
default:
|
||||
return const Placeholder();
|
||||
|
@ -8,8 +8,12 @@ class AttachmentList extends StatelessWidget {
|
||||
final List<SnAttachment> data;
|
||||
final bool? bordered;
|
||||
final double? maxListHeight;
|
||||
const AttachmentList(
|
||||
{super.key, required this.data, this.bordered, this.maxListHeight});
|
||||
const AttachmentList({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.bordered,
|
||||
this.maxListHeight,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -37,13 +41,19 @@ class AttachmentList extends StatelessWidget {
|
||||
itemBuilder: (context, idx) {
|
||||
const radius = BorderRadius.all(Radius.circular(8));
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width - 20,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(top: borderSide, bottom: borderSide),
|
||||
borderRadius: radius,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: radius,
|
||||
child: AttachmentItem(data: data[idx]),
|
||||
child: AspectRatio(
|
||||
aspectRatio: data[idx].metadata['ratio']?.toDouble() ?? 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: radius,
|
||||
child: AttachmentItem(data: data[idx]),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
67
lib/widgets/post/post_media_pending_list.dart
Normal file
67
lib/widgets/post/post_media_pending_list.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -467,6 +467,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -66,6 +66,7 @@ dependencies:
|
||||
croppy: ^1.3.1
|
||||
flutter_expandable_fab: ^2.3.0
|
||||
dropdown_button2: ^2.3.9
|
||||
flutter_context_menu: ^0.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user