import 'dart:math'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:image_picker/image_picker.dart'; import 'package:pweb/utils/error/snackbar.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; class ImageTile extends AbstractSettingsTile { final String? imageUrl; final double? maxWidth; final double? maxHeight; final String? imageUpdateError; final Future Function(XFile?) onUpdate; final String? title; final String? description; final Widget? imagePreview; final double previewWidth; final double previewHeight; const ImageTile({ super.key, required this.imageUrl, this.maxWidth, this.maxHeight, this.imageUpdateError, required this.onUpdate, this.title, this.description, this.imagePreview, this.previewHeight = 40.0, this.previewWidth = 40.0, }); Future _pickImage(BuildContext context) async { final picker = ImagePicker(); final locs = AppLocalizations.of(context)!; final sm = ScaffoldMessenger.of(context); final picked = await picker.pickImage( source: ImageSource.gallery, maxWidth: maxWidth, maxHeight: maxHeight, ); if (picked == null) return; try { await onUpdate(picked); if (imageUrl != null) { CachedNetworkImage.evictFromCache(imageUrl!); } } catch (e) { notifyUserOfErrorX( scaffoldMessenger: sm, errorSituation: imageUpdateError ?? locs.settingsImageUpdateError, exception: e, appLocalizations: locs, ); } } @override Widget build(BuildContext context) => SettingsTile.navigation( leading: imagePreview ?? ClipRRect( borderRadius: BorderRadius.circular(0.1 * (previewWidth < previewHeight ? previewWidth : previewHeight)), child: imageUrl != null ? CachedNetworkImage( imageUrl: imageUrl!, width: previewWidth, height: previewHeight, fit: BoxFit.cover, progressIndicatorBuilder: (ctx, url, downloadProgress) { // compute 10% of the smaller image dimension, but no more than 40px final baseSize = min(previewWidth, previewHeight) * 0.1; final indicatorSize = baseSize.clamp(0.0, 40.0); return Center( child: SizedBox( width: indicatorSize, height: indicatorSize, child: CircularProgressIndicator( value: downloadProgress.progress, // from 0.0 to 1.0 strokeWidth: max(indicatorSize * 0.1, 2.0), // 10% of size, but at least 2px so it’s visible ), ), ); }, errorWidget: (ctx, url, err) => const Icon(Icons.error), ) : Container( width: previewWidth, height: previewHeight, color: Theme.of(context).colorScheme.surfaceContainerHighest, child: Icon( Icons.image_not_supported, size: previewWidth * 0.6, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), title: Text(title ?? AppLocalizations.of(context)!.settingsImageTitle), description: Text(description ?? AppLocalizations.of(context)!.settingsImageHint), onPressed: (_) => _pickImage(context), ); }