feat: media launch review (#6198)

* fix: unexpected thrown exceptions~

* fix: wrap toggle rebuild

* fix: copywriting + checkbox group

* fix: only show item menu on hover

* feat: click to open image in viewer

* feat: improve row detail ux

* fix: add delete confirmation dialog

---------

Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
This commit is contained in:
Mathias Mogensen 2024-09-09 07:42:23 +02:00 committed by GitHub
parent 006ea02bfe
commit 614f3ce81b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 422 additions and 338 deletions

View File

@ -147,6 +147,8 @@ class FieldOptionValues {
timeFormat: timeFormat,
includeTime: includeTime,
).writeToBuffer();
case FieldType.Media:
return MediaTypeOptionPB().writeToBuffer();
default:
throw UnimplementedError();
}

View File

@ -140,6 +140,9 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
final result = await DatabaseEventRenameMediaFile(payload).send();
result.fold((l) => null, (err) => Log.error(err));
},
toggleShowAllFiles: () {
emit(state.copyWith(showAllFiles: !state.showAllFiles));
},
);
},
);
@ -199,6 +202,8 @@ class MediaCellEvent with _$MediaCellEvent {
required String fileId,
required String name,
}) = _RenameFile;
const factory MediaCellEvent.toggleShowAllFiles() = _ToggleShowAllFiles;
}
@freezed
@ -207,6 +212,7 @@ class MediaCellState with _$MediaCellState {
UserProfilePB? userProfile,
required String fieldName,
@Default([]) List<MediaFilePB> files,
@Default(false) showAllFiles,
}) = _MediaCellState;
factory MediaCellState.initial(MediaCellController cellController) {

View File

@ -200,11 +200,15 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
Widget _buildHeaderIcon(GroupData customData) =>
switch (customData.fieldType) {
FieldType.Checkbox => FlowySvg(
customData.asCheckboxGroup()!.isCheck
? FlowySvgs.check_filled_s
: FlowySvgs.uncheck_s,
blendMode: BlendMode.dst,
FieldType.Checkbox => Padding(
padding: const EdgeInsets.only(right: 6),
child: FlowySvg(
customData.asCheckboxGroup()!.isCheck
? FlowySvgs.check_filled_s
: FlowySvgs.uncheck_s,
blendMode: BlendMode.dst,
size: const Size.square(18),
),
),
_ => const SizedBox.shrink(),
};

View File

@ -10,15 +10,18 @@ import 'package:appflowy/plugins/database/widgets/cell_editor/media_cell_editor.
import 'package:appflowy/plugins/database/widgets/media_file_type_ext.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_block_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_util.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/util/xfile_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart';
import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/media_entities.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:cross_file/cross_file.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
@ -26,11 +29,15 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
const _defaultFilesToDisplay = 5;
class DekstopRowDetailMediaCellSkin extends IEditableMediaCellSkin {
final mutex = PopoverMutex();
final addFileController = PopoverController();
@override
void dispose() {
addFileController.close();
mutex.dispose();
}
@ -46,38 +53,28 @@ class DekstopRowDetailMediaCellSkin extends IEditableMediaCellSkin {
child: Builder(
builder: (context) => BlocBuilder<MediaCellBloc, MediaCellState>(
builder: (context, state) {
final filesToDisplay = state.files.take(4).toList();
final extraCount = state.files.length - filesToDisplay.length;
final filesToDisplay = state.showAllFiles
? state.files
: state.files.take(_defaultFilesToDisplay).toList();
final extraCount = state.files.length - _defaultFilesToDisplay;
return SizedBox(
width: double.infinity,
child: LayoutBuilder(
builder: (context, constraints) {
if (state.files.isEmpty) {
return GestureDetector(
onTap: () => popoverController.show(),
child: AppFlowyPopover(
mutex: mutex,
controller: popoverController,
asBarrier: true,
constraints: const BoxConstraints(
minWidth: 250,
maxWidth: 250,
maxHeight: 400,
return _AddFileButton(
controller: addFileController,
direction: PopoverDirection.bottomWithLeftAligned,
mutex: mutex,
child: FlowyHover(
style: HoverStyle(
hoverColor:
AFThemeExtension.of(context).lightGreyHover,
),
offset: const Offset(0, 10),
margin: EdgeInsets.zero,
direction: PopoverDirection.bottomWithLeftAligned,
popupBuilder: (_) => BlocProvider.value(
value: context.read<MediaCellBloc>(),
child: const MediaCellEditor(),
),
onClose: () => cellContainerNotifier.isFocus = false,
child: FlowyHover(
style: HoverStyle(
hoverColor:
AFThemeExtension.of(context).lightGreyHover,
),
child: GestureDetector(
onTap: addFileController.show,
behavior: HitTestBehavior.translucent,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
@ -99,52 +96,6 @@ class DekstopRowDetailMediaCellSkin extends IEditableMediaCellSkin {
runSpacing: 12,
spacing: 12,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
AppFlowyPopover(
mutex: mutex,
controller: popoverController,
asBarrier: true,
constraints: const BoxConstraints(
minWidth: 250,
maxWidth: 250,
maxHeight: 400,
),
offset: const Offset(0, 10),
margin: EdgeInsets.zero,
direction: PopoverDirection.bottomWithLeftAligned,
popupBuilder: (_) => BlocProvider.value(
value: context.read<MediaCellBloc>(),
child: const MediaCellEditor(),
),
onClose: () =>
cellContainerNotifier.isFocus = false,
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
),
child: FlowyHover(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
child: Row(
children: [
const FlowySvg(FlowySvgs.edit_s),
const HSpace(4),
FlowyText.regular(
LocaleKeys.button_edit.tr(),
),
],
),
),
),
),
),
],
),
...filesToDisplay.map(
(file) => _FilePreviewRender(
key: ValueKey(file.id),
@ -153,13 +104,39 @@ class DekstopRowDetailMediaCellSkin extends IEditableMediaCellSkin {
mutex: mutex,
),
),
if (extraCount > 0)
_ExtraInfo(
extraCount: extraCount,
controller: popoverController,
SizedBox(
width: size,
height: size / 2,
child: _AddFileButton(
controller: addFileController,
mutex: mutex,
cellContainerNotifier: cellContainerNotifier,
child: FlowyHover(
resetHoverOnRebuild: false,
child: GestureDetector(
onTap: addFileController.show,
behavior: HitTestBehavior.translucent,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FlowySvg(
FlowySvgs.add_s,
size: Size.square(24),
),
const VSpace(4),
FlowyText(
LocaleKeys.grid_media_addFileOrImage.tr(),
),
],
),
),
),
),
),
),
if (extraCount > 0)
_ShowAllFilesButton(extraCount: extraCount),
],
);
},
@ -172,6 +149,91 @@ class DekstopRowDetailMediaCellSkin extends IEditableMediaCellSkin {
}
}
class _AddFileButton extends StatelessWidget {
const _AddFileButton({
this.mutex,
required this.controller,
this.direction = PopoverDirection.bottomWithCenterAligned,
required this.child,
});
final PopoverController controller;
final PopoverMutex? mutex;
final PopoverDirection direction;
final Widget child;
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
triggerActions: PopoverTriggerFlags.none,
controller: controller,
mutex: mutex,
offset: const Offset(0, 10),
direction: direction,
popupBuilder: (_) => FileUploadMenu(
onInsertLocalFile: (file) => insertLocalFile(
context,
file,
userProfile: context.read<MediaCellBloc>().state.userProfile,
documentId: context.read<MediaCellBloc>().rowId,
onUploadSuccess: (path, isLocalMode) {
final mediaCellBloc = context.read<MediaCellBloc>();
if (mediaCellBloc.isClosed) {
return;
}
mediaCellBloc.add(
MediaCellEvent.addFile(
url: path,
name: file.name,
uploadType: isLocalMode
? MediaUploadTypePB.LocalMedia
: MediaUploadTypePB.CloudMedia,
fileType: file.fileType.toMediaFileTypePB(),
),
);
controller.close();
},
),
onInsertNetworkFile: (url) {
if (url.isEmpty) return;
final uri = Uri.tryParse(url);
if (uri == null) {
return;
}
final fakeFile = XFile(uri.path);
MediaFileTypePB fileType = fakeFile.fileType.toMediaFileTypePB();
fileType = fileType == MediaFileTypePB.Other
? MediaFileTypePB.Link
: fileType;
String name =
uri.pathSegments.isNotEmpty ? uri.pathSegments.last : "";
if (name.isEmpty && uri.pathSegments.length > 1) {
name = uri.pathSegments[uri.pathSegments.length - 2];
} else if (name.isEmpty) {
name = uri.host;
}
context.read<MediaCellBloc>().add(
MediaCellEvent.addFile(
url: url,
name: name,
uploadType: MediaUploadTypePB.NetworkMedia,
fileType: fileType,
),
);
controller.close();
},
),
child: child,
);
}
}
class _FilePreviewRender extends StatefulWidget {
const _FilePreviewRender({
super.key,
@ -189,7 +251,11 @@ class _FilePreviewRender extends StatefulWidget {
}
class _FilePreviewRenderState extends State<_FilePreviewRender> {
final nameController = TextEditingController();
final errorMessage = ValueNotifier<String?>(null);
final controller = PopoverController();
bool isHovering = false;
bool isSelected = false;
@override
void dispose() {
@ -234,229 +300,233 @@ class _FilePreviewRenderState extends State<_FilePreviewRender> {
);
}
return Stack(
children: [
DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Corners.s6Radius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 2,
),
],
),
child: Column(
children: [
Container(
height: widget.size,
width: widget.size,
constraints: BoxConstraints(
maxHeight: widget.size < 150 ? 100 : 195,
minHeight: widget.size < 150 ? 100 : 195,
),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: AFThemeExtension.of(context).greyHover,
borderRadius: const BorderRadius.only(
topLeft: Corners.s6Radius,
topRight: Corners.s6Radius,
),
),
child: child,
),
Container(
height: 28,
width: widget.size,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).isLightMode
? Theme.of(context).cardColor
: AFThemeExtension.of(context).greyHover,
borderRadius: const BorderRadius.only(
bottomLeft: Corners.s6Radius,
bottomRight: Corners.s6Radius,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Center(
child: FlowyText.medium(
widget.file.name,
overflow: TextOverflow.ellipsis,
fontSize: 12,
color: AFThemeExtension.of(context).secondaryTextColor,
),
),
),
),
],
),
),
Positioned(top: 5, right: 5, child: FileItemMenu(file: widget.file)),
],
);
}
}
class FileItemMenu extends StatefulWidget {
const FileItemMenu({super.key, required this.file});
final MediaFilePB file;
@override
State<FileItemMenu> createState() => _FileItemMenuState();
}
class _FileItemMenuState extends State<FileItemMenu> {
final popoverController = PopoverController();
final nameController = TextEditingController();
final errorMessage = ValueNotifier<String?>(null);
@override
void initState() {
super.initState();
nameController.text = widget.file.name;
}
@override
void dispose() {
popoverController.close();
errorMessage.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
controller: popoverController,
controller: controller,
constraints: const BoxConstraints(maxWidth: 150),
direction: PopoverDirection.bottomWithRightAligned,
offset: const Offset(0, 5),
popupBuilder: (_) {
return SeparatedColumn(
separatorBuilder: () => const VSpace(4),
mainAxisSize: MainAxisSize.min,
children: [
if (widget.file.fileType == MediaFileTypePB.Image) ...[
FlowyButton(
onTap: () {
popoverController.close();
showDialog(
context: context,
builder: (_) => InteractiveImageViewer(
userProfile:
context.read<MediaCellBloc>().state.userProfile,
imageProvider: AFBlockImageProvider(
images: [
ImageBlockData(
url: widget.file.url,
type: widget.file.uploadType.toCustomImageType(),
),
],
onDeleteImage: (_) => context
.read<MediaCellBloc>()
.deleteFile(widget.file.id),
),
),
);
},
leftIcon: FlowySvg(
FlowySvgs.full_view_s,
color: Theme.of(context).iconTheme.color,
size: const Size.square(18),
),
text: FlowyText.regular(
LocaleKeys.settings_files_open.tr(),
color: AFThemeExtension.of(context).textColor,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
],
onClose: () => setState(() => isSelected = false),
popupBuilder: (_) => SeparatedColumn(
separatorBuilder: () => const VSpace(4),
mainAxisSize: MainAxisSize.min,
children: [
if (widget.file.fileType == MediaFileTypePB.Image) ...[
FlowyButton(
leftIcon: const FlowySvg(FlowySvgs.edit_s),
text: FlowyText.regular(LocaleKeys.grid_media_rename.tr()),
onTap: () {
popoverController.close();
nameController.text = widget.file.name;
nameController.selection = TextSelection(
baseOffset: 0,
extentOffset: nameController.text.length,
);
showCustomConfirmDialog(
controller.close();
showDialog(
context: context,
title: LocaleKeys.document_plugins_file_renameFile_title.tr(),
description: LocaleKeys
.document_plugins_file_renameFile_description
.tr(),
closeOnConfirm: false,
builder: (dialogContext) => FileRenameTextField(
nameController: nameController,
errorMessage: errorMessage,
onSubmitted: () => _saveName(context),
disposeController: false,
builder: (_) => InteractiveImageViewer(
userProfile:
context.read<MediaCellBloc>().state.userProfile,
imageProvider: AFBlockImageProvider(
images: [
ImageBlockData(
url: widget.file.url,
type: widget.file.uploadType.toCustomImageType(),
),
],
onDeleteImage: (_) => context
.read<MediaCellBloc>()
.deleteFile(widget.file.id),
),
),
confirmLabel: LocaleKeys.button_save.tr(),
onConfirm: () => _saveName(context),
);
},
),
FlowyButton(
onTap: () async => downloadMediaFile(
context,
widget.file,
userProfile: context.read<MediaCellBloc>().state.userProfile,
),
leftIcon: FlowySvg(
FlowySvgs.download_s,
FlowySvgs.full_view_s,
color: Theme.of(context).iconTheme.color,
size: const Size.square(18),
),
text: FlowyText.regular(
LocaleKeys.button_download.tr(),
color: AFThemeExtension.of(context).textColor,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
FlowyButton(
onTap: () => context.read<MediaCellBloc>().add(
MediaCellEvent.removeFile(
fileId: widget.file.id,
),
),
leftIcon: FlowySvg(
FlowySvgs.delete_s,
color: Theme.of(context).iconTheme.color,
size: const Size.square(18),
),
text: FlowyText.regular(
LocaleKeys.button_delete.tr(),
LocaleKeys.settings_files_open.tr(),
color: AFThemeExtension.of(context).textColor,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
],
);
},
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.all(Corners.s8Radius),
),
child: Padding(
padding: const EdgeInsets.all(3),
child: FlowyIconButton(
width: 20,
radius: BorderRadius.circular(0),
icon: FlowySvg(
FlowySvgs.three_dots_s,
FlowyButton(
leftIcon: FlowySvg(
FlowySvgs.edit_s,
color: Theme.of(context).iconTheme.color,
),
text: FlowyText.regular(
LocaleKeys.grid_media_rename.tr(),
color: AFThemeExtension.of(context).textColor,
),
onTap: () {
controller.close();
nameController.text = widget.file.name;
nameController.selection = TextSelection(
baseOffset: 0,
extentOffset: nameController.text.length,
);
showCustomConfirmDialog(
context: context,
title: LocaleKeys.document_plugins_file_renameFile_title.tr(),
description: LocaleKeys
.document_plugins_file_renameFile_description
.tr(),
closeOnConfirm: false,
builder: (dialogContext) => FileRenameTextField(
nameController: nameController,
errorMessage: errorMessage,
onSubmitted: () => _saveName(context),
disposeController: false,
),
confirmLabel: LocaleKeys.button_save.tr(),
onConfirm: () => _saveName(context),
);
},
),
FlowyButton(
onTap: () async => downloadMediaFile(
context,
widget.file,
userProfile: context.read<MediaCellBloc>().state.userProfile,
),
leftIcon: FlowySvg(
FlowySvgs.download_s,
color: Theme.of(context).iconTheme.color,
size: const Size.square(18),
),
text: FlowyText.regular(
LocaleKeys.button_download.tr(),
color: AFThemeExtension.of(context).textColor,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
FlowyButton(
onTap: () {
controller.close();
showConfirmDeletionDialog(
context: context,
name: widget.file.name,
description: LocaleKeys.grid_media_deleteFileDescription.tr(),
onConfirm: () => context
.read<MediaCellBloc>()
.add(MediaCellEvent.removeFile(fileId: widget.file.id)),
);
},
leftIcon: FlowySvg(
FlowySvgs.delete_s,
color: Theme.of(context).colorScheme.error,
size: const Size.square(18),
),
text: FlowyText.regular(
LocaleKeys.button_delete.tr(),
color: Theme.of(context).colorScheme.error,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
],
),
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: widget.file.fileType != MediaFileTypePB.Image
? null
: () => openInteractiveViewerFromFile(
context,
widget.file,
userProfile: context.read<MediaCellBloc>().state.userProfile,
onDeleteImage: (_) =>
context.read<MediaCellBloc>().deleteFile(widget.file.id),
),
child: FlowyHover(
isSelected: () => isSelected,
resetHoverOnRebuild: false,
onHover: (hovering) => setState(() => isHovering = hovering),
child: Stack(
children: [
DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Corners.s6Radius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 2,
),
],
),
child: Column(
children: [
Container(
height: widget.size,
width: widget.size,
constraints: BoxConstraints(
maxHeight: widget.size < 150 ? 100 : 195,
minHeight: widget.size < 150 ? 100 : 195,
),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: AFThemeExtension.of(context).greyHover,
borderRadius: const BorderRadius.only(
topLeft: Corners.s6Radius,
topRight: Corners.s6Radius,
),
),
child: child,
),
Container(
height: 28,
width: widget.size,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).isLightMode
? Theme.of(context).cardColor
: AFThemeExtension.of(context).greyHover,
borderRadius: const BorderRadius.only(
bottomLeft: Corners.s6Radius,
bottomRight: Corners.s6Radius,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Center(
child: FlowyText.medium(
widget.file.name,
overflow: TextOverflow.ellipsis,
fontSize: 12,
color:
AFThemeExtension.of(context).secondaryTextColor,
),
),
),
),
],
),
),
if (isHovering || isSelected)
Positioned(
top: 5,
right: 5,
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.all(Corners.s8Radius),
),
child: Padding(
padding: const EdgeInsets.all(3),
child: FlowyIconButton(
onPressed: () {
setState(() => isSelected = true);
controller.show();
},
width: 20,
radius: BorderRadius.circular(0),
icon: FlowySvg(
FlowySvgs.three_dots_s,
color: AFThemeExtension.of(context).textColor,
),
),
),
),
),
],
),
),
),
@ -476,57 +546,45 @@ class _FileItemMenuState extends State<FileItemMenu> {
}
}
class _ExtraInfo extends StatelessWidget {
const _ExtraInfo({
required this.extraCount,
required this.controller,
required this.mutex,
required this.cellContainerNotifier,
});
class _ShowAllFilesButton extends StatelessWidget {
const _ShowAllFilesButton({required this.extraCount});
final int extraCount;
final PopoverController controller;
final PopoverMutex mutex;
final CellContainerNotifier cellContainerNotifier;
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
key: const Key('extra_info'),
mutex: mutex,
controller: controller,
triggerActions: PopoverTriggerFlags.none,
constraints: const BoxConstraints(
minWidth: 250,
maxWidth: 250,
maxHeight: 400,
),
margin: EdgeInsets.zero,
direction: PopoverDirection.bottomWithLeftAligned,
popupBuilder: (_) => BlocProvider.value(
value: context.read<MediaCellBloc>(),
child: const MediaCellEditor(),
),
onClose: () => cellContainerNotifier.isFocus = false,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: controller.show,
child: FlowyHover(
resetHoverOnRebuild: false,
child: Container(
height: 38,
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AFThemeExtension.of(context).greyHover,
borderRadius: BorderRadius.circular(4),
),
child: FlowyText.regular(
LocaleKeys.grid_media_showMore.tr(args: ['$extraCount']),
lineHeight: 1,
),
final show = context.read<MediaCellBloc>().state.showAllFiles;
final label = show
? extraCount == 1
? LocaleKeys.grid_media_hideFile.tr()
: LocaleKeys.grid_media_hideFiles.tr(args: ['$extraCount'])
: extraCount == 1
? LocaleKeys.grid_media_showFile.tr()
: LocaleKeys.grid_media_showFiles.tr(args: ['$extraCount']);
final quarterTurns = show ? 1 : 3;
return SizedBox(
height: 30,
child: FlowyButton(
text: FlowyText.medium(
label,
lineHeight: 1.0,
color: Theme.of(context).hintColor,
),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
leftIcon: RotatedBox(
quarterTurns: quarterTurns,
child: FlowySvg(
FlowySvgs.arrow_left_s,
color: Theme.of(context).hintColor,
),
),
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6),
onTap: () => context
.read<MediaCellBloc>()
.add(const MediaCellEvent.toggleShowAllFiles()),
),
);
}

View File

@ -55,6 +55,7 @@ class _MediaCellEditorState extends State<MediaCellEditor> {
children: [
if (state.files.isNotEmpty) ...[
ReorderableListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
buildDefaultDragHandles: false,
itemBuilder: (_, index) => BlocProvider.value(
@ -465,19 +466,22 @@ class _MediaItemMenuState extends State<MediaItemMenu> {
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
FlowyButton(
onTap: () => context.read<MediaCellBloc>().add(
MediaCellEvent.removeFile(
fileId: widget.file.id,
),
),
onTap: () => showConfirmDeletionDialog(
context: context,
name: widget.file.name,
description: LocaleKeys.grid_media_deleteFileDescription.tr(),
onConfirm: () => context
.read<MediaCellBloc>()
.add(MediaCellEvent.removeFile(fileId: widget.file.id)),
),
leftIcon: FlowySvg(
FlowySvgs.delete_s,
color: Theme.of(context).iconTheme.color,
color: Theme.of(context).colorScheme.error,
size: const Size.square(18),
),
text: FlowyText.regular(
LocaleKeys.button_delete.tr(),
color: AFThemeExtension.of(context).textColor,
color: Theme.of(context).colorScheme.error,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,

View File

@ -196,6 +196,7 @@ class FieldActionCell extends StatelessWidget {
}
return FlowyButton(
resetHoverOnRebuild: false,
disable: !enable,
text: FlowyText.medium(
action.title(fieldInfo),

View File

@ -88,6 +88,7 @@ extension FieldTypeExtension on FieldType {
FieldType.Summary => const Color(0xFF6859A7),
FieldType.Time => const Color(0xFFFDEDA7),
FieldType.Translate => const Color(0xFF6859A7),
FieldType.Media => const Color(0xFFBE9090),
_ => throw UnimplementedError(),
};
}

View File

@ -1,12 +1,13 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
class FlowyIconTextButton extends StatelessWidget {
final Widget Function(bool onHover) textBuilder;
@ -160,6 +161,7 @@ class FlowyButton extends StatelessWidget {
final bool expand;
final Color? borderColor;
final Color? backgroundColor;
final bool resetHoverOnRebuild;
const FlowyButton({
super.key,
@ -185,6 +187,7 @@ class FlowyButton extends StatelessWidget {
this.expand = false,
this.borderColor,
this.backgroundColor,
this.resetHoverOnRebuild = true,
});
@override
@ -207,6 +210,7 @@ class FlowyButton extends StatelessWidget {
onTap: disable ? null : onTap,
onSecondaryTap: disable ? null : onSecondaryTap,
child: FlowyHover(
resetHoverOnRebuild: resetHoverOnRebuild,
cursor:
disable ? SystemMouseCursors.forbidden : SystemMouseCursors.click,
style: HoverStyle(

View File

@ -1469,11 +1469,15 @@
"download": "Download",
"open": "Open",
"delete": "Delete",
"moreFilesHint": "+{} file(s)",
"showMore": "There are {} more file(s), click to view",
"moreFilesHint": "+{}",
"addFileOrImage": "Add a file, image, or link",
"attachmentsHint": "{} attachment(s)",
"addFileMobile": "Add file"
"attachmentsHint": "{}",
"addFileMobile": "Add file",
"showFile": "Show 1 file",
"showFiles": "Show {} files",
"hideFile": "Hide 1 file",
"hideFiles": "Hide {} files",
"deleteFileDescription": "Are you sure you want to delete this file? This action is irreversible."
}
},
"document": {