improvements in ui 2

master
gitea 2 months ago
parent 9061470e9b
commit 374a968546

@ -765,7 +765,10 @@ class _RecipeCard extends StatelessWidget {
Widget _buildRecipeImage(String imageUrl) { Widget _buildRecipeImage(String imageUrl) {
final mediaService = ServiceLocator.instance.mediaService; final mediaService = ServiceLocator.instance.mediaService;
if (mediaService == null) { if (mediaService == null) {
return Image.network(imageUrl, fit: BoxFit.cover); return _FadeInImageWidget(
imageUrl: imageUrl,
isNetwork: true,
);
} }
// Try to extract asset ID from Immich URL (format: .../api/assets/{id}/original) // Try to extract asset ID from Immich URL (format: .../api/assets/{id}/original)
@ -799,41 +802,16 @@ class _RecipeCard extends StatelessWidget {
); );
} }
return Image.memory( return _FadeInImageWidget(
snapshot.data!, imageBytes: snapshot.data!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
); );
}, },
); );
} }
return Image.network( return _FadeInImageWidget(
imageUrl, imageUrl: imageUrl,
fit: BoxFit.cover, isNetwork: true,
width: double.infinity,
height: double.infinity,
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey.shade200,
child: const Icon(Icons.broken_image, size: 48),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
color: Colors.grey.shade200,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
); );
} }
@ -842,6 +820,125 @@ class _RecipeCard extends StatelessWidget {
} }
} }
/// Widget that displays an image with a smooth fade-in animation.
class _FadeInImageWidget extends StatefulWidget {
final String? imageUrl;
final Uint8List? imageBytes;
final bool isNetwork;
const _FadeInImageWidget({
this.imageUrl,
this.imageBytes,
this.isNetwork = false,
}) : assert(imageUrl != null || imageBytes != null, 'Either imageUrl or imageBytes must be provided');
@override
State<_FadeInImageWidget> createState() => _FadeInImageWidgetState();
}
class _FadeInImageWidgetState extends State<_FadeInImageWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
bool _imageLoaded = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_fadeAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeOut,
);
// Start with opacity 0
_controller.value = 0.0;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onImageLoaded() {
if (!_imageLoaded && mounted) {
_imageLoaded = true;
_controller.forward();
}
}
@override
Widget build(BuildContext context) {
Widget imageWidget;
if (widget.imageBytes != null) {
// For memory images, use frameBuilder to detect when image is ready
imageWidget = Image.memory(
widget.imageBytes!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
if ((wasSynchronouslyLoaded || frame != null) && !_imageLoaded) {
WidgetsBinding.instance.addPostFrameCallback((_) => _onImageLoaded());
}
return child;
},
);
} else if (widget.imageUrl != null) {
// For network images, use frameBuilder to detect when image is ready
imageWidget = Image.network(
widget.imageUrl!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey.shade200,
child: const Icon(Icons.broken_image, size: 48),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
// Image is loaded, return child and let frameBuilder handle fade-in
return child;
}
return Container(
color: Colors.grey.shade200,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
if ((wasSynchronouslyLoaded || frame != null) && !_imageLoaded) {
WidgetsBinding.instance.addPostFrameCallback((_) => _onImageLoaded());
}
return child;
},
);
} else {
return Container(
color: Colors.grey.shade200,
child: const Icon(Icons.broken_image, size: 48),
);
}
return FadeTransition(
opacity: _fadeAnimation,
child: imageWidget,
);
}
}
/// Widget that displays a video thumbnail preview with a few seconds of playback. /// Widget that displays a video thumbnail preview with a few seconds of playback.
class _VideoThumbnailPreview extends StatefulWidget { class _VideoThumbnailPreview extends StatefulWidget {
final String videoUrl; final String videoUrl;

Loading…
Cancel
Save

Powered by TurnKey Linux.