diff --git a/example/lib/pages/components/file_picker.dart b/example/lib/pages/components/file_picker.dart index f2fd8bc..a856f5a 100644 --- a/example/lib/pages/components/file_picker.dart +++ b/example/lib/pages/components/file_picker.dart @@ -11,17 +11,17 @@ class FilePickerDemo extends StatefulWidget { class _FilePickerDemoState extends State { final ZdsFilePickerController controller = ZdsFilePickerController(); - static const pickerConfig = FilePickerConfig( + static const pickerConfig = ZdsFilePickerConfig( maxFilesAllowed: 5, maxFileSize: 2500000, maxVideoTimeInSeconds: 10, options: [ - FilePickerOptions.FILE, - FilePickerOptions.GIF, - FilePickerOptions.LINK, - FilePickerOptions.CAMERA, - FilePickerOptions.GALLERY, - FilePickerOptions.VIDEO, + ZdsFilePickerOptions.FILE, + ZdsFilePickerOptions.GIF, + ZdsFilePickerOptions.LINK, + ZdsFilePickerOptions.CAMERA, + ZdsFilePickerOptions.GALLERY, + ZdsFilePickerOptions.VIDEO, ], ); diff --git a/lib/src/components/organisms/chat/message_input.dart b/lib/src/components/organisms/chat/message_input.dart index 2e71514..b3b72b3 100644 --- a/lib/src/components/organisms/chat/message_input.dart +++ b/lib/src/components/organisms/chat/message_input.dart @@ -134,28 +134,28 @@ class ZdsMessageInputState extends State with SingleTickerProvi setState(() => __hasText = value); } - late final _moreConfig = FilePickerConfig( + late final _moreConfig = ZdsFilePickerConfig( maxFilesAllowed: 1, maxFileSize: widget.maxAttachSize, allowedExtensions: widget.allowedFileTypes, maxPixelSize: widget.maxPixelSize, options: [ - FilePickerOptions.FILE, - FilePickerOptions.GIF, - FilePickerOptions.GALLERY, - FilePickerOptions.VIDEO, - FilePickerOptions.CAMERA, + ZdsFilePickerOptions.FILE, + ZdsFilePickerOptions.GIF, + ZdsFilePickerOptions.GALLERY, + ZdsFilePickerOptions.VIDEO, + ZdsFilePickerOptions.CAMERA, ], ); - FilePickerConfig get _inlineConfig { - return FilePickerConfig( + ZdsFilePickerConfig get _inlineConfig { + return ZdsFilePickerConfig( maxFilesAllowed: 1, maxFileSize: widget.maxAttachSize, allowedExtensions: widget.allowedFileTypes, maxPixelSize: widget.maxPixelSize, options: [ - FilePickerOptions.CAMERA, + ZdsFilePickerOptions.CAMERA, ], ); } @@ -199,7 +199,7 @@ class ZdsMessageInputState extends State with SingleTickerProvi } } - void _onFilesChanged(List items) { + void _onFilesChanged(List items) { if (items.isEmpty) { return; } else if (items.first.content is XFile) { @@ -378,7 +378,7 @@ class ZdsMessageInputState extends State with SingleTickerProvi final modalController = ZdsFilePickerController(); final zetaColors = Zeta.of(context).colors; unawaited( - showZdsBottomSheet( + showZdsBottomSheet( enforceSheet: true, backgroundColor: zetaColors.surfacePrimary, context: context, diff --git a/lib/src/components/organisms/file_picker/file_annotations.dart b/lib/src/components/organisms/file_picker/file_annotations.dart index d0624bc..0d822c5 100644 --- a/lib/src/components/organisms/file_picker/file_annotations.dart +++ b/lib/src/components/organisms/file_picker/file_annotations.dart @@ -16,14 +16,14 @@ class ZdsImageAnnotationPostProcessor implements ZdsFilePostProcessor { final BuildContextProvider buildContext; @override - Future process(FilePickerConfig config, FileWrapper file) async { + Future process(ZdsFilePickerConfig config, ZdsFileWrapper file) async { if (kIsWeb) return file; if (file.isImage() && file.content != null) { final File originalFile = File(file.xFilePath); final XFile? result = await _editFile(buildContext.call(), originalFile); if (result != null) { - return FileWrapper(file.type, result); + return ZdsFileWrapper(file.type, result); } } diff --git a/lib/src/components/organisms/file_picker/file_compress.dart b/lib/src/components/organisms/file_picker/file_compress.dart index bb92f71..956b247 100644 --- a/lib/src/components/organisms/file_picker/file_compress.dart +++ b/lib/src/components/organisms/file_picker/file_compress.dart @@ -21,13 +21,13 @@ class ZdsFileCompressPostProcessor implements ZdsFilePostProcessor { const ZdsFileCompressPostProcessor(); @override - Future process(FilePickerConfig config, FileWrapper file) async { + Future process(ZdsFilePickerConfig config, ZdsFileWrapper file) async { if (kIsWeb) { return file; } else if (file.isImage()) { - return FileWrapper(file.type, await _compressImage(File(file.xFilePath), config)); + return ZdsFileWrapper(file.type, await _compressImage(File(file.xFilePath), config)); } else if (file.isVideo()) { - return FileWrapper( + return ZdsFileWrapper( file.type, await _compressVideo(File(file.xFilePath), config), ); @@ -36,7 +36,7 @@ class ZdsFileCompressPostProcessor implements ZdsFilePostProcessor { } } - Future _compressVideo(File video, FilePickerConfig config) async { + Future _compressVideo(File video, ZdsFilePickerConfig config) async { try { final String dir = await zdsTempDirectory(); final String fileExtension = path.extension(video.path).toLowerCase().replaceAll('.', ''); @@ -79,7 +79,7 @@ class ZdsFileCompressPostProcessor implements ZdsFilePostProcessor { return qualityMap[config] ?? VideoQuality.Res640x480Quality; } - Future _compressImage(File image, FilePickerConfig config) async { + Future _compressImage(File image, ZdsFilePickerConfig config) async { try { final int fileSize = config.maxFileSize == 0 ? _maxImageUploadSize : config.maxFileSize; final int h = config.maxPixelSize <= 0 ? 1080 : config.maxPixelSize; @@ -126,7 +126,7 @@ class ZdsFileCompressPostProcessor implements ZdsFilePostProcessor { } } - CompressFormat? _getCompressFormat(String extension, FilePickerConfig config) { + CompressFormat? _getCompressFormat(String extension, ZdsFilePickerConfig config) { final Iterable allowedExt = config.allowedExtensions.map((String e) => e.toLowerCase()); // If allowed file extension list is empty then return jpeg diff --git a/lib/src/components/organisms/file_picker/file_edit.dart b/lib/src/components/organisms/file_picker/file_edit.dart index ab17e93..a592d3f 100644 --- a/lib/src/components/organisms/file_picker/file_edit.dart +++ b/lib/src/components/organisms/file_picker/file_edit.dart @@ -17,7 +17,7 @@ class ZdsFileEditPostProcessor implements ZdsFilePostProcessor { final BuildContextProvider buildContext; @override - Future process(FilePickerConfig config, FileWrapper file) async { + Future process(ZdsFilePickerConfig config, ZdsFileWrapper file) async { if (kIsWeb) return file; if (file.isImage() && file.content != null) { @@ -42,7 +42,7 @@ class ZdsFileEditPostProcessor implements ZdsFilePostProcessor { await originalFile.delete(recursive: true); final File result = File(path.join(dir, path.basename(originalFile.absolute.path))); await result.writeAsBytes(bytes); - return FileWrapper(file.type, ZdsXFile.fromFile(result)); + return ZdsFileWrapper(file.type, ZdsXFile.fromFile(result)); } } diff --git a/lib/src/components/organisms/file_picker/file_picker.dart b/lib/src/components/organisms/file_picker/file_picker.dart index ca2c463..370bba5 100644 --- a/lib/src/components/organisms/file_picker/file_picker.dart +++ b/lib/src/components/organisms/file_picker/file_picker.dart @@ -37,11 +37,15 @@ export 'xfile.dart'; /// This method will be called once the file is selected typedef ZdsFileValidator = Future Function( ZdsFilePickerController controller, - FilePickerConfig config, - FileWrapper fileWrapper, - FilePickerOptions option, + ZdsFilePickerConfig config, + ZdsFileWrapper fileWrapper, + ZdsFilePickerOptions option, ); +/// The configuration used in a [ZdsFilePicker]. +@Deprecated('Use ZdsFilePickerConfig instead of FilePickerConfig.') +typedef FilePickerConfig = ZdsFilePickerConfig; + /// The configuration used in a [ZdsFilePicker]. /// /// [maxPixelSize] is used to set a maximum side dimension for an image. For example, if the [maxPixelSize] is 500, @@ -50,9 +54,9 @@ typedef ZdsFileValidator = Future Function( /// See also: /// /// * [ZdsFilePicker] -class FilePickerConfig { +class ZdsFilePickerConfig { /// Creates the configuration to use in the [ZdsFilePicker]. - const FilePickerConfig({ + const ZdsFilePickerConfig({ this.videoCompressionLevel = 3, this.maxVideoTimeInSeconds, this.maxFilesAllowed = 0, @@ -63,10 +67,10 @@ class FilePickerConfig { this.showCapturePreview = true, this.giphyKey, this.options = const [ - FilePickerOptions.VIDEO, - FilePickerOptions.FILE, - FilePickerOptions.CAMERA, - FilePickerOptions.GALLERY, + ZdsFilePickerOptions.VIDEO, + ZdsFilePickerOptions.FILE, + ZdsFilePickerOptions.CAMERA, + ZdsFilePickerOptions.GALLERY, ], }) : assert(maxPixelSize >= 0, 'maxPixelSize must be greater than or equal to 0'), assert(maxFileSize >= 0, 'maxFileSize must be greater than or equal to 0'); @@ -105,7 +109,7 @@ class FilePickerConfig { /// The options that will be shown in the file picker. /// /// Defaults to all of the options. - final List options; + final List options; /// The maximum video duration in seconds final int? maxVideoTimeInSeconds; @@ -131,17 +135,17 @@ class FilePickerConfig { /// Defaults to true. final bool showCapturePreview; - /// Creates a copy of this [FilePickerConfig], but with the given fields replaced wih the new values. - FilePickerConfig copyWith({ + /// Creates a copy of this [ZdsFilePickerConfig], but with the given fields replaced wih the new values. + ZdsFilePickerConfig copyWith({ int? videoCompressionLevel, int? maxFilesAllowed, int? maxFileSize, int? maxPixelSize, Set? allowedExtensions, bool? useLiveMediaOnly, - List? options, + List? options, }) { - return FilePickerConfig( + return ZdsFilePickerConfig( maxFilesAllowed: maxFilesAllowed ?? this.maxFilesAllowed, videoCompressionLevel: videoCompressionLevel ?? this.videoCompressionLevel, maxFileSize: maxFileSize ?? this.maxFileSize, @@ -196,7 +200,7 @@ enum ZdsOptionDisplay { /// /// See also: /// -/// * [FilePickerConfig], the configuration for this [ZdsFilePicker]. +/// * [ZdsFilePickerConfig], the configuration for this [ZdsFilePicker]. /// * [FilePicker], the interface this widget uses to select a file. /// * [ImagePicker], a widget used to select a single image and show its preview. /// * [ZdsFilePreview], which this component uses to show previews of the selected files. @@ -206,7 +210,8 @@ class ZdsFilePicker extends StatefulWidget { required this.controller, super.key, this.onChange, - this.config = const FilePickerConfig(), + this.onPicked, + this.config = const ZdsFilePickerConfig(), this.displayStyle = ZdsFilePickerDisplayStyle.vertical, this.validator = zdsValidator, this.onError = zdsFileError, @@ -242,13 +247,16 @@ class ZdsFilePicker extends StatefulWidget { final VisualDensity? visualDensity; /// The configuration for this file picker. - final FilePickerConfig config; + final ZdsFilePickerConfig config; /// The controller attached to this file picker. final ZdsFilePickerController controller; /// A function called whenever the selected items change, i.e. an item gets removed or added. - final void Function(List items)? onChange; + final void Function(List items)? onChange; + + /// A function called whenever one selection routine is done + final void Function(List items)? onPicked; /// Whether to allow the user to give a name to the links attached. /// @@ -268,7 +276,7 @@ class ZdsFilePicker extends StatefulWidget { /// A function called whenever any exception is thrown in selection process /// /// Defaults to [zdsFileError] - final void Function(BuildContext context, FilePickerConfig config, Exception exception)? onError; + final void Function(BuildContext context, ZdsFilePickerConfig config, Exception exception)? onError; @override ZdsFilePickerState createState() => ZdsFilePickerState(); @@ -282,14 +290,15 @@ class ZdsFilePicker extends StatefulWidget { ..add(EnumProperty('optionDisplay', optionDisplay)) ..add(EnumProperty('displayStyle', displayStyle)) ..add(DiagnosticsProperty('visualDensity', visualDensity)) - ..add(DiagnosticsProperty('config', config)) + ..add(DiagnosticsProperty('config', config)) ..add(DiagnosticsProperty('controller', controller)) - ..add(ObjectFlagProperty items)?>.has('onChange', onChange)) + ..add(ObjectFlagProperty items)?>.has('onChange', onChange)) ..add(DiagnosticsProperty('showLinkName', showLinkName)) ..add(IterableProperty('postProcessors', postProcessors)) ..add(ObjectFlagProperty.has('validator', validator)) + ..add(ObjectFlagProperty items)?>.has('onPicked', onPicked)) ..add( - ObjectFlagProperty.has( + ObjectFlagProperty.has( 'onError', onError, ), @@ -310,16 +319,18 @@ class ZdsFilePickerState extends State with AutomaticKeepAliveCli }); } + int _previewKey = DateTime.now().millisecondsSinceEpoch; + /// Current [ZdsFilePickerController] for this state ZdsFilePickerController get controller => widget.controller; - /// Current [FilePickerConfig] for this state - FilePickerConfig get config => widget.config; + /// Current [ZdsFilePickerConfig] for this state + ZdsFilePickerConfig get config => widget.config; - List get _allowedOptions { - final List list = [...config.options]; + List get _allowedOptions { + final List list = [...config.options]; if (config.giphyKey == null || config.giphyKey!.isEmpty) { - list.remove(FilePickerOptions.GIF); + list.remove(ZdsFilePickerOptions.GIF); } return list; @@ -341,7 +352,8 @@ class ZdsFilePickerState extends State with AutomaticKeepAliveCli super.build(context); final int maxFiles = config.maxFilesAllowed; final bool busy = _busy || controller.busy; - final List attachmentList = controller.items.where((FileWrapper element) => !element.isLink).toList(); + final List attachmentList = + controller.items.where((ZdsFileWrapper element) => !element.isLink).toList(); final bool disableWidget = _busy || controller.busy || (maxFiles != 0 && maxFiles <= (attachmentList.length + controller.remoteItems.length)); @@ -364,7 +376,7 @@ class ZdsFilePickerState extends State with AutomaticKeepAliveCli child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: _allowedOptions - .map((FilePickerOptions option) => _buildOption(context, option)) + .map((ZdsFilePickerOptions option) => _buildOption(context, option)) .toList() .divide(_divider) .toList(), @@ -388,38 +400,43 @@ class ZdsFilePickerState extends State with AutomaticKeepAliveCli /// Builds a [ZdsFilePicker as a horizontal row. Widget _buildHorizontalDisplay({double height = 120}) { - if (controller.items.isEmpty) return const SizedBox(); + if (controller.items.isEmpty) return const SizedBox.shrink(); return SizedBox( height: height, - child: Builder( - builder: (BuildContext context) { - return DefaultTextStyle( - style: Theme.of(context).textTheme.bodySmall!, - child: ZdsHorizontalList.builder( - isReducedHeight: true, - itemCount: controller.items.length, - itemBuilder: (BuildContext context, int index) { - final FileWrapper fileWrapper = controller.items[index]; - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - ZdsFilePreview( - file: fileWrapper, - size: height * 0.7, - onDelete: () => controller.removeFile(fileWrapper), - onTap: () async => controller.openFile(context, config, fileWrapper), - ), - if (fileWrapper.content is XFile) Center(child: ZdsFileSize(file: fileWrapper.content as XFile)), - ], - ); - }, - ), + child: ZdsHorizontalList.builder( + key: ValueKey(_previewKey), + isReducedHeight: true, + itemCount: controller.items.length, + itemBuilder: (BuildContext context, int index) { + final ZdsFileWrapper fileWrapper = controller.items[index]; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ZdsFilePreview( + file: fileWrapper, + size: height * 0.7, + onDelete: () => controller.removeFile(fileWrapper), + onTap: () => unawaited(controller.openFile(context, config, fileWrapper, onUpdate: _onUpdate)), + ), + if (fileWrapper.content is XFile) + DefaultTextStyle( + style: Theme.of(context).textTheme.bodySmall!, + child: ZdsFileSize(file: fileWrapper.content as XFile), + ), + ], ); }, ), ); } + void _onUpdate(int updateIndex) { + if (updateIndex >= 0) { + _previewKey = DateTime.now().microsecondsSinceEpoch; + setState(() {}); + } + } + /// Builds a [ZdsFilePicker] as a vertical column. Widget _buildVerticalDisplay() { if (controller.items.isEmpty) return const SizedBox(); @@ -430,7 +447,7 @@ class ZdsFilePickerState extends State with AutomaticKeepAliveCli separatorBuilder: (_, __) => const Divider(), physics: const NeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int index) { - final FileWrapper wrapper = controller.items[index]; + final ZdsFileWrapper wrapper = controller.items[index]; return SwipeActionCell( key: ObjectKey(wrapper.hashCode), backgroundColor: Theme.of(context).colorScheme.surface, @@ -485,32 +502,32 @@ class ZdsFilePickerState extends State with AutomaticKeepAliveCli super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('controller', controller)) - ..add(DiagnosticsProperty('config', config)); + ..add(DiagnosticsProperty('config', config)); } } -extension _FileWrapperIcon on FilePickerOptions { +extension _FileWrapperIcon on ZdsFilePickerOptions { IconData get icon { - final Map map = { - FilePickerOptions.FILE: ZdsIcons.upload, - FilePickerOptions.LINK: ZdsIcons.add_link, - FilePickerOptions.GALLERY: ZdsIcons.image, - FilePickerOptions.VIDEO: ZdsIcons.video, - FilePickerOptions.CAMERA: ZdsIcons.camera, - FilePickerOptions.GIF: Icons.gif_box_outlined, + final Map map = { + ZdsFilePickerOptions.FILE: ZdsIcons.upload, + ZdsFilePickerOptions.LINK: ZdsIcons.add_link, + ZdsFilePickerOptions.GALLERY: ZdsIcons.image, + ZdsFilePickerOptions.VIDEO: ZdsIcons.video, + ZdsFilePickerOptions.CAMERA: ZdsIcons.camera, + ZdsFilePickerOptions.GIF: Icons.gif_box_outlined, }; return map[this] ?? Icons.error; } String getLabel(BuildContext context) { - final Map map = { - FilePickerOptions.FILE: ComponentStrings.of(context).get('FILE', 'File'), - FilePickerOptions.LINK: ComponentStrings.of(context).get('LINK', 'Link'), - FilePickerOptions.GALLERY: ComponentStrings.of(context).get('GALLERY', 'Gallery'), - FilePickerOptions.VIDEO: ComponentStrings.of(context).get('VIDEO', 'Video'), - FilePickerOptions.CAMERA: ComponentStrings.of(context).get('CAMERA', 'Camera'), - FilePickerOptions.GIF: ComponentStrings.of(context).get('GIF', 'Gif'), + final Map map = { + ZdsFilePickerOptions.FILE: ComponentStrings.of(context).get('FILE', 'File'), + ZdsFilePickerOptions.LINK: ComponentStrings.of(context).get('LINK', 'Link'), + ZdsFilePickerOptions.GALLERY: ComponentStrings.of(context).get('GALLERY', 'Gallery'), + ZdsFilePickerOptions.VIDEO: ComponentStrings.of(context).get('VIDEO', 'Video'), + ZdsFilePickerOptions.CAMERA: ComponentStrings.of(context).get('CAMERA', 'Camera'), + ZdsFilePickerOptions.GIF: ComponentStrings.of(context).get('GIF', 'Gif'), }; return map[this] ?? ''; @@ -518,18 +535,18 @@ extension _FileWrapperIcon on FilePickerOptions { } extension _Methods on ZdsFilePickerState { - Future handleOptionAction(BuildContext context, FilePickerOptions option) async { - if (option == FilePickerOptions.FILE) { + Future handleOptionAction(BuildContext context, ZdsFilePickerOptions option) async { + if (option == ZdsFilePickerOptions.FILE) { await _handleFileAction(context); - } else if (option == FilePickerOptions.LINK) { + } else if (option == ZdsFilePickerOptions.LINK) { await _handleLinkAction(context); - } else if (option == FilePickerOptions.GALLERY) { + } else if (option == ZdsFilePickerOptions.GALLERY) { await _handleGalleryAction(context); - } else if (option == FilePickerOptions.VIDEO) { + } else if (option == ZdsFilePickerOptions.VIDEO) { await _handleVideoAction(context); - } else if (option == FilePickerOptions.CAMERA) { + } else if (option == ZdsFilePickerOptions.CAMERA) { await _handleCameraAction(context); - } else if (option == FilePickerOptions.GIF && config.giphyKey != null && config.giphyKey!.isNotEmpty) { + } else if (option == ZdsFilePickerOptions.GIF && config.giphyKey != null && config.giphyKey!.isNotEmpty) { await _handleGifAction(context); } } @@ -577,8 +594,8 @@ extension _Methods on ZdsFilePickerState { final Uri? uri = Uri.tryParse(urlField.value); if (uri != null) { final String name = nameField?.value ?? ''; - controller.addFiles([ - FileWrapper(FilePickerOptions.LINK, XUri(uri: uri, name: name.isEmpty ? uri.toString() : name)), + controller.addFiles([ + ZdsFileWrapper(ZdsFilePickerOptions.LINK, XUri(uri: uri, name: name.isEmpty ? uri.toString() : name)), ]); } } @@ -594,7 +611,15 @@ extension _Methods on ZdsFilePickerState { try { if (gif == null) return; _busy = true; - if (context.mounted) await onPicked(context, FileWrapper(FilePickerOptions.GIF, gif), FilePickerOptions.GIF); + if (context.mounted) { + final file = await onPicked( + context, + ZdsFileWrapper(ZdsFilePickerOptions.GIF, gif), + ZdsFilePickerOptions.GIF, + ); + + if (file != null) widget.onPicked?.call([file]); + } } on Exception catch (e) { if (context.mounted) widget.onError?.call(context, config, e); } finally { @@ -607,8 +632,13 @@ extension _Methods on ZdsFilePickerState { if (!mounted) return; final photo = await ZdsCamera.takePhoto(context, showPreview: config.showCapturePreview); if (photo != null && context.mounted) { - final FileWrapper file = FileWrapper(FilePickerOptions.CAMERA, photo); - await onPicked(context, file, FilePickerOptions.CAMERA); + final file = await onPicked( + context, + ZdsFileWrapper(ZdsFilePickerOptions.CAMERA, photo), + ZdsFilePickerOptions.CAMERA, + ); + + if (file != null) widget.onPicked?.call([file]); } } on Exception catch (e) { if (context.mounted) widget.onError?.call(context, config, e); @@ -628,8 +658,13 @@ extension _Methods on ZdsFilePickerState { ); if (video != null && context.mounted) { - final FileWrapper file = FileWrapper(FilePickerOptions.VIDEO, video); - await onPicked(context, file, FilePickerOptions.VIDEO); + final file = await onPicked( + context, + ZdsFileWrapper(ZdsFilePickerOptions.VIDEO, video), + ZdsFilePickerOptions.VIDEO, + ); + + if (file != null) widget.onPicked?.call([file]); } } on Exception catch (e) { if (context.mounted) widget.onError?.call(context, config, e); @@ -658,13 +693,13 @@ extension _Methods on ZdsFilePickerState { return; } - await _handleFileAction(context, type: fileType, option: FilePickerOptions.GALLERY); + await _handleFileAction(context, type: fileType, option: ZdsFilePickerOptions.GALLERY); } Future _handleFileAction( BuildContext context, { FileType type = FileType.any, - FilePickerOptions option = FilePickerOptions.FILE, + ZdsFilePickerOptions option = ZdsFilePickerOptions.FILE, }) async { try { final allowedFileExt = Set.from( @@ -695,8 +730,9 @@ extension _Methods on ZdsFilePickerState { ); if (result != null && context.mounted) { + final List processedFiles = []; for (final PlatformFile file in result.files) { - final itemsLength = controller.items.where((FileWrapper element) => !element.isLink).toList().length + + final itemsLength = controller.items.where((ZdsFileWrapper element) => !element.isLink).toList().length + controller.remoteItems.length; if (maxFilesAllowed != 0 && itemsLength >= maxFilesAllowed) { break; @@ -704,13 +740,23 @@ extension _Methods on ZdsFilePickerState { if (kIsWeb) { final String? mimeType = lookupMimeType(file.name); final XFile xfile = XFile.fromData(file.bytes!, name: file.name, length: file.size, mimeType: mimeType); - await onPicked(context, FileWrapper(option, xfile), option); + final fileWrapper = await onPicked(context, ZdsFileWrapper(option, xfile), option); + if (fileWrapper != null) { + processedFiles.add(fileWrapper); + } } else { final String? mimeType = lookupMimeType(file.path ?? ''); final XFile xfile = XFile(file.path!, name: file.name, length: file.size, mimeType: mimeType); - await onPicked(context, FileWrapper(option, xfile), option); + final fileWrapper = await onPicked(context, ZdsFileWrapper(option, xfile), option); + if (fileWrapper != null) { + processedFiles.add(fileWrapper); + } } } + + if (processedFiles.isNotEmpty) { + widget.onPicked?.call(processedFiles); + } } } on Exception catch (e) { if (context.mounted) widget.onError?.call(context, config, e); @@ -719,31 +765,37 @@ extension _Methods on ZdsFilePickerState { } } - Future onPicked( + Future onPicked( BuildContext context, - FileWrapper file, - FilePickerOptions option, + ZdsFileWrapper file, + ZdsFilePickerOptions option, ) async { try { - if (file.content == null) return; + if (file.content == null) return null; _busy = true; final FilePickerException? exception = await widget.validator?.call(controller, config, file, option); - FileWrapper input = file; + ZdsFileWrapper input = file; if (exception == null && widget.postProcessors != null) { for (final ZdsFilePostProcessor p in widget.postProcessors!) { input = await p.process(config, input); } } - if (exception != null && context.mounted) { - widget.onError?.call(context, config, exception); + if (exception != null) { + if (context.mounted) widget.onError?.call(context, config, exception); } else { - if (input.content != null) controller.addFiles([input]); + if (input.content != null) { + controller.addFiles([input]); + return input; + } } + + return null; } on Exception catch (e) { if (context.mounted) widget.onError?.call(context, config, e); + return null; } finally { _busy = false; } @@ -764,7 +816,7 @@ extension on ZdsFilePickerState { : const SizedBox.shrink(); } - Widget _buildOption(BuildContext context, FilePickerOptions option) { + Widget _buildOption(BuildContext context, ZdsFilePickerOptions option) { final bool isStandard = widget.visualDensity == VisualDensity.standard; final TextStyle? style = isStandard ? Theme.of(context).textTheme.bodyMedium @@ -827,20 +879,20 @@ class ZdsFilePickerController extends ChangeNotifier { } /// The selected files. - List items = []; + List items = []; /// file from server to check itemCount only List remoteItems = []; /// Programmatically adds a list of files to the linked [ZdsFilePicker]. - void addFiles(List files) { + void addFiles(List files) { items += files; notifyListeners(); } /// Programmatically removes files from the linked [ZdsFilePicker]. - int removeFile(FileWrapper file, {bool notify = true}) { - final int index = items.indexWhere((FileWrapper element) => element == file); + int removeFile(ZdsFileWrapper file, {bool notify = true}) { + final int index = items.indexWhere((ZdsFileWrapper element) => element == file); if (index >= 0) { items.removeAt(index); if (notify) notifyListeners(); @@ -852,19 +904,25 @@ class ZdsFilePickerController extends ChangeNotifier { /// Opens the provided file. /// /// Opens links in an InAppWebView, otherwise opens the file using the native viewer. - Future openFile(BuildContext context, FilePickerConfig config, FileWrapper file) async { + Future openFile( + BuildContext context, + ZdsFilePickerConfig config, + ZdsFileWrapper file, { + void Function(int updateIndex)? onUpdate, + }) async { if (file.content != null && file.content is XFile) { final XFile fileToOpen = file.content as XFile; if (file.isImage()) { // Edit the file final ZdsFileEditPostProcessor editPostProcessor = ZdsFileEditPostProcessor(() => context); - final FileWrapper editedFile = await editPostProcessor.process(config, file); + final ZdsFileWrapper editedFile = await editPostProcessor.process(config, file); if (editedFile.content != file.content) { - const ZdsFileCompressPostProcessor compressPostProcessor = ZdsFileCompressPostProcessor(); - final FileWrapper compressedFile = await compressPostProcessor.process(config, editedFile); - final int index = removeFile(file); + final renamedFile = await const ZdsFileRenamePostProcessor().process(config, editedFile); + final compressedFile = await const ZdsFileCompressPostProcessor().process(config, renamedFile); + final index = removeFile(file, notify: false); items.insert(index, compressedFile); notifyListeners(); + onUpdate?.call(index); } } else { await OpenFilex.open(fileToOpen.path); diff --git a/lib/src/components/organisms/file_picker/file_post_processor.dart b/lib/src/components/organisms/file_picker/file_post_processor.dart index 3cf14f0..6d69872 100644 --- a/lib/src/components/organisms/file_picker/file_post_processor.dart +++ b/lib/src/components/organisms/file_picker/file_post_processor.dart @@ -14,7 +14,7 @@ typedef BuildContextProvider = BuildContext Function(); // ignore: one_member_abstracts abstract class ZdsFilePostProcessor { /// method that is to be implemented. - Future process(FilePickerConfig config, FileWrapper wrapper); + Future process(ZdsFilePickerConfig config, ZdsFileWrapper wrapper); } /// Default post processors diff --git a/lib/src/components/organisms/file_picker/file_rename.dart b/lib/src/components/organisms/file_picker/file_rename.dart index f900abf..a868989 100644 --- a/lib/src/components/organisms/file_picker/file_rename.dart +++ b/lib/src/components/organisms/file_picker/file_rename.dart @@ -13,31 +13,31 @@ class ZdsFileRenamePostProcessor implements ZdsFilePostProcessor { const ZdsFileRenamePostProcessor(); @override - Future process(FilePickerConfig config, FileWrapper file) async { + Future process(ZdsFilePickerConfig config, ZdsFileWrapper file) async { if (kIsWeb || file.content is! XFile) return file; final String photoDir = path.dirname(file.xFilePath); final String epoch = DateFormat('yyyyMMdd_HHmmssSSS').format(DateTime.now()); final String fileName = '${file.type.toPrefix()}_$epoch${path.extension(file.xFilePath)}'; final String newPath = path.join(photoDir, fileName); final File photoFile = File(file.xFilePath).renameSync(newPath); - return FileWrapper(file.type, ZdsXFile.fromFile(photoFile)); + return ZdsFileWrapper(file.type, ZdsXFile.fromFile(photoFile)); } } -extension on FilePickerOptions { +extension on ZdsFilePickerOptions { String toPrefix() { switch (this) { - case FilePickerOptions.CAMERA: + case ZdsFilePickerOptions.CAMERA: return 'IMG'; - case FilePickerOptions.FILE: + case ZdsFilePickerOptions.FILE: return 'FIL'; - case FilePickerOptions.GALLERY: + case ZdsFilePickerOptions.GALLERY: return 'GAL'; - case FilePickerOptions.GIF: + case ZdsFilePickerOptions.GIF: return 'GIF'; - case FilePickerOptions.LINK: + case ZdsFilePickerOptions.LINK: return 'LNK'; - case FilePickerOptions.VIDEO: + case ZdsFilePickerOptions.VIDEO: return 'VID'; } } diff --git a/lib/src/components/organisms/file_picker/file_validator.dart b/lib/src/components/organisms/file_picker/file_validator.dart index f2b0d1a..0066b0d 100644 --- a/lib/src/components/organisms/file_picker/file_validator.dart +++ b/lib/src/components/organisms/file_picker/file_validator.dart @@ -58,16 +58,16 @@ enum PickerExceptionType { /// Default file validator Future zdsValidator( ZdsFilePickerController controller, - FilePickerConfig config, - FileWrapper wrapper, - FilePickerOptions option, + ZdsFilePickerConfig config, + ZdsFileWrapper wrapper, + ZdsFilePickerOptions option, ) async { final dynamic file = wrapper.content; if (file is! XFile) return null; //file type check if [useLiveMediaOnly] is true and //option will be [FilePickerOptions.FILE] - if (config.useLiveMediaOnly && option == FilePickerOptions.FILE) { + if (config.useLiveMediaOnly && option == ZdsFilePickerOptions.FILE) { final allowedFileTypes = getAllowedFileBrowserTypes( useLiveMediaOnly: config.useLiveMediaOnly, allowedFileTypes: config.allowedExtensions, @@ -96,7 +96,7 @@ Future zdsValidator( } /// Default error handler -void zdsFileError(BuildContext context, FilePickerConfig config, Exception exception) { +void zdsFileError(BuildContext context, ZdsFilePickerConfig config, Exception exception) { String message(Exception exception) { if (exception is FilePickerException) { return exception.type.message(context, args: exception.args); diff --git a/lib/src/components/organisms/file_picker/file_wrapper.dart b/lib/src/components/organisms/file_picker/file_wrapper.dart index e19ee48..a6abeff 100644 --- a/lib/src/components/organisms/file_picker/file_wrapper.dart +++ b/lib/src/components/organisms/file_picker/file_wrapper.dart @@ -9,7 +9,11 @@ import 'file_picker.dart'; /// Extension on FilePickerException to show message /// Types of files the [ZdsFilePicker] can be used to pick. -enum FilePickerOptions { +@Deprecated('Use ZdsFilePickerOptions instead of FilePickerOptions.') +typedef FilePickerOptions = ZdsFilePickerOptions; + +/// Types of files the [ZdsFilePicker] can be used to pick. +enum ZdsFilePickerOptions { /// Opens native video file picker. VIDEO, @@ -29,14 +33,18 @@ enum FilePickerOptions { GIF } +/// Wrapper around files picked using [ZdsFilePicker]. +@Deprecated('Use ZdsFileWrapper instead of FileWrapper.') +typedef FileWrapper = ZdsFileWrapper; + /// Wrapper around files picked using [ZdsFilePicker]. @immutable -class FileWrapper { - /// Constructs a [FileWrapper]. - const FileWrapper(this.type, this.content); +class ZdsFileWrapper { + /// Constructs a [ZdsFileWrapper]. + const ZdsFileWrapper(this.type, this.content); /// The type of file wrapped. - final FilePickerOptions type; + final ZdsFilePickerOptions type; /// The content of the picked file. final dynamic content; @@ -114,7 +122,7 @@ class FileWrapper { int get hashCode => type.hashCode ^ content.hashCode; @override - bool operator ==(covariant FileWrapper other) { + bool operator ==(covariant ZdsFileWrapper other) { // ignore: avoid_dynamic_calls if (other.content.runtimeType != content.runtimeType) return false; if (content is XFile && other.content is XFile) { diff --git a/lib/src/components/organisms/file_picker/image_crop.dart b/lib/src/components/organisms/file_picker/image_crop.dart index fc9ce4f..06c2bca 100644 --- a/lib/src/components/organisms/file_picker/image_crop.dart +++ b/lib/src/components/organisms/file_picker/image_crop.dart @@ -17,7 +17,7 @@ class ZdsImageCropPostProcessor implements ZdsFilePostProcessor { final BuildContextProvider buildContext; @override - Future process(FilePickerConfig config, FileWrapper file) async { + Future process(ZdsFilePickerConfig config, ZdsFileWrapper file) async { if (kIsWeb) return file; if (file.isImage() && file.content != null) { @@ -40,10 +40,10 @@ class ZdsImageCropPostProcessor implements ZdsFilePostProcessor { await originalFile.delete(recursive: true); final result = File(path.join(dir, path.basename(originalFile.absolute.path))); await result.writeAsBytes(bytes); - return FileWrapper(file.type, ZdsXFile.fromFile(result)); + return ZdsFileWrapper(file.type, ZdsXFile.fromFile(result)); } } // Clicking cancel returns an empty file. - return const FileWrapper(FilePickerOptions.GALLERY, null); + return const ZdsFileWrapper(ZdsFilePickerOptions.GALLERY, null); } } diff --git a/lib/src/components/organisms/file_preview.dart b/lib/src/components/organisms/file_preview.dart index 565d018..b944e6a 100644 --- a/lib/src/components/organisms/file_preview.dart +++ b/lib/src/components/organisms/file_preview.dart @@ -33,7 +33,7 @@ class ZdsFilePreview extends StatelessWidget { }); /// The file that needs to be previewed. - final FileWrapper file; + final ZdsFileWrapper file; /// The maximum preview size. /// @@ -121,10 +121,10 @@ class ZdsFilePreview extends StatelessWidget { : const SizedBox(); } - Widget _getFile(FileWrapper file) { + Widget _getFile(ZdsFileWrapper file) { return Builder( builder: (BuildContext context) { - final bool isUrl = file.type == FilePickerOptions.LINK; + final bool isUrl = file.type == ZdsFilePickerOptions.LINK; final themeData = Theme.of(context); return Column( mainAxisSize: MainAxisSize.min, @@ -154,7 +154,7 @@ class ZdsFilePreview extends StatelessWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty('file', file)) + ..add(DiagnosticsProperty('file', file)) ..add(DoubleProperty('size', size)) ..add(DiagnosticsProperty('useCard', useCard)) ..add(ObjectFlagProperty.has('onDelete', onDelete)) diff --git a/lib/src/components/organisms/html_preview/table_html_extension.dart b/lib/src/components/organisms/html_preview/table_html_extension.dart index faf4f3a..94977c8 100644 --- a/lib/src/components/organisms/html_preview/table_html_extension.dart +++ b/lib/src/components/organisms/html_preview/table_html_extension.dart @@ -23,6 +23,65 @@ const zdsTableTags = { 'ul', 'li', 'colgroup', + 'hr', + 'a', + 'abbr', + 'acronym', + 'address', + 'b', + 'bdi', + 'bdo', + 'big', + 'cite', + 'code', + 'data', + 'del', + 'dfn', + 'em', + 'font', + 'i', + 'ins', + 'kbd', + 'mark', + 'q', + 'rt', + 's', + 'samp', + 'small', + 'span', + 'strike', + 'strong', + 'sub', + 'sup', + 'time', + 'tt', + 'u', + 'var', + 'wbr', + 'article', + 'aside', + 'body', + 'center', + 'dd', + 'dl', + 'dt', + 'figcaption', + 'figure', + 'footer', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'header', + 'html', + 'main', + 'nav', + 'noscript', + 'pre', + 'section', + 'summary', }; /// [TableHtmlExtension] adds support for the element to the flutter_html library. diff --git a/lib/src/utils/localizations/translation.dart b/lib/src/utils/localizations/translation.dart index 6c7c0bc..e1a825a 100644 --- a/lib/src/utils/localizations/translation.dart +++ b/lib/src/utils/localizations/translation.dart @@ -13,7 +13,7 @@ import '../../../zds_flutter.dart'; class ComponentStrings { /// Constructs a [ComponentStrings]. /// default value for [debugStrings] is false. - ComponentStrings(this.locale, {this.debugStrings = false}); + ComponentStrings(this.locale, {this.debugStrings = false, this.testing = false}); /// Default locale of the components is English. static const Locale defaultLocale = Locale('en'); @@ -28,6 +28,9 @@ class ComponentStrings { /// If true then [get] method returns key instead of value final bool debugStrings; + ///Running in test mode + final bool testing; + /// Delegate used to get the translated strings. static LocalizationsDelegate delegate = ComponentDelegate(); @@ -147,7 +150,7 @@ class ComponentStrings { /// Loads strings from file and sets [_strings]. Future load() async { // Loading strings from assets - _strings = await _loadStrings(); + _strings = testing ? {} : await _loadStrings(); return this; } @@ -202,7 +205,7 @@ abstract class ComponentDeltaProvider { /// Delegate to get translations for these components. class ComponentDelegate extends LocalizationsDelegate { /// Constructs a [ComponentDelegate]. - ComponentDelegate({this.deltaProvider, this.debugStrings = false}); + ComponentDelegate({this.deltaProvider, this.debugStrings = false, this.testing = false}); /// Custom string delta final ComponentDeltaProvider? deltaProvider; @@ -210,15 +213,21 @@ class ComponentDelegate extends LocalizationsDelegate { ///debug strings final bool debugStrings; + ///Running in test mode + final bool testing; + @override Future load(Locale locale) async { - final ComponentStrings strings = await ComponentStrings(locale).load(); + final ComponentStrings strings = ComponentStrings(locale, testing: testing, debugStrings: debugStrings); + await strings.load(); + if (deltaProvider != null) { final Map delta = await deltaProvider!.loadDelta(locale); if (delta.isNotEmpty) { strings.update(delta); } } + return strings; }