Frontend first draft

This commit is contained in:
Arseni
2025-11-13 15:06:15 +03:00
parent e47f343afb
commit ddb54ddfdc
504 changed files with 25498 additions and 1 deletions

View 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 its 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),
);
}