feat: багофикс по запросам волбачия

This commit is contained in:
Jganenokk
2026-08-15 15:51:52 +07:00
parent 198f137826
commit ecaa132e8f
33 changed files with 904 additions and 154 deletions
@@ -55,6 +55,8 @@ class PhotoBubble extends StatelessWidget {
);
} else if (count == 2) {
photosWidget = _buildTwoPhotos(ctx, photos[0], photos[1]);
} else if (count == 3) {
photosWidget = _buildThreePhotos(ctx, photos);
} else {
photosWidget = _buildPhotoGrid(ctx, photos);
}
@@ -307,6 +309,39 @@ class PhotoBubble extends StatelessWidget {
);
}
Widget _buildThreePhotos(BubbleContext ctx, List<PhotoAttachment> photos) {
final matchTop =
ctx.hasMultiplePhotosNoCaption && ctx.shape == BubbleShape.singleTop;
final matchBottom =
ctx.hasMultiplePhotosNoCaption && ctx.shape == BubbleShape.singleBottom;
return ClipRRect(
borderRadius: _multiPhotoCornerRadius(
matchTop: matchTop,
matchBottom: matchBottom,
isMe: ctx.isMe,
),
child: AspectRatio(
aspectRatio: 3 / 2,
child: Row(
children: [
Expanded(flex: 2, child: _buildFillTile(ctx, photos[0], 0)),
const SizedBox(width: 2),
Expanded(
child: Column(
children: [
Expanded(child: _buildFillTile(ctx, photos[1], 1)),
const SizedBox(height: 2),
Expanded(child: _buildFillTile(ctx, photos[2], 2)),
],
),
),
],
),
),
);
}
Widget _buildPhotoGrid(BubbleContext ctx, List<PhotoAttachment> photos) {
final displayCount = photos.length > 4 ? 4 : photos.length;
final remaining = photos.length - 4;
@@ -361,30 +396,30 @@ class PhotoBubble extends StatelessWidget {
return _buildPhotoTile(ctx, photos[index], index);
}
Widget _buildPhotoTile(BubbleContext ctx, PhotoAttachment photo, int index) {
Widget _buildPhotoTile(BubbleContext ctx, PhotoAttachment photo, int index) =>
AspectRatio(aspectRatio: 1, child: _buildFillTile(ctx, photo, index));
Widget _buildFillTile(BubbleContext ctx, PhotoAttachment photo, int index) {
final cachePx =
(BubbleContext.photoMaxSize /
2 *
MediaQuery.of(ctx.context).devicePixelRatio)
.round();
return AspectRatio(
aspectRatio: 1,
child: Stack(
children: [
_buildPhotoImage(
ctx,
photo,
double.infinity,
double.infinity,
memWidth: cachePx,
memHeight: cachePx,
),
if (ctx.uploadProgress != null)
_buildUploadOverlay(ctx.uploadProgress!, index),
if (ctx.uploadProgress == null)
_buildTileTapTarget(ctx, index, cachePx),
],
),
return Stack(
children: [
_buildPhotoImage(
ctx,
photo,
double.infinity,
double.infinity,
memWidth: cachePx,
memHeight: cachePx,
),
if (ctx.uploadProgress != null)
_buildUploadOverlay(ctx.uploadProgress!, index),
if (ctx.uploadProgress == null)
_buildTileTapTarget(ctx, index, cachePx),
],
);
}
@@ -59,6 +59,8 @@ class VoiceMessageBubble extends StatefulWidget {
State<VoiceMessageBubble> createState() => _VoiceMessageBubbleState();
}
const double _transcriptionMaxHeight = 132;
class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
bool _transcriptionVisible = false;
String? _transcriptionText;
@@ -82,15 +84,39 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
fallbackDuration: Duration(seconds: widget.duration),
);
_audio.failure.addListener(_onFailure);
TranscriptionCache.listen(_sourceMessageId, _onTranscriptionPush);
_adoptCachedTranscription();
}
@override
void dispose() {
TranscriptionCache.unlisten(_sourceMessageId, _onTranscriptionPush);
_audio.failure.removeListener(_onFailure);
MediaPlayback.instance.releaseVoice(_audio);
super.dispose();
}
void _adoptCachedTranscription() {
final cached = TranscriptionCache.get(_sourceMessageId);
if (cached == null || cached.status != 1) return;
_transcriptionText = cached.text ?? 'не удалось распознать текст';
_transcriptionVisible = TranscriptionCache.isExpanded(_sourceMessageId);
}
void _onTranscriptionPush() {
if (!mounted) return;
setState(() {
_transcriptionLoading = false;
_adoptCachedTranscription();
});
}
void _showTranscription(String text) {
_transcriptionText = text;
_transcriptionVisible = true;
TranscriptionCache.setExpanded(_sourceMessageId, true);
}
String get _cacheName => '${widget.audioId ?? _sourceMessageId}.ogg';
int get _sourceChatId => widget.sourceChatId ?? widget.chatId;
@@ -359,15 +385,21 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
curve: Curves.easeOut,
alignment: Alignment.topLeft,
child: _transcriptionVisible
? Text(
_transcriptionText ?? '',
style: TextStyle(
color: widget.textColor.withValues(alpha: 0.8),
fontSize: 12,
height: 1.3,
? ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: _transcriptionMaxHeight,
),
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Text(
_transcriptionText ?? '',
style: TextStyle(
color: widget.textColor.withValues(alpha: 0.8),
fontSize: 12,
height: 1.3,
),
),
),
maxLines: 10,
overflow: TextOverflow.ellipsis,
)
: const SizedBox.shrink(),
),
@@ -438,16 +470,16 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
if (_transcriptionVisible && _transcriptionText != null) {
setState(() {
_transcriptionVisible = false;
TranscriptionCache.setExpanded(_sourceMessageId, false);
});
return;
}
if (TranscriptionCache.has(_sourceMessageId)) {
final cached = TranscriptionCache.get(_sourceMessageId)!;
setState(() {
_transcriptionText = cached.text ?? 'не удалось распознать текст';
_transcriptionVisible = true;
});
setState(
() => _showTranscription(cached.text ?? 'не удалось распознать текст'),
);
return;
}
@@ -468,13 +500,13 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
setState(() {
_transcriptionLoading = false;
if (result.status == 1) {
_transcriptionText = (result.text == null || result.text!.isEmpty)
? 'не удалось распознать текст'
: result.text;
_transcriptionVisible = true;
_showTranscription(
(result.text == null || result.text!.isEmpty)
? 'не удалось распознать текст'
: result.text!,
);
} else if (result.status == 0) {
_transcriptionText = 'транскрибация...';
_transcriptionVisible = true;
_showTranscription('транскрибация...');
}
});
} catch (e) {
@@ -482,8 +514,7 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
if (!mounted) return;
setState(() {
_transcriptionLoading = false;
_transcriptionText = 'ошибка транскрибации';
_transcriptionVisible = true;
_showTranscription('ошибка транскрибации');
});
}
}
@@ -64,9 +64,15 @@ class _AvatarHistoryScreenState extends State<AvatarHistoryScreen> {
final current = widget.currentAvatarUrl;
_current = (current != null && current.isNotEmpty) ? current : null;
_rebuildPages();
_load();
if (_hasHistory) {
_load();
} else {
_loading = false;
}
}
bool get _hasHistory => widget.contactId > 0;
@override
void dispose() {
_pageController.dispose();
@@ -100,7 +106,9 @@ class _AvatarHistoryScreenState extends State<AvatarHistoryScreen> {
}
Future<void> _loadMore() async {
if (_loadingMore || _history.length >= _historyTotal) return;
if (!_hasHistory || _loadingMore || _history.length >= _historyTotal) {
return;
}
_loadingMore = true;
final photos = await ContactsModule.fetchPhotos(
api,
@@ -286,9 +294,8 @@ class _AvatarHistoryScreenState extends State<AvatarHistoryScreen> {
imageUrl: _pages[i],
fit: BoxFit.contain,
fadeInDuration: const Duration(milliseconds: 120),
placeholder: (_, _) => const Center(
child: SmallSpinner(size: 36, color: Colors.white),
),
placeholder: (_, _) =>
const Center(child: SmallSpinner(size: 36, color: Colors.white)),
errorWidget: (_, _, _) =>
const Icon(Symbols.broken_image, color: Colors.white54, size: 64),
),
@@ -29,6 +29,21 @@ class HeaderPullScrollPhysics extends ScrollPhysics {
);
}
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
if (delta <= 0) {
if (value < position.pixels &&
position.pixels <= position.minScrollExtent) {
return value - position.pixels;
}
if (value < position.minScrollExtent &&
position.minScrollExtent < position.pixels) {
return value - position.minScrollExtent;
}
}
return super.applyBoundaryConditions(position, value);
}
@override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
if (delta <= 0 || offset <= 0 || position.pixels <= 0) {
@@ -107,8 +122,10 @@ class MorphHeaderDelegate extends SliverPersistentHeaderDelegate {
double get maxExtent => expandedExtent;
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration();
OverScrollHeaderStretchConfiguration? get stretchConfiguration =>
expandedExtent > collapsedExtent
? OverScrollHeaderStretchConfiguration()
: null;
@override
Widget build(
+6 -3
View File
@@ -33,9 +33,12 @@ class ProfileHeroAvatar extends StatelessWidget {
final from = _AvatarHeroChild.of(fromHeroContext);
final to = _AvatarHeroChild.of(toHeroContext);
final sharpest = from.size >= to.size ? from : to;
return FittedBox(
fit: BoxFit.fill,
child: SizedBox.square(dimension: sharpest.size, child: sharpest.child),
return Material(
type: MaterialType.transparency,
child: FittedBox(
fit: BoxFit.fill,
child: SizedBox.square(dimension: sharpest.size, child: sharpest.child),
),
);
}
}