Frontend first draft
This commit is contained in:
112
frontend/pweb/lib/pages/settings/widgets/image.dart
Normal file
112
frontend/pweb/lib/pages/settings/widgets/image.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
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),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user