feat: add uploaded at & improvements

This commit is contained in:
Mathias Mogensen 2024-07-31 02:40:25 +02:00
parent 61d30fad6c
commit fd3f850d63
5 changed files with 112 additions and 26 deletions

View File

@ -7,12 +7,14 @@ import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart'; import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart';
import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_file.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
@ -20,6 +22,7 @@ import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:cross_file/cross_file.dart';
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
@ -195,9 +198,27 @@ class _DocumentPageState extends State<DocumentPage>
} }
final isLocalMode = context.read<DocumentBloc>().isLocalMode; final isLocalMode = context.read<DocumentBloc>().isLocalMode;
final List<XFile> imageFiles = [];
final List<XFile> otherfiles = [];
for (final file in details.files) {
if (file.mimeType?.startsWith('image/') ??
false || imgExtensionRegex.hasMatch(file.name)) {
imageFiles.add(file);
} else {
otherfiles.add(file);
}
}
await editorState!.dropImages( await editorState!.dropImages(
data.dropTarget!, data.dropTarget!,
details.files, imageFiles,
widget.view.id,
isLocalMode,
);
await editorState!.dropFiles(
data.dropTarget!,
otherfiles,
widget.view.id, widget.view.id,
isLocalMode, isLocalMode,
); );

View File

@ -0,0 +1,40 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_block.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_util.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:cross_file/cross_file.dart';
extension PasteFromFile on EditorState {
Future<void> dropFiles(
Node dropNode,
List<XFile> files,
String documentId,
bool isLocalMode,
) async {
for (final file in files) {
String? path;
FileUrlType? type;
if (isLocalMode) {
path = await saveFileToLocalStorage(file.path);
type = FileUrlType.local;
} else {
(path, _) = await saveFileToCloudStorage(file.path, documentId);
type = FileUrlType.cloud;
}
if (path == null) {
continue;
}
final t = transaction
..insertNode(
dropNode.path,
fileNode(
url: path,
type: type,
name: file.name,
),
);
await apply(t);
}
}
}

View File

@ -90,12 +90,15 @@ enum FileUrlType {
Node fileNode({ Node fileNode({
required String url, required String url,
FileUrlType type = FileUrlType.local, FileUrlType type = FileUrlType.local,
String? name,
}) { }) {
return Node( return Node(
type: FileBlockKeys.type, type: FileBlockKeys.type,
attributes: { attributes: {
FileBlockKeys.url: url, FileBlockKeys.url: url,
FileBlockKeys.urlType: type.toIntValue(), FileBlockKeys.urlType: type.toIntValue(),
FileBlockKeys.name: name,
FileBlockKeys.uploadedAt: DateTime.now().millisecondsSinceEpoch,
}, },
); );
} }
@ -149,7 +152,6 @@ class FileBlockComponentState extends State<FileBlockComponent>
final showActionsNotifier = ValueNotifier<bool>(false); final showActionsNotifier = ValueNotifier<bool>(false);
final controller = PopoverController(); final controller = PopoverController();
final menuController = PopoverController(); final menuController = PopoverController();
final menuMutex = PopoverMutex();
late final editorState = Provider.of<EditorState>(context, listen: false); late final editorState = Provider.of<EditorState>(context, listen: false);
@ -157,26 +159,6 @@ class FileBlockComponentState extends State<FileBlockComponent>
bool isDragging = false; bool isDragging = false;
bool isHovering = false; bool isHovering = false;
@override
void initState() {
super.initState();
final url = node.attributes[FileBlockKeys.url] as String?;
if (url != null && url.isNotEmpty) {
// If the name attribute is not set, extract the file name from the url.
final name = node.attributes[FileBlockKeys.name] as String?;
if (name == null || name.isEmpty) {
final name = Uri.tryParse(url)?.pathSegments.last ?? url;
final attributes = node.attributes;
attributes[FileBlockKeys.name] = name;
final transaction = editorState.transaction;
transaction.updateNode(node, attributes);
editorState.apply(transaction);
}
}
}
@override @override
void didChangeDependencies() { void didChangeDependencies() {
dropManagerState = context.read<EditorDropManagerState>(); dropManagerState = context.read<EditorDropManagerState>();
@ -202,8 +184,9 @@ class FileBlockComponentState extends State<FileBlockComponent>
opaque: false, opaque: false,
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: onTap: url != null && url.isNotEmpty
url != null && url.isNotEmpty ? () => afLaunchUrlString(url) : null, ? () => afLaunchUrlString(url)
: controller.show,
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isHovering color: isHovering
@ -328,7 +311,6 @@ class FileBlockComponentState extends State<FileBlockComponent>
onTap: menuController.show, onTap: menuController.show,
child: AppFlowyPopover( child: AppFlowyPopover(
controller: menuController, controller: menuController,
mutex: menuMutex,
triggerActions: PopoverTriggerFlags.none, triggerActions: PopoverTriggerFlags.none,
direction: PopoverDirection.bottomWithRightAligned, direction: PopoverDirection.bottomWithRightAligned,
onClose: () { onClose: () {
@ -406,6 +388,7 @@ class FileBlockComponentState extends State<FileBlockComponent>
FileBlockKeys.url: url, FileBlockKeys.url: url,
FileBlockKeys.urlType: urlType.toIntValue(), FileBlockKeys.urlType: urlType.toIntValue(),
FileBlockKeys.name: name, FileBlockKeys.name: name,
FileBlockKeys.uploadedAt: DateTime.now().millisecondsSinceEpoch,
}); });
await editorState.apply(transaction); await editorState.apply(transaction);
} }
@ -428,6 +411,7 @@ class FileBlockComponentState extends State<FileBlockComponent>
FileBlockKeys.url: url, FileBlockKeys.url: url,
FileBlockKeys.urlType: FileUrlType.network.toIntValue(), FileBlockKeys.urlType: FileUrlType.network.toIntValue(),
FileBlockKeys.name: name, FileBlockKeys.name: name,
FileBlockKeys.uploadedAt: DateTime.now().millisecondsSinceEpoch,
}); });
await editorState.apply(transaction); await editorState.apply(transaction);
} }

View File

@ -3,12 +3,15 @@ import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_block.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_block.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FileBlockMenu extends StatefulWidget { class FileBlockMenu extends StatefulWidget {
const FileBlockMenu({ const FileBlockMenu({
@ -41,9 +44,25 @@ class _FileBlockMenuState extends State<FileBlockMenu> {
); );
} }
@override
void dispose() {
errorMessage.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final uploadedAtInMS =
widget.node.attributes[FileBlockKeys.uploadedAt] as int?;
final uploadedAt = uploadedAtInMS != null
? DateTime.fromMillisecondsSinceEpoch(uploadedAtInMS)
: null;
final dateFormat = context.read<AppearanceSettingsCubit>().state.dateFormat;
final urlType =
FileUrlType.fromIntValue(widget.node.attributes[FileBlockKeys.urlType]);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
HoverButton( HoverButton(
@ -84,6 +103,25 @@ class _FileBlockMenuState extends State<FileBlockMenu> {
widget.controller.close(); widget.controller.close();
}, },
), ),
if (uploadedAt != null) ...[
const Divider(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: FlowyText.regular(
[FileUrlType.cloud, FileUrlType.local].contains(urlType)
? LocaleKeys.document_plugins_file_uploadedAt.tr(
args: [dateFormat.formatDate(uploadedAt, false)],
)
: LocaleKeys.document_plugins_file_linkedAt.tr(
args: [dateFormat.formatDate(uploadedAt, false)],
),
fontSize: 14,
maxLines: 2,
color: Theme.of(context).hintColor,
),
),
const VSpace(2),
],
], ],
); );
} }
@ -133,6 +171,7 @@ class _RenameTextFieldState extends State<_RenameTextField> {
@override @override
void dispose() { void dispose() {
widget.errorMessage.removeListener(_setState); widget.errorMessage.removeListener(_setState);
widget.nameController.dispose();
super.dispose(); super.dispose();
} }

View File

@ -1580,7 +1580,9 @@
"title": "Rename file", "title": "Rename file",
"description": "Enter the new name for this file", "description": "Enter the new name for this file",
"nameEmptyError": "File name cannot be left empty." "nameEmptyError": "File name cannot be left empty."
} },
"uploadedAt": "Uploaded on {}",
"linkedAt": "Link added on {}"
} }
}, },
"outlineBlock": { "outlineBlock": {