feat/fix/refactor: Кнопка добавления человека в контакты, возможность развертывания аватарки на экране профиля, включение сторисов по умолчанию, показ сторисов на экране чатов, чата, и при просмотре списка участников, миниапы у ботов. Фикс прогресса просмотра сториса, фикс предпросмотра медиав пикере, прогресс бар отправки фото/видео/файлов + фото продолжат отправку даже если выйти с чата. Вынес превью фото, добавил отдельный модуль отправки медиа
This commit is contained in:
@@ -160,6 +160,10 @@ class _AttachmentSheetState extends State<AttachmentSheet> {
|
||||
_thumbKeys.putIfAbsent(id, () => GlobalKey<_ThumbnailState>());
|
||||
|
||||
void _openPreview(GalleryItem item) {
|
||||
if (item.isVideo) {
|
||||
_toggleSelection(item);
|
||||
return;
|
||||
}
|
||||
final thumbKey = _thumbKey(item.id);
|
||||
final hero = PhotoHeroController(
|
||||
origin: () => photoHeroRect(thumbKey),
|
||||
@@ -1089,7 +1093,7 @@ class _ThumbnailState extends State<_Thumbnail> {
|
||||
}
|
||||
|
||||
void _resolveProvider() {
|
||||
final file = widget.editedFile ?? widget.item.localFile;
|
||||
final file = widget.editedFile ?? (widget.item.isVideo ? null : widget.item.localFile);
|
||||
if (file != null) {
|
||||
_provider = ResizeImage(
|
||||
FileImage(file),
|
||||
@@ -1102,7 +1106,7 @@ class _ThumbnailState extends State<_Thumbnail> {
|
||||
final id = widget.item.id;
|
||||
widget.item.thumbnail(_pixelSize).then((data) {
|
||||
if (!mounted || data == null || widget.item.id != id) return;
|
||||
if (widget.editedFile != null || widget.item.localFile != null) return;
|
||||
if (widget.editedFile != null) return;
|
||||
setState(() => _provider = MemoryImage(data));
|
||||
});
|
||||
}
|
||||
@@ -1119,5 +1123,16 @@ class _ThumbnailState extends State<_Thumbnail> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _placeholder() => ColoredBox(color: widget.cs.surfaceContainerHighest);
|
||||
Widget _placeholder() => ColoredBox(
|
||||
color: widget.cs.surfaceContainerHighest,
|
||||
child: widget.item.isVideo
|
||||
? Center(
|
||||
child: Icon(
|
||||
Symbols.movie,
|
||||
size: 28,
|
||||
color: widget.cs.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../../../core/config/komet_settings.dart';
|
||||
import '../../../../core/utils/format.dart';
|
||||
import '../../../../models/attachment.dart';
|
||||
import '../../formatted_message_text.dart';
|
||||
import '../../sending_clock_icon.dart';
|
||||
import '../../photo_viewer.dart';
|
||||
|
||||
enum MessageType { text, attachment, voice, control }
|
||||
@@ -202,6 +203,9 @@ class BubbleContext {
|
||||
|
||||
Widget _statusIconFor(String? status, {Color? color, double size = 14}) {
|
||||
final v = messageStatusVisual(status, dimColor: color ?? dim);
|
||||
if (isSendingStatus(status)) {
|
||||
return SendingClockIcon(color: v.color, size: size);
|
||||
}
|
||||
return Icon(v.icon, size: size, color: v.color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import '../../../../models/attachment.dart';
|
||||
import '../../custom_notification.dart';
|
||||
import '../../decrypted_photo.dart';
|
||||
import '../../photo_viewer.dart';
|
||||
import '../../upload_progress_ring.dart';
|
||||
import 'bubble_context.dart';
|
||||
|
||||
class FileBubble extends StatelessWidget {
|
||||
@@ -69,11 +70,24 @@ class FileBubble extends StatelessWidget {
|
||||
color: isMe ? ctx.systemTint : ctx.cs.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(
|
||||
Symbols.description,
|
||||
color: isMe ? ctx.cs.onPrimaryContainer : ctx.cs.primary,
|
||||
size: 20,
|
||||
),
|
||||
child: ctx.uploadProgress == null
|
||||
? Icon(
|
||||
Symbols.description,
|
||||
color: isMe
|
||||
? ctx.cs.onPrimaryContainer
|
||||
: ctx.cs.primary,
|
||||
size: 20,
|
||||
)
|
||||
: UploadProgressRing(
|
||||
progress: ctx.uploadProgress!,
|
||||
color: isMe
|
||||
? ctx.cs.onPrimaryContainer
|
||||
: ctx.cs.primary,
|
||||
size: 38,
|
||||
strokeWidth: 2.4,
|
||||
iconSize: 14,
|
||||
padding: const EdgeInsets.all(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../../../../models/attachment.dart';
|
||||
import '../../lottie_image.dart';
|
||||
import '../../sending_clock_icon.dart';
|
||||
import 'bubble_context.dart';
|
||||
|
||||
class StickerBubble extends StatelessWidget {
|
||||
@@ -86,6 +87,9 @@ class StickerBubble extends StatelessWidget {
|
||||
Widget _buildStickerStatusIcon() {
|
||||
final status = ctx.overrideStatus ?? ctx.message.status;
|
||||
final v = messageStatusVisual(status, dimColor: Colors.white);
|
||||
if (isSendingStatus(status)) {
|
||||
return SendingClockIcon(color: v.color, size: 13);
|
||||
}
|
||||
return Icon(v.icon, size: 13, color: v.color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:komet/main.dart';
|
||||
|
||||
import '../../../../core/media/preview_image.dart';
|
||||
import '../../../../core/utils/format.dart';
|
||||
import '../../../../core/utils/haptics.dart';
|
||||
import '../../../../models/attachment.dart';
|
||||
import '../../custom_notification.dart';
|
||||
import '../../upload_progress_ring.dart';
|
||||
import '../../photo_viewer.dart';
|
||||
import 'bubble_context.dart';
|
||||
import 'video_note_bubble.dart';
|
||||
@@ -63,39 +65,78 @@ class VideoBubble extends StatelessWidget {
|
||||
child: Icon(Symbols.videocam, size: 48, color: ctx.cs.onSurfaceVariant),
|
||||
);
|
||||
|
||||
final localThumb = dataUriImage(video, video.previewData);
|
||||
final uploading = ctx.uploadProgress;
|
||||
|
||||
Widget previewImage() {
|
||||
if (previewUrl.isNotEmpty && !previewUrl.startsWith('data:')) {
|
||||
return CachedNetworkImage(
|
||||
imageUrl: previewUrl,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.cover,
|
||||
memCacheWidth: (width * dpr).round(),
|
||||
fadeInDuration: Duration.zero,
|
||||
placeholderFadeInDuration: Duration.zero,
|
||||
errorWidget: (_, _, _) => localThumb == null
|
||||
? placeholder()
|
||||
: Image(
|
||||
image: localThumb,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (localThumb != null) {
|
||||
return Image(
|
||||
image: localThumb,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.cover,
|
||||
gaplessPlayback: true,
|
||||
errorBuilder: (_, _, _) => placeholder(),
|
||||
);
|
||||
}
|
||||
return placeholder();
|
||||
}
|
||||
|
||||
final preview = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(BubbleContext.photoBorderRadius),
|
||||
child: Stack(
|
||||
children: [
|
||||
previewUrl.isEmpty
|
||||
? placeholder()
|
||||
: CachedNetworkImage(
|
||||
imageUrl: previewUrl,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.cover,
|
||||
memCacheWidth: (width * dpr).round(),
|
||||
fadeInDuration: Duration.zero,
|
||||
placeholderFadeInDuration: Duration.zero,
|
||||
errorWidget: (_, _, _) => placeholder(),
|
||||
previewImage(),
|
||||
if (uploading != null)
|
||||
Positioned.fill(
|
||||
child: ColoredBox(
|
||||
color: Colors.black.withValues(alpha: 0.35),
|
||||
child: Center(
|
||||
child: UploadProgressRing(
|
||||
progress: uploading,
|
||||
color: Colors.white,
|
||||
trackColor: Colors.white24,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black54,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Symbols.play_arrow,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
)
|
||||
else
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black54,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Symbols.play_arrow,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (durationMs != null && durationMs > 0)
|
||||
Positioned(
|
||||
left: 6,
|
||||
@@ -112,12 +153,13 @@ class VideoBubble extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _playVideo(ctx.context, video),
|
||||
if (uploading == null)
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _playVideo(ctx.context, video),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user