113 lines
3.6 KiB
Dart
113 lines
3.6 KiB
Dart
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<void> 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<void> _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),
|
||
);
|
||
}
|