mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: sync release 0.1.1 (#2075)
This commit is contained in:
parent
92878d7e89
commit
98f1ac52b4
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,5 +1,17 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## Version 0.1.1 - 03/21/2023
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
- AppFlowy brings the power of OpenAI into your AppFlowy pages. Ask AI to write anything for you in AppFlowy.
|
||||||
|
- Support adding a cover image to your page, making your pages beautiful.
|
||||||
|
- More shortcuts become available. Click on '?' at the bottom right to access our shortcut guide.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
## Version 0.1.0 - 02/09/2023
|
## Version 0.1.0 - 02/09/2023
|
||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
@ -23,7 +23,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
|||||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||||
LIB_NAME = "dart_ffi"
|
LIB_NAME = "dart_ffi"
|
||||||
CURRENT_APP_VERSION = "0.1.0"
|
CURRENT_APP_VERSION = "0.1.1"
|
||||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||||
PRODUCT_NAME = "AppFlowy"
|
PRODUCT_NAME = "AppFlowy"
|
||||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||||
|
@ -1,10 +1,30 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: fa5883b78e566877613ad1ccb48dd92075cb5c23
|
revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
||||||
channel: dev
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
||||||
|
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
||||||
|
- platform: windows
|
||||||
|
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
||||||
|
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
@ -45,7 +45,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.example.appflowy_flutter"
|
applicationId "io.appflowy.appflowy"
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.appflowy_flutter">
|
package="io.appflowy.appflowy">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.appflowy_flutter">
|
package="io.appflowy.appflowy">
|
||||||
<application
|
<application
|
||||||
android:label="appflowy_flutter"
|
android:label="appflowy_flutter"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.example.appflowy_flutter
|
package io.appflowy.appflowy
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.appflowy_flutter">
|
package="io.appflowy.appflowy">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
|
@ -138,7 +138,8 @@
|
|||||||
"keep": "Keep",
|
"keep": "Keep",
|
||||||
"tryAgain": "Try again",
|
"tryAgain": "Try again",
|
||||||
"discard": "Discard",
|
"discard": "Discard",
|
||||||
"replace": "Replace"
|
"replace": "Replace",
|
||||||
|
"insertBelow": "Insert Below"
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"welcome": "Welcome!",
|
"welcome": "Welcome!",
|
||||||
@ -345,20 +346,21 @@
|
|||||||
"plugins": {
|
"plugins": {
|
||||||
"referencedBoard": "Referenced Board",
|
"referencedBoard": "Referenced Board",
|
||||||
"referencedGrid": "Referenced Grid",
|
"referencedGrid": "Referenced Grid",
|
||||||
"autoCompletionMenuItemName": "Auto Completion",
|
"autoGeneratorMenuItemName": "OpenAI Writer",
|
||||||
"autoGeneratorMenuItemName": "Auto Generator",
|
|
||||||
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
||||||
"autoGeneratorLearnMore": "Learn more",
|
"autoGeneratorLearnMore": "Learn more",
|
||||||
"autoGeneratorGenerate": "Generate",
|
"autoGeneratorGenerate": "Generate",
|
||||||
"autoGeneratorHintText": "Tell us what you want to generate by OpenAI ...",
|
"autoGeneratorHintText": "Ask OpenAI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
|
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
|
||||||
"smartEdit": "Smart Edit",
|
"smartEdit": "AI Assistants",
|
||||||
"smartEditTitleName": "OpenAI: Smart Edit",
|
"openAI": "OpenAI",
|
||||||
"smartEditFixSpelling": "Fix spelling",
|
"smartEditFixSpelling": "Fix spelling",
|
||||||
|
"warning": "⚠️ AI responses can be inaccurate or misleading.",
|
||||||
"smartEditSummarize": "Summarize",
|
"smartEditSummarize": "Summarize",
|
||||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
||||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
||||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||||
|
"discardResponse": "Do you want to discard the AI responses?",
|
||||||
"cover": {
|
"cover": {
|
||||||
"changeCover": "Change Cover",
|
"changeCover": "Change Cover",
|
||||||
"colors": "Colors",
|
"colors": "Colors",
|
||||||
@ -380,6 +382,7 @@
|
|||||||
"imageSavingFailed": "Image Saving Failed",
|
"imageSavingFailed": "Image Saving Failed",
|
||||||
"addIcon": "Add Icon"
|
"addIcon": "Add Icon"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"board": {
|
"board": {
|
||||||
|
@ -349,7 +349,6 @@
|
|||||||
"autoGeneratorGenerate": "Gerar",
|
"autoGeneratorGenerate": "Gerar",
|
||||||
"autoGeneratorHintText": "Diga-nos o que você deseja gerar por IA ...",
|
"autoGeneratorHintText": "Diga-nos o que você deseja gerar por IA ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Não foi possível obter a chave da OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Não foi possível obter a chave da OpenAI",
|
||||||
"smartEditTitleName": "IA: edição inteligente",
|
|
||||||
"smartEditFixSpelling": "Corrigir ortografia",
|
"smartEditFixSpelling": "Corrigir ortografia",
|
||||||
"smartEditSummarize": "Resumir",
|
"smartEditSummarize": "Resumir",
|
||||||
"smartEditCouldNotFetchResult": "Não foi possível obter o resultado do OpenAI",
|
"smartEditCouldNotFetchResult": "Não foi possível obter o resultado do OpenAI",
|
||||||
|
@ -359,7 +359,7 @@
|
|||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appFlowy;
|
PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -483,7 +483,7 @@
|
|||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appFlowy;
|
PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@ -502,7 +502,7 @@
|
|||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appFlowy;
|
PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
|
|
||||||
import 'text_completion.dart';
|
import 'text_completion.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
@ -125,6 +124,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
|||||||
String? suffix,
|
String? suffix,
|
||||||
int maxTokens = 2048,
|
int maxTokens = 2048,
|
||||||
double temperature = 0.3,
|
double temperature = 0.3,
|
||||||
|
bool useAction = false,
|
||||||
}) async {
|
}) async {
|
||||||
final parameters = {
|
final parameters = {
|
||||||
'model': 'text-davinci-003',
|
'model': 'text-davinci-003',
|
||||||
@ -151,14 +151,22 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
|||||||
.transform(const Utf8Decoder())
|
.transform(const Utf8Decoder())
|
||||||
.transform(const LineSplitter())) {
|
.transform(const LineSplitter())) {
|
||||||
syntax += 1;
|
syntax += 1;
|
||||||
if (syntax == 3) {
|
if (!useAction) {
|
||||||
await onStart();
|
if (syntax == 3) {
|
||||||
continue;
|
await onStart();
|
||||||
} else if (syntax < 3) {
|
continue;
|
||||||
continue;
|
} else if (syntax < 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (syntax == 2) {
|
||||||
|
await onStart();
|
||||||
|
continue;
|
||||||
|
} else if (syntax < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final data = chunk.trim().split('data: ');
|
final data = chunk.trim().split('data: ');
|
||||||
Log.editor.info(data.toString());
|
|
||||||
if (data.length > 1) {
|
if (data.length > 1) {
|
||||||
if (data[1] != '[DONE]') {
|
if (data[1] != '[DONE]') {
|
||||||
final response = TextCompletionResponse.fromJson(
|
final response = TextCompletionResponse.fromJson(
|
||||||
@ -173,7 +181,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
|||||||
previousSyntax = response.choices.first.text;
|
previousSyntax = response.choices.first.text;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onEnd();
|
await onEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,6 +191,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
|||||||
OpenAIError.fromJson(json.decode(body)['error']),
|
OpenAIError.fromJson(json.decode(body)['error']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
Future<void> openLearnMorePage() async {
|
||||||
|
final uri = Uri.parse(
|
||||||
|
'https://appflowy.gitbook.io/docs/essential-documentation/appflowy-x-openai');
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/util/learn_more_action.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/discard_dialog.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/loading.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/loading.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
@ -9,7 +11,7 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -56,6 +58,7 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
|
|||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
final textFieldFocusNode = FocusNode();
|
final textFieldFocusNode = FocusNode();
|
||||||
|
final interceptor = SelectionInterceptor();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -63,6 +66,34 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
|
|||||||
|
|
||||||
textFieldFocusNode.addListener(_onFocusChanged);
|
textFieldFocusNode.addListener(_onFocusChanged);
|
||||||
textFieldFocusNode.requestFocus();
|
textFieldFocusNode.requestFocus();
|
||||||
|
widget.editorState.service.selectionService.register(interceptor
|
||||||
|
..canTap = (details) {
|
||||||
|
final renderBox = context.findRenderObject() as RenderBox?;
|
||||||
|
if (renderBox != null) {
|
||||||
|
if (!isTapDownDetailsInRenderBox(details, renderBox)) {
|
||||||
|
if (text.isNotEmpty || controller.text.isNotEmpty) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return DiscardDialog(
|
||||||
|
onConfirm: () => _onDiscard(),
|
||||||
|
onCancel: () {},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (controller.text.isEmpty) {
|
||||||
|
_onExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTapDownDetailsInRenderBox(TapDownDetails details, RenderBox box) {
|
||||||
|
var result = BoxHitTestResult();
|
||||||
|
box.hitTest(result, position: box.globalToLocal(details.globalPosition));
|
||||||
|
return result.path.any((entry) => entry.target == box);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -71,6 +102,7 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
|
|||||||
textFieldFocusNode.removeListener(_onFocusChanged);
|
textFieldFocusNode.removeListener(_onFocusChanged);
|
||||||
widget.editorState.service.selectionService.currentSelection
|
widget.editorState.service.selectionService.currentSelection
|
||||||
.removeListener(_onCancelWhenSelectionChanged);
|
.removeListener(_onCancelWhenSelectionChanged);
|
||||||
|
widget.editorState.service.selectionService.unRegister(interceptor);
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -119,34 +151,26 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
FlowyText.regular(
|
FlowyButton(
|
||||||
LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(),
|
useIntrinsicWidth: true,
|
||||||
),
|
text: FlowyText.regular(
|
||||||
|
LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
await openLearnMorePage();
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInputWidget(BuildContext context) {
|
Widget _buildInputWidget(BuildContext context) {
|
||||||
return RawKeyboardListener(
|
return FlowyTextField(
|
||||||
focusNode: focusNode,
|
hintText: LocaleKeys.document_plugins_autoGeneratorHintText.tr(),
|
||||||
onKey: (RawKeyEvent event) async {
|
controller: controller,
|
||||||
if (event is! RawKeyDownEvent) return;
|
maxLines: 3,
|
||||||
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
focusNode: textFieldFocusNode,
|
||||||
if (controller.text.isNotEmpty) {
|
autoFocus: false,
|
||||||
textFieldFocusNode.unfocus();
|
|
||||||
await _onGenerate();
|
|
||||||
}
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
|
||||||
await _onExit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: FlowyTextField(
|
|
||||||
hintText: LocaleKeys.document_plugins_autoGeneratorHintText.tr(),
|
|
||||||
controller: controller,
|
|
||||||
maxLines: 3,
|
|
||||||
focusNode: textFieldFocusNode,
|
|
||||||
autoFocus: false,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,15 +181,9 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '${LocaleKeys.button_generate.tr()} ',
|
text: LocaleKeys.button_generate.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
TextSpan(
|
|
||||||
text: '↵',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () async => await _onGenerate(),
|
onPressed: () async => await _onGenerate(),
|
||||||
@ -175,19 +193,23 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '${LocaleKeys.button_Cancel.tr()} ',
|
text: LocaleKeys.button_Cancel.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
TextSpan(
|
|
||||||
text: LocaleKeys.button_esc.tr(),
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () async => await _onExit(),
|
onPressed: () async => await _onExit(),
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: FlowyText.regular(
|
||||||
|
LocaleKeys.document_plugins_warning.tr(),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,13 @@ import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/au
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
SelectionMenuItem autoGeneratorMenuItem = SelectionMenuItem.node(
|
SelectionMenuItem autoGeneratorMenuItem = SelectionMenuItem.node(
|
||||||
name: 'Auto Generator',
|
name: LocaleKeys.document_plugins_autoGeneratorMenuItemName.tr(),
|
||||||
iconData: Icons.generating_tokens,
|
iconData: Icons.generating_tokens,
|
||||||
keywords: ['autogenerator', 'auto generator'],
|
keywords: ['ai', 'openai' 'writer', 'autogenerator'],
|
||||||
nodeBuilder: (editorState) {
|
nodeBuilder: (editorState) {
|
||||||
final node = Node(
|
final node = Node(
|
||||||
type: kAutoCompletionInputType,
|
type: kAutoCompletionInputType,
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class DiscardDialog extends StatelessWidget {
|
||||||
|
const DiscardDialog({
|
||||||
|
super.key,
|
||||||
|
required this.onConfirm,
|
||||||
|
required this.onCancel,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onConfirm;
|
||||||
|
final VoidCallback onCancel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return NavigatorOkCancelDialog(
|
||||||
|
message: LocaleKeys.document_plugins_discardResponse.tr(),
|
||||||
|
okTitle: LocaleKeys.button_discard.tr(),
|
||||||
|
cancelTitle: LocaleKeys.button_Cancel.tr(),
|
||||||
|
onOkPressed: onConfirm,
|
||||||
|
onCancelPressed: onCancel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -10,11 +10,39 @@ enum SmartEditAction {
|
|||||||
String get toInstruction {
|
String get toInstruction {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case SmartEditAction.summarize:
|
case SmartEditAction.summarize:
|
||||||
return 'Make this shorter and more concise:';
|
return 'Tl;dr';
|
||||||
case SmartEditAction.fixSpelling:
|
case SmartEditAction.fixSpelling:
|
||||||
return 'Correct this to standard English:';
|
return 'Correct this to standard English:';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String prompt(String input) {
|
||||||
|
switch (this) {
|
||||||
|
case SmartEditAction.summarize:
|
||||||
|
return '$input\n\nTl;dr';
|
||||||
|
case SmartEditAction.fixSpelling:
|
||||||
|
return 'Correct this to standard English:\n\n$input';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmartEditAction from(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return SmartEditAction.summarize;
|
||||||
|
case 1:
|
||||||
|
return SmartEditAction.fixSpelling;
|
||||||
|
}
|
||||||
|
return SmartEditAction.fixSpelling;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get name {
|
||||||
|
switch (this) {
|
||||||
|
case SmartEditAction.summarize:
|
||||||
|
return LocaleKeys.document_plugins_smartEditSummarize.tr();
|
||||||
|
case SmartEditAction.fixSpelling:
|
||||||
|
return LocaleKeys.document_plugins_smartEditFixSpelling.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SmartEditActionWrapper extends ActionCell {
|
class SmartEditActionWrapper extends ActionCell {
|
||||||
@ -26,11 +54,6 @@ class SmartEditActionWrapper extends ActionCell {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get name {
|
String get name {
|
||||||
switch (inner) {
|
return inner.name;
|
||||||
case SmartEditAction.summarize:
|
|
||||||
return LocaleKeys.document_plugins_smartEditSummarize.tr();
|
|
||||||
case SmartEditAction.fixSpelling:
|
|
||||||
return LocaleKeys.document_plugins_smartEditFixSpelling.tr();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/error.dart';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/util/learn_more_action.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/discard_dialog.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/style_widget/decoration.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
|
||||||
import 'package:appflowy/util/either_extension.dart';
|
|
||||||
|
|
||||||
const String kSmartEditType = 'smart_edit_input';
|
const String kSmartEditType = 'smart_edit_input';
|
||||||
const String kSmartEditInstructionType = 'smart_edit_instruction';
|
const String kSmartEditInstructionType = 'smart_edit_instruction';
|
||||||
@ -22,15 +21,15 @@ const String kSmartEditInputType = 'smart_edit_input';
|
|||||||
class SmartEditInputBuilder extends NodeWidgetBuilder<Node> {
|
class SmartEditInputBuilder extends NodeWidgetBuilder<Node> {
|
||||||
@override
|
@override
|
||||||
NodeValidator<Node> get nodeValidator => (node) {
|
NodeValidator<Node> get nodeValidator => (node) {
|
||||||
return SmartEditAction.values.map((e) => e.toInstruction).contains(
|
return SmartEditAction.values
|
||||||
node.attributes[kSmartEditInstructionType],
|
.map((e) => e.index)
|
||||||
) &&
|
.contains(node.attributes[kSmartEditInstructionType]) &&
|
||||||
node.attributes[kSmartEditInputType] is String;
|
node.attributes[kSmartEditInputType] is String;
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(NodeWidgetContext<Node> context) {
|
Widget build(NodeWidgetContext<Node> context) {
|
||||||
return _SmartEditInput(
|
return _HoverSmartInput(
|
||||||
key: context.node.key,
|
key: context.node.key,
|
||||||
node: context.node,
|
node: context.node,
|
||||||
editorState: context.editorState,
|
editorState: context.editorState,
|
||||||
@ -38,28 +37,111 @@ class SmartEditInputBuilder extends NodeWidgetBuilder<Node> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SmartEditInput extends StatefulWidget {
|
class _HoverSmartInput extends StatefulWidget {
|
||||||
final Node node;
|
const _HoverSmartInput({
|
||||||
|
required super.key,
|
||||||
final EditorState editorState;
|
|
||||||
const _SmartEditInput({
|
|
||||||
Key? key,
|
|
||||||
required this.node,
|
required this.node,
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final Node node;
|
||||||
|
final EditorState editorState;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_HoverSmartInput> createState() => _HoverSmartInputState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HoverSmartInputState extends State<_HoverSmartInput> {
|
||||||
|
final popoverController = PopoverController();
|
||||||
|
final key = GlobalKey(debugLabel: 'smart_edit_input');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
|
popoverController.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final width = _maxWidth();
|
||||||
|
|
||||||
|
return AppFlowyPopover(
|
||||||
|
controller: popoverController,
|
||||||
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
|
triggerActions: PopoverTriggerFlags.none,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
constraints: BoxConstraints(maxWidth: width),
|
||||||
|
decoration: FlowyDecoration.decoration(
|
||||||
|
Colors.transparent,
|
||||||
|
Colors.transparent,
|
||||||
|
),
|
||||||
|
child: const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
),
|
||||||
|
canClose: () async {
|
||||||
|
final completer = Completer<bool>();
|
||||||
|
final state = key.currentState as _SmartEditInputState;
|
||||||
|
if (state.result.isEmpty) {
|
||||||
|
completer.complete(true);
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return DiscardDialog(
|
||||||
|
onConfirm: () => completer.complete(true),
|
||||||
|
onCancel: () => completer.complete(false),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return completer.future;
|
||||||
|
},
|
||||||
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
|
return _SmartEditInput(
|
||||||
|
key: key,
|
||||||
|
node: widget.node,
|
||||||
|
editorState: widget.editorState,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _maxWidth() {
|
||||||
|
var width = double.infinity;
|
||||||
|
final editorSize = widget.editorState.renderBox?.size;
|
||||||
|
final padding = widget.editorState.editorStyle.padding;
|
||||||
|
if (editorSize != null && padding != null) {
|
||||||
|
width = editorSize.width - padding.left - padding.right;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SmartEditInput extends StatefulWidget {
|
||||||
|
const _SmartEditInput({
|
||||||
|
required super.key,
|
||||||
|
required this.node,
|
||||||
|
required this.editorState,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Node node;
|
||||||
|
final EditorState editorState;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_SmartEditInput> createState() => _SmartEditInputState();
|
State<_SmartEditInput> createState() => _SmartEditInputState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SmartEditInputState extends State<_SmartEditInput> {
|
class _SmartEditInputState extends State<_SmartEditInput> {
|
||||||
String get instruction => widget.node.attributes[kSmartEditInstructionType];
|
SmartEditAction get action =>
|
||||||
|
SmartEditAction.from(widget.node.attributes[kSmartEditInstructionType]);
|
||||||
String get input => widget.node.attributes[kSmartEditInputType];
|
String get input => widget.node.attributes[kSmartEditInputType];
|
||||||
|
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
final client = http.Client();
|
final client = http.Client();
|
||||||
dartz.Either<OpenAIError, TextEditResponse>? result;
|
|
||||||
bool loading = true;
|
bool loading = true;
|
||||||
|
String result = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -72,12 +154,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
widget.editorState.service.keyboardService?.enable();
|
widget.editorState.service.keyboardService?.enable();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_requestEdits().then(
|
_requestCompletions();
|
||||||
(value) => setState(() {
|
|
||||||
result = value;
|
|
||||||
loading = false;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -99,28 +176,16 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSmartEditPanel(BuildContext context) {
|
Widget _buildSmartEditPanel(BuildContext context) {
|
||||||
return RawKeyboardListener(
|
return Column(
|
||||||
focusNode: focusNode,
|
mainAxisSize: MainAxisSize.min,
|
||||||
onKey: (RawKeyEvent event) async {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
if (event is! RawKeyDownEvent) return;
|
children: [
|
||||||
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
_buildHeaderWidget(context),
|
||||||
await _onReplace();
|
const Space(0, 10),
|
||||||
await _onExit();
|
_buildResultWidget(context),
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
const Space(0, 10),
|
||||||
await _onExit();
|
_buildInputFooterWidget(context),
|
||||||
}
|
],
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildHeaderWidget(context),
|
|
||||||
const Space(0, 10),
|
|
||||||
_buildResultWidget(context),
|
|
||||||
const Space(0, 10),
|
|
||||||
_buildInputFooterWidget(context),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,13 +193,19 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
FlowyText.medium(
|
FlowyText.medium(
|
||||||
LocaleKeys.document_plugins_smartEditTitleName.tr(),
|
'${LocaleKeys.document_plugins_openAI.tr()}: ${action.name}',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
FlowyText.regular(
|
FlowyButton(
|
||||||
LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(),
|
useIntrinsicWidth: true,
|
||||||
),
|
text: FlowyText.regular(
|
||||||
|
LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
await openLearnMorePage();
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -147,25 +218,14 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
child: const CircularProgressIndicator(),
|
child: const CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (result == null) {
|
if (result.isEmpty) {
|
||||||
return loading;
|
return loading;
|
||||||
}
|
}
|
||||||
return result!.fold((error) {
|
return Flexible(
|
||||||
return Flexible(
|
child: Text(
|
||||||
child: Text(
|
result,
|
||||||
error.message,
|
),
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
);
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, (response) {
|
|
||||||
return Flexible(
|
|
||||||
child: Text(
|
|
||||||
response.choices.map((e) => e.text).join('\n'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInputFooterWidget(BuildContext context) {
|
Widget _buildInputFooterWidget(BuildContext context) {
|
||||||
@ -175,19 +235,13 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '${LocaleKeys.button_replace.tr()} ',
|
text: LocaleKeys.button_replace.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
TextSpan(
|
|
||||||
text: '↵',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
_onReplace();
|
await _onReplace();
|
||||||
_onExit();
|
_onExit();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -196,19 +250,33 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '${LocaleKeys.button_Cancel.tr()} ',
|
text: LocaleKeys.button_insertBelow.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await _onInsertBelow();
|
||||||
|
_onExit();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Space(10, 0),
|
||||||
|
FlowyRichTextButton(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: LocaleKeys.button_esc.tr(),
|
text: LocaleKeys.button_Cancel.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () async => await _onExit(),
|
onPressed: () async => await _onExit(),
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
FlowyText.regular(
|
||||||
|
LocaleKeys.document_plugins_warning.tr(),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -219,12 +287,11 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
final selectedNodes = widget
|
final selectedNodes = widget
|
||||||
.editorState.service.selectionService.currentSelectedNodes.normalized
|
.editorState.service.selectionService.currentSelectedNodes.normalized
|
||||||
.whereType<TextNode>();
|
.whereType<TextNode>();
|
||||||
if (selection == null || result == null || result!.isLeft()) {
|
if (selection == null || result.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final texts = result!.asRight().choices.first.text.split('\n')
|
final texts = result.split('\n')..removeWhere((element) => element.isEmpty);
|
||||||
..removeWhere((element) => element.isEmpty);
|
|
||||||
final transaction = widget.editorState.transaction;
|
final transaction = widget.editorState.transaction;
|
||||||
transaction.replaceTexts(
|
transaction.replaceTexts(
|
||||||
selectedNodes.toList(growable: false),
|
selectedNodes.toList(growable: false),
|
||||||
@ -234,6 +301,25 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
return widget.editorState.apply(transaction);
|
return widget.editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onInsertBelow() async {
|
||||||
|
final selection = widget.editorState.service.selectionService
|
||||||
|
.currentSelection.value?.normalized;
|
||||||
|
if (selection == null || result.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final texts = result.split('\n')..removeWhere((element) => element.isEmpty);
|
||||||
|
final transaction = widget.editorState.transaction;
|
||||||
|
transaction.insertNodes(
|
||||||
|
selection.normalized.end.path.next,
|
||||||
|
texts.map(
|
||||||
|
(e) => TextNode(
|
||||||
|
delta: Delta()..insert(e),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return widget.editorState.apply(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onExit() async {
|
Future<void> _onExit() async {
|
||||||
final transaction = widget.editorState.transaction;
|
final transaction = widget.editorState.transaction;
|
||||||
transaction.deleteNode(widget.node);
|
transaction.deleteNode(widget.node);
|
||||||
@ -246,35 +332,63 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dartz.Either<OpenAIError, TextEditResponse>> _requestEdits() async {
|
Future<void> _requestCompletions() async {
|
||||||
final result = await UserBackendService.getCurrentUserProfile();
|
final result = await UserBackendService.getCurrentUserProfile();
|
||||||
return result.fold((userProfile) async {
|
return result.fold((l) async {
|
||||||
final openAIRepository = HttpOpenAIRepository(
|
final openAIRepository = HttpOpenAIRepository(
|
||||||
client: client,
|
client: client,
|
||||||
apiKey: userProfile.openaiKey,
|
apiKey: l.openaiKey,
|
||||||
);
|
);
|
||||||
final edits = await openAIRepository.getEdits(
|
|
||||||
input: input,
|
var lines = input.split('\n\n');
|
||||||
instruction: instruction,
|
if (action == SmartEditAction.summarize) {
|
||||||
n: 1,
|
lines = [lines.join('\n')];
|
||||||
);
|
}
|
||||||
return edits.fold((error) async {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
return dartz.Left(
|
final element = lines[i];
|
||||||
OpenAIError(
|
await openAIRepository.getStreamedCompletions(
|
||||||
message:
|
useAction: true,
|
||||||
LocaleKeys.document_plugins_smartEditCouldNotFetchResult.tr(),
|
prompt: action.prompt(element),
|
||||||
),
|
onStart: () async {
|
||||||
|
setState(() {
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onProcess: (response) async {
|
||||||
|
setState(() {
|
||||||
|
this.result += response.choices.first.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEnd: () async {
|
||||||
|
setState(() {
|
||||||
|
if (i != lines.length - 1) {
|
||||||
|
this.result += '\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) async {
|
||||||
|
await _showError(error.message);
|
||||||
|
await _onExit();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}, (textEdit) async {
|
}
|
||||||
return dartz.Right(textEdit);
|
}, (r) async {
|
||||||
});
|
await _showError(r.msg);
|
||||||
}, (error) async {
|
await _onExit();
|
||||||
// error
|
|
||||||
return dartz.Left(
|
|
||||||
OpenAIError(
|
|
||||||
message: LocaleKeys.document_plugins_smartEditCouldNotFetchKey.tr(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,14 +101,17 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
|||||||
textNodes.normalized,
|
textNodes.normalized,
|
||||||
selection.normalized,
|
selection.normalized,
|
||||||
);
|
);
|
||||||
|
while (input.last.isEmpty) {
|
||||||
|
input.removeLast();
|
||||||
|
}
|
||||||
final transaction = widget.editorState.transaction;
|
final transaction = widget.editorState.transaction;
|
||||||
transaction.insertNode(
|
transaction.insertNode(
|
||||||
selection.normalized.end.path.next,
|
selection.normalized.end.path.next,
|
||||||
Node(
|
Node(
|
||||||
type: kSmartEditType,
|
type: kSmartEditType,
|
||||||
attributes: {
|
attributes: {
|
||||||
kSmartEditInstructionType: actionWrapper.inner.toInstruction,
|
kSmartEditInstructionType: actionWrapper.inner.index,
|
||||||
kSmartEditInputType: input,
|
kSmartEditInputType: input.join('\n\n'),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
24
frontend/appflowy_flutter/lib/util/debounce.dart
Normal file
24
frontend/appflowy_flutter/lib/util/debounce.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Debounce {
|
||||||
|
final Duration duration;
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
Debounce({
|
||||||
|
this.duration = const Duration(milliseconds: 1000),
|
||||||
|
});
|
||||||
|
|
||||||
|
void call(VoidCallback action) {
|
||||||
|
dispose();
|
||||||
|
_timer = Timer(duration, () {
|
||||||
|
action();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/util/debounce.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -98,11 +99,20 @@ class _OpenaiKeyInput extends StatefulWidget {
|
|||||||
|
|
||||||
class _OpenaiKeyInputState extends State<_OpenaiKeyInput> {
|
class _OpenaiKeyInputState extends State<_OpenaiKeyInput> {
|
||||||
bool visible = false;
|
bool visible = false;
|
||||||
|
final textEditingController = TextEditingController();
|
||||||
|
final debounce = Debounce();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
textEditingController.text = widget.openAIKey;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: TextEditingController()..text = widget.openAIKey,
|
controller: textEditingController,
|
||||||
obscureText: !visible,
|
obscureText: !visible,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'OpenAI Key',
|
labelText: 'OpenAI Key',
|
||||||
@ -120,13 +130,21 @@ class _OpenaiKeyInputState extends State<_OpenaiKeyInput> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onSubmitted: (val) {
|
onChanged: (value) {
|
||||||
context
|
debounce.call(() {
|
||||||
.read<SettingsUserViewBloc>()
|
context
|
||||||
.add(SettingsUserEvent.updateUserOpenAIKey(val));
|
.read<SettingsUserViewBloc>()
|
||||||
|
.add(SettingsUserEvent.updateUserOpenAIKey(value));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
debounce.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CurrentIcon extends StatelessWidget {
|
class _CurrentIcon extends StatelessWidget {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(runner LANGUAGES CXX)
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
set(BINARY_NAME "appflowy_flutter")
|
set(BINARY_NAME "AppFlowy")
|
||||||
set(APPLICATION_ID "com.example.appflowy_flutter")
|
set(APPLICATION_ID "io.appflowy.appflowy")
|
||||||
|
|
||||||
cmake_policy(SET CMP0063 NEW)
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Name=AppFlowy
|
Name=AppFlowy
|
||||||
Comment=An Open Source Alternative to Notion
|
Comment=An Open Source Alternative to Notion
|
||||||
Icon=[CHANGE_THIS]/AppFlowy/flowy_logo.svg
|
Icon=[CHANGE_THIS]/AppFlowy/flowy_logo.svg
|
||||||
Exec=[CHANGE_THIS]/AppFlowy/appflowy_flutter
|
Exec=[CHANGE_THIS]/AppFlowy/AppFlowy
|
||||||
Categories=Office
|
Categories=Office
|
||||||
Type=Application
|
Type=Application
|
||||||
Terminal=false
|
Terminal=false
|
@ -7,17 +7,19 @@
|
|||||||
|
|
||||||
#include "flutter/generated_plugin_registrant.h"
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
struct _MyApplication {
|
struct _MyApplication
|
||||||
|
{
|
||||||
GtkApplication parent_instance;
|
GtkApplication parent_instance;
|
||||||
char** dart_entrypoint_arguments;
|
char **dart_entrypoint_arguments;
|
||||||
};
|
};
|
||||||
|
|
||||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
// Implements GApplication::activate.
|
// Implements GApplication::activate.
|
||||||
static void my_application_activate(GApplication* application) {
|
static void my_application_activate(GApplication *application)
|
||||||
MyApplication* self = MY_APPLICATION(application);
|
{
|
||||||
GtkWindow* window =
|
MyApplication *self = MY_APPLICATION(application);
|
||||||
|
GtkWindow *window =
|
||||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||||
|
|
||||||
// Use a header bar when running in GNOME as this is the common style used
|
// Use a header bar when running in GNOME as this is the common style used
|
||||||
@ -29,22 +31,27 @@ static void my_application_activate(GApplication* application) {
|
|||||||
// if future cases occur).
|
// if future cases occur).
|
||||||
gboolean use_header_bar = TRUE;
|
gboolean use_header_bar = TRUE;
|
||||||
#ifdef GDK_WINDOWING_X11
|
#ifdef GDK_WINDOWING_X11
|
||||||
GdkScreen* screen = gtk_window_get_screen(window);
|
GdkScreen *screen = gtk_window_get_screen(window);
|
||||||
if (GDK_IS_X11_SCREEN(screen)) {
|
if (GDK_IS_X11_SCREEN(screen))
|
||||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
{
|
||||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||||
|
if (g_strcmp0(wm_name, "GNOME Shell") != 0)
|
||||||
|
{
|
||||||
use_header_bar = FALSE;
|
use_header_bar = FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (use_header_bar) {
|
if (use_header_bar)
|
||||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
{
|
||||||
|
GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
gtk_header_bar_set_title(header_bar, "appflowy_flutter");
|
gtk_header_bar_set_title(header_bar, "AppFlowy");
|
||||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
} else {
|
}
|
||||||
gtk_window_set_title(window, "appflowy_flutter");
|
else
|
||||||
|
{
|
||||||
|
gtk_window_set_title(window, "AppFlowy");
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_window_set_default_size(window, 1280, 720);
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
@ -53,7 +60,7 @@ static void my_application_activate(GApplication* application) {
|
|||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
FlView* view = fl_view_new(project);
|
FlView *view = fl_view_new(project);
|
||||||
gtk_widget_show(GTK_WIDGET(view));
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
@ -63,16 +70,18 @@ static void my_application_activate(GApplication* application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implements GApplication::local_command_line.
|
// Implements GApplication::local_command_line.
|
||||||
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status)
|
||||||
MyApplication* self = MY_APPLICATION(application);
|
{
|
||||||
|
MyApplication *self = MY_APPLICATION(application);
|
||||||
// Strip out the first argument as it is the binary name.
|
// Strip out the first argument as it is the binary name.
|
||||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||||
|
|
||||||
g_autoptr(GError) error = nullptr;
|
g_autoptr(GError) error = nullptr;
|
||||||
if (!g_application_register(application, nullptr, &error)) {
|
if (!g_application_register(application, nullptr, &error))
|
||||||
g_warning("Failed to register: %s", error->message);
|
{
|
||||||
*exit_status = 1;
|
g_warning("Failed to register: %s", error->message);
|
||||||
return TRUE;
|
*exit_status = 1;
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_application_activate(application);
|
g_application_activate(application);
|
||||||
@ -82,21 +91,24 @@ static gboolean my_application_local_command_line(GApplication* application, gch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implements GObject::dispose.
|
// Implements GObject::dispose.
|
||||||
static void my_application_dispose(GObject* object) {
|
static void my_application_dispose(GObject *object)
|
||||||
MyApplication* self = MY_APPLICATION(object);
|
{
|
||||||
|
MyApplication *self = MY_APPLICATION(object);
|
||||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void my_application_class_init(MyApplicationClass* klass) {
|
static void my_application_class_init(MyApplicationClass *klass)
|
||||||
|
{
|
||||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void my_application_init(MyApplication* self) {}
|
static void my_application_init(MyApplication *self) {}
|
||||||
|
|
||||||
MyApplication* my_application_new() {
|
MyApplication *my_application_new()
|
||||||
|
{
|
||||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||||
"application-id", APPLICATION_ID,
|
"application-id", APPLICATION_ID,
|
||||||
"flags", G_APPLICATION_NON_UNIQUE,
|
"flags", G_APPLICATION_NON_UNIQUE,
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
// 'flutter create' template.
|
// 'flutter create' template.
|
||||||
|
|
||||||
// The application's name. By default this is also the title of the Flutter window.
|
// The application's name. By default this is also the title of the Flutter window.
|
||||||
PRODUCT_NAME = appflowy_flutter
|
PRODUCT_NAME = AppFlowy
|
||||||
|
|
||||||
// The application's bundle identifier
|
// The application's bundle identifier
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appFlowy
|
PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy
|
||||||
|
|
||||||
// The copyright displayed in application information
|
// The copyright displayed in application information
|
||||||
PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved.
|
PRODUCT_COPYRIGHT = Copyright © 2023 AppFlowy.IO. All rights reserved.
|
||||||
|
@ -52,7 +52,7 @@ extension CommandExtension on EditorState {
|
|||||||
throw Exception('path and textNode cannot be null at the same time');
|
throw Exception('path and textNode cannot be null at the same time');
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTextInSelection(
|
List<String> getTextInSelection(
|
||||||
List<TextNode> textNodes,
|
List<TextNode> textNodes,
|
||||||
Selection selection,
|
Selection selection,
|
||||||
) {
|
) {
|
||||||
@ -77,6 +77,6 @@ extension CommandExtension on EditorState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.join('\n');
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,11 +264,11 @@ extension TextTransaction on Transaction {
|
|||||||
if (index != 0 && attributes == null) {
|
if (index != 0 && attributes == null) {
|
||||||
newAttributes =
|
newAttributes =
|
||||||
textNode.delta.slice(max(index - 1, 0), index).first.attributes;
|
textNode.delta.slice(max(index - 1, 0), index).first.attributes;
|
||||||
if (newAttributes != null) {
|
if (newAttributes == null) {
|
||||||
newAttributes = {...newAttributes}; // make a copy
|
final slicedDelta = textNode.delta.slice(index, index + length);
|
||||||
} else {
|
if (slicedDelta.isNotEmpty) {
|
||||||
newAttributes =
|
newAttributes = slicedDelta.first.attributes;
|
||||||
textNode.delta.slice(index, index + length).first.attributes;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateText(
|
updateText(
|
||||||
@ -276,7 +276,7 @@ extension TextTransaction on Transaction {
|
|||||||
Delta()
|
Delta()
|
||||||
..retain(index)
|
..retain(index)
|
||||||
..delete(length)
|
..delete(length)
|
||||||
..insert(text, attributes: newAttributes),
|
..insert(text, attributes: {...newAttributes ?? {}}),
|
||||||
);
|
);
|
||||||
afterSelection = Selection.collapsed(
|
afterSelection = Selection.collapsed(
|
||||||
Position(
|
Position(
|
||||||
@ -347,24 +347,22 @@ extension TextTransaction on Transaction {
|
|||||||
textNode.toPlainText().length,
|
textNode.toPlainText().length,
|
||||||
texts.first,
|
texts.first,
|
||||||
);
|
);
|
||||||
} else if (i == length - 1) {
|
} else if (i == length - 1 && texts.length >= 2) {
|
||||||
replaceText(
|
replaceText(
|
||||||
textNode,
|
textNode,
|
||||||
0,
|
0,
|
||||||
selection.endIndex,
|
selection.endIndex,
|
||||||
texts.last,
|
texts.last,
|
||||||
);
|
);
|
||||||
|
} else if (i < texts.length - 1) {
|
||||||
|
replaceText(
|
||||||
|
textNode,
|
||||||
|
0,
|
||||||
|
textNode.toPlainText().length,
|
||||||
|
texts[i],
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (i < texts.length - 1) {
|
deleteNode(textNode);
|
||||||
replaceText(
|
|
||||||
textNode,
|
|
||||||
0,
|
|
||||||
textNode.toPlainText().length,
|
|
||||||
texts[i],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
deleteNode(textNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
afterSelection = null;
|
afterSelection = null;
|
||||||
|
@ -8,7 +8,9 @@ ShortcutEventHandler selectAllHandler = (editorState, event) {
|
|||||||
if (editorState.document.root.children.isEmpty) {
|
if (editorState.document.root.children.isEmpty) {
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
final firstNode = editorState.document.root.children.first;
|
final firstNode = editorState.document.root.children.firstWhere(
|
||||||
|
(element) => element is TextNode,
|
||||||
|
);
|
||||||
final lastNode = editorState.document.root.children.last;
|
final lastNode = editorState.document.root.children.last;
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
if (lastNode is TextNode) {
|
if (lastNode is TextNode) {
|
||||||
|
@ -82,6 +82,13 @@ abstract class AppFlowySelectionService {
|
|||||||
|
|
||||||
/// The current selection areas's rect in editor.
|
/// The current selection areas's rect in editor.
|
||||||
List<Rect> get selectionRects;
|
List<Rect> get selectionRects;
|
||||||
|
|
||||||
|
void register(SelectionInterceptor interceptor);
|
||||||
|
void unRegister(SelectionInterceptor interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectionInterceptor {
|
||||||
|
bool Function(TapDownDetails details)? canTap;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppFlowySelection extends StatefulWidget {
|
class AppFlowySelection extends StatefulWidget {
|
||||||
@ -212,6 +219,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||||||
|
|
||||||
selectionRects.clear();
|
selectionRects.clear();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
_clearToolbar();
|
||||||
|
|
||||||
if (selection != null) {
|
if (selection != null) {
|
||||||
if (selection.isCollapsed) {
|
if (selection.isCollapsed) {
|
||||||
@ -286,6 +294,10 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onTapDown(TapDownDetails details) {
|
void _onTapDown(TapDownDetails details) {
|
||||||
|
final canTap =
|
||||||
|
_interceptors.every((element) => element.canTap?.call(details) ?? true);
|
||||||
|
if (!canTap) return;
|
||||||
|
|
||||||
// clear old state.
|
// clear old state.
|
||||||
_panStartOffset = null;
|
_panStartOffset = null;
|
||||||
|
|
||||||
@ -701,4 +713,15 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<SelectionInterceptor> _interceptors = [];
|
||||||
|
@override
|
||||||
|
void register(SelectionInterceptor interceptor) {
|
||||||
|
_interceptors.add(interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void unRegister(SelectionInterceptor interceptor) {
|
||||||
|
_interceptors.removeWhere((element) => element == interceptor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@ void main() {
|
|||||||
.editorState.service.selectionService.currentSelectedNodes
|
.editorState.service.selectionService.currentSelectedNodes
|
||||||
.whereType<TextNode>()
|
.whereType<TextNode>()
|
||||||
.toList(growable: false);
|
.toList(growable: false);
|
||||||
final text = editor.editorState.getTextInSelection(
|
final texts = editor.editorState.getTextInSelection(
|
||||||
textNodes.normalized,
|
textNodes.normalized,
|
||||||
selection.normalized,
|
selection.normalized,
|
||||||
);
|
);
|
||||||
expect(text, 'me\nto\nAppfl');
|
expect(texts, ['me', 'to', 'Appfl']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,43 @@ void main() async {
|
|||||||
expect(textNodes[3].toPlainText(), 'ABC456789');
|
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'];
|
||||||
|
transaction.replaceTexts(textNodes, selection, texts);
|
||||||
|
editor.editorState.apply(transaction);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 1);
|
||||||
|
textNodes = [0]
|
||||||
|
.map((e) => editor.nodeAtPath([e])!)
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.toList(growable: false);
|
||||||
|
expect(textNodes[0].toPlainText(), '0123ABC');
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('test replaceTexts, textNodes.length < texts.length',
|
testWidgets('test replaceTexts, textNodes.length < texts.length',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
@ -10,7 +10,8 @@ void main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('slash_handler.dart', () {
|
group('slash_handler.dart', () {
|
||||||
testWidgets('Presses / to trigger selection menu', (tester) async {
|
testWidgets('Presses / to trigger selection menu in 0 index',
|
||||||
|
(tester) async {
|
||||||
const text = 'Welcome to Appflowy 😁';
|
const text = 'Welcome to Appflowy 😁';
|
||||||
const lines = 3;
|
const lines = 3;
|
||||||
final editor = tester.editor;
|
final editor = tester.editor;
|
||||||
@ -41,5 +42,38 @@ void main() async {
|
|||||||
findsNothing,
|
findsNothing,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses / to trigger selection menu in not 0 index',
|
||||||
|
(tester) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
const lines = 3;
|
||||||
|
final editor = tester.editor;
|
||||||
|
for (var i = 0; i < lines; i++) {
|
||||||
|
editor.insertTextNode(text);
|
||||||
|
}
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [1], startOffset: 5));
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.slash);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 1000));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.byType(SelectionMenuWidget, skipOffstage: false),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final item in defaultSelectionMenuItems) {
|
||||||
|
expect(find.text(item.name), findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
await editor.updateSelection(Selection.single(path: [1], startOffset: 0));
|
||||||
|
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||||
|
findsNothing,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,7 @@ void main() async {
|
|||||||
await editor.updateSelection(
|
await editor.updateSelection(
|
||||||
Selection.single(path: [1], startOffset: 0, endOffset: text.length * 2),
|
Selection.single(path: [1], startOffset: 0, endOffset: text.length * 2),
|
||||||
);
|
);
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
testHighlight(false);
|
testHighlight(false);
|
||||||
|
|
||||||
await editor.updateSelection(
|
await editor.updateSelection(
|
||||||
@ -103,6 +104,7 @@ void main() async {
|
|||||||
endOffset: text.length * 2,
|
endOffset: text.length * 2,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
testHighlight(true);
|
testHighlight(true);
|
||||||
|
|
||||||
await editor.updateSelection(
|
await editor.updateSelection(
|
||||||
@ -112,6 +114,7 @@ void main() async {
|
|||||||
endOffset: text.length * 2 - 2,
|
endOffset: text.length * 2 - 2,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
testHighlight(true);
|
testHighlight(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,6 +72,7 @@ class Popover extends StatefulWidget {
|
|||||||
final PopoverDirection direction;
|
final PopoverDirection direction;
|
||||||
|
|
||||||
final void Function()? onClose;
|
final void Function()? onClose;
|
||||||
|
final Future<bool> Function()? canClose;
|
||||||
|
|
||||||
final bool asBarrier;
|
final bool asBarrier;
|
||||||
|
|
||||||
@ -92,6 +93,7 @@ class Popover extends StatefulWidget {
|
|||||||
this.mutex,
|
this.mutex,
|
||||||
this.windowPadding,
|
this.windowPadding,
|
||||||
this.onClose,
|
this.onClose,
|
||||||
|
this.canClose,
|
||||||
this.asBarrier = false,
|
this.asBarrier = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -122,7 +124,12 @@ class PopoverState extends State<Popover> {
|
|||||||
children.add(
|
children.add(
|
||||||
PopoverMask(
|
PopoverMask(
|
||||||
decoration: widget.maskDecoration,
|
decoration: widget.maskDecoration,
|
||||||
onTap: () => _removeRootOverlay(),
|
onTap: () async {
|
||||||
|
if (!(await widget.canClose?.call() ?? true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_removeRootOverlay();
|
||||||
|
},
|
||||||
onExit: () => _removeRootOverlay(),
|
onExit: () => _removeRootOverlay(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -10,11 +10,13 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
final int triggerActions;
|
final int triggerActions;
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
final void Function()? onClose;
|
final void Function()? onClose;
|
||||||
|
final Future<bool> Function()? canClose;
|
||||||
final PopoverMutex? mutex;
|
final PopoverMutex? mutex;
|
||||||
final Offset? offset;
|
final Offset? offset;
|
||||||
final bool asBarrier;
|
final bool asBarrier;
|
||||||
final EdgeInsets margin;
|
final EdgeInsets margin;
|
||||||
final EdgeInsets windowPadding;
|
final EdgeInsets windowPadding;
|
||||||
|
final Decoration? decoration;
|
||||||
|
|
||||||
const AppFlowyPopover({
|
const AppFlowyPopover({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -22,6 +24,7 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
required this.popupBuilder,
|
required this.popupBuilder,
|
||||||
this.direction = PopoverDirection.rightWithTopAligned,
|
this.direction = PopoverDirection.rightWithTopAligned,
|
||||||
this.onClose,
|
this.onClose,
|
||||||
|
this.canClose,
|
||||||
this.constraints = const BoxConstraints(maxWidth: 240, maxHeight: 600),
|
this.constraints = const BoxConstraints(maxWidth: 240, maxHeight: 600),
|
||||||
this.mutex,
|
this.mutex,
|
||||||
this.triggerActions = PopoverTriggerFlags.click,
|
this.triggerActions = PopoverTriggerFlags.click,
|
||||||
@ -30,6 +33,7 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
this.asBarrier = false,
|
this.asBarrier = false,
|
||||||
this.margin = const EdgeInsets.all(6),
|
this.margin = const EdgeInsets.all(6),
|
||||||
this.windowPadding = const EdgeInsets.all(8.0),
|
this.windowPadding = const EdgeInsets.all(8.0),
|
||||||
|
this.decoration,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -37,6 +41,7 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
return Popover(
|
return Popover(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
onClose: onClose,
|
onClose: onClose,
|
||||||
|
canClose: canClose,
|
||||||
direction: direction,
|
direction: direction,
|
||||||
mutex: mutex,
|
mutex: mutex,
|
||||||
asBarrier: asBarrier,
|
asBarrier: asBarrier,
|
||||||
@ -49,6 +54,7 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
return _PopoverContainer(
|
return _PopoverContainer(
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
margin: margin,
|
margin: margin,
|
||||||
|
decoration: decoration,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -61,19 +67,23 @@ class _PopoverContainer extends StatelessWidget {
|
|||||||
final Widget child;
|
final Widget child;
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
final EdgeInsets margin;
|
final EdgeInsets margin;
|
||||||
|
final Decoration? decoration;
|
||||||
|
|
||||||
const _PopoverContainer({
|
const _PopoverContainer({
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.margin,
|
required this.margin,
|
||||||
required this.constraints,
|
required this.constraints,
|
||||||
|
required this.decoration,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final decoration = FlowyDecoration.decoration(
|
final decoration = this.decoration ??
|
||||||
Theme.of(context).colorScheme.surface,
|
FlowyDecoration.decoration(
|
||||||
Theme.of(context).colorScheme.shadow.withOpacity(0.15),
|
Theme.of(context).colorScheme.surface,
|
||||||
);
|
Theme.of(context).colorScheme.shadow.withOpacity(0.15),
|
||||||
|
);
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(appflowy_flutter LANGUAGES CXX)
|
project(appflowy_flutter LANGUAGES CXX)
|
||||||
|
|
||||||
set(BINARY_NAME "appflowy_flutter")
|
set(BINARY_NAME "AppFlowy")
|
||||||
|
|
||||||
cmake_policy(SET CMP0063 NEW)
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
|||||||
|
|
||||||
# Configure build options.
|
# Configure build options.
|
||||||
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
|
|
||||||
if(IS_MULTICONFIG)
|
if(IS_MULTICONFIG)
|
||||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||||
CACHE STRING "" FORCE)
|
CACHE STRING "" FORCE)
|
||||||
@ -50,14 +51,15 @@ add_subdirectory("runner")
|
|||||||
# them to the application.
|
# them to the application.
|
||||||
include(flutter/generated_plugins.cmake)
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
# === Installation ===
|
# === Installation ===
|
||||||
# Support files are copied into place next to the executable, so that it can
|
# Support files are copied into place next to the executable, so that it can
|
||||||
# run in place. This is done instead of making a separate bundle (as on Linux)
|
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||||
# so that building and running from within Visual Studio will work.
|
# so that building and running from within Visual Studio will work.
|
||||||
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||||
|
|
||||||
# Make the "install" step default, as it's required to run.
|
# Make the "install" step default, as it's required to run.
|
||||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||||
|
|
||||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(runner LANGUAGES CXX)
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
# Define the application target. To change its name, change BINARY_NAME in the
|
||||||
|
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||||
|
# work.
|
||||||
|
#
|
||||||
|
# Any new source files that you add to the application should be added here.
|
||||||
add_executable(${BINARY_NAME} WIN32
|
add_executable(${BINARY_NAME} WIN32
|
||||||
"flutter_window.cpp"
|
"flutter_window.cpp"
|
||||||
"main.cpp"
|
"main.cpp"
|
||||||
@ -10,13 +15,25 @@ add_executable(${BINARY_NAME} WIN32
|
|||||||
"Runner.rc"
|
"Runner.rc"
|
||||||
"runner.exe.manifest"
|
"runner.exe.manifest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
|
# that need different build settings.
|
||||||
apply_standard_settings(${BINARY_NAME})
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
|
||||||
|
# Add preprocessor definitions for the build version.
|
||||||
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
|
||||||
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
|
||||||
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
|
||||||
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
|
||||||
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
|
||||||
|
|
||||||
|
# Disable Windows macros that collide with C++ standard library functions.
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||||
|
|
||||||
|
# Add dependency libraries and include directories. Add any application-specific
|
||||||
|
# dependencies here.
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
||||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
|
||||||
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
|
||||||
|
|
||||||
# === Flutter Library ===
|
|
||||||
#set(DART_FFI "${CMAKE_CURRENT_SOURCE_DIR}/dart_ffi/dart_ffi.dll")
|
|
||||||
#set(DART_FFI ${DART_FFI} PARENT_SCOPE)
|
|
@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico"
|
|||||||
// Version
|
// Version
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifdef FLUTTER_BUILD_NUMBER
|
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
|
||||||
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
|
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
|
||||||
#else
|
#else
|
||||||
#define VERSION_AS_NUMBER 1,0,0
|
#define VERSION_AS_NUMBER 1,0,0,0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef FLUTTER_BUILD_NAME
|
#if defined(FLUTTER_VERSION)
|
||||||
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
|
#define VERSION_AS_STRING FLUTTER_VERSION
|
||||||
#else
|
#else
|
||||||
#define VERSION_AS_STRING "1.0.0"
|
#define VERSION_AS_STRING "1.0.0"
|
||||||
#endif
|
#endif
|
||||||
@ -89,13 +89,13 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "040904e4"
|
BLOCK "040904e4"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "com.example" "\0"
|
VALUE "CompanyName", "io.appflowy" "\0"
|
||||||
VALUE "FileDescription", "AppFlowy" "\0"
|
VALUE "FileDescription", "AppFlowy" "\0"
|
||||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||||
VALUE "InternalName", "appflowy_flutter" "\0"
|
VALUE "InternalName", "AppFlowy" "\0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0"
|
VALUE "LegalCopyright", "Copyright (C) 2023 io.appflowy. All rights reserved." "\0"
|
||||||
VALUE "OriginalFilename", "appflowy_flutter.exe" "\0"
|
VALUE "OriginalFilename", "AppFlowy.exe" "\0"
|
||||||
VALUE "ProductName", "appflowy_flutter" "\0"
|
VALUE "ProductName", "AppFlowy" "\0"
|
||||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
@ -6,10 +6,12 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
_In_ wchar_t *command_line, _In_ int show_command)
|
||||||
|
{
|
||||||
// Attach to console when present (e.g., 'flutter run') or create a
|
// Attach to console when present (e.g., 'flutter run') or create a
|
||||||
// new console when running with a debugger.
|
// new console when running with a debugger.
|
||||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())
|
||||||
|
{
|
||||||
CreateAndAttachConsole();
|
CreateAndAttachConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,13 +29,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
|||||||
FlutterWindow window(project);
|
FlutterWindow window(project);
|
||||||
Win32Window::Point origin(10, 10);
|
Win32Window::Point origin(10, 10);
|
||||||
Win32Window::Size size(1280, 720);
|
Win32Window::Size size(1280, 720);
|
||||||
if (!window.CreateAndShow(L"appflowy_flutter", origin, size)) {
|
if (!window.CreateAndShow(L"AppFlowy", origin, size))
|
||||||
|
{
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
window.SetQuitOnClose(true);
|
window.SetQuitOnClose(true);
|
||||||
|
|
||||||
::MSG msg;
|
::MSG msg;
|
||||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
while (::GetMessage(&msg, nullptr, 0, 0))
|
||||||
|
{
|
||||||
::TranslateMessage(&msg);
|
::TranslateMessage(&msg);
|
||||||
::DispatchMessage(&msg);
|
::DispatchMessage(&msg);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
Type=Application
|
Type=Application
|
||||||
Name=AppFlowy
|
Name=AppFlowy
|
||||||
Icon=io.appflowy.AppFlowy
|
Icon=io.appflowy.AppFlowy
|
||||||
Exec=env GDK_GL=gles appflowy_flutter %U
|
Exec=env GDK_GL=gles AppFlowy %U
|
||||||
Categories=Network;Productivity;
|
Categories=Network;Productivity;
|
||||||
Keywords=Notes
|
Keywords=Notes
|
||||||
|
@ -2,7 +2,7 @@ app-id: io.appflowy.AppFlowy
|
|||||||
runtime: org.freedesktop.Platform
|
runtime: org.freedesktop.Platform
|
||||||
runtime-version: '21.08'
|
runtime-version: '21.08'
|
||||||
sdk: org.freedesktop.Sdk
|
sdk: org.freedesktop.Sdk
|
||||||
command: appflowy_flutter
|
command: AppFlowy
|
||||||
separate-locales: false
|
separate-locales: false
|
||||||
finish-args:
|
finish-args:
|
||||||
- --share=ipc
|
- --share=ipc
|
||||||
@ -18,10 +18,10 @@ modules:
|
|||||||
build-commands:
|
build-commands:
|
||||||
# - ls .
|
# - ls .
|
||||||
- cp -r appflowy /app/appflowy
|
- cp -r appflowy /app/appflowy
|
||||||
- chmod +x /app/appflowy/appflowy_flutter
|
- chmod +x /app/appflowy/AppFlowy
|
||||||
- install -Dm644 logo.svg /app/share/icons/hicolor/scalable/apps/io.appflowy.AppFlowy.svg
|
- install -Dm644 logo.svg /app/share/icons/hicolor/scalable/apps/io.appflowy.AppFlowy.svg
|
||||||
- mkdir /app/bin
|
- mkdir /app/bin
|
||||||
- ln -s /app/appflowy/appflowy_flutter /app/bin/appflowy_flutter
|
- ln -s /app/appflowy/AppFlowy /app/bin/AppFlowy
|
||||||
- install -Dm644 io.appflowy.AppFlowy.desktop /app/share/applications/io.appflowy.AppFlowy.desktop
|
- install -Dm644 io.appflowy.AppFlowy.desktop /app/share/applications/io.appflowy.AppFlowy.desktop
|
||||||
sources:
|
sources:
|
||||||
- type: archive
|
- type: archive
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
if [ -e /usr/local/bin/appflowy ]; then
|
if [ -e /usr/local/bin/AppFlowy ]; then
|
||||||
echo "Symlink already exists, skipping."
|
echo "Symlink already exists, skipping."
|
||||||
else
|
else
|
||||||
echo "Creating Symlink in /usr/local/bin/appflowy"
|
echo "Creating Symlink in /usr/local/bin/appflowy"
|
||||||
ln -s /opt/AppFlowy/appflowy_flutter /usr/local/bin/appflowy
|
ln -s /opt/AppFlowy/AppFlowy /usr/local/bin/AppFlowy
|
||||||
fi
|
fi
|
||||||
|
@ -7,15 +7,15 @@ SolidCompression=yes
|
|||||||
DefaultDirName={autopf}\AppFlowy\
|
DefaultDirName={autopf}\AppFlowy\
|
||||||
DefaultGroupName=AppFlowy
|
DefaultGroupName=AppFlowy
|
||||||
SetupIconFile=flowy_logo.ico
|
SetupIconFile=flowy_logo.ico
|
||||||
UninstallDisplayIcon={app}\appflowy_flutter.exe
|
UninstallDisplayIcon={app}\AppFlowy.exe
|
||||||
UninstallDisplayName=AppFlowy
|
UninstallDisplayName=AppFlowy
|
||||||
AppPublisher=AppFlowy-IO
|
AppPublisher=AppFlowy-IO
|
||||||
VersionInfoVersion={#AppVersion}
|
VersionInfoVersion={#AppVersion}
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "AppFlowy\AppFlowy.exe";DestDir: "{app}";DestName: "appflowy_flutter.exe"
|
Source: "AppFlowy\AppFlowy.exe";DestDir: "{app}";DestName: "AppFlowy.exe"
|
||||||
Source: "AppFlowy\*";DestDir: "{app}"
|
Source: "AppFlowy\*";DestDir: "{app}"
|
||||||
Source: "AppFlowy\data\*";DestDir: "{app}\data\"; Flags: recursesubdirs
|
Source: "AppFlowy\data\*";DestDir: "{app}\data\"; Flags: recursesubdirs
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\AppFlowy";Filename: "{app}\appflowy_flutter.exe"
|
Name: "{group}\AppFlowy";Filename: "{app}\AppFlowy.exe"
|
Loading…
Reference in New Issue
Block a user