♻️ Refactored attachment loading system

This commit is contained in:
2024-12-26 22:19:01 +08:00
parent 619c90cdd9
commit 7656c08832
15 changed files with 341 additions and 276 deletions

View File

@ -15,16 +15,9 @@ import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/universal_image.dart';
enum PostWriteMediaType {
image,
video,
audio,
file,
}
class PostWriteMedia {
late String name;
late PostWriteMediaType type;
late SnMediaType type;
final SnAttachment? attachment;
final XFile? file;
final Uint8List? raw;
@ -36,16 +29,16 @@ class PostWriteMedia {
switch (attachment?.mimetype.split('/').firstOrNull) {
case 'image':
type = PostWriteMediaType.image;
type = SnMediaType.image;
break;
case 'video':
type = PostWriteMediaType.video;
type = SnMediaType.video;
break;
case 'audio':
type = PostWriteMediaType.audio;
type = SnMediaType.audio;
break;
default:
type = PostWriteMediaType.file;
type = SnMediaType.file;
}
}
@ -57,16 +50,16 @@ class PostWriteMedia {
switch (mimetype?.split('/').firstOrNull) {
case 'image':
type = PostWriteMediaType.image;
type = SnMediaType.image;
break;
case 'video':
type = PostWriteMediaType.video;
type = SnMediaType.video;
break;
case 'audio':
type = PostWriteMediaType.audio;
type = SnMediaType.audio;
break;
default:
type = PostWriteMediaType.file;
type = SnMediaType.file;
}
}
@ -244,7 +237,7 @@ class PostWriteController extends ChangeNotifier {
media.name,
'interactive',
null,
mimetype: media.raw != null && media.type == PostWriteMediaType.image ? 'image/png' : null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
);
final item = await attach.chunkedUploadParts(
@ -301,7 +294,7 @@ class PostWriteController extends ChangeNotifier {
media.name,
'interactive',
null,
mimetype: media.raw != null && media.type == PostWriteMediaType.image ? 'image/png' : null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
);
final item = await attach.chunkedUploadParts(

View File

@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/widget.dart';
import 'package:surface/types/account.dart';
class UserProvider extends ChangeNotifier {
@ -13,12 +12,10 @@ class UserProvider extends ChangeNotifier {
SnAccount? user;
late final SnNetworkProvider _sn;
late final HomeWidgetProvider _home;
late final ConfigProvider _config;
UserProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_home = context.read<HomeWidgetProvider>();
_config = context.read<ConfigProvider>();
}

View File

@ -1,16 +1,11 @@
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
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:pasteboard/pasteboard.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/config.dart';

View File

@ -33,7 +33,6 @@ Future<ThemeData> createAppTheme(
brightness: brightness,
);
final hasBackground = prefs.getBool(kAppBackgroundStoreKey) ?? false;
final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
return ThemeData(

View File

@ -1,10 +1,20 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'attachment.freezed.dart';
part 'attachment.g.dart';
enum SnMediaType {
image,
video,
audio,
file,
}
@freezed
class SnAttachment with _$SnAttachment {
const SnAttachment._();
const factory SnAttachment({
required int id,
required DateTime createdAt,
@ -19,9 +29,10 @@ class SnAttachment with _$SnAttachment {
required String hash,
required int destination,
required int refCount,
@Default(0) int contentRating,
@Default(0) int qualityRating,
required dynamic fileChunks,
required dynamic cleanedAt,
required bool isMature,
required bool isAnalyzed,
required bool isUploaded,
required bool isSelfRef,
@ -30,11 +41,23 @@ class SnAttachment with _$SnAttachment {
required SnAttachmentPool? pool,
required int poolId,
required int accountId,
@Default({}) Map<String, dynamic> usermeta,
@Default({}) Map<String, dynamic> metadata,
}) = _SnAttachment;
factory SnAttachment.fromJson(Map<String, Object?> json) =>
_$SnAttachmentFromJson(json);
factory SnAttachment.fromJson(Map<String, Object?> json) => _$SnAttachmentFromJson(json);
Map<String, dynamic> get data => {
...metadata,
...usermeta,
};
SnMediaType get mediaType => switch (mimetype.split('/').firstOrNull) {
'image' => SnMediaType.image,
'video' => SnMediaType.video,
'audio' => SnMediaType.audio,
_ => SnMediaType.file,
};
}
@freezed
@ -51,6 +74,5 @@ class SnAttachmentPool with _$SnAttachmentPool {
required int? accountId,
}) = _SnAttachmentPool;
factory SnAttachmentPool.fromJson(Map<String, Object?> json) =>
_$SnAttachmentPoolFromJson(json);
factory SnAttachmentPool.fromJson(Map<String, Object?> json) => _$SnAttachmentPoolFromJson(json);
}

View File

@ -33,9 +33,10 @@ mixin _$SnAttachment {
String get hash => throw _privateConstructorUsedError;
int get destination => throw _privateConstructorUsedError;
int get refCount => throw _privateConstructorUsedError;
int get contentRating => throw _privateConstructorUsedError;
int get qualityRating => throw _privateConstructorUsedError;
dynamic get fileChunks => throw _privateConstructorUsedError;
dynamic get cleanedAt => throw _privateConstructorUsedError;
bool get isMature => throw _privateConstructorUsedError;
bool get isAnalyzed => throw _privateConstructorUsedError;
bool get isUploaded => throw _privateConstructorUsedError;
bool get isSelfRef => throw _privateConstructorUsedError;
@ -44,6 +45,7 @@ mixin _$SnAttachment {
SnAttachmentPool? get pool => throw _privateConstructorUsedError;
int get poolId => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError;
Map<String, dynamic> get usermeta => throw _privateConstructorUsedError;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
/// Serializes this SnAttachment to a JSON map.
@ -76,9 +78,10 @@ abstract class $SnAttachmentCopyWith<$Res> {
String hash,
int destination,
int refCount,
int contentRating,
int qualityRating,
dynamic fileChunks,
dynamic cleanedAt,
bool isMature,
bool isAnalyzed,
bool isUploaded,
bool isSelfRef,
@ -87,6 +90,7 @@ abstract class $SnAttachmentCopyWith<$Res> {
SnAttachmentPool? pool,
int poolId,
int accountId,
Map<String, dynamic> usermeta,
Map<String, dynamic> metadata});
$SnAttachmentPoolCopyWith<$Res>? get pool;
@ -120,9 +124,10 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
Object? hash = null,
Object? destination = null,
Object? refCount = null,
Object? contentRating = null,
Object? qualityRating = null,
Object? fileChunks = freezed,
Object? cleanedAt = freezed,
Object? isMature = null,
Object? isAnalyzed = null,
Object? isUploaded = null,
Object? isSelfRef = null,
@ -131,6 +136,7 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
Object? pool = freezed,
Object? poolId = null,
Object? accountId = null,
Object? usermeta = null,
Object? metadata = null,
}) {
return _then(_value.copyWith(
@ -186,6 +192,14 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
? _value.refCount
: refCount // ignore: cast_nullable_to_non_nullable
as int,
contentRating: null == contentRating
? _value.contentRating
: contentRating // ignore: cast_nullable_to_non_nullable
as int,
qualityRating: null == qualityRating
? _value.qualityRating
: qualityRating // ignore: cast_nullable_to_non_nullable
as int,
fileChunks: freezed == fileChunks
? _value.fileChunks
: fileChunks // ignore: cast_nullable_to_non_nullable
@ -194,10 +208,6 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
? _value.cleanedAt
: cleanedAt // ignore: cast_nullable_to_non_nullable
as dynamic,
isMature: null == isMature
? _value.isMature
: isMature // ignore: cast_nullable_to_non_nullable
as bool,
isAnalyzed: null == isAnalyzed
? _value.isAnalyzed
: isAnalyzed // ignore: cast_nullable_to_non_nullable
@ -230,6 +240,10 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
usermeta: null == usermeta
? _value.usermeta
: usermeta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
metadata: null == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
@ -274,9 +288,10 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
String hash,
int destination,
int refCount,
int contentRating,
int qualityRating,
dynamic fileChunks,
dynamic cleanedAt,
bool isMature,
bool isAnalyzed,
bool isUploaded,
bool isSelfRef,
@ -285,6 +300,7 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
SnAttachmentPool? pool,
int poolId,
int accountId,
Map<String, dynamic> usermeta,
Map<String, dynamic> metadata});
@override
@ -317,9 +333,10 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
Object? hash = null,
Object? destination = null,
Object? refCount = null,
Object? contentRating = null,
Object? qualityRating = null,
Object? fileChunks = freezed,
Object? cleanedAt = freezed,
Object? isMature = null,
Object? isAnalyzed = null,
Object? isUploaded = null,
Object? isSelfRef = null,
@ -328,6 +345,7 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
Object? pool = freezed,
Object? poolId = null,
Object? accountId = null,
Object? usermeta = null,
Object? metadata = null,
}) {
return _then(_$SnAttachmentImpl(
@ -383,6 +401,14 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
? _value.refCount
: refCount // ignore: cast_nullable_to_non_nullable
as int,
contentRating: null == contentRating
? _value.contentRating
: contentRating // ignore: cast_nullable_to_non_nullable
as int,
qualityRating: null == qualityRating
? _value.qualityRating
: qualityRating // ignore: cast_nullable_to_non_nullable
as int,
fileChunks: freezed == fileChunks
? _value.fileChunks
: fileChunks // ignore: cast_nullable_to_non_nullable
@ -391,10 +417,6 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
? _value.cleanedAt
: cleanedAt // ignore: cast_nullable_to_non_nullable
as dynamic,
isMature: null == isMature
? _value.isMature
: isMature // ignore: cast_nullable_to_non_nullable
as bool,
isAnalyzed: null == isAnalyzed
? _value.isAnalyzed
: isAnalyzed // ignore: cast_nullable_to_non_nullable
@ -427,6 +449,10 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
usermeta: null == usermeta
? _value._usermeta
: usermeta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
metadata: null == metadata
? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable
@ -437,7 +463,7 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$SnAttachmentImpl implements _SnAttachment {
class _$SnAttachmentImpl extends _SnAttachment {
const _$SnAttachmentImpl(
{required this.id,
required this.createdAt,
@ -452,9 +478,10 @@ class _$SnAttachmentImpl implements _SnAttachment {
required this.hash,
required this.destination,
required this.refCount,
this.contentRating = 0,
this.qualityRating = 0,
required this.fileChunks,
required this.cleanedAt,
required this.isMature,
required this.isAnalyzed,
required this.isUploaded,
required this.isSelfRef,
@ -463,8 +490,11 @@ class _$SnAttachmentImpl implements _SnAttachment {
required this.pool,
required this.poolId,
required this.accountId,
final Map<String, dynamic> usermeta = const {},
final Map<String, dynamic> metadata = const {}})
: _metadata = metadata;
: _usermeta = usermeta,
_metadata = metadata,
super._();
factory _$SnAttachmentImpl.fromJson(Map<String, dynamic> json) =>
_$$SnAttachmentImplFromJson(json);
@ -496,12 +526,16 @@ class _$SnAttachmentImpl implements _SnAttachment {
@override
final int refCount;
@override
@JsonKey()
final int contentRating;
@override
@JsonKey()
final int qualityRating;
@override
final dynamic fileChunks;
@override
final dynamic cleanedAt;
@override
final bool isMature;
@override
final bool isAnalyzed;
@override
final bool isUploaded;
@ -517,6 +551,15 @@ class _$SnAttachmentImpl implements _SnAttachment {
final int poolId;
@override
final int accountId;
final Map<String, dynamic> _usermeta;
@override
@JsonKey()
Map<String, dynamic> get usermeta {
if (_usermeta is EqualUnmodifiableMapView) return _usermeta;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_usermeta);
}
final Map<String, dynamic> _metadata;
@override
@JsonKey()
@ -528,7 +571,7 @@ class _$SnAttachmentImpl implements _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, fileChunks: $fileChunks, cleanedAt: $cleanedAt, isMature: $isMature, isAnalyzed: $isAnalyzed, isUploaded: $isUploaded, isSelfRef: $isSelfRef, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, 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, fileChunks: $fileChunks, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isUploaded: $isUploaded, isSelfRef: $isSelfRef, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, usermeta: $usermeta, metadata: $metadata)';
}
@override
@ -554,11 +597,13 @@ class _$SnAttachmentImpl implements _SnAttachment {
other.destination == destination) &&
(identical(other.refCount, refCount) ||
other.refCount == refCount) &&
(identical(other.contentRating, contentRating) ||
other.contentRating == contentRating) &&
(identical(other.qualityRating, qualityRating) ||
other.qualityRating == qualityRating) &&
const DeepCollectionEquality()
.equals(other.fileChunks, fileChunks) &&
const DeepCollectionEquality().equals(other.cleanedAt, cleanedAt) &&
(identical(other.isMature, isMature) ||
other.isMature == isMature) &&
(identical(other.isAnalyzed, isAnalyzed) ||
other.isAnalyzed == isAnalyzed) &&
(identical(other.isUploaded, isUploaded) ||
@ -571,6 +616,7 @@ class _$SnAttachmentImpl implements _SnAttachment {
(identical(other.poolId, poolId) || other.poolId == poolId) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId) &&
const DeepCollectionEquality().equals(other._usermeta, _usermeta) &&
const DeepCollectionEquality().equals(other._metadata, _metadata));
}
@ -591,9 +637,10 @@ class _$SnAttachmentImpl implements _SnAttachment {
hash,
destination,
refCount,
contentRating,
qualityRating,
const DeepCollectionEquality().hash(fileChunks),
const DeepCollectionEquality().hash(cleanedAt),
isMature,
isAnalyzed,
isUploaded,
isSelfRef,
@ -602,6 +649,7 @@ class _$SnAttachmentImpl implements _SnAttachment {
pool,
poolId,
accountId,
const DeepCollectionEquality().hash(_usermeta),
const DeepCollectionEquality().hash(_metadata)
]);
@ -621,7 +669,7 @@ class _$SnAttachmentImpl implements _SnAttachment {
}
}
abstract class _SnAttachment implements SnAttachment {
abstract class _SnAttachment extends SnAttachment {
const factory _SnAttachment(
{required final int id,
required final DateTime createdAt,
@ -636,9 +684,10 @@ abstract class _SnAttachment implements SnAttachment {
required final String hash,
required final int destination,
required final int refCount,
final int contentRating,
final int qualityRating,
required final dynamic fileChunks,
required final dynamic cleanedAt,
required final bool isMature,
required final bool isAnalyzed,
required final bool isUploaded,
required final bool isSelfRef,
@ -647,7 +696,9 @@ abstract class _SnAttachment implements SnAttachment {
required final SnAttachmentPool? pool,
required final int poolId,
required final int accountId,
final Map<String, dynamic> usermeta,
final Map<String, dynamic> metadata}) = _$SnAttachmentImpl;
const _SnAttachment._() : super._();
factory _SnAttachment.fromJson(Map<String, dynamic> json) =
_$SnAttachmentImpl.fromJson;
@ -679,12 +730,14 @@ abstract class _SnAttachment implements SnAttachment {
@override
int get refCount;
@override
int get contentRating;
@override
int get qualityRating;
@override
dynamic get fileChunks;
@override
dynamic get cleanedAt;
@override
bool get isMature;
@override
bool get isAnalyzed;
@override
bool get isUploaded;
@ -701,6 +754,8 @@ abstract class _SnAttachment implements SnAttachment {
@override
int get accountId;
@override
Map<String, dynamic> get usermeta;
@override
Map<String, dynamic> get metadata;
/// Create a copy of SnAttachment

View File

@ -21,9 +21,10 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
hash: json['hash'] as String,
destination: (json['destination'] as num).toInt(),
refCount: (json['ref_count'] as num).toInt(),
contentRating: (json['content_rating'] as num?)?.toInt() ?? 0,
qualityRating: (json['quality_rating'] as num?)?.toInt() ?? 0,
fileChunks: json['file_chunks'],
cleanedAt: json['cleaned_at'],
isMature: json['is_mature'] as bool,
isAnalyzed: json['is_analyzed'] as bool,
isUploaded: json['is_uploaded'] as bool,
isSelfRef: json['is_self_ref'] as bool,
@ -34,6 +35,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(),
usermeta: json['usermeta'] as Map<String, dynamic>? ?? const {},
metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
);
@ -52,9 +54,10 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'hash': instance.hash,
'destination': instance.destination,
'ref_count': instance.refCount,
'content_rating': instance.contentRating,
'quality_rating': instance.qualityRating,
'file_chunks': instance.fileChunks,
'cleaned_at': instance.cleanedAt,
'is_mature': instance.isMature,
'is_analyzed': instance.isAnalyzed,
'is_uploaded': instance.isUploaded,
'is_self_ref': instance.isSelfRef,
@ -63,6 +66,7 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'pool': instance.pool?.toJson(),
'pool_id': instance.poolId,
'account_id': instance.accountId,
'usermeta': instance.usermeta,
'metadata': instance.metadata,
};

View File

@ -99,10 +99,10 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
),
actions: [
TextButton(
child: Text('dialogDismiss').tr(),
onPressed: _isBusy ? null : () {
Navigator.pop(context);
},
child: Text('dialogDismiss').tr(),
),
TextButton(
onPressed: _isBusy ? null : () => _finishUp(),

View File

@ -18,6 +18,7 @@ import 'package:uuid/uuid.dart';
class AttachmentItem extends StatelessWidget {
final SnAttachment? data;
final String? heroTag;
const AttachmentItem({
super.key,
required this.data,
@ -60,9 +61,14 @@ class AttachmentItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (data!.isMature) {
return _AttachmentItemSensitiveBlur(
child: _buildContent(context),
if (data!.contentRating > 0) {
return LayoutBuilder(
builder: (context, constraints) {
return _AttachmentItemSensitiveBlur(
isCompact: constraints.maxHeight < 360,
child: _buildContent(context),
);
}
);
}
@ -72,15 +78,15 @@ class AttachmentItem extends StatelessWidget {
class _AttachmentItemSensitiveBlur extends StatefulWidget {
final Widget child;
const _AttachmentItemSensitiveBlur({super.key, required this.child});
final bool isCompact;
const _AttachmentItemSensitiveBlur({super.key, required this.child, this.isCompact = false});
@override
State<_AttachmentItemSensitiveBlur> createState() =>
_AttachmentItemSensitiveBlurState();
State<_AttachmentItemSensitiveBlur> createState() => _AttachmentItemSensitiveBlurState();
}
class _AttachmentItemSensitiveBlurState
extends State<_AttachmentItemSensitiveBlur> {
class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBlur> {
bool _doesShow = false;
@override
@ -104,24 +110,21 @@ class _AttachmentItemSensitiveBlurState
color: Colors.white,
size: 32,
),
const Gap(8),
Text('sensitiveContent', textAlign: TextAlign.center)
.tr()
.fontSize(20)
.textColor(Colors.white)
.bold(),
Text(
'sensitiveContentDescription',
textAlign: TextAlign.center,
)
.tr()
.fontSize(14)
.textColor(Colors.white.withOpacity(0.8)),
const Gap(16),
InkWell(
child: Text('sensitiveContentReveal')
if (!widget.isCompact) const Gap(8),
if (!widget.isCompact)
Text('sensitiveContent', textAlign: TextAlign.center)
.tr()
.textColor(Colors.white),
.fontSize(20)
.textColor(Colors.white)
.bold(),
if (!widget.isCompact)
Text(
'sensitiveContentDescription',
textAlign: TextAlign.center,
).tr().fontSize(14).textColor(Colors.white.withOpacity(0.8)),
if (!widget.isCompact) const Gap(16),
InkWell(
child: Text('sensitiveContentReveal').tr().textColor(Colors.white),
onTap: () {
setState(() => _doesShow = !_doesShow);
},
@ -131,9 +134,7 @@ class _AttachmentItemSensitiveBlurState
).center(),
),
),
)
.opacity(_doesShow ? 0 : 1, animate: true)
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
).opacity(_doesShow ? 0 : 1, animate: true).animate(const Duration(milliseconds: 300), Curves.easeInOut),
if (_doesShow)
Positioned(
top: 0,
@ -163,6 +164,7 @@ class _AttachmentItemSensitiveBlurState
class _AttachmentItemContentVideo extends StatefulWidget {
final SnAttachment data;
final bool isAutoload;
const _AttachmentItemContentVideo({
super.key,
required this.data,
@ -170,12 +172,10 @@ class _AttachmentItemContentVideo extends StatefulWidget {
});
@override
State<_AttachmentItemContentVideo> createState() =>
_AttachmentItemContentVideoState();
State<_AttachmentItemContentVideo> createState() => _AttachmentItemContentVideoState();
}
class _AttachmentItemContentVideoState
extends State<_AttachmentItemContentVideo> {
class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo> {
bool _showContent = false;
Player? _videoPlayer;
@ -266,10 +266,7 @@ class _AttachmentItemContentVideoState
),
Text(
Duration(
milliseconds:
(widget.data.metadata['duration'] ?? 0)
.toInt() *
1000,
milliseconds: (widget.data.metadata['duration'] ?? 0).toInt() * 1000,
).toString(),
style: GoogleFonts.robotoMono(
fontSize: 12,
@ -317,6 +314,7 @@ class _AttachmentItemContentVideoState
class _AttachmentItemContentAudio extends StatefulWidget {
final SnAttachment data;
final bool isAutoload;
const _AttachmentItemContentAudio({
super.key,
required this.data,
@ -324,12 +322,10 @@ class _AttachmentItemContentAudio extends StatefulWidget {
});
@override
State<_AttachmentItemContentAudio> createState() =>
_AttachmentItemContentAudioState();
State<_AttachmentItemContentAudio> createState() => _AttachmentItemContentAudioState();
}
class _AttachmentItemContentAudioState
extends State<_AttachmentItemContentAudio> {
class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio> {
bool _showContent = false;
double? _draggingValue;
@ -499,12 +495,8 @@ class _AttachmentItemContentAudioState
overlayShape: SliderComponentShape.noOverlay,
),
child: Slider(
secondaryTrackValue: _bufferedPosition
.inMilliseconds
.abs()
.toDouble(),
value: _draggingValue?.abs() ??
_position.inMilliseconds.toDouble().abs(),
secondaryTrackValue: _bufferedPosition.inMilliseconds.abs().toDouble(),
value: _draggingValue?.abs() ?? _position.inMilliseconds.toDouble().abs(),
min: 0,
max: math
.max(
@ -544,9 +536,7 @@ class _AttachmentItemContentAudioState
),
const Gap(16),
IconButton.filled(
icon: _isPlaying
? const Icon(Symbols.pause)
: const Icon(Symbols.play_arrow),
icon: _isPlaying ? const Icon(Symbols.pause) : const Icon(Symbols.play_arrow),
onPressed: () {
_audioPlayer!.playOrPause();
},

View File

@ -58,7 +58,7 @@ class _AttachmentListState extends State<AttachmentList> {
if (widget.data.isEmpty) return const SizedBox.shrink();
if (widget.data.length == 1) {
final singleAspectRatio = widget.data[0]?.metadata['ratio']?.toDouble() ??
final singleAspectRatio = widget.data[0]?.data['ratio']?.toDouble() ??
switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
'audio' => 16 / 9,
'video' => 16 / 9,
@ -114,6 +114,7 @@ class _AttachmentListState extends State<AttachmentList> {
},
),
onTap: () {
if (widget.data.firstOrNull?.mediaType != SnMediaType.image) return;
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
@ -136,7 +137,7 @@ class _AttachmentListState extends State<AttachmentList> {
children: widget.data
.mapIndexed(
(idx, ele) => AspectRatio(
aspectRatio: (ele?.metadata['ratio'] ?? 1).toDouble(),
aspectRatio: (ele?.data['ratio'] ?? 1).toDouble(),
child: Container(
decoration: BoxDecoration(
color: backgroundColor,
@ -161,7 +162,7 @@ class _AttachmentListState extends State<AttachmentList> {
}
return AspectRatio(
aspectRatio: (widget.data.firstOrNull?.metadata['ratio'] ?? 1).toDouble(),
aspectRatio: (widget.data.firstOrNull?.data['ratio'] ?? 1).toDouble(),
child: Container(
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
child: ScrollConfiguration(
@ -173,12 +174,14 @@ class _AttachmentListState extends State<AttachmentList> {
return Container(
constraints: constraints,
child: AspectRatio(
aspectRatio: (widget.data[idx]?.metadata['ratio'] ?? 1).toDouble(),
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
child: GestureDetector(
onTap: () {
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
data:
widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
initialIndex: idx,
heroTags: heroTags,
),

View File

@ -1,18 +1,14 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:image_picker/image_picker.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:pasteboard/pasteboard.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/chat_message_controller.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/markdown_content.dart';
@ -80,7 +76,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
media.name,
'messaging',
null,
mimetype: media.raw != null && media.type == PostWriteMediaType.image ? 'image/png' : null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
);
final item = await attach.chunkedUploadParts(

View File

@ -124,7 +124,7 @@ class PostMediaPendingList extends StatelessWidget {
ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) {
return ContextMenu(
entries: [
if (media.attachment != null && media.type == PostWriteMediaType.video)
if (media.attachment != null && media.type == SnMediaType.video)
MenuItem(
label: 'attachmentSetThumbnail'.tr(),
icon: Symbols.image,
@ -140,7 +140,7 @@ class PostMediaPendingList extends StatelessWidget {
onUpload!(idx);
}),
if (media.attachment != null &&
media.type == PostWriteMediaType.image &&
media.type == SnMediaType.image &&
onPostSetThumbnail != null &&
idx != -1)
MenuItem(
@ -150,7 +150,7 @@ class PostMediaPendingList extends StatelessWidget {
onPostSetThumbnail!(idx);
},
)
else if (media.attachment != null && media.type == PostWriteMediaType.image && onPostSetThumbnail != null)
else if (media.attachment != null && media.type == SnMediaType.image && onPostSetThumbnail != null)
MenuItem(
label: 'attachmentUnsetAsPostThumbnail'.tr(),
icon: Symbols.cancel,
@ -166,7 +166,7 @@ class PostMediaPendingList extends StatelessWidget {
onInsertLink!(idx);
},
),
if (media.type == PostWriteMediaType.image && media.attachment != null)
if (media.type == SnMediaType.image && media.attachment != null)
MenuItem(
label: 'preview'.tr(),
icon: Symbols.preview,
@ -177,7 +177,7 @@ class PostMediaPendingList extends StatelessWidget {
);
},
),
if (media.type == PostWriteMediaType.image && media.attachment == null)
if (media.type == SnMediaType.image && media.attachment == null)
MenuItem(
label: 'crop'.tr(),
icon: Symbols.crop,
@ -219,10 +219,6 @@ class PostMediaPendingList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
final sn = context.read<SnNetworkProvider>();
return Container(
constraints: const BoxConstraints(maxHeight: 120),
child: Row(
@ -285,7 +281,7 @@ class _PostMediaPendingItem extends StatelessWidget {
child: AspectRatio(
aspectRatio: 1,
child: switch (media.type) {
PostWriteMediaType.image => Container(
SnMediaType.image => Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child: LayoutBuilder(builder: (context, constraints) {
return Image(
@ -298,7 +294,7 @@ class _PostMediaPendingItem extends StatelessWidget {
);
}),
),
PostWriteMediaType.video => Container(
SnMediaType.video => Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child: media.attachment?.metadata['thumbnail'] != null
? AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment?.metadata['thumbnail']))
@ -345,7 +341,7 @@ class AddPostMediaButton extends StatelessWidget {
PostWriteMedia.fromBytes(
imageBytes,
'attachmentPastedImage'.tr(),
PostWriteMediaType.image,
SnMediaType.image,
),
]);
}