marco.pms.mobileapp/lib/helpers/widgets/image_viewer_dialog.dart

345 lines
14 KiB
Dart

import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:photo_view/photo_view.dart';
import 'package:share_plus/share_plus.dart';
import 'package:gallery_saver_plus/gallery_saver.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:get/get.dart';
import 'package:on_field_work/helpers/widgets/my_confirmation_dialog.dart';
class ImageViewerDialog extends StatefulWidget {
final List<dynamic> imageSources;
final int initialIndex;
final List<String>? captions;
final String? title;
const ImageViewerDialog({
Key? key,
required this.imageSources,
required this.initialIndex,
this.captions,
this.title,
}) : super(key: key);
@override
State<ImageViewerDialog> createState() => _ImageViewerDialogState();
}
class _ImageViewerDialogState extends State<ImageViewerDialog> {
late final PageController _controller;
late int currentIndex;
bool isFile(dynamic item) => item is File;
@override
void initState() {
super.initState();
currentIndex = widget.initialIndex;
_controller = PageController(initialPage: widget.initialIndex);
}
Future<void> shareImage(dynamic image) async {
try {
if (isFile(image)) {
await Share.shareXFiles([XFile(image.path)],
text: 'Check out this image!');
} else if (image is String) {
await Share.share(image, subject: 'Check out this image!');
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to share image: $e')),
);
}
}
Future<void> downloadImage(dynamic image) async {
try {
if (isFile(image)) {
await GallerySaver.saveImage(image.path);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Image saved to gallery')),
);
} else if (image is String) {
final response = await http.get(Uri.parse(image));
final bytes = response.bodyBytes;
final tempDir = await getTemporaryDirectory();
final filePath =
'${tempDir.path}/${DateTime.now().millisecondsSinceEpoch}.png';
final file = await File(filePath).writeAsBytes(bytes);
await GallerySaver.saveImage(file.path);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Image saved to gallery')),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to save image: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF101018), Color(0xFF050509)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
// Add vertical padding to avoid overlap with header/footer
Padding(
padding:
const EdgeInsets.only(top: 72, bottom: 110), // Adjust as needed
child: PageView.builder(
controller: _controller,
itemCount: widget.imageSources.length,
onPageChanged: (index) => setState(() => currentIndex = index),
itemBuilder: (context, index) {
final item = widget.imageSources[index];
final ImageProvider provider = isFile(item)
? FileImage(item)
: CachedNetworkImageProvider(item);
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: PhotoView(
imageProvider: provider,
backgroundDecoration:
const BoxDecoration(color: Colors.transparent),
loadingBuilder: (context, event) =>
const Center(child: CircularProgressIndicator()),
errorBuilder: (context, error, stackTrace) =>
const Center(
child: Icon(Icons.broken_image,
size: 64, color: Colors.white54),
),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 3,
),
),
);
},
),
),
// Top blurred app bar with back button and title
SafeArea(
child: Padding(
padding: const EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.4),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.4),
blurRadius: 8,
offset: Offset(0, 3),
),
],
),
child: Row(
children: [
Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(24),
onTap: () => Navigator.of(context).pop(),
child: const Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: Colors.white, size: 24),
),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.title ?? 'Preview',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
),
),
),
// Bottom control panel with blur and rounded corners
Align(
alignment: Alignment.bottomCenter,
child: SafeArea(
top: false,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: ClipRRect(
borderRadius: BorderRadius.circular(22),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 14),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.4),
boxShadow: [
BoxShadow(
color: Colors.black54,
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Text(
'${currentIndex + 1}/${widget.imageSources.length}',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
const SizedBox(width: 16),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
widget.imageSources.length, (index) {
final bool active = index == currentIndex;
return AnimatedContainer(
duration:
const Duration(milliseconds: 300),
margin: const EdgeInsets.symmetric(
horizontal: 4),
height: 8,
width: active ? 22 : 8,
decoration: BoxDecoration(
color: active
? Colors.white
: Colors.white54,
borderRadius: BorderRadius.circular(30),
boxShadow: active
? [
BoxShadow(
color: Colors.white70,
blurRadius: 6,
spreadRadius: 1,
)
]
: [],
),
);
}),
),
),
const SizedBox(width: 16),
Tooltip(
message: 'Download Image',
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: () async {
final confirmed = await Get.dialog<bool>(
ConfirmDialog(
title: "Download Image",
message:
"Do you want to download this image to your device?",
confirmText: "Download",
cancelText: "Cancel",
onConfirm: () async {
// Call your existing download method here for the current image
await downloadImage(widget
.imageSources[currentIndex]);
},
confirmColor: Colors.blueAccent,
confirmIcon: Icons.download_rounded,
icon: Icons.download_rounded,
errorMessage:
"Failed to download image. Please try again.",
loadingText: "Downloading…",
),
barrierDismissible: false,
);
// Optionally handle if the dialog was cancelled (confirmed == false or null)
if (confirmed != true) {
// Download cancelled by user
}
},
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(Icons.download_rounded,
color: Colors.white, size: 28),
),
),
),
),
],
),
if (widget.captions != null &&
widget.captions!.isNotEmpty &&
currentIndex < widget.captions!.length)
Padding(
padding: const EdgeInsets.only(top: 8),
child: AnimatedOpacity(
opacity: 1.0,
duration: const Duration(milliseconds: 400),
child: Text(
widget.captions![currentIndex],
style: const TextStyle(
color: Colors.white70,
fontSize: 13,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
],
),
),
),
),
),
),
),
],
),
);
}
}