forked from eatvio/eatvio_app
fix: issue-372, issue-227, issue-406
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExpandablePageView extends StatefulWidget {
|
||||
const ExpandablePageView({
|
||||
super.key,
|
||||
required this.itemCount,
|
||||
required this.itemBuilder,
|
||||
this.onPageChanged,
|
||||
});
|
||||
|
||||
final int itemCount;
|
||||
final IndexedWidgetBuilder itemBuilder;
|
||||
final ValueChanged<int>? onPageChanged;
|
||||
|
||||
@override
|
||||
State<ExpandablePageView> createState() => _ExpandablePageViewState();
|
||||
}
|
||||
|
||||
class _ExpandablePageViewState extends State<ExpandablePageView> {
|
||||
late List<double> _heights;
|
||||
int _currentPage = 0;
|
||||
|
||||
double get _currentHeight {
|
||||
if (_heights.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
final page = _currentPage.clamp(0, _heights.length - 1);
|
||||
return _heights[page];
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_heights = List<double>.filled(widget.itemCount, 0, growable: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ExpandablePageView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.itemCount != oldWidget.itemCount) {
|
||||
_heights = List<double>.filled(widget.itemCount, 0, growable: true);
|
||||
if (widget.itemCount == 0) {
|
||||
_currentPage = 0;
|
||||
} else {
|
||||
_currentPage = _currentPage.clamp(0, widget.itemCount - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: _currentHeight,
|
||||
child: PageView.builder(
|
||||
itemCount: widget.itemCount,
|
||||
onPageChanged: (int index) {
|
||||
setState(() {
|
||||
_currentPage = index;
|
||||
});
|
||||
widget.onPageChanged?.call(index);
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return OverflowBox(
|
||||
alignment: Alignment.topCenter,
|
||||
minHeight: 0,
|
||||
maxHeight: double.infinity,
|
||||
child: _SizeReportingWidget(
|
||||
onSizeChange: (Size size) {
|
||||
if (index >= _heights.length ||
|
||||
_heights[index] == size.height) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_heights[index] = size.height;
|
||||
});
|
||||
},
|
||||
child: widget.itemBuilder(context, index),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SizeReportingWidget extends StatefulWidget {
|
||||
const _SizeReportingWidget({required this.child, required this.onSizeChange});
|
||||
|
||||
final Widget child;
|
||||
final ValueChanged<Size> onSizeChange;
|
||||
|
||||
@override
|
||||
State<_SizeReportingWidget> createState() => _SizeReportingWidgetState();
|
||||
}
|
||||
|
||||
class _SizeReportingWidgetState extends State<_SizeReportingWidget> {
|
||||
Size? _oldSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize());
|
||||
return widget.child;
|
||||
}
|
||||
|
||||
void _notifySize() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final size = context.size;
|
||||
if (size != null && _oldSize != size) {
|
||||
_oldSize = size;
|
||||
widget.onSizeChange(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,11 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
||||
totalLikes = post.totalLikes;
|
||||
final imageUrl = PostFeedMapper.resolveMediaUrl(post.image);
|
||||
final embedUrl = post.videoEmbedUrl?.trim();
|
||||
final hasMedia = PostFeedMapper.hasVisibleMedia(
|
||||
image: imageUrl,
|
||||
images: post.images,
|
||||
videoEmbedUrl: embedUrl,
|
||||
);
|
||||
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@@ -79,40 +84,12 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: Colors.black12,
|
||||
foregroundImage: NetworkImage(
|
||||
post.author.authorProfileIcon.contains('https')
|
||||
? post.author.authorProfileIcon
|
||||
: sl<AppConfig>().baseUrl +
|
||||
post.author.authorProfileIcon,
|
||||
Text(
|
||||
'${calculateDifference(post.createdAt)} в ${post.createdAt.hour.toString().padLeft(2, '0')}:${post.createdAt.minute.toString().padLeft(2, '0')}',
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black45,
|
||||
),
|
||||
backgroundImage: const AssetImage(
|
||||
'assets/images/no_avatar.png',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
post.author.authorName,
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${calculateDifference(post.createdAt)} в ${post.createdAt.hour.toString().padLeft(2, '0')}:${post.createdAt.minute.toString().padLeft(2, '0')}',
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black45,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
CustomPopup(
|
||||
@@ -167,33 +144,34 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (post.title.isNotEmpty || post.description.isNotEmpty)
|
||||
if (hasMedia && (post.title.isNotEmpty || post.description.isNotEmpty))
|
||||
const SizedBox(height: 8),
|
||||
BlogPostMediaContent(
|
||||
galleryImages: post.images,
|
||||
imageUrl: imageUrl,
|
||||
videoEmbedUrl: embedUrl != null && embedUrl.isNotEmpty
|
||||
? embedUrl
|
||||
: null,
|
||||
videoUrlThumb: post.videoUrlThumb != null
|
||||
? PostFeedMapper.resolveMediaUrl(post.videoUrlThumb)
|
||||
: null,
|
||||
currentGalleryIndex: currentGalleryIndex,
|
||||
heroTag: 'profile-blog-post-${post.id}-cover',
|
||||
onOpenGallery: (urls, index) => _openGallery(
|
||||
urls,
|
||||
index,
|
||||
heroTag: urls.length == 1
|
||||
? 'profile-blog-post-${post.id}-cover'
|
||||
if (hasMedia)
|
||||
BlogPostMediaContent(
|
||||
galleryImages: post.images,
|
||||
imageUrl: imageUrl,
|
||||
videoEmbedUrl: embedUrl != null && embedUrl.isNotEmpty
|
||||
? embedUrl
|
||||
: null,
|
||||
videoUrlThumb: post.videoUrlThumb != null
|
||||
? PostFeedMapper.resolveMediaUrl(post.videoUrlThumb)
|
||||
: null,
|
||||
currentGalleryIndex: currentGalleryIndex,
|
||||
heroTag: 'profile-blog-post-${post.id}-cover',
|
||||
onOpenGallery: (urls, index) => _openGallery(
|
||||
urls,
|
||||
index,
|
||||
heroTag: urls.length == 1
|
||||
? 'profile-blog-post-${post.id}-cover'
|
||||
: null,
|
||||
),
|
||||
onGalleryChanged: (index) {
|
||||
setState(() {
|
||||
currentGalleryIndex = index;
|
||||
});
|
||||
},
|
||||
),
|
||||
onGalleryChanged: (index) {
|
||||
setState(() {
|
||||
currentGalleryIndex = index;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(height: hasMedia ? 12 : 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
|
||||
Reference in New Issue
Block a user