fix: release/0.1.1 known issues. (#1984)

* fix: #1976 Adding a cover image via upload doesn't work

* fix: #1973 Using the mouse to highlight text very easy to miss the first letter

* fix: #1962 Disable but still show the AI assistants icon in the toolbar menu when an OpenAI key is not provided

* fix: #1964 Text on the UI

* fix: #1966 the loading icon too close to the edge

* fix: #1967  the summarize feature generates duplicate answers

* fix: flutter analyze
This commit is contained in:
Lucas.Xu 2023-03-14 01:06:08 +08:00 committed by GitHub
parent ad5213cfad
commit 3686eabcb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 296 additions and 37 deletions

View File

@ -344,16 +344,18 @@
"referencedGrid": "Referenced Grid",
"autoCompletionMenuItemName": "Auto Completion",
"autoGeneratorMenuItemName": "Auto Generator",
"autoGeneratorTitleName": "Open AI: Auto Generator",
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
"autoGeneratorLearnMore": "Learn more",
"autoGeneratorGenerate": "Generate",
"autoGeneratorHintText": "Tell us what you want to generate by OpenAI ...",
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
"smartEditTitleName": "Open AI: Smart Edit",
"smartEdit": "Smart Edit",
"smartEditTitleName": "OpenAI: Smart Edit",
"smartEditFixSpelling": "Fix spelling",
"smartEditSummarize": "Summarize",
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
"smartEditDisabled": "Connect OpenAI in Settings",
"cover": {
"changeCover": "Change Cover",
"colors": "Colors",

View File

@ -183,9 +183,7 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
],
],
toolbarItems: [
if (openAIKey != null && openAIKey!.isNotEmpty) ...[
smartEditItem,
]
smartEditItem,
],
themeData: theme.copyWith(extensions: [
...theme.extensions.values,

View File

@ -16,6 +16,7 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:path/path.dart' as path;
const String kLocalImagesKey = 'local_images';
@ -263,7 +264,7 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
if (path != null) {
final directory = await _coverPath();
final newPath = await File(path).copy(
'$directory/${path.split('/').last}',
'$directory/${path.split(path).last}}',
);
imageNames.add(newPath.path);
}
@ -274,7 +275,7 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
Future<String> _coverPath() async {
final directory = await getIt<SettingsLocationCubit>().fetchLocation();
return Directory('$directory/covers')
return Directory(path.join(directory, 'covers'))
.create(recursive: true)
.then((value) => value.path);
}

View File

@ -10,9 +10,9 @@ enum SmartEditAction {
String get toInstruction {
switch (this) {
case SmartEditAction.summarize:
return 'Make it shorter';
return 'Make this shorter and more concise:';
case SmartEditAction.fixSpelling:
return 'Fix all the spelling mistakes';
return 'Correct this to standard English:';
}
}
}

View File

@ -140,9 +140,12 @@ class _SmartEditInputState extends State<_SmartEditInput> {
}
Widget _buildResultWidget(BuildContext context) {
final loading = SizedBox.fromSize(
size: const Size.square(14),
child: const CircularProgressIndicator(),
final loading = Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: SizedBox.fromSize(
size: const Size.square(14),
child: const CircularProgressIndicator(),
),
);
if (result == null) {
return loading;
@ -222,7 +225,6 @@ class _SmartEditInputState extends State<_SmartEditInput> {
final texts = result!.asRight().choices.first.text.split('\n')
..removeWhere((element) => element.isEmpty);
assert(texts.length == selectedNodes.length);
final transaction = widget.editorState.transaction;
transaction.replaceTexts(
selectedNodes.toList(growable: false),
@ -254,7 +256,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
final edits = await openAIRepository.getEdits(
input: input,
instruction: instruction,
n: input.split('\n').length,
n: 1,
);
return edits.fold((error) async {
return dartz.Left(

View File

@ -1,10 +1,14 @@
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
ToolbarItem smartEditItem = ToolbarItem(
id: 'appflowy.toolbar.smart_edit',
@ -33,6 +37,20 @@ class _SmartEditWidget extends StatefulWidget {
}
class _SmartEditWidgetState extends State<_SmartEditWidget> {
bool isOpenAIEnabled = false;
@override
void initState() {
super.initState();
UserBackendService.getCurrentUserProfile().then((value) {
setState(() {
isOpenAIEnabled =
value.fold((l) => l.openaiKey.isNotEmpty, (r) => false);
});
});
}
@override
Widget build(BuildContext context) {
return PopoverActionList<SmartEditActionWrapper>(
@ -43,7 +61,9 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
buildChild: (controller) {
return FlowyIconButton(
hoverColor: Colors.transparent,
tooltipText: 'Smart Edit',
tooltipText: isOpenAIEnabled
? LocaleKeys.document_plugins_smartEdit.tr()
: LocaleKeys.document_plugins_smartEditDisabled.tr(),
preferBelow: false,
icon: const Icon(
Icons.lightbulb_outline,
@ -51,7 +71,11 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
color: Colors.white,
),
onPressed: () {
controller.show();
if (isOpenAIEnabled) {
controller.show();
} else {
_showError(LocaleKeys.document_plugins_smartEditDisabled.tr());
}
},
);
},
@ -97,4 +121,18 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
withUpdateCursor: false,
);
}
Future<void> _showError(String message) async {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
action: SnackBarAction(
label: LocaleKeys.button_Cancel.tr(),
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
content: FlowyText(message),
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:appflowy_backend/appflowy_backend.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import '../startup.dart';
@ -35,11 +36,11 @@ Future<Directory> appFlowyDocumentDirectory() async {
switch (integrationEnv()) {
case IntegrationMode.develop:
Directory documentsDir = await getApplicationDocumentsDirectory();
return Directory('${documentsDir.path}/flowy_dev').create();
return Directory(path.join(documentsDir.path, 'flowy_dev')).create();
case IntegrationMode.release:
Directory documentsDir = await getApplicationDocumentsDirectory();
return Directory('${documentsDir.path}/flowy').create();
return Directory(path.join(documentsDir.path, 'flowy')).create();
case IntegrationMode.test:
return Directory("${Directory.current.path}/.sandbox");
return Directory(path.join(Directory.current.path, '.sandbox'));
}
}

View File

@ -57,7 +57,10 @@ extension CommandExtension on EditorState {
Selection selection,
) {
List<String> res = [];
if (!selection.isCollapsed) {
if (selection.isSingle) {
final plainText = textNodes.first.toPlainText();
res.add(plainText.substring(selection.startIndex, selection.endIndex));
} else if (!selection.isCollapsed) {
for (var i = 0; i < textNodes.length; i++) {
final plainText = textNodes[i].toPlainText();
if (i == 0) {

View File

@ -291,46 +291,125 @@ extension TextTransaction on Transaction {
Selection selection,
List<String> texts,
) {
if (textNodes.isEmpty) {
if (textNodes.isEmpty || texts.isEmpty) {
return;
}
if (selection.isSingle) {
assert(textNodes.length == 1 && texts.length == 1);
replaceText(
textNodes.first,
selection.startIndex,
selection.length,
texts.first,
);
} else {
if (textNodes.length == texts.length) {
final length = textNodes.length;
for (var i = 0; i < length; i++) {
if (length == 1) {
replaceText(
textNodes.first,
selection.startIndex,
selection.endIndex - selection.startIndex,
texts.first,
);
return;
}
for (var i = 0; i < textNodes.length; i++) {
final textNode = textNodes[i];
final text = texts[i];
if (i == 0) {
replaceText(
textNode,
selection.startIndex,
textNode.toPlainText().length,
text,
texts.first,
);
} else if (i == length - 1) {
replaceText(
textNode,
0,
selection.endIndex,
text,
texts.last,
);
} else {
replaceText(
textNode,
0,
textNode.toPlainText().length,
text,
texts[i],
);
}
}
return;
}
if (textNodes.length > texts.length) {
final length = textNodes.length;
for (var i = 0; i < textNodes.length; i++) {
final textNode = textNodes[i];
if (i == 0) {
replaceText(
textNode,
selection.startIndex,
textNode.toPlainText().length,
texts.first,
);
} else if (i == length - 1) {
replaceText(
textNode,
0,
selection.endIndex,
texts.last,
);
} else {
if (i < texts.length - 1) {
replaceText(
textNode,
0,
textNode.toPlainText().length,
texts[i],
);
} else {
deleteNode(textNode);
}
}
}
afterSelection = null;
return;
}
if (textNodes.length < texts.length) {
final length = texts.length;
for (var i = 0; i < texts.length; i++) {
final text = texts[i];
if (i == 0) {
replaceText(
textNodes.first,
selection.startIndex,
textNodes.first.toPlainText().length,
text,
);
} else if (i == length - 1) {
replaceText(
textNodes.last,
0,
selection.endIndex,
text,
);
} else {
if (i < textNodes.length - 1) {
replaceText(
textNodes[i],
0,
textNodes[i].toPlainText().length,
text,
);
} else {
var path = textNodes.first.path;
var j = i - textNodes.length + length - 1;
while (j > 0) {
path = path.next;
j--;
}
insertNode(path, TextNode(delta: Delta()..insert(text)));
}
}
}
afterSelection = null;
return;
}
}
}

View File

@ -112,6 +112,7 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
isFocus = false;
this.showCursor = showCursor;
_focusNode.unfocus(disposition: disposition);
_onFocusChange(false);
}
@override

View File

@ -347,8 +347,9 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
void _onPanStart(DragStartDetails details) {
clearSelection();
_clearToolbar();
_panStartOffset = details.globalPosition;
_panStartOffset = details.globalPosition.translate(-3.0, 0);
_panStartScrollDy = editorState.service.scrollService?.dy;
_enableInteraction();

View File

@ -0,0 +1,132 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../infra/test_editor.dart';
Document createEmptyDocument() {
return Document(
root: Node(
type: 'editor',
),
);
}
void main() async {
group('transaction.dart', () {
testWidgets('test replaceTexts, textNodes.length == texts.length',
(tester) async {
TestWidgetsFlutterBinding.ensureInitialized();
final editor = tester.editor
..insertTextNode('0123456789')
..insertTextNode('0123456789')
..insertTextNode('0123456789')
..insertTextNode('0123456789');
await editor.startTesting();
await tester.pumpAndSettle();
expect(editor.documentLength, 4);
final selection = Selection(
start: Position(path: [0], offset: 4),
end: Position(path: [3], offset: 4),
);
final transaction = editor.editorState.transaction;
var textNodes = [0, 1, 2, 3]
.map((e) => editor.nodeAtPath([e])!)
.whereType<TextNode>()
.toList(growable: false);
final texts = ['ABC', 'ABC', 'ABC', 'ABC'];
transaction.replaceTexts(textNodes, selection, texts);
editor.editorState.apply(transaction);
await tester.pumpAndSettle();
expect(editor.documentLength, 4);
textNodes = [0, 1, 2, 3]
.map((e) => editor.nodeAtPath([e])!)
.whereType<TextNode>()
.toList(growable: false);
expect(textNodes[0].toPlainText(), '0123ABC');
expect(textNodes[1].toPlainText(), 'ABC');
expect(textNodes[2].toPlainText(), 'ABC');
expect(textNodes[3].toPlainText(), 'ABC456789');
});
testWidgets('test replaceTexts, textNodes.length > texts.length',
(tester) async {
TestWidgetsFlutterBinding.ensureInitialized();
final editor = tester.editor
..insertTextNode('0123456789')
..insertTextNode('0123456789')
..insertTextNode('0123456789')
..insertTextNode('0123456789')
..insertTextNode('0123456789');
await editor.startTesting();
await tester.pumpAndSettle();
expect(editor.documentLength, 5);
final selection = Selection(
start: Position(path: [0], offset: 4),
end: Position(path: [4], offset: 4),
);
final transaction = editor.editorState.transaction;
var textNodes = [0, 1, 2, 3, 4]
.map((e) => editor.nodeAtPath([e])!)
.whereType<TextNode>()
.toList(growable: false);
final texts = ['ABC', 'ABC', 'ABC', 'ABC'];
transaction.replaceTexts(textNodes, selection, texts);
editor.editorState.apply(transaction);
await tester.pumpAndSettle();
expect(editor.documentLength, 4);
textNodes = [0, 1, 2, 3]
.map((e) => editor.nodeAtPath([e])!)
.whereType<TextNode>()
.toList(growable: false);
expect(textNodes[0].toPlainText(), '0123ABC');
expect(textNodes[1].toPlainText(), 'ABC');
expect(textNodes[2].toPlainText(), 'ABC');
expect(textNodes[3].toPlainText(), 'ABC456789');
});
testWidgets('test replaceTexts, textNodes.length < texts.length',
(tester) async {
TestWidgetsFlutterBinding.ensureInitialized();
final editor = tester.editor
..insertTextNode('0123456789')
..insertTextNode('0123456789')
..insertTextNode('0123456789');
await editor.startTesting();
await tester.pumpAndSettle();
expect(editor.documentLength, 3);
final selection = Selection(
start: Position(path: [0], offset: 4),
end: Position(path: [2], offset: 4),
);
final transaction = editor.editorState.transaction;
var textNodes = [0, 1, 2]
.map((e) => editor.nodeAtPath([e])!)
.whereType<TextNode>()
.toList(growable: false);
final texts = ['ABC', 'ABC', 'ABC', 'ABC'];
transaction.replaceTexts(textNodes, selection, texts);
editor.editorState.apply(transaction);
await tester.pumpAndSettle();
expect(editor.documentLength, 4);
textNodes = [0, 1, 2, 3]
.map((e) => editor.nodeAtPath([e])!)
.whereType<TextNode>()
.toList(growable: false);
expect(textNodes[0].toPlainText(), '0123ABC');
expect(textNodes[1].toPlainText(), 'ABC');
expect(textNodes[2].toPlainText(), 'ABC');
expect(textNodes[3].toPlainText(), 'ABC456789');
});
});
}

View File

@ -830,7 +830,7 @@ packages:
source: hosted
version: "1.0.5"
path:
dependency: transitive
dependency: "direct main"
description:
name: path
url: "https://pub.dartlang.org"

View File

@ -95,6 +95,7 @@ dependencies:
window_manager: ^0.3.0
http: ^0.13.5
json_annotation: ^4.7.0
path: ^1.8.2
dev_dependencies:
flutter_lints: ^2.0.1